Repository: eclipse-che/che-server Branch: main Commit: ebef23d5e797 Files: 1954 Total size: 13.4 MB Directory structure: gitextract__5pkrvkt/ ├── .ci/ │ └── openshift-ci/ │ ├── Dockerfile │ ├── ca.crt │ ├── common.sh │ ├── devworkspace-test.yaml │ ├── htpasswdProvider.yaml │ ├── oauth-secret.yaml │ ├── pat-secret.yaml │ ├── pod-che-smoke-test.yaml │ ├── pod-oauth-factory-test.yaml │ ├── ssh-secret.yaml │ ├── test-azure-no-pat-oauth-flow-raw-devfile-url.sh │ ├── test-azure-no-pat-oauth-flow-ssh-url.sh │ ├── test-azure-no-pat-oauth-flow.sh │ ├── test-azure-with-pat-setup-flow.sh │ ├── test-bitbucket-no-pat-oauth-flow-raw-devfile-url.sh │ ├── test-bitbucket-no-pat-oauth-flow-ssh-url.sh │ ├── test-bitbucket-no-pat-oauth-flow.sh │ ├── test-che-smoke-test.sh │ ├── test-gitea-no-pat-oauth-flow.sh │ ├── test-gitea-with-pat-setup-flow.sh │ ├── test-github-no-pat-oauth-flow-raw-devfile-url.sh │ ├── test-github-no-pat-oauth-flow-ssh-url.sh │ ├── test-github-no-pat-oauth-flow.sh │ ├── test-github-with-pat-setup-flow.sh │ ├── test-gitlab-no-pat-oauth-flow-raw-devfile-url.sh │ ├── test-gitlab-no-pat-oauth-flow-ssh-url.sh │ ├── test-gitlab-no-pat-oauth-flow.sh │ ├── test-gitlab-with-oauth-setup-flow.sh │ └── test-gitlab-with-pat-setup-flow.sh ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── build-pr-check.yml │ ├── che-properties-docs-update.yml │ ├── generate-maven-sbom.yml │ ├── next-build.yml │ ├── release.yml │ └── try-in-web-ide.yaml ├── .gitignore ├── .mvn/ │ └── jvm.config ├── CONTRIBUTING.md ├── LICENSE ├── NUMBERING.md ├── README.md ├── RELEASE.md ├── assembly/ │ ├── assembly-che-tomcat/ │ │ ├── pom.xml │ │ └── src/ │ │ └── assembly/ │ │ ├── LICENSE-tomcat.txt │ │ ├── assembly.xml │ │ └── conf/ │ │ ├── logback-access.xml │ │ ├── logback-json-appenders.xml │ │ ├── logback-plaintext-appenders.xml │ │ ├── logback.xml │ │ └── server.xml │ ├── assembly-main/ │ │ ├── pom.xml │ │ └── src/ │ │ └── assembly/ │ │ ├── LICENSE │ │ ├── README │ │ └── assembly.xml │ ├── assembly-root-war/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ ├── ApiAccessRejectionFilter.java │ │ │ │ ├── DashboardModule.java │ │ │ │ └── DashboardRedirectionFilter.java │ │ │ └── webapp/ │ │ │ ├── META-INF/ │ │ │ │ └── context.xml │ │ │ ├── WEB-INF/ │ │ │ │ ├── rewrite.config │ │ │ │ └── web.xml │ │ │ └── _app/ │ │ │ ├── keycloackLoader.js │ │ │ ├── loader.css │ │ │ ├── loader.html │ │ │ ├── loader.js │ │ │ ├── oauth.html │ │ │ └── oauthLoader.js │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ ├── ApiAccessRejectionFilterTest.java │ │ └── DashboardRedirectionFilterTest.java │ ├── assembly-swagger-war/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── webapp/ │ │ ├── index.html │ │ ├── oauth2-redirect.html │ │ ├── swagger-ui-bundle.js │ │ ├── swagger-ui-es-bundle-core.js │ │ ├── swagger-ui-es-bundle.js │ │ ├── swagger-ui-standalone-preset.js │ │ ├── swagger-ui.css │ │ └── swagger-ui.js │ ├── assembly-wsmaster-war/ │ │ ├── .deps/ │ │ │ ├── EXCLUDED/ │ │ │ │ ├── dev.md │ │ │ │ └── prod.md │ │ │ ├── dev.md │ │ │ ├── problems.md │ │ │ └── prod.md │ │ ├── gen-deps.sh │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ ├── api/ │ │ │ │ │ └── deploy/ │ │ │ │ │ ├── ReplicationModule.java │ │ │ │ │ ├── WsMasterModule.java │ │ │ │ │ ├── WsMasterServletModule.java │ │ │ │ │ └── jsonrpc/ │ │ │ │ │ ├── CheJsonRpcWebSocketConfigurationModule.java │ │ │ │ │ ├── CheMajorWebSocketEndpoint.java │ │ │ │ │ └── CheMajorWebSocketEndpointConfiguration.java │ │ │ │ └── swagger/ │ │ │ │ └── deploy/ │ │ │ │ └── DocsModule.java │ │ │ ├── resources/ │ │ │ │ └── META-INF/ │ │ │ │ └── persistence.xml │ │ │ └── webapp/ │ │ │ └── WEB-INF/ │ │ │ ├── classes/ │ │ │ │ ├── che/ │ │ │ │ │ ├── che.properties │ │ │ │ │ └── multiuser.properties │ │ │ │ ├── che_aliases.properties │ │ │ │ ├── jgroups/ │ │ │ │ │ └── che-tcp.xml │ │ │ │ ├── keycloak/ │ │ │ │ │ ├── OIDCKeycloak.js │ │ │ │ │ ├── oidcCallback.js │ │ │ │ │ ├── oidcCallbackDashboard.html │ │ │ │ │ └── oidcCallbackIde.html │ │ │ │ └── logging.properties │ │ │ ├── openapi-configuration.json │ │ │ └── web.xml │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── integration/ │ │ │ └── IntegrityConfigurationTest.java │ │ └── resources/ │ │ └── logback-test.xml │ └── pom.xml ├── build/ │ ├── README.md │ ├── build.sh │ └── dockerfiles/ │ ├── Dockerfile │ ├── brew.Dockerfile │ └── entrypoint.sh ├── check_properties_description.sh ├── core/ │ ├── che-core-api-core/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ ├── api/ │ │ │ │ │ └── core/ │ │ │ │ │ ├── ApiException.java │ │ │ │ │ ├── AuthenticationException.java │ │ │ │ │ ├── BadRequestException.java │ │ │ │ │ ├── ConflictException.java │ │ │ │ │ ├── ErrorCodes.java │ │ │ │ │ ├── ForbiddenException.java │ │ │ │ │ ├── NotFoundException.java │ │ │ │ │ ├── Page.java │ │ │ │ │ ├── Pages.java │ │ │ │ │ ├── ServerException.java │ │ │ │ │ ├── UnauthorizedException.java │ │ │ │ │ ├── ValidationException.java │ │ │ │ │ ├── cors/ │ │ │ │ │ │ ├── CheCorsFilter.java │ │ │ │ │ │ └── CheCorsFilterConfig.java │ │ │ │ │ ├── factory/ │ │ │ │ │ │ └── FactoryParameter.java │ │ │ │ │ ├── jsonrpc/ │ │ │ │ │ │ ├── commons/ │ │ │ │ │ │ │ ├── ClientSubscriptionHandler.java │ │ │ │ │ │ │ ├── JsonRpcComposer.java │ │ │ │ │ │ │ ├── JsonRpcError.java │ │ │ │ │ │ │ ├── JsonRpcErrorTransmitter.java │ │ │ │ │ │ │ ├── JsonRpcException.java │ │ │ │ │ │ │ ├── JsonRpcMarshaller.java │ │ │ │ │ │ │ ├── JsonRpcMessageReceiver.java │ │ │ │ │ │ │ ├── JsonRpcMethodInvokerFilter.java │ │ │ │ │ │ │ ├── JsonRpcParams.java │ │ │ │ │ │ │ ├── JsonRpcPromise.java │ │ │ │ │ │ │ ├── JsonRpcQualifier.java │ │ │ │ │ │ │ ├── JsonRpcRequest.java │ │ │ │ │ │ │ ├── JsonRpcResponse.java │ │ │ │ │ │ │ ├── JsonRpcResult.java │ │ │ │ │ │ │ ├── JsonRpcUnmarshaller.java │ │ │ │ │ │ │ ├── JsonRpcUtils.java │ │ │ │ │ │ │ ├── NotificationHandler.java │ │ │ │ │ │ │ ├── RequestDispatcher.java │ │ │ │ │ │ │ ├── RequestHandler.java │ │ │ │ │ │ │ ├── RequestHandlerConfigurator.java │ │ │ │ │ │ │ ├── RequestHandlerManager.java │ │ │ │ │ │ │ ├── RequestProcessor.java │ │ │ │ │ │ │ ├── RequestProcessorConfigurationProvider.java │ │ │ │ │ │ │ ├── RequestTransmitter.java │ │ │ │ │ │ │ ├── ResponseDispatcher.java │ │ │ │ │ │ │ ├── TimeoutActionRunner.java │ │ │ │ │ │ │ ├── reception/ │ │ │ │ │ │ │ │ ├── ConsumerConfiguratorManyToNone.java │ │ │ │ │ │ │ │ ├── ConsumerConfiguratorNoneToNone.java │ │ │ │ │ │ │ │ ├── ConsumerConfiguratorOneToNone.java │ │ │ │ │ │ │ │ ├── FunctionConfiguratorManyToMany.java │ │ │ │ │ │ │ │ ├── FunctionConfiguratorManyToOne.java │ │ │ │ │ │ │ │ ├── FunctionConfiguratorNoneToMany.java │ │ │ │ │ │ │ │ ├── FunctionConfiguratorNoneToOne.java │ │ │ │ │ │ │ │ ├── FunctionConfiguratorOneToMany.java │ │ │ │ │ │ │ │ ├── FunctionConfiguratorOneToOne.java │ │ │ │ │ │ │ │ ├── MethodNameConfigurator.java │ │ │ │ │ │ │ │ ├── ParamsConfigurator.java │ │ │ │ │ │ │ │ ├── PromiseConfigurationOneToOne.java │ │ │ │ │ │ │ │ ├── ResultConfiguratorFromMany.java │ │ │ │ │ │ │ │ ├── ResultConfiguratorFromNone.java │ │ │ │ │ │ │ │ └── ResultConfiguratorFromOne.java │ │ │ │ │ │ │ └── transmission/ │ │ │ │ │ │ │ ├── EndpointIdConfigurator.java │ │ │ │ │ │ │ ├── MethodNameConfigurator.java │ │ │ │ │ │ │ ├── ParamsConfigurator.java │ │ │ │ │ │ │ ├── SendConfiguratorFromMany.java │ │ │ │ │ │ │ ├── SendConfiguratorFromNone.java │ │ │ │ │ │ │ └── SendConfiguratorFromOne.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ ├── GsonJsonRpcComposer.java │ │ │ │ │ │ ├── GsonJsonRpcMarshaller.java │ │ │ │ │ │ ├── GsonJsonRpcQualifier.java │ │ │ │ │ │ ├── GsonJsonRpcUnmarshaller.java │ │ │ │ │ │ ├── JsonRpcModule.java │ │ │ │ │ │ ├── ServerSideRequestProcessor.java │ │ │ │ │ │ ├── ServerSideRequestProcessorConfigurator.java │ │ │ │ │ │ └── ServerSideTimeoutActionRunner.java │ │ │ │ │ ├── notification/ │ │ │ │ │ │ ├── EventOrigin.java │ │ │ │ │ │ ├── EventService.java │ │ │ │ │ │ ├── EventSubscriber.java │ │ │ │ │ │ ├── InmemoryRemoteSubscriptionStorage.java │ │ │ │ │ │ ├── RemoteSubscriptionContext.java │ │ │ │ │ │ ├── RemoteSubscriptionManager.java │ │ │ │ │ │ ├── RemoteSubscriptionStorage.java │ │ │ │ │ │ └── dto/ │ │ │ │ │ │ └── EventSubscription.java │ │ │ │ │ ├── rest/ │ │ │ │ │ │ ├── ApiExceptionMapper.java │ │ │ │ │ │ ├── ApiInfoProvider.java │ │ │ │ │ │ ├── ApiInfoService.java │ │ │ │ │ │ ├── AuthenticationExceptionMapper.java │ │ │ │ │ │ ├── CheJsonProvider.java │ │ │ │ │ │ ├── Constants.java │ │ │ │ │ │ ├── CoreRestModule.java │ │ │ │ │ │ ├── DefaultHttpJsonRequest.java │ │ │ │ │ │ ├── DefaultHttpJsonRequestFactory.java │ │ │ │ │ │ ├── DefaultHttpJsonResponse.java │ │ │ │ │ │ ├── DownloadFileResponseFilter.java │ │ │ │ │ │ ├── HttpJsonRequest.java │ │ │ │ │ │ ├── HttpJsonRequestFactory.java │ │ │ │ │ │ ├── HttpJsonResponse.java │ │ │ │ │ │ ├── HttpOutputMessage.java │ │ │ │ │ │ ├── HttpRequestHelper.java │ │ │ │ │ │ ├── HttpServletProxyResponse.java │ │ │ │ │ │ ├── JAXRSDownloadFileResponseFilter.java │ │ │ │ │ │ ├── LivenessProbeService.java │ │ │ │ │ │ ├── MessageBodyAdapter.java │ │ │ │ │ │ ├── MessageBodyAdapterInterceptor.java │ │ │ │ │ │ ├── OutputProvider.java │ │ │ │ │ │ ├── RemoteServiceDescriptor.java │ │ │ │ │ │ ├── RuntimeExceptionMapper.java │ │ │ │ │ │ ├── Service.java │ │ │ │ │ │ ├── ServiceContext.java │ │ │ │ │ │ ├── WebApplicationExceptionMapper.java │ │ │ │ │ │ ├── annotations/ │ │ │ │ │ │ │ ├── Description.java │ │ │ │ │ │ │ ├── GenerateLink.java │ │ │ │ │ │ │ ├── OPTIONS.java │ │ │ │ │ │ │ ├── Required.java │ │ │ │ │ │ │ └── Valid.java │ │ │ │ │ │ └── shared/ │ │ │ │ │ │ ├── Links.java │ │ │ │ │ │ ├── ParameterType.java │ │ │ │ │ │ └── dto/ │ │ │ │ │ │ ├── ApiInfo.java │ │ │ │ │ │ ├── ExtendedError.java │ │ │ │ │ │ ├── Hyperlinks.java │ │ │ │ │ │ ├── Link.java │ │ │ │ │ │ ├── LinkParameter.java │ │ │ │ │ │ ├── RequestBodyDescriptor.java │ │ │ │ │ │ ├── ServiceDescriptor.java │ │ │ │ │ │ └── ServiceError.java │ │ │ │ │ ├── util/ │ │ │ │ │ │ ├── AbstractLineConsumer.java │ │ │ │ │ │ ├── AbstractMessageConsumer.java │ │ │ │ │ │ ├── ApiInfoLogInformer.java │ │ │ │ │ │ ├── Cancellable.java │ │ │ │ │ │ ├── CommandLine.java │ │ │ │ │ │ ├── CompositeLineConsumer.java │ │ │ │ │ │ ├── ContentTypeGuesser.java │ │ │ │ │ │ ├── DownloadPlugin.java │ │ │ │ │ │ ├── ErrorFilteredConsumer.java │ │ │ │ │ │ ├── FileCleaner.java │ │ │ │ │ │ ├── FileLineConsumer.java │ │ │ │ │ │ ├── HttpDownloadPlugin.java │ │ │ │ │ │ ├── IndentWrapperLineConsumer.java │ │ │ │ │ │ ├── JsonRpcEndpointIdProvider.java │ │ │ │ │ │ ├── JsonRpcEndpointIdsHolder.java │ │ │ │ │ │ ├── JsonRpcEndpointToMachineNameHolder.java │ │ │ │ │ │ ├── JsonRpcLineConsumer.java │ │ │ │ │ │ ├── JsonRpcMessageConsumer.java │ │ │ │ │ │ ├── LineConsumer.java │ │ │ │ │ │ ├── LineConsumerFactory.java │ │ │ │ │ │ ├── LinksHelper.java │ │ │ │ │ │ ├── ListLineConsumer.java │ │ │ │ │ │ ├── MessageConsumer.java │ │ │ │ │ │ ├── PagingUtil.java │ │ │ │ │ │ ├── RateExceedDetector.java │ │ │ │ │ │ ├── ShellFactory.java │ │ │ │ │ │ ├── StreamPump.java │ │ │ │ │ │ ├── SystemInfo.java │ │ │ │ │ │ ├── ValueHolder.java │ │ │ │ │ │ ├── Watchdog.java │ │ │ │ │ │ ├── WritableLineConsumer.java │ │ │ │ │ │ └── lineconsumer/ │ │ │ │ │ │ ├── ConcurrentCompositeLineConsumer.java │ │ │ │ │ │ ├── ConcurrentFileLineConsumer.java │ │ │ │ │ │ └── ConsumerAlreadyClosedException.java │ │ │ │ │ └── websocket/ │ │ │ │ │ ├── commons/ │ │ │ │ │ │ ├── WebSocketMessageReceiver.java │ │ │ │ │ │ └── WebSocketMessageTransmitter.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── BasicWebSocketEndpoint.java │ │ │ │ │ ├── BasicWebSocketMessageTransmitter.java │ │ │ │ │ ├── GuiceInjectorEndpointConfigurator.java │ │ │ │ │ ├── MessagesReSender.java │ │ │ │ │ ├── WebSocketModule.java │ │ │ │ │ ├── WebSocketSessionRegistry.java │ │ │ │ │ └── WebsocketIdService.java │ │ │ │ ├── commons/ │ │ │ │ │ ├── env/ │ │ │ │ │ │ └── EnvironmentContext.java │ │ │ │ │ ├── proxy/ │ │ │ │ │ │ └── ProxyAuthenticator.java │ │ │ │ │ └── subject/ │ │ │ │ │ ├── Subject.java │ │ │ │ │ └── SubjectImpl.java │ │ │ │ ├── everrest/ │ │ │ │ │ ├── CheMethodInvokerFilter.java │ │ │ │ │ ├── ETagResponseFilter.java │ │ │ │ │ └── EverrestDownloadFileResponseFilter.java │ │ │ │ └── security/ │ │ │ │ ├── PBKDF2PasswordEncryptor.java │ │ │ │ ├── PasswordEncryptor.java │ │ │ │ └── SHA512PasswordEncryptor.java │ │ │ └── resources/ │ │ │ └── content-types.properties │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ ├── api/ │ │ │ │ └── core/ │ │ │ │ ├── PageTest.java │ │ │ │ ├── PagesTest.java │ │ │ │ ├── jsonrpc/ │ │ │ │ │ └── commons/ │ │ │ │ │ ├── JsonRpcMessageReceiverTest.java │ │ │ │ │ └── RequestDispatcherTest.java │ │ │ │ ├── notification/ │ │ │ │ │ └── EventServiceTest.java │ │ │ │ ├── rest/ │ │ │ │ │ ├── DefaultHttpJsonRequestTest.java │ │ │ │ │ ├── DefaultHttpJsonResponseTest.java │ │ │ │ │ ├── LinkHeaderGenerationTest.java │ │ │ │ │ ├── RemoteServiceDescriptorTest.java │ │ │ │ │ ├── RuntimeExceptionMapperTest.java │ │ │ │ │ ├── ServiceDescriptorTest.java │ │ │ │ │ ├── TestService.java │ │ │ │ │ └── it/ │ │ │ │ │ └── ApiInfoProviderTest.java │ │ │ │ ├── util/ │ │ │ │ │ ├── CompositeLineConsumerTest.java │ │ │ │ │ ├── ErrorFilteredConsumerTest.java │ │ │ │ │ ├── FileLineConsumerTest.java │ │ │ │ │ ├── PagingUtilTest.java │ │ │ │ │ ├── RateExceedDetectorTest.java │ │ │ │ │ ├── StandardLinuxShellTest.java │ │ │ │ │ ├── WatchdogTest.java │ │ │ │ │ └── lineconsumer/ │ │ │ │ │ ├── ConcurrentCompositeLineConsumerTest.java │ │ │ │ │ └── ConcurrentFileLineConsumerTest.java │ │ │ │ └── websocket/ │ │ │ │ └── impl/ │ │ │ │ ├── BasicWebSocketMessageTransmitterTest.java │ │ │ │ ├── MessagesReSenderTest.java │ │ │ │ └── WebSocketSessionRegistryTest.java │ │ │ ├── commons/ │ │ │ │ ├── env/ │ │ │ │ │ └── EnvironmentContextTest.java │ │ │ │ └── proxy/ │ │ │ │ └── ProxyAuthenticatorTest.java │ │ │ ├── everrest/ │ │ │ │ ├── DownloadFileResponseFilterTest.java │ │ │ │ └── ETagResponseFilterTest.java │ │ │ └── security/ │ │ │ └── PasswordEncryptorsTest.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── che-core-api-dto/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── dto/ │ │ │ ├── generator/ │ │ │ │ ├── DtoGenerator.java │ │ │ │ ├── DtoImpl.java │ │ │ │ ├── DtoImplClientTemplate.java │ │ │ │ ├── DtoImplServerTemplate.java │ │ │ │ └── DtoTemplate.java │ │ │ ├── server/ │ │ │ │ ├── DtoFactory.java │ │ │ │ ├── DtoFactoryVisitor.java │ │ │ │ ├── DtoProvider.java │ │ │ │ ├── JsonArrayImpl.java │ │ │ │ ├── JsonSerializable.java │ │ │ │ ├── JsonStringMapImpl.java │ │ │ │ ├── NullOrEmptyCollectionAdapter.java │ │ │ │ ├── NullOrEmptyMapAdapter.java │ │ │ │ └── SerializableInterfaceAdapterFactory.java │ │ │ └── shared/ │ │ │ ├── CompactJsonDto.java │ │ │ ├── DTO.java │ │ │ ├── DTOImpl.java │ │ │ ├── DelegateRule.java │ │ │ ├── DelegateTo.java │ │ │ ├── JsonArray.java │ │ │ ├── JsonFieldName.java │ │ │ ├── JsonStringMap.java │ │ │ └── SerializationIndex.java │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── dto/ │ │ ├── ServerDtoTest.java │ │ └── definitions/ │ │ ├── ComplicatedDto.java │ │ ├── DTOHierarchy.java │ │ ├── DtoWithAny.java │ │ ├── DtoWithDelegate.java │ │ ├── DtoWithFieldNames.java │ │ ├── DtoWithSerializable.java │ │ ├── SimpleDto.java │ │ ├── TestInterface.java │ │ ├── Util.java │ │ └── model/ │ │ ├── Model.java │ │ ├── ModelComponent.java │ │ ├── ModelComponentDto.java │ │ └── ModelDto.java │ ├── che-core-api-dto-maven-plugin/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── dto/ │ │ └── generator/ │ │ └── maven/ │ │ └── plugin/ │ │ └── DtoGeneratorMojo.java │ ├── che-core-api-model/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── core/ │ │ └── model/ │ │ ├── factory/ │ │ │ ├── Action.java │ │ │ ├── Author.java │ │ │ ├── Factory.java │ │ │ ├── Ide.java │ │ │ ├── OnAppClosed.java │ │ │ ├── OnAppLoaded.java │ │ │ ├── OnProjectsLoaded.java │ │ │ ├── Policies.java │ │ │ └── ScmInfo.java │ │ ├── project/ │ │ │ ├── ProjectProblem.java │ │ │ └── type/ │ │ │ ├── Attribute.java │ │ │ ├── ProjectType.java │ │ │ └── Value.java │ │ ├── user/ │ │ │ ├── Profile.java │ │ │ └── User.java │ │ └── workspace/ │ │ ├── Runtime.java │ │ ├── Warning.java │ │ ├── Workspace.java │ │ ├── WorkspaceConfig.java │ │ ├── WorkspaceStatus.java │ │ ├── config/ │ │ │ ├── Command.java │ │ │ ├── Environment.java │ │ │ ├── MachineConfig.java │ │ │ ├── ProjectConfig.java │ │ │ ├── Recipe.java │ │ │ ├── ServerConfig.java │ │ │ ├── SourceStorage.java │ │ │ └── Volume.java │ │ ├── devfile/ │ │ │ ├── Action.java │ │ │ ├── Command.java │ │ │ ├── Component.java │ │ │ ├── Devfile.java │ │ │ ├── Endpoint.java │ │ │ ├── Entrypoint.java │ │ │ ├── Env.java │ │ │ ├── Metadata.java │ │ │ ├── PreviewUrl.java │ │ │ ├── Project.java │ │ │ ├── Source.java │ │ │ ├── UserDevfile.java │ │ │ └── Volume.java │ │ └── runtime/ │ │ ├── BootstrapperStatus.java │ │ ├── Machine.java │ │ ├── MachineStatus.java │ │ ├── RuntimeIdentity.java │ │ ├── Server.java │ │ └── ServerStatus.java │ ├── che-core-logback/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── commons/ │ │ └── logback/ │ │ ├── EnvironmentVariablesLogLevelPropagator.java │ │ └── filter/ │ │ ├── IdentityIdLoggerFilter.java │ │ └── RequestIdLoggerFilter.java │ ├── che-core-metrics-core/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── core/ │ │ │ └── metrics/ │ │ │ ├── ApiResponseCounter.java │ │ │ ├── ApiResponseMetricFilter.java │ │ │ ├── FileStoresMeterBinder.java │ │ │ ├── MetricsBinder.java │ │ │ ├── MetricsModule.java │ │ │ ├── MetricsServer.java │ │ │ ├── MetricsServletModule.java │ │ │ ├── PrometheusMeterRegistryProvider.java │ │ │ └── TomcatMetricsProvider.java │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── core/ │ │ │ └── metrics/ │ │ │ ├── ApiResponseCounterTest.java │ │ │ ├── ApiResponseMetricFilterTest.java │ │ │ └── FileStoresMeterTest.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── che-core-tracing-core/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── core/ │ │ └── tracing/ │ │ ├── NopTracingModule.java │ │ ├── TracerProvider.java │ │ ├── TracingInterceptor.java │ │ └── TracingModule.java │ ├── che-core-tracing-metrics/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── core/ │ │ └── tracing/ │ │ └── metrics/ │ │ ├── MeteredTracerProvider.java │ │ ├── MicrometerMetricsReporterProvider.java │ │ └── TracingMetricsModule.java │ ├── che-core-tracing-web/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ ├── io/ │ │ │ └── opentracing/ │ │ │ └── contrib/ │ │ │ └── web/ │ │ │ └── servlet/ │ │ │ └── filter/ │ │ │ ├── HttpServletRequestExtractAdapter.java │ │ │ ├── ServletFilterSpanDecorator.java │ │ │ ├── TracingFilter.java │ │ │ └── decorator/ │ │ │ └── ServletFilterHeaderSpanDecorator.java │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── core/ │ │ └── tracing/ │ │ └── web/ │ │ ├── TracingFilterProvider.java │ │ └── TracingWebModule.java │ ├── che-core-typescript-dto-maven-plugin/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── it/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── plugin/ │ │ │ │ └── typescript/ │ │ │ │ └── dto/ │ │ │ │ └── TypeScriptDTOGeneratorMojoTST.java │ │ │ └── resources/ │ │ │ ├── dto.spec.ts │ │ │ └── package.json │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── plugin/ │ │ │ │ └── typescript/ │ │ │ │ └── dto/ │ │ │ │ ├── DTOHelper.java │ │ │ │ ├── TypeScriptDTOGeneratorMojo.java │ │ │ │ ├── TypeScriptDtoDTSGenerator.java │ │ │ │ ├── TypeScriptDtoGenerator.java │ │ │ │ └── model/ │ │ │ │ ├── DtoModel.java │ │ │ │ ├── DtoNamespace.java │ │ │ │ ├── FieldAttributeModel.java │ │ │ │ ├── MethodModel.java │ │ │ │ └── ParameterMethodModel.java │ │ │ └── resources/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── plugin/ │ │ │ └── typescript/ │ │ │ └── dto/ │ │ │ ├── typescript.d.ts.template │ │ │ └── typescript.template │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── plugin/ │ │ │ └── typescript/ │ │ │ └── dto/ │ │ │ ├── MyCustomDTO.java │ │ │ ├── MyDtoWithSerializableDTO.java │ │ │ ├── MyOtherDTO.java │ │ │ ├── MySimpleDTO.java │ │ │ ├── MySuperClassDTO.java │ │ │ ├── MySuperSuperClass.java │ │ │ ├── Status.java │ │ │ ├── TypeScriptDTOGeneratorMojoTest.java │ │ │ ├── internal/ │ │ │ │ └── InternalDto.java │ │ │ └── stub/ │ │ │ └── TypeScriptDTOGeneratorMojoProjectStub.java │ │ └── projects/ │ │ ├── project/ │ │ │ └── pom.xml │ │ └── project-d-ts/ │ │ └── pom.xml │ ├── commons/ │ │ ├── che-core-commons-annotations/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── commons/ │ │ │ └── annotation/ │ │ │ ├── Nullable.java │ │ │ └── Traced.java │ │ ├── che-core-commons-inject/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── org/ │ │ │ │ │ └── eclipse/ │ │ │ │ │ └── che/ │ │ │ │ │ └── inject/ │ │ │ │ │ ├── CheBootstrap.java │ │ │ │ │ ├── ConfigurationException.java │ │ │ │ │ ├── ConfigurationProperties.java │ │ │ │ │ ├── DynaModule.java │ │ │ │ │ ├── FileConverter.java │ │ │ │ │ ├── Matchers.java │ │ │ │ │ ├── ModuleFinder.java │ │ │ │ │ ├── ModuleScanner.java │ │ │ │ │ ├── PairArrayConverter.java │ │ │ │ │ ├── PairConverter.java │ │ │ │ │ ├── PathConverter.java │ │ │ │ │ ├── StringArrayConverter.java │ │ │ │ │ ├── URIConverter.java │ │ │ │ │ ├── URLConverter.java │ │ │ │ │ └── lifecycle/ │ │ │ │ │ ├── DestroyErrorHandler.java │ │ │ │ │ ├── DestroyModule.java │ │ │ │ │ ├── Destroyer.java │ │ │ │ │ ├── InitModule.java │ │ │ │ │ └── LifecycleModule.java │ │ │ │ └── resources/ │ │ │ │ └── META-INF/ │ │ │ │ └── services/ │ │ │ │ └── jakarta.servlet.ServletContainerInitializer │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── inject/ │ │ │ │ ├── CheBootstrapTest.java │ │ │ │ ├── LifecycleTest.java │ │ │ │ ├── MultiBindingTest.java │ │ │ │ └── PathConverterTest.java │ │ │ └── resources/ │ │ │ └── logback-test.xml │ │ ├── che-core-commons-j2ee/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ ├── com/ │ │ │ │ │ └── xemantic/ │ │ │ │ │ └── tadedon/ │ │ │ │ │ └── servlet/ │ │ │ │ │ ├── CacheDisablingFilter.java │ │ │ │ │ ├── CacheForcingFilter.java │ │ │ │ │ └── SimpleFilter.java │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── filter/ │ │ │ │ ├── CheCacheDisablingFilter.java │ │ │ │ └── CheCacheForcingFilter.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── filter/ │ │ │ ├── CheCacheDisablingFilterTest.java │ │ │ └── CheCacheForcingFilterTest.java │ │ ├── che-core-commons-json/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── commons/ │ │ │ │ └── json/ │ │ │ │ ├── JsonHelper.java │ │ │ │ ├── JsonNameConvention.java │ │ │ │ ├── JsonNameConventions.java │ │ │ │ ├── JsonParseException.java │ │ │ │ ├── NameConventionJsonParser.java │ │ │ │ └── NameConventionJsonWriter.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── commons/ │ │ │ └── json/ │ │ │ └── JsonTest.java │ │ ├── che-core-commons-lang/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── commons/ │ │ │ │ └── lang/ │ │ │ │ ├── Deserializer.java │ │ │ │ ├── FlushingStreamWriter.java │ │ │ │ ├── IoUtil.java │ │ │ │ ├── NameGenerator.java │ │ │ │ ├── Pair.java │ │ │ │ ├── PathUtil.java │ │ │ │ ├── Size.java │ │ │ │ ├── StringUtils.java │ │ │ │ ├── TopologicalSort.java │ │ │ │ ├── URLEncodedUtils.java │ │ │ │ ├── UrlUtils.java │ │ │ │ ├── ZipUtils.java │ │ │ │ ├── concurrent/ │ │ │ │ │ ├── CopyThreadLocalCallable.java │ │ │ │ │ ├── CopyThreadLocalRunnable.java │ │ │ │ │ ├── LoggingUncaughtExceptionHandler.java │ │ │ │ │ ├── PropagatedThreadLocalsProvider.java │ │ │ │ │ ├── StripedLocks.java │ │ │ │ │ ├── ThreadLocalPropagateContext.java │ │ │ │ │ └── Unlocker.java │ │ │ │ ├── execution/ │ │ │ │ │ ├── CommandLine.java │ │ │ │ │ ├── ExecutionException.java │ │ │ │ │ ├── Executor.java │ │ │ │ │ ├── ExecutorServiceBuilder.java │ │ │ │ │ ├── JavaParameters.java │ │ │ │ │ ├── OutputReader.java │ │ │ │ │ ├── ParametersList.java │ │ │ │ │ ├── ProcessEvent.java │ │ │ │ │ ├── ProcessExecutor.java │ │ │ │ │ ├── ProcessHandler.java │ │ │ │ │ ├── ProcessListener.java │ │ │ │ │ ├── ProcessOutputType.java │ │ │ │ │ └── WaitForProcessEnd.java │ │ │ │ ├── os/ │ │ │ │ │ └── WindowsPathEscaper.java │ │ │ │ ├── reflect/ │ │ │ │ │ └── ParameterizedTypeImpl.java │ │ │ │ └── ws/ │ │ │ │ └── rs/ │ │ │ │ └── ExtMediaType.java │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── commons/ │ │ │ │ └── lang/ │ │ │ │ ├── IoUtilTest.java │ │ │ │ ├── PathUtilTest.java │ │ │ │ ├── SizeTest.java │ │ │ │ ├── TestURLEncodedUtils.java │ │ │ │ ├── TopologicalSortTest.java │ │ │ │ ├── UrlUtilsTest.java │ │ │ │ ├── ZipUtilsTest.java │ │ │ │ ├── ZipUtilsWriteTest.java │ │ │ │ ├── concurrent/ │ │ │ │ │ └── ThreadLocalPropagateContextTest.java │ │ │ │ ├── execution/ │ │ │ │ │ └── ExecutorServiceBuilderTest.java │ │ │ │ └── os/ │ │ │ │ └── WindowsPathEscaperTest.java │ │ │ └── resources/ │ │ │ ├── findbugs-exclude.xml │ │ │ └── logback-test.xml │ │ ├── che-core-commons-observability/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ ├── io/ │ │ │ │ │ └── micrometer/ │ │ │ │ │ └── core/ │ │ │ │ │ └── instrument/ │ │ │ │ │ └── internal/ │ │ │ │ │ └── TimedCronExecutorService.java │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── commons/ │ │ │ │ └── observability/ │ │ │ │ ├── CountedRejectedExecutionHandler.java │ │ │ │ ├── CountedThreadFactory.java │ │ │ │ ├── ExecutorServiceWrapper.java │ │ │ │ ├── MeteredAndTracedExecutorServiceWrapper.java │ │ │ │ ├── MeteredExecutorServiceWrapper.java │ │ │ │ ├── NoopExecutorServiceWrapper.java │ │ │ │ ├── ObservableThreadPullLauncher.java │ │ │ │ ├── TracedCronExecutorService.java │ │ │ │ ├── TracedExecutorServiceWrapper.java │ │ │ │ └── deploy/ │ │ │ │ └── ExecutorWrapperModule.java │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ ├── io/ │ │ │ │ │ └── micrometer/ │ │ │ │ │ └── core/ │ │ │ │ │ └── instrument/ │ │ │ │ │ └── internal/ │ │ │ │ │ └── TimedCronExecutorServiceTest.java │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── commons/ │ │ │ │ └── observability/ │ │ │ │ ├── CountedRejectedExecutionHandlerTest.java │ │ │ │ ├── CountedThreadFactoryTest.java │ │ │ │ ├── MeteredExecutorServiceWrapperTest.java │ │ │ │ └── NoopExecutorServiceWrapperTest.java │ │ │ └── resources/ │ │ │ └── logback-test.xml │ │ ├── che-core-commons-schedule/ │ │ │ ├── README.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ ├── commons/ │ │ │ │ │ └── schedule/ │ │ │ │ │ ├── Launcher.java │ │ │ │ │ ├── ScheduleCron.java │ │ │ │ │ ├── ScheduleDelay.java │ │ │ │ │ ├── ScheduleRate.java │ │ │ │ │ └── executor/ │ │ │ │ │ ├── CronExecutorService.java │ │ │ │ │ ├── CronExpression.java │ │ │ │ │ ├── CronThreadPoolExecutor.java │ │ │ │ │ ├── LoggedRunnable.java │ │ │ │ │ ├── ScheduleModule.java │ │ │ │ │ └── ThreadPullLauncher.java │ │ │ │ └── inject/ │ │ │ │ └── lifecycle/ │ │ │ │ ├── InternalScheduleModule.java │ │ │ │ └── ScheduleInjectionListener.java │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── commons/ │ │ │ │ └── schedule/ │ │ │ │ └── executor/ │ │ │ │ └── CronExpressionTest.java │ │ │ └── resources/ │ │ │ ├── findbugs-exclude.xml │ │ │ └── logback-test.xml │ │ ├── che-core-commons-test/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── org/ │ │ │ │ │ └── eclipse/ │ │ │ │ │ └── che/ │ │ │ │ │ └── commons/ │ │ │ │ │ └── test/ │ │ │ │ │ ├── AssertRetry.java │ │ │ │ │ ├── SystemPropertiesHelper.java │ │ │ │ │ ├── mockito/ │ │ │ │ │ │ └── answer/ │ │ │ │ │ │ ├── SelfReturningAnswer.java │ │ │ │ │ │ └── WaitingAnswer.java │ │ │ │ │ ├── servlet/ │ │ │ │ │ │ └── MockServletInputStream.java │ │ │ │ │ └── tck/ │ │ │ │ │ ├── JpaCleaner.java │ │ │ │ │ ├── TckListener.java │ │ │ │ │ ├── TckModule.java │ │ │ │ │ ├── TckResourcesCleaner.java │ │ │ │ │ ├── TestListenerAdapter.java │ │ │ │ │ └── repository/ │ │ │ │ │ ├── JpaTckRepository.java │ │ │ │ │ ├── TckRepository.java │ │ │ │ │ └── TckRepositoryException.java │ │ │ │ └── resources/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── commons/ │ │ │ │ └── test/ │ │ │ │ └── db/ │ │ │ │ └── persistence.xml.template │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── commons/ │ │ │ │ └── test/ │ │ │ │ ├── AssertRetryTest.java │ │ │ │ └── tck/ │ │ │ │ ├── DBServerListener.java │ │ │ │ ├── TckComponentsTest.java │ │ │ │ ├── TestModule1.java │ │ │ │ └── TestModule2.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── services/ │ │ │ │ ├── org.eclipse.che.commons.test.tck.TckModule │ │ │ │ └── org.testng.ITestNGListener │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── commons/ │ │ │ └── test/ │ │ │ └── db/ │ │ │ └── test-persistence-1.xml │ │ ├── che-core-commons-tracing/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── commons/ │ │ │ └── tracing/ │ │ │ ├── AnnotationAwareBooleanTag.java │ │ │ ├── AnnotationAwareIntTag.java │ │ │ ├── AnnotationAwareStringTag.java │ │ │ └── TracingTags.java │ │ └── pom.xml │ └── pom.xml ├── deploy/ │ └── cert-manager/ │ ├── ca-cert-generator-role-binding.yml │ ├── ca-cert-generator-role.yml │ ├── che-certificate.yml │ └── che-cluster-issuer.yml ├── devfile.yaml ├── docs/ │ ├── README.md │ └── grafana/ │ ├── dashboard.json │ └── openshift-console-dashboard.json ├── infrastructures/ │ ├── infrastructure-distributed/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── multiuser/ │ │ │ └── api/ │ │ │ └── distributed/ │ │ │ ├── WorkspaceStopPropagator.java │ │ │ ├── cache/ │ │ │ │ ├── JGroupsWorkspaceStatusCache.java │ │ │ │ ├── ReplicatedMapNotificationAdapter.java │ │ │ │ └── StatusChangeListener.java │ │ │ ├── lock/ │ │ │ │ └── JGroupsWorkspaceLockService.java │ │ │ └── subscription/ │ │ │ └── DistributedRemoteSubscriptionStorage.java │ │ └── test/ │ │ └── resources/ │ │ └── logback-test.xml │ ├── infrastructure-factory/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── api/ │ │ │ └── factory/ │ │ │ └── server/ │ │ │ └── scm/ │ │ │ ├── KubernetesScmModule.java │ │ │ └── kubernetes/ │ │ │ ├── KubernetesAuthorisationRequestManager.java │ │ │ ├── KubernetesGitCredentialManager.java │ │ │ └── KubernetesPersonalAccessTokenManager.java │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── api/ │ │ │ └── factory/ │ │ │ └── server/ │ │ │ └── scm/ │ │ │ └── kubernetes/ │ │ │ ├── KubernetesAuthorisationRequestManagerTest.java │ │ │ ├── KubernetesGitCredentialManagerTest.java │ │ │ └── KubernetesPersonalAccessTokenManagerTest.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── infrastructure-metrics/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── workspace/ │ │ │ └── infrastructure/ │ │ │ └── metrics/ │ │ │ ├── CurrentLogwatchersMeterBinder.java │ │ │ └── InfrastructureMetricsModule.java │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── workspace/ │ │ └── infrastructure/ │ │ └── metrics/ │ │ └── CurrentLogwatchersMeterBinderTest.java │ ├── infrastructure-permission/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── multiuser/ │ │ │ └── permission/ │ │ │ └── workspace/ │ │ │ └── infra/ │ │ │ └── kubernetes/ │ │ │ └── BrokerServicePermissionFilter.java │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── multiuser/ │ │ │ └── permission/ │ │ │ └── workspace/ │ │ │ └── infra/ │ │ │ └── kubernetes/ │ │ │ └── BrokerServicePermissionFilterTest.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── kubernetes/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── workspace/ │ │ │ └── infrastructure/ │ │ │ └── kubernetes/ │ │ │ ├── Annotations.java │ │ │ ├── CheServerKubernetesClientFactory.java │ │ │ ├── Constants.java │ │ │ ├── K8sInfraNamespaceWsAttributeValidator.java │ │ │ ├── KubernetesClientConfigFactory.java │ │ │ ├── KubernetesClientFactory.java │ │ │ ├── KubernetesClientTermination.java │ │ │ ├── KubernetesEnvironmentProvisioner.java │ │ │ ├── KubernetesInfraModule.java │ │ │ ├── KubernetesInfrastructure.java │ │ │ ├── KubernetesInfrastructureException.java │ │ │ ├── MachineLogsPublisher.java │ │ │ ├── Names.java │ │ │ ├── RuntimeLogsPublisher.java │ │ │ ├── StartSynchronizer.java │ │ │ ├── StartSynchronizerFactory.java │ │ │ ├── Warnings.java │ │ │ ├── api/ │ │ │ │ ├── server/ │ │ │ │ │ ├── KubernetesNamespaceService.java │ │ │ │ │ └── impls/ │ │ │ │ │ └── KubernetesNamespaceMetaImpl.java │ │ │ │ └── shared/ │ │ │ │ ├── KubernetesNamespaceMeta.java │ │ │ │ └── dto/ │ │ │ │ ├── DockerAuthConfig.java │ │ │ │ ├── DockerAuthConfigs.java │ │ │ │ └── KubernetesNamespaceMetaDto.java │ │ │ ├── authorization/ │ │ │ │ ├── AuthorizationChecker.java │ │ │ │ ├── AuthorizationException.java │ │ │ │ ├── KubernetesOIDCAuthorizationCheckerImpl.java │ │ │ │ └── PermissionsCleaner.java │ │ │ ├── cache/ │ │ │ │ ├── KubernetesMachineCache.java │ │ │ │ ├── KubernetesRuntimeStateCache.java │ │ │ │ └── jpa/ │ │ │ │ ├── JpaKubernetesMachineCache.java │ │ │ │ ├── JpaKubernetesRuntimeCacheModule.java │ │ │ │ └── JpaKubernetesRuntimeStateCache.java │ │ │ ├── devfile/ │ │ │ │ ├── ContainerSearch.java │ │ │ │ ├── DockerimageComponentToWorkspaceApplier.java │ │ │ │ ├── KubernetesComponentToWorkspaceApplier.java │ │ │ │ ├── KubernetesComponentValidator.java │ │ │ │ ├── KubernetesDevfileBindings.java │ │ │ │ ├── KubernetesEnvironmentProvisioner.java │ │ │ │ └── SelectorFilter.java │ │ │ ├── docker/ │ │ │ │ └── auth/ │ │ │ │ └── UserSpecificDockerRegistryCredentialsProvider.java │ │ │ ├── environment/ │ │ │ │ ├── CheInstallationLocation.java │ │ │ │ ├── KubernetesEnvironment.java │ │ │ │ ├── KubernetesEnvironmentFactory.java │ │ │ │ ├── KubernetesEnvironmentPodsValidator.java │ │ │ │ ├── KubernetesEnvironmentValidator.java │ │ │ │ ├── KubernetesRecipeParser.java │ │ │ │ ├── PodMerger.java │ │ │ │ └── util/ │ │ │ │ ├── EntryPoint.java │ │ │ │ └── EntryPointParser.java │ │ │ ├── event/ │ │ │ │ ├── KubernetesRuntimeStoppedEvent.java │ │ │ │ └── KubernetesRuntimeStoppingEvent.java │ │ │ ├── model/ │ │ │ │ ├── KubernetesMachineImpl.java │ │ │ │ ├── KubernetesRuntimeCommandImpl.java │ │ │ │ ├── KubernetesRuntimeState.java │ │ │ │ └── KubernetesServerImpl.java │ │ │ ├── multiuser/ │ │ │ │ └── oauth/ │ │ │ │ └── KubernetesOidcProviderConfigFactory.java │ │ │ ├── namespace/ │ │ │ │ ├── AbstractWorkspaceServiceAccount.java │ │ │ │ ├── K8sVersion.java │ │ │ │ ├── KubernetesConfigsMaps.java │ │ │ │ ├── KubernetesDeployments.java │ │ │ │ ├── KubernetesIngresses.java │ │ │ │ ├── KubernetesNamespace.java │ │ │ │ ├── KubernetesNamespaceFactory.java │ │ │ │ ├── KubernetesObjectUtil.java │ │ │ │ ├── KubernetesPersistentVolumeClaims.java │ │ │ │ ├── KubernetesSecrets.java │ │ │ │ ├── KubernetesServices.java │ │ │ │ ├── KubernetesWorkspaceServiceAccount.java │ │ │ │ ├── NamespaceNameValidator.java │ │ │ │ ├── RemoveNamespaceOnWorkspaceRemove.java │ │ │ │ ├── configurator/ │ │ │ │ │ ├── CredentialsSecretConfigurator.java │ │ │ │ │ ├── GitconfigConfigurator.java │ │ │ │ │ ├── NamespaceConfigurator.java │ │ │ │ │ ├── OAuthTokenSecretsConfigurator.java │ │ │ │ │ ├── PreferencesConfigMapConfigurator.java │ │ │ │ │ ├── SshConfigConfigurator.java │ │ │ │ │ ├── SshKeysConfigurator.java │ │ │ │ │ ├── UserPermissionConfigurator.java │ │ │ │ │ ├── UserPreferencesConfigurator.java │ │ │ │ │ ├── UserProfileConfigurator.java │ │ │ │ │ └── WorkspaceServiceAccountConfigurator.java │ │ │ │ ├── event/ │ │ │ │ │ ├── PodActionHandler.java │ │ │ │ │ ├── PodEvent.java │ │ │ │ │ └── PodEventHandler.java │ │ │ │ └── log/ │ │ │ │ ├── ContainerLogWatch.java │ │ │ │ ├── LogWatchTimeouts.java │ │ │ │ ├── LogWatcher.java │ │ │ │ ├── PodLogHandler.java │ │ │ │ ├── PodLogToEventPublisher.java │ │ │ │ └── event/ │ │ │ │ ├── WatchLogStartedEvent.java │ │ │ │ └── WatchLogStoppedEvent.java │ │ │ ├── provision/ │ │ │ │ ├── CertificateProvisioner.java │ │ │ │ ├── ConfigurationProvisioner.java │ │ │ │ ├── GatewayRouterProvisioner.java │ │ │ │ ├── GatewayTlsProvisioner.java │ │ │ │ ├── GitConfigProvisioner.java │ │ │ │ ├── ImagePullSecretProvisioner.java │ │ │ │ ├── IngressTlsProvisioner.java │ │ │ │ ├── KubernetesCheApiExternalEnvVarProvider.java │ │ │ │ ├── KubernetesCheApiInternalEnvVarProvider.java │ │ │ │ ├── KubernetesPreviewUrlCommandProvisioner.java │ │ │ │ ├── NamespaceProvisioner.java │ │ │ │ ├── PodTerminationGracePeriodProvisioner.java │ │ │ │ ├── PreviewUrlCommandProvisioner.java │ │ │ │ ├── SecurityContextProvisioner.java │ │ │ │ ├── ServiceAccountProvisioner.java │ │ │ │ ├── SshKeysProvisioner.java │ │ │ │ ├── TlsProvisioner.java │ │ │ │ ├── TlsProvisionerProvider.java │ │ │ │ ├── UniqueNamesProvisioner.java │ │ │ │ ├── VcsSslCertificateProvisioner.java │ │ │ │ ├── env/ │ │ │ │ │ └── EnvVarsConverter.java │ │ │ │ ├── limits/ │ │ │ │ │ └── ram/ │ │ │ │ │ └── ContainerResourceProvisioner.java │ │ │ │ ├── restartpolicy/ │ │ │ │ │ └── RestartPolicyRewriter.java │ │ │ │ ├── secret/ │ │ │ │ │ ├── EnvironmentVariableSecretApplier.java │ │ │ │ │ ├── FileSecretApplier.java │ │ │ │ │ ├── GitCredentialStorageFileSecretApplier.java │ │ │ │ │ ├── KubernetesSecretAnnotationNames.java │ │ │ │ │ ├── KubernetesSecretApplier.java │ │ │ │ │ └── SecretAsContainerResourceProvisioner.java │ │ │ │ └── server/ │ │ │ │ └── ServersConverter.java │ │ │ ├── server/ │ │ │ │ ├── AbstractExposureStrategyAwareProvider.java │ │ │ │ ├── IngressAnnotationsProvider.java │ │ │ │ ├── KubernetesServerExposer.java │ │ │ │ ├── PreviewUrlExposer.java │ │ │ │ ├── RuntimeServerBuilder.java │ │ │ │ ├── ServerServiceBuilder.java │ │ │ │ ├── WorkspaceExposureType.java │ │ │ │ ├── external/ │ │ │ │ │ ├── CombinedSingleHostServerExposer.java │ │ │ │ │ ├── DefaultHostExternalServiceExposureStrategy.java │ │ │ │ │ ├── ExternalServerExposer.java │ │ │ │ │ ├── ExternalServerExposerProvider.java │ │ │ │ │ ├── ExternalServerIngressBuilder.java │ │ │ │ │ ├── ExternalServiceExposureStrategy.java │ │ │ │ │ ├── GatewayRouteConfigGenerator.java │ │ │ │ │ ├── GatewayRouteConfigGeneratorFactory.java │ │ │ │ │ ├── GatewayServerExposer.java │ │ │ │ │ ├── IngressPathTransformInverter.java │ │ │ │ │ ├── IngressServerExposer.java │ │ │ │ │ ├── KubernetesExternalServerExposerProvider.java │ │ │ │ │ ├── MultiHostExternalServiceExposureStrategy.java │ │ │ │ │ ├── MultihostIngressServerExposer.java │ │ │ │ │ ├── ServiceExposureStrategyProvider.java │ │ │ │ │ ├── SingleHostExternalServiceExposureStrategy.java │ │ │ │ │ └── TraefikGatewayRouteConfigGenerator.java │ │ │ │ ├── resolver/ │ │ │ │ │ ├── AbstractServerResolver.java │ │ │ │ │ ├── AbstractServerResolverFactory.java │ │ │ │ │ ├── ConfigMapServerResolver.java │ │ │ │ │ ├── IngressServerResolver.java │ │ │ │ │ ├── KubernetesServerResolverFactory.java │ │ │ │ │ └── ServerResolver.java │ │ │ │ └── secure/ │ │ │ │ ├── DefaultSecureServerExposer.java │ │ │ │ ├── ProxyProvisioner.java │ │ │ │ ├── ProxyProvisionerFactory.java │ │ │ │ ├── SecureServerExposer.java │ │ │ │ ├── SecureServerExposerFactory.java │ │ │ │ ├── SecureServerExposerFactoryProvider.java │ │ │ │ └── jwtproxy/ │ │ │ │ ├── AbstractJwtProxyProvisioner.java │ │ │ │ ├── CookiePathStrategy.java │ │ │ │ ├── JwtProxyConfigBuilder.java │ │ │ │ ├── JwtProxyProvisioner.java │ │ │ │ ├── JwtProxySecureServerExposer.java │ │ │ │ ├── MultiHostCookiePathStrategy.java │ │ │ │ ├── PassThroughProxyProvisioner.java │ │ │ │ ├── PassThroughProxySecureServerExposer.java │ │ │ │ ├── factory/ │ │ │ │ │ ├── JwtProxyConfigBuilderFactory.java │ │ │ │ │ ├── JwtProxyProvisionerFactory.java │ │ │ │ │ ├── JwtProxySecureServerExposerFactory.java │ │ │ │ │ ├── PassThroughProxyProvisionerFactory.java │ │ │ │ │ └── PassThroughProxySecureServerExposerFactory.java │ │ │ │ └── model/ │ │ │ │ ├── Config.java │ │ │ │ ├── JWTProxy.java │ │ │ │ ├── RegistrableComponentConfig.java │ │ │ │ ├── SignerProxyConfig.java │ │ │ │ ├── VerifierConfig.java │ │ │ │ └── VerifierProxyConfig.java │ │ │ ├── util/ │ │ │ │ ├── Containers.java │ │ │ │ ├── EnvVars.java │ │ │ │ ├── GatewayConfigmapLabels.java │ │ │ │ ├── Ingresses.java │ │ │ │ ├── KubernetesSharedPool.java │ │ │ │ ├── KubernetesSize.java │ │ │ │ ├── NonTlsDistributedClusterModeNotifier.java │ │ │ │ ├── PodEvents.java │ │ │ │ ├── RuntimeEventsPublisher.java │ │ │ │ ├── Services.java │ │ │ │ ├── TracingSpanConstants.java │ │ │ │ ├── UnrecoverablePodEventListener.java │ │ │ │ └── UnrecoverablePodEventListenerFactory.java │ │ │ └── wsplugins/ │ │ │ ├── BrokersResult.java │ │ │ ├── ChePluginsVolumeApplier.java │ │ │ ├── K8sContainerResolver.java │ │ │ ├── K8sContainerResolverBuilder.java │ │ │ ├── KubernetesArtifactsBrokerApplier.java │ │ │ ├── KubernetesPluginsToolingApplier.java │ │ │ ├── KubernetesPluginsToolingValidator.java │ │ │ ├── MachineResolver.java │ │ │ ├── MachineResolverBuilder.java │ │ │ ├── PluginBrokerManager.java │ │ │ ├── SidecarServicesProvisioner.java │ │ │ ├── brokerphases/ │ │ │ │ ├── BrokerEnvironmentFactory.java │ │ │ │ ├── BrokerPhase.java │ │ │ │ ├── DeployBroker.java │ │ │ │ ├── KubernetesBrokerEnvironmentFactory.java │ │ │ │ ├── ListenBrokerEvents.java │ │ │ │ ├── PrepareStorage.java │ │ │ │ └── WaitBrokerResult.java │ │ │ └── events/ │ │ │ ├── BrokerEvent.java │ │ │ ├── BrokerService.java │ │ │ └── BrokerStatusListener.java │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── workspace/ │ │ │ └── infrastructure/ │ │ │ └── kubernetes/ │ │ │ ├── AnnotationsTest.java │ │ │ ├── K8sInfraNamespaceWsAttributeValidatorTest.java │ │ │ ├── KubernetesArtifactsBrokerApplierTest.java │ │ │ ├── KubernetesEnvironmentProvisionerTest.java │ │ │ ├── StartSynchronizerTest.java │ │ │ ├── api/ │ │ │ │ └── server/ │ │ │ │ └── KubernetesNamespaceServiceTest.java │ │ │ ├── authorization/ │ │ │ │ └── KubernetesAuthorizationCheckerTest.java │ │ │ ├── devfile/ │ │ │ │ ├── ContainerSearchTest.java │ │ │ │ ├── DockerimageComponentToWorkspaceApplierTest.java │ │ │ │ ├── KubernetesComponentIntegrityValidatorTest.java │ │ │ │ ├── KubernetesComponentToWorkspaceApplierTest.java │ │ │ │ └── KubernetesEnvironmentProvisionerTest.java │ │ │ ├── docker/ │ │ │ │ └── auth/ │ │ │ │ └── UserSpecificDockerRegistryCredentialsProviderTest.java │ │ │ ├── environment/ │ │ │ │ ├── CheInstallationLocationTest.java │ │ │ │ ├── KubernetesEnvironmentFactoryTest.java │ │ │ │ ├── KubernetesEnvironmentPodsValidatorTest.java │ │ │ │ ├── KubernetesEnvironmentValidatorTest.java │ │ │ │ ├── PodMergerTest.java │ │ │ │ └── util/ │ │ │ │ └── EntryPointParserTest.java │ │ │ ├── multiuser/ │ │ │ │ └── oauth/ │ │ │ │ └── KubernetesOidcProviderConfigFactoryTest.java │ │ │ ├── namespace/ │ │ │ │ ├── K8sVersionTest.java │ │ │ │ ├── KubernetesDeploymentsTest.java │ │ │ │ ├── KubernetesNamespaceFactoryTest.java │ │ │ │ ├── KubernetesNamespaceTest.java │ │ │ │ ├── KubernetesObjectUtilTest.java │ │ │ │ ├── KubernetesSecretsTest.java │ │ │ │ ├── KubernetesWorkspaceServiceAccountTest.java │ │ │ │ ├── NamespaceNameValidatorTest.java │ │ │ │ ├── RemoveNamespaceOnWorkspaceRemoveTest.java │ │ │ │ ├── configurator/ │ │ │ │ │ ├── CredentialsSecretConfiguratorTest.java │ │ │ │ │ ├── GitconfigConfiguratorTest.java │ │ │ │ │ ├── PreferencesConfigMapConfiguratorTest.java │ │ │ │ │ ├── SshKeysConfiguratorTest.java │ │ │ │ │ ├── UserPermissionConfiguratorTest.java │ │ │ │ │ ├── UserPreferencesConfiguratorTest.java │ │ │ │ │ └── WorkspaceServiceAccountConfiguratorTest.java │ │ │ │ ├── log/ │ │ │ │ │ ├── ContainerLogWatchTest.java │ │ │ │ │ ├── LogWatcherTest.java │ │ │ │ │ └── PodLogToEventPublisherTest.java │ │ │ │ └── pvc/ │ │ │ │ └── TestObjects.java │ │ │ ├── provision/ │ │ │ │ ├── CertificateProvisionerTest.java │ │ │ │ ├── GatewayRouterProvisionerTest.java │ │ │ │ ├── GatewayTlsProvisionerTest.java │ │ │ │ ├── GitConfigProvisionerTest.java │ │ │ │ ├── ImagePullSecretProvisionerTest.java │ │ │ │ ├── IngressTlsProvisionerTest.java │ │ │ │ ├── KubernetesPreviewUrlCommandProvisionerTest.java │ │ │ │ ├── SecurityContextProvisionerTest.java │ │ │ │ ├── SshKeySecretProvisionerTest.java │ │ │ │ ├── UniqueNamesProvisionerTest.java │ │ │ │ ├── VcsSslCertificateProvisionerTest.java │ │ │ │ ├── env/ │ │ │ │ │ └── EnvVarsConverterTest.java │ │ │ │ ├── limits/ │ │ │ │ │ └── ram/ │ │ │ │ │ └── ContainerResourceProvisionerTest.java │ │ │ │ ├── restartpolicy/ │ │ │ │ │ └── RestartPolicyRewriterTest.java │ │ │ │ └── secret/ │ │ │ │ ├── EnvironmentVariableSecretApplierTest.java │ │ │ │ ├── FileSecretApplierTest.java │ │ │ │ ├── GitCredentialStorageFileSecretApplierTest.java │ │ │ │ └── SecretAsContainerResourceProvisionerTest.java │ │ │ ├── server/ │ │ │ │ ├── IngressServerResolverTest.java │ │ │ │ ├── KubernetesServerExposerTest.java │ │ │ │ ├── PreviewUrlExposerTest.java │ │ │ │ ├── external/ │ │ │ │ │ ├── CombinedSingleHostServerExposerTest.java │ │ │ │ │ ├── DefaultHostExternalServiceExposureStrategyTest.java │ │ │ │ │ ├── ExternalServerIngressBuilderTest.java │ │ │ │ │ ├── GatewayServerExposerTest.java │ │ │ │ │ ├── IngressPathTransformInverterTest.java │ │ │ │ │ ├── IngressServerExposerTest.java │ │ │ │ │ ├── MultiHostExternalServiceExposureStrategyTest.java │ │ │ │ │ └── TraefikGatewayRouteConfigGeneratorTest.java │ │ │ │ ├── resolver/ │ │ │ │ │ ├── AbstractServerResolverTest.java │ │ │ │ │ └── ConfigMapServerResolverTest.java │ │ │ │ └── secure/ │ │ │ │ ├── SecureServerExposerFactoryProviderTest.java │ │ │ │ └── jwtproxy/ │ │ │ │ ├── JwtProxyConfigBuilderTest.java │ │ │ │ ├── JwtProxyProvisionerTest.java │ │ │ │ ├── JwtProxySecureServerExposerTest.java │ │ │ │ └── PassThroughProxyProvisionerTest.java │ │ │ ├── util/ │ │ │ │ ├── ContainersTest.java │ │ │ │ ├── EnvVarsTest.java │ │ │ │ ├── GatewayConfigmapLabelsTest.java │ │ │ │ ├── IngressesTest.java │ │ │ │ ├── KubernetesSizeTest.java │ │ │ │ ├── PodEventsTest.java │ │ │ │ ├── ServicesTest.java │ │ │ │ └── UnrecoverablePodEventListenerTest.java │ │ │ └── wsplugins/ │ │ │ ├── BrokersResultTest.java │ │ │ ├── K8sContainerResolverBuilderTest.java │ │ │ ├── K8sContainerResolverTest.java │ │ │ ├── KubernetesPluginsToolingApplierTest.java │ │ │ ├── MachineResolverTest.java │ │ │ ├── SidecarServicesProvisionerTest.java │ │ │ ├── brokerphases/ │ │ │ │ ├── BrokerEnvironmentFactoryTest.java │ │ │ │ └── DeployBrokerTest.java │ │ │ └── events/ │ │ │ └── BrokerStatusListenerTest.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ └── org.eclipse.che.commons.test.tck.TckModule │ │ ├── devfile/ │ │ │ ├── petclinic.yaml │ │ │ └── petclinicPods.yaml │ │ ├── jwtproxy-confg.yaml │ │ └── logback-test.xml │ ├── openshift/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── workspace/ │ │ │ └── infrastructure/ │ │ │ └── openshift/ │ │ │ ├── CheServerOpenshiftClientFactory.java │ │ │ ├── Constants.java │ │ │ ├── OpenShiftClientFactory.java │ │ │ ├── OpenShiftEnvironmentProvisioner.java │ │ │ ├── OpenShiftInfraModule.java │ │ │ ├── OpenShiftInfrastructure.java │ │ │ ├── authorization/ │ │ │ │ └── OpenShiftAuthorizationCheckerImpl.java │ │ │ ├── devfile/ │ │ │ │ └── OpenshiftComponentToWorkspaceApplier.java │ │ │ ├── environment/ │ │ │ │ ├── OpenShiftEnvironment.java │ │ │ │ ├── OpenShiftEnvironmentFactory.java │ │ │ │ └── OpenShiftEnvironmentValidator.java │ │ │ ├── multiuser/ │ │ │ │ └── oauth/ │ │ │ │ └── OpenshiftTokenInitializationFilter.java │ │ │ ├── project/ │ │ │ │ ├── OpenShiftProject.java │ │ │ │ ├── OpenShiftProjectFactory.java │ │ │ │ ├── OpenShiftRoutes.java │ │ │ │ ├── OpenShiftWorkspaceServiceAccount.java │ │ │ │ └── configurator/ │ │ │ │ └── OpenShiftWorkspaceServiceAccountConfigurator.java │ │ │ ├── provision/ │ │ │ │ ├── OpenShiftPreviewUrlCommandProvisioner.java │ │ │ │ ├── OpenShiftUniqueNamesProvisioner.java │ │ │ │ └── RouteTlsProvisioner.java │ │ │ ├── server/ │ │ │ │ ├── OpenShiftCookiePathStrategy.java │ │ │ │ ├── OpenShiftPreviewUrlExposer.java │ │ │ │ ├── OpenShiftServerExposureStrategy.java │ │ │ │ ├── OpenShiftServerResolverFactory.java │ │ │ │ ├── RouteServerExposer.java │ │ │ │ ├── RouteServerResolver.java │ │ │ │ └── external/ │ │ │ │ └── OpenShiftExternalServerExposerProvider.java │ │ │ ├── util/ │ │ │ │ └── Routes.java │ │ │ └── wsplugins/ │ │ │ └── brokerphases/ │ │ │ └── OpenshiftBrokerEnvironmentFactory.java │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── workspace/ │ │ │ └── infrastructure/ │ │ │ └── openshift/ │ │ │ ├── OpenShiftEnvironmentProvisionerTest.java │ │ │ ├── RouteServerResolverTest.java │ │ │ ├── authorization/ │ │ │ │ └── OpenShiftAuthorizationCheckerTest.java │ │ │ ├── devfile/ │ │ │ │ └── OpenshiftComponentToWorkspaceApplierTest.java │ │ │ ├── environment/ │ │ │ │ ├── OpenShiftEnvironmentFactoryTest.java │ │ │ │ └── OpenShiftEnvironmentValidatorTest.java │ │ │ ├── multiuser/ │ │ │ │ └── oauth/ │ │ │ │ └── OpenshiftTokenInitializationFilterTest.java │ │ │ ├── project/ │ │ │ │ ├── OpenShiftProjectFactoryTest.java │ │ │ │ ├── OpenShiftProjectTest.java │ │ │ │ └── configurator/ │ │ │ │ └── OpenShiftWorkspaceServiceAccountConfiguratorTest.java │ │ │ ├── provision/ │ │ │ │ ├── OpenShiftPreviewUrlCommandProvisionerTest.java │ │ │ │ ├── OpenShiftUniqueNamesProvisionerTest.java │ │ │ │ └── RouteTlsProvisionerTest.java │ │ │ ├── server/ │ │ │ │ ├── OpenShiftExternalServerExposerTest.java │ │ │ │ └── OpenShiftPreviewUrlExposerTest.java │ │ │ └── util/ │ │ │ └── RoutesTest.java │ │ └── resources/ │ │ ├── devfile/ │ │ │ └── petclinic.yaml │ │ └── logback-test.xml │ └── pom.xml ├── make-release.sh ├── multiuser/ │ ├── api/ │ │ ├── che-multiuser-api-authentication-commons/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── multiuser/ │ │ │ │ └── api/ │ │ │ │ └── authentication/ │ │ │ │ └── commons/ │ │ │ │ ├── Constants.java │ │ │ │ ├── DestroySessionListener.java │ │ │ │ ├── SessionStore.java │ │ │ │ ├── SubjectHttpRequestWrapper.java │ │ │ │ ├── filter/ │ │ │ │ │ └── MultiUserEnvironmentInitializationFilter.java │ │ │ │ └── token/ │ │ │ │ ├── ChainedTokenExtractor.java │ │ │ │ ├── HeaderRequestTokenExtractor.java │ │ │ │ ├── QueryRequestTokenExtractor.java │ │ │ │ └── RequestTokenExtractor.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── multiuser/ │ │ │ └── api/ │ │ │ └── authentication/ │ │ │ └── commons/ │ │ │ ├── SessionStoreTest.java │ │ │ ├── SubjectHttpRequestWrapperTest.java │ │ │ ├── filter/ │ │ │ │ └── MultiUserEnvironmentInitializationFilterTest.java │ │ │ └── token/ │ │ │ └── HeaderRequestTokenExtractorTest.java │ │ ├── che-multiuser-api-authorization/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── multiuser/ │ │ │ └── api/ │ │ │ └── permission/ │ │ │ └── server/ │ │ │ ├── AuthorizedSubject.java │ │ │ ├── HttpPermissionCheckerImpl.java │ │ │ └── PermissionChecker.java │ │ ├── che-multiuser-api-authorization-impl/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── multiuser/ │ │ │ │ └── api/ │ │ │ │ └── permission/ │ │ │ │ └── server/ │ │ │ │ └── PermissionCheckerImpl.java │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── multiuser/ │ │ │ │ └── api/ │ │ │ │ └── permission/ │ │ │ │ └── server/ │ │ │ │ └── PermissionCheckerImplTest.java │ │ │ └── resources/ │ │ │ └── logback-test.xml │ │ ├── che-multiuser-api-permission/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── multiuser/ │ │ │ └── api/ │ │ │ └── permission/ │ │ │ └── server/ │ │ │ ├── AbstractPermissionsDomain.java │ │ │ ├── InstanceParameterValidator.java │ │ │ ├── PermissionsManager.java │ │ │ ├── PermissionsModule.java │ │ │ ├── SuperPrivilegesChecker.java │ │ │ ├── SystemDomain.java │ │ │ ├── account/ │ │ │ │ ├── AccountOperation.java │ │ │ │ └── AccountPermissionsChecker.java │ │ │ ├── event/ │ │ │ │ ├── PermissionsCreatedEvent.java │ │ │ │ └── PermissionsRemovedEvent.java │ │ │ ├── filter/ │ │ │ │ ├── GetPermissionsFilter.java │ │ │ │ ├── RemovePermissionsFilter.java │ │ │ │ ├── SetPermissionsFilter.java │ │ │ │ └── check/ │ │ │ │ ├── DefaultRemovePermissionsChecker.java │ │ │ │ ├── DefaultSetPermissionsChecker.java │ │ │ │ ├── DomainsPermissionsCheckers.java │ │ │ │ ├── RemovePermissionsChecker.java │ │ │ │ └── SetPermissionsChecker.java │ │ │ ├── jsonrpc/ │ │ │ │ ├── JsonRpcPermissionsFilterAdapter.java │ │ │ │ ├── RemoteSubscriptionPermissionCheck.java │ │ │ │ └── RemoteSubscriptionPermissionManager.java │ │ │ ├── model/ │ │ │ │ └── impl/ │ │ │ │ ├── AbstractPermissions.java │ │ │ │ └── SystemPermissionsImpl.java │ │ │ └── spi/ │ │ │ └── PermissionsDao.java │ │ ├── che-multiuser-api-permission-shared/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── multiuser/ │ │ │ └── api/ │ │ │ └── permission/ │ │ │ └── shared/ │ │ │ ├── dto/ │ │ │ │ ├── DomainDto.java │ │ │ │ └── PermissionsDto.java │ │ │ ├── event/ │ │ │ │ ├── EventType.java │ │ │ │ └── PermissionsEvent.java │ │ │ └── model/ │ │ │ ├── Permissions.java │ │ │ └── PermissionsDomain.java │ │ └── pom.xml │ ├── machine-auth/ │ │ ├── che-multiuser-machine-authentication/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── multiuser/ │ │ │ │ └── machine/ │ │ │ │ └── authentication/ │ │ │ │ └── server/ │ │ │ │ ├── MachineAuthModule.java │ │ │ │ ├── MachineAuthenticatedResource.java │ │ │ │ ├── MachineLoginFilter.java │ │ │ │ ├── MachineSessionInvalidator.java │ │ │ │ ├── MachineSigningKeyResolver.java │ │ │ │ ├── MachineTokenAccessFilter.java │ │ │ │ ├── MachineTokenAuthorizedSubject.java │ │ │ │ ├── MachineTokenProviderImpl.java │ │ │ │ ├── MachineTokenRegistry.java │ │ │ │ ├── NotMachineTokenJwtException.java │ │ │ │ └── signature/ │ │ │ │ ├── SignatureAlgorithmEnvProvider.java │ │ │ │ ├── SignatureKey.java │ │ │ │ ├── SignatureKeyManager.java │ │ │ │ ├── SignatureKeyManagerException.java │ │ │ │ ├── SignatureKeyPair.java │ │ │ │ ├── SignaturePublicKeyEnvProvider.java │ │ │ │ ├── jpa/ │ │ │ │ │ └── JpaSignatureKeyDao.java │ │ │ │ ├── model/ │ │ │ │ │ └── impl/ │ │ │ │ │ ├── SignatureKeyImpl.java │ │ │ │ │ └── SignatureKeyPairImpl.java │ │ │ │ └── spi/ │ │ │ │ └── SignatureKeyDao.java │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── eclipse/ │ │ │ │ └── che/ │ │ │ │ └── multiuser/ │ │ │ │ └── machine/ │ │ │ │ └── authentication/ │ │ │ │ └── server/ │ │ │ │ ├── MachineLoginFilterTest.java │ │ │ │ └── signature/ │ │ │ │ └── SignatureKeyManagerTest.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── services/ │ │ │ │ └── org.eclipse.che.commons.test.tck.TckModule │ │ │ └── logback-test.xml │ │ ├── che-multiuser-machine-authentication-shared/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── multiuser/ │ │ │ └── machine/ │ │ │ └── authentication/ │ │ │ └── shared/ │ │ │ ├── Constants.java │ │ │ └── dto/ │ │ │ └── MachineTokenDto.java │ │ └── pom.xml │ ├── oidc/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── eclipse/ │ │ │ └── che/ │ │ │ └── multiuser/ │ │ │ └── oidc/ │ │ │ ├── OIDCInfo.java │ │ │ ├── OIDCInfoProvider.java │ │ │ ├── OIDCJwkProvider.java │ │ │ ├── OIDCJwtParserProvider.java │ │ │ ├── OIDCSigningKeyResolver.java │ │ │ └── filter/ │ │ │ └── OidcTokenInitializationFilter.java │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── multiuser/ │ │ └── oidc/ │ │ └── filter/ │ │ └── OidcTokenInitializationFilterTest.java │ └── pom.xml ├── pom.xml ├── typescript-dto/ │ ├── .gitignore │ ├── .yarn/ │ │ └── releases/ │ │ └── yarn-4.12.0.cjs │ ├── .yarnrc.yml │ ├── Dockerfile │ ├── README.md │ ├── build.sh │ ├── dto-pom.xml │ ├── package.json │ └── publish.sh └── wsmaster/ ├── che-core-api-account/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── account/ │ │ ├── api/ │ │ │ └── AccountManager.java │ │ ├── shared/ │ │ │ └── model/ │ │ │ └── Account.java │ │ └── spi/ │ │ ├── AccountDao.java │ │ ├── AccountImpl.java │ │ └── AccountValidator.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── account/ │ │ └── spi/ │ │ └── AccountValidatorTest.java │ └── resources/ │ ├── META-INF/ │ │ └── services/ │ │ └── org.eclipse.che.commons.test.tck.TckModule │ └── logback-test.xml ├── che-core-api-auth/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── security/ │ │ ├── oauth/ │ │ │ ├── EmbeddedOAuthAPI.java │ │ │ ├── OAuth2.gwt.xml │ │ │ ├── OAuthAPI.java │ │ │ ├── OAuthAuthenticationException.java │ │ │ ├── OAuthAuthenticationService.java │ │ │ ├── OAuthAuthenticator.java │ │ │ ├── OAuthAuthenticatorProvider.java │ │ │ ├── OAuthAuthenticatorProviderImpl.java │ │ │ └── OAuthAuthenticatorTokenProvider.java │ │ └── oauth1/ │ │ ├── OAuthAuthenticationException.java │ │ ├── OAuthAuthenticationService.java │ │ ├── OAuthAuthenticator.java │ │ ├── OAuthAuthenticatorProvider.java │ │ └── UserDeniedOAuthAuthenticationException.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── security/ │ │ ├── oauth/ │ │ │ └── EmbeddedOAuthAPITest.java │ │ └── oauth1/ │ │ ├── OAuthAuthenticationServiceTest.java │ │ └── OAuthAuthenticatorTest.java │ └── resources/ │ └── logback-test.xml ├── che-core-api-auth-azure-devops/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── security/ │ └── oauth/ │ ├── AzureDevOpsModule.java │ ├── AzureDevOpsOAuthAuthenticator.java │ ├── AzureDevOpsOAuthAuthenticatorProvider.java │ ├── AzureDevOpsRefreshToken.java │ ├── AzureDevOpsTokenResponse.java │ └── AzureDevOpsUserProfile.java ├── che-core-api-auth-bitbucket/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── security/ │ │ ├── BitbucketModule.java │ │ ├── oauth/ │ │ │ ├── BitbucketOAuthAuthenticator.java │ │ │ ├── BitbucketOAuthAuthenticatorProvider.java │ │ │ └── BitbucketUser.java │ │ └── oauth1/ │ │ ├── BitbucketServerOAuthAuthenticator.java │ │ ├── BitbucketServerOAuthAuthenticatorProvider.java │ │ └── NoopOAuthAuthenticator.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── security/ │ │ ├── BitbucketOAuthAuthenticatorProviderTest.java │ │ └── oauth1/ │ │ └── BitbucketServerOAuthAuthenticatorProviderTest.java │ └── resources/ │ └── logback-test.xml ├── che-core-api-auth-github/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── security/ │ │ └── oauth/ │ │ ├── GitHubOAuthAuthenticatorProvider.java │ │ ├── GitHubOAuthAuthenticatorProviderSecond.java │ │ └── GithubModule.java │ └── test/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── security/ │ └── oauth/ │ └── GitHubOAuthAuthenticatorProviderTest.java ├── che-core-api-auth-github-common/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── security/ │ └── oauth/ │ ├── AbstractGitHubOAuthAuthenticatorProvider.java │ ├── GitHubOAuthAuthenticator.java │ └── GitHubUser.java ├── che-core-api-auth-gitlab/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── security/ │ │ └── oauth/ │ │ ├── GitLabModule.java │ │ ├── GitLabOAuthAuthenticatorProvider.java │ │ └── GitLabOAuthAuthenticatorProviderSecond.java │ └── test/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── security/ │ └── oauth/ │ ├── GitLabAuthenticatorTest.java │ └── GitLabOAuthAuthenticatorProviderTest.java ├── che-core-api-auth-gitlab-common/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── security/ │ └── oauth/ │ ├── AbstractGitLabOAuthAuthenticatorProvider.java │ ├── GitLabOAuthAuthenticator.java │ └── GitLabUser.java ├── che-core-api-auth-openshift/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── security/ │ └── oauth/ │ ├── OpenShiftOAuthAuthenticator.java │ └── OpenShiftOAuthModule.java ├── che-core-api-auth-shared/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ ├── api/ │ │ └── auth/ │ │ └── shared/ │ │ └── dto/ │ │ └── OAuthToken.java │ └── security/ │ └── oauth/ │ └── shared/ │ ├── OAuthAuthorizationHeaderProvider.java │ ├── OAuthTokenProvider.java │ ├── User.java │ └── dto/ │ └── OAuthAuthenticatorDescriptor.java ├── che-core-api-devfile/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── devfile/ │ │ └── server/ │ │ ├── DtoConverter.java │ │ ├── UserDevfileEntityProvider.java │ │ └── model/ │ │ └── impl/ │ │ └── UserDevfileImpl.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── devfile/ │ │ └── server/ │ │ ├── TestObjectGenerator.java │ │ └── jpa/ │ │ └── UserDevfileTckModule.java │ └── resources/ │ ├── META-INF/ │ │ └── services/ │ │ └── org.eclipse.che.commons.test.tck.TckModule │ └── logback-test.xml ├── che-core-api-devfile-shared/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── api/ │ └── devfile/ │ └── shared/ │ ├── Constants.java │ ├── dto/ │ │ └── UserDevfileDto.java │ └── event/ │ ├── DevfileCreatedEvent.java │ ├── DevfileDeletedEvent.java │ └── DevfileUpdatedEvent.java ├── che-core-api-factory/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ ├── AdditionalFilenamesProvider.java │ │ ├── ApiExceptionMapper.java │ │ ├── BaseFactoryParameterResolver.java │ │ ├── DtoConverter.java │ │ ├── FactoryAcceptValidator.java │ │ ├── FactoryConstants.java │ │ ├── FactoryLinksHelper.java │ │ ├── FactoryManager.java │ │ ├── FactoryParameterValidator.java │ │ ├── FactoryParametersResolver.java │ │ ├── FactoryResolverPriority.java │ │ ├── FactoryService.java │ │ ├── LegacyConverter.java │ │ ├── RawDevfileUrlFactoryParameterResolver.java │ │ ├── ScmFileResolver.java │ │ ├── ScmService.java │ │ ├── ValueHelper.java │ │ ├── impl/ │ │ │ ├── FactoryAcceptValidatorImpl.java │ │ │ ├── FactoryBaseValidator.java │ │ │ └── SourceStorageParametersValidator.java │ │ ├── jpa/ │ │ │ ├── FactoryJpaModule.java │ │ │ └── JpaFactoryDao.java │ │ ├── model/ │ │ │ └── impl/ │ │ │ ├── ActionImpl.java │ │ │ ├── AuthorImpl.java │ │ │ ├── FactoryImpl.java │ │ │ ├── IdeImpl.java │ │ │ ├── OnAppClosedImpl.java │ │ │ ├── OnAppLoadedImpl.java │ │ │ ├── OnProjectsLoadedImpl.java │ │ │ └── PoliciesImpl.java │ │ ├── scm/ │ │ │ ├── AbstractGitUserDataFetcher.java │ │ │ ├── AuthorisationRequestManager.java │ │ │ ├── AuthorizingFileContentProvider.java │ │ │ ├── GitCredentialManager.java │ │ │ ├── GitUserData.java │ │ │ ├── GitUserDataFetcher.java │ │ │ ├── PersonalAccessToken.java │ │ │ ├── PersonalAccessTokenFetcher.java │ │ │ ├── PersonalAccessTokenManager.java │ │ │ ├── PersonalAccessTokenParams.java │ │ │ ├── ScmPersonalAccessTokenFetcher.java │ │ │ └── exception/ │ │ │ ├── ExceptionMessages.java │ │ │ ├── ScmBadRequestException.java │ │ │ ├── ScmCommunicationException.java │ │ │ ├── ScmConfigurationPersistenceException.java │ │ │ ├── ScmItemNotFoundException.java │ │ │ ├── ScmUnauthorizedException.java │ │ │ ├── UnknownScmProviderException.java │ │ │ └── UnsatisfiedScmPreconditionException.java │ │ ├── spi/ │ │ │ └── FactoryDao.java │ │ └── urlfactory/ │ │ ├── DefaultFactoryUrl.java │ │ ├── DevfileFilenamesProvider.java │ │ ├── RemoteFactoryUrl.java │ │ └── URLFactoryBuilder.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ ├── ApiExceptionMapperTest.java │ │ ├── BaseFactoryParameterResolverTest.java │ │ ├── FactoryLinksHelperTest.java │ │ ├── FactoryManagerTest.java │ │ ├── FactoryServiceTest.java │ │ ├── RawDevfileUrlFactoryParameterResolverTest.java │ │ ├── impl/ │ │ │ ├── FactoryBaseValidatorTest.java │ │ │ ├── SourceProjectParametersValidatorTest.java │ │ │ └── TesterFactoryBaseValidator.java │ │ ├── jpa/ │ │ │ └── FactoryTckModule.java │ │ ├── scm/ │ │ │ └── AuthorizingFactoryParameterResolverTest.java │ │ └── urlfactory/ │ │ ├── DefaultFactoryUrlTest.java │ │ └── URLFactoryBuilderTest.java │ └── resources/ │ ├── META-INF/ │ │ └── services/ │ │ └── org.eclipse.che.commons.test.tck.TckModule │ ├── logback-test.xml │ └── logging.properties ├── che-core-api-factory-azure-devops/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── azure/ │ │ └── devops/ │ │ ├── AzureDevOps.java │ │ ├── AzureDevOpsApiClient.java │ │ ├── AzureDevOpsAuthorizingFileContentProvider.java │ │ ├── AzureDevOpsFactoryParametersResolver.java │ │ ├── AzureDevOpsModule.java │ │ ├── AzureDevOpsPersonalAccessTokenFetcher.java │ │ ├── AzureDevOpsScmFileResolver.java │ │ ├── AzureDevOpsServerApiClient.java │ │ ├── AzureDevOpsServerUserIdentity.java │ │ ├── AzureDevOpsServerUserPreferences.java │ │ ├── AzureDevOpsServerUserProfile.java │ │ ├── AzureDevOpsURLParser.java │ │ ├── AzureDevOpsUrl.java │ │ ├── AzureDevOpsUser.java │ │ └── AzureDevOpsUserDataFetcher.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── azure/ │ │ └── devops/ │ │ ├── AzureDevOpsPersonalAccessTokenFetcherTest.java │ │ ├── AzureDevOpsURLParserTest.java │ │ └── AzureDevOpsURLTest.java │ └── resources/ │ ├── __files/ │ │ ├── azure-devops/ │ │ │ └── rest/ │ │ │ └── user/ │ │ │ ├── email/ │ │ │ │ └── response.json │ │ │ └── response.json │ │ └── azure-devops-server/ │ │ └── rest/ │ │ └── user/ │ │ └── response.json │ └── logback-test.xml ├── che-core-api-factory-bitbucket/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── bitbucket/ │ │ ├── BitbucketApiClient.java │ │ ├── BitbucketAuthorizingFileContentProvider.java │ │ ├── BitbucketFactoryParametersResolver.java │ │ ├── BitbucketModule.java │ │ ├── BitbucketPersonalAccessTokenFetcher.java │ │ ├── BitbucketScmFileResolver.java │ │ ├── BitbucketSourceStorageBuilder.java │ │ ├── BitbucketURLParser.java │ │ ├── BitbucketUrl.java │ │ ├── BitbucketUser.java │ │ ├── BitbucketUserDataFetcher.java │ │ └── BitbucketUserEmail.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── bitbucket/ │ │ ├── BitbucketApiClientTest.java │ │ ├── BitbucketAuthorizingFileContentProviderTest.java │ │ ├── BitbucketFactoryParametersResolverTest.java │ │ ├── BitbucketGitUserDataFetcherTest.java │ │ ├── BitbucketPersonalAccessTokenFetcherTest.java │ │ ├── BitbucketScmFileResolverTest.java │ │ ├── BitbucketURLParserTest.java │ │ └── BitbucketUrlTest.java │ └── resources/ │ ├── __files/ │ │ └── bitbucket/ │ │ └── rest/ │ │ └── user/ │ │ ├── email/ │ │ │ └── response.json │ │ └── response.json │ └── logback-test.xml ├── che-core-api-factory-bitbucket-server/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── bitbucket/ │ │ ├── BitbucketServerApiProvider.java │ │ ├── BitbucketServerAuthorizingFactoryParametersResolver.java │ │ ├── BitbucketServerAuthorizingFileContentProvider.java │ │ ├── BitbucketServerModule.java │ │ ├── BitbucketServerPersonalAccessTokenFetcher.java │ │ ├── BitbucketServerScmFileResolver.java │ │ ├── BitbucketServerURLParser.java │ │ ├── BitbucketServerUrl.java │ │ ├── BitbucketServerUserDataFetcher.java │ │ ├── HttpBitbucketServerApiClient.java │ │ └── server/ │ │ ├── BitbucketApplicationProperties.java │ │ ├── BitbucketPersonalAccessToken.java │ │ ├── BitbucketServerApiClient.java │ │ ├── BitbucketUser.java │ │ ├── NoopBitbucketServerApiClient.java │ │ └── Page.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── bitbucket/ │ │ ├── BitbucketServerApiClientProviderTest.java │ │ ├── BitbucketServerAuthorizingFactoryParametersResolverTest.java │ │ ├── BitbucketServerAuthorizingFileContentProviderTest.java │ │ ├── BitbucketServerPersonalAccessTokenFetcherTest.java │ │ ├── BitbucketServerScmFileResolverTest.java │ │ ├── BitbucketServerURLParserTest.java │ │ ├── BitbucketServerURLTest.java │ │ ├── BitbucketServerUserDataFetcherTest.java │ │ └── HttpBitbucketServerApiClientTest.java │ └── resources/ │ ├── __files/ │ │ └── bitbucket/ │ │ └── rest/ │ │ ├── access-tokens/ │ │ │ └── 1.0/ │ │ │ └── users/ │ │ │ └── ksmster/ │ │ │ ├── newtoken.json │ │ │ └── response.json │ │ ├── api/ │ │ │ └── 1.0/ │ │ │ └── users/ │ │ │ ├── email-user/ │ │ │ │ └── response.json │ │ │ ├── filtered/ │ │ │ │ └── response.json │ │ │ ├── ksmster/ │ │ │ │ └── response.json │ │ │ ├── response.json │ │ │ ├── response_s0_l25.json │ │ │ ├── response_s3_l25.json │ │ │ ├── response_s6_l25.json │ │ │ └── response_s9_l25.json │ │ └── api.1.0.application-properties/ │ │ └── response.json │ └── logback-test.xml ├── che-core-api-factory-git-ssh/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── git/ │ │ └── ssh/ │ │ ├── GitSshAuthorizingFileContentProvider.java │ │ ├── GitSshFactoryParametersResolver.java │ │ ├── GitSshScmFileResolver.java │ │ ├── GitSshURLParser.java │ │ └── GitSshUrl.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── git/ │ │ └── ssh/ │ │ ├── GitSshFactoryParametersResolverTest.java │ │ ├── GitSshURLParserTest.java │ │ └── GitSshUrlTest.java │ └── resources/ │ └── logback-test.xml ├── che-core-api-factory-github/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── github/ │ │ ├── GithubFactoryParametersResolver.java │ │ ├── GithubFactoryParametersResolverSecond.java │ │ ├── GithubModule.java │ │ ├── GithubPersonalAccessTokenFetcher.java │ │ ├── GithubPersonalAccessTokenFetcherSecond.java │ │ ├── GithubScmFileResolver.java │ │ ├── GithubScmFileResolverSecond.java │ │ ├── GithubURLParser.java │ │ ├── GithubURLParserSecond.java │ │ ├── GithubUserDataFetcher.java │ │ └── GithubUserDataFetcherSecond.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── github/ │ │ ├── GithubApiClientTest.java │ │ ├── GithubAuthorizingFileContentProviderTest.java │ │ ├── GithubFactoryParametersResolverTest.java │ │ ├── GithubGitUserDataFetcherTest.java │ │ ├── GithubPersonalAccessTokenFetcherTest.java │ │ ├── GithubScmFileResolverTest.java │ │ ├── GithubURLParserTest.java │ │ └── GithubUrlTest.java │ └── resources/ │ ├── __files/ │ │ └── github/ │ │ └── rest/ │ │ ├── pullRequest/ │ │ │ └── response.json │ │ └── user/ │ │ └── response.json │ └── logback-test.xml ├── che-core-api-factory-github-common/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── api/ │ └── factory/ │ └── server/ │ └── github/ │ ├── AbstractGithubFactoryParametersResolver.java │ ├── AbstractGithubPersonalAccessTokenFetcher.java │ ├── AbstractGithubScmFileResolver.java │ ├── AbstractGithubURLParser.java │ ├── AbstractGithubUserDataFetcher.java │ ├── GithubApiClient.java │ ├── GithubAuthorizingFileContentProvider.java │ ├── GithubCommit.java │ ├── GithubPullRequest.java │ ├── GithubSourceStorageBuilder.java │ ├── GithubUrl.java │ └── GithubUser.java ├── che-core-api-factory-gitlab/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── gitlab/ │ │ ├── GitlabFactoryParametersResolver.java │ │ ├── GitlabFactoryParametersResolverSecond.java │ │ ├── GitlabModule.java │ │ ├── GitlabOAuthTokenFetcher.java │ │ ├── GitlabOAuthTokenFetcherSecond.java │ │ ├── GitlabScmFileResolver.java │ │ ├── GitlabScmFileResolverSecond.java │ │ ├── GitlabUrlParser.java │ │ ├── GitlabUrlParserSecond.java │ │ ├── GitlabUserDataFetcher.java │ │ └── GitlabUserDataFetcherSecond.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── factory/ │ │ └── server/ │ │ └── gitlab/ │ │ ├── GitlabApiClientTest.java │ │ ├── GitlabAuthorizingFileContentProviderTest.java │ │ ├── GitlabCustomPortUrlParserTest.java │ │ ├── GitlabFactoryParametersResolverTest.java │ │ ├── GitlabOAuthTokenFetcherTest.java │ │ ├── GitlabScmFileResolverTest.java │ │ ├── GitlabUrlCustomPortTest.java │ │ ├── GitlabUrlParserTest.java │ │ ├── GitlabUrlTest.java │ │ └── GitlabUserDataFetcherTest.java │ └── resources/ │ ├── __files/ │ │ └── gitlab/ │ │ └── rest/ │ │ └── api/ │ │ └── v4/ │ │ └── user/ │ │ ├── PAT_info.json │ │ ├── response.json │ │ ├── token_info.json │ │ └── token_info_lack_scopes.json │ └── logback-test.xml ├── che-core-api-factory-gitlab-common/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── api/ │ └── factory/ │ └── server/ │ └── gitlab/ │ ├── AbstractGitlabFactoryParametersResolver.java │ ├── AbstractGitlabOAuthTokenFetcher.java │ ├── AbstractGitlabScmFileResolver.java │ ├── AbstractGitlabUrlParser.java │ ├── AbstractGitlabUserDataFetcher.java │ ├── GitlabApiClient.java │ ├── GitlabAuthorizingFileContentProvider.java │ ├── GitlabOauthTokenInfo.java │ ├── GitlabPersonalAccessTokenInfo.java │ ├── GitlabUrl.java │ └── GitlabUser.java ├── che-core-api-factory-shared/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── api/ │ └── factory/ │ └── shared/ │ ├── Constants.java │ └── dto/ │ ├── AuthorDto.java │ ├── FactoryDevfileV2Dto.java │ ├── FactoryMetaDto.java │ ├── FactoryVisitor.java │ ├── IdeActionDto.java │ ├── IdeDto.java │ ├── OnAppClosedDto.java │ ├── OnAppLoadedDto.java │ ├── OnProjectsLoadedDto.java │ ├── PoliciesDto.java │ └── ScmInfoDto.java ├── che-core-api-logger/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── logger/ │ │ ├── ErrorRuntimeLogEventLogger.java │ │ ├── LoggerService.java │ │ └── deploy/ │ │ └── LoggerModule.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── logger/ │ │ └── LoggerServiceTest.java │ └── resources/ │ └── logback-test.xml ├── che-core-api-logger-shared/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── api/ │ └── logger/ │ └── shared/ │ └── dto/ │ └── LoggerDto.java ├── che-core-api-metrics/ │ ├── extending-che-monitoring-metrics.adoc │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── metrics/ │ │ ├── RuntimeLogMeterBinder.java │ │ ├── UserMeterBinder.java │ │ ├── WorkspaceBinders.java │ │ ├── WorkspaceFailureMeterBinder.java │ │ ├── WorkspaceInterruptedStartAttemptsMeterBinder.java │ │ ├── WorkspaceStartAttemptsMeterBinder.java │ │ ├── WorkspaceStartTrackerMeterBinder.java │ │ ├── WorkspaceStopTrackerMeterBinder.java │ │ ├── WorkspaceSuccessfulStartAttemptsMeterBinder.java │ │ ├── WorkspaceSuccessfulStopAttemptsMeterBinder.java │ │ └── WsMasterMetricsModule.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── metrics/ │ │ ├── UserMeterBinderTest.java │ │ ├── WorkspaceFailureMeterBinderTest.java │ │ ├── WorkspaceInterruptedStartAttemptsMeterBinderTest.java │ │ ├── WorkspaceStartAttemptsMeterBinderTest.java │ │ ├── WorkspaceStartTrackerMeterBinderTest.java │ │ ├── WorkspaceStopTrackerMeterBinderTest.java │ │ ├── WorkspaceSuccessfulStartAttemptsMeterBinderTest.java │ │ └── WorkspaceSuccessfulStopAttemptsMeterBinderTest.java │ └── resources/ │ └── logback-test.xml ├── che-core-api-ssh/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── api/ │ └── ssh/ │ └── server/ │ ├── SshManager.java │ ├── SshService.java │ ├── jpa/ │ │ ├── JpaSshDao.java │ │ ├── SshJpaModule.java │ │ └── SshPairPrimaryKey.java │ ├── model/ │ │ └── impl/ │ │ └── SshPairImpl.java │ └── spi/ │ └── SshDao.java ├── che-core-api-ssh-shared/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── api/ │ └── ssh/ │ └── shared/ │ ├── Constants.java │ ├── dto/ │ │ ├── GenerateSshPairRequest.java │ │ └── SshPairDto.java │ └── model/ │ └── SshPair.java ├── che-core-api-system/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── system/ │ │ └── server/ │ │ ├── CronThreadPullTermination.java │ │ ├── DtoConverter.java │ │ ├── JvmManager.java │ │ ├── JvmService.java │ │ ├── ServiceTermination.java │ │ ├── ServiceTerminator.java │ │ ├── SystemEventsWebsocketBroadcaster.java │ │ ├── SystemManager.java │ │ ├── SystemModule.java │ │ └── SystemService.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── system/ │ │ └── server/ │ │ ├── DtoConverterTest.java │ │ ├── JvmServiceTest.java │ │ ├── SystemManagerTest.java │ │ └── SystemTerminatorTest.java │ └── resources/ │ └── logback-test.xml ├── che-core-api-system-shared/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── api/ │ └── system/ │ └── shared/ │ ├── SystemStatus.java │ ├── dto/ │ │ ├── SystemEventDto.java │ │ ├── SystemServiceEventDto.java │ │ ├── SystemServiceItemStoppedEventDto.java │ │ ├── SystemStateDto.java │ │ └── SystemStatusChangedEventDto.java │ └── event/ │ ├── EventType.java │ ├── SystemEvent.java │ ├── SystemStatusChangedEvent.java │ └── service/ │ ├── StoppingSystemServiceEvent.java │ ├── SuspendingSystemServiceEvent.java │ ├── SystemServiceEvent.java │ ├── SystemServiceItemStoppedEvent.java │ ├── SystemServiceItemSuspendedEvent.java │ ├── SystemServiceStoppedEvent.java │ └── SystemServiceSuspendedEvent.java ├── che-core-api-user/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── user/ │ │ └── server/ │ │ ├── AppStatesPreferenceCleaner.java │ │ ├── CheUserCreator.java │ │ ├── Constants.java │ │ ├── DtoConverter.java │ │ ├── NotImplementedTokenValidator.java │ │ ├── PreferenceManager.java │ │ ├── ProfileManager.java │ │ ├── TokenValidator.java │ │ ├── UserManager.java │ │ ├── UserService.java │ │ ├── UserValidator.java │ │ ├── event/ │ │ │ ├── UserCreatedEvent.java │ │ │ └── UserRemovedEvent.java │ │ ├── jpa/ │ │ │ ├── JpaPreferenceDao.java │ │ │ ├── JpaProfileDao.java │ │ │ ├── JpaUserDao.java │ │ │ ├── PreferenceEntity.java │ │ │ └── UserJpaModule.java │ │ ├── model/ │ │ │ └── impl/ │ │ │ ├── ProfileImpl.java │ │ │ └── UserImpl.java │ │ └── spi/ │ │ ├── PreferenceDao.java │ │ ├── ProfileDao.java │ │ └── UserDao.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── user/ │ │ └── server/ │ │ ├── PreferenceManagerTest.java │ │ ├── ProfileManagerTest.java │ │ ├── UserManagerTest.java │ │ └── UserValidatorTest.java │ └── resources/ │ └── META-INF/ │ └── services/ │ └── org.eclipse.che.commons.test.tck.TckModule ├── che-core-api-user-shared/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── api/ │ └── user/ │ └── shared/ │ └── dto/ │ ├── ProfileDto.java │ └── UserDto.java ├── che-core-api-workspace/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── workspace/ │ │ └── server/ │ │ ├── DefaultWorkspaceLockService.java │ │ ├── DefaultWorkspaceStatusCache.java │ │ ├── DtoConverter.java │ │ ├── NoEnvironmentFactory.java │ │ ├── PreviewUrlLinksVariableGenerator.java │ │ ├── SidecarToolingWorkspaceUtil.java │ │ ├── URLRewriter.java │ │ ├── WorkspaceAttributeValidator.java │ │ ├── WorkspaceEntityProvider.java │ │ ├── WorkspaceKeyValidator.java │ │ ├── WorkspaceLockService.java │ │ ├── WorkspaceSharedPool.java │ │ ├── WorkspaceStatusCache.java │ │ ├── WorkspaceValidator.java │ │ ├── devfile/ │ │ │ ├── Components.java │ │ │ ├── Constants.java │ │ │ ├── DevfileBindings.java │ │ │ ├── DevfileEntityProvider.java │ │ │ ├── DevfileModule.java │ │ │ ├── DevfileParser.java │ │ │ ├── DevfileRecipeFormatException.java │ │ │ ├── DevfileVersionDetector.java │ │ │ ├── FileContentProvider.java │ │ │ ├── OverridePropertiesApplier.java │ │ │ ├── PreferencesDeserializer.java │ │ │ ├── SerializableConverter.java │ │ │ ├── URLFetcher.java │ │ │ ├── URLFileContentProvider.java │ │ │ ├── convert/ │ │ │ │ ├── CommandConverter.java │ │ │ │ ├── DefaultEditorProvisioner.java │ │ │ │ ├── DevfileConverter.java │ │ │ │ ├── ProjectConverter.java │ │ │ │ └── component/ │ │ │ │ ├── ComponentFQNParser.java │ │ │ │ ├── ComponentToWorkspaceApplier.java │ │ │ │ ├── editor/ │ │ │ │ │ └── EditorComponentToWorkspaceApplier.java │ │ │ │ └── plugin/ │ │ │ │ └── PluginComponentToWorkspaceApplier.java │ │ │ ├── exception/ │ │ │ │ ├── DevfileException.java │ │ │ │ ├── DevfileFormatException.java │ │ │ │ ├── OverrideParameterException.java │ │ │ │ └── WorkspaceExportException.java │ │ │ └── validator/ │ │ │ ├── ComponentIntegrityValidator.java │ │ │ ├── DevfileIntegrityValidator.java │ │ │ └── ErrorMessageComposer.java │ │ ├── event/ │ │ │ ├── MachineStatusJsonRpcMessenger.java │ │ │ ├── RuntimeAbnormalStoppedEvent.java │ │ │ ├── RuntimeAbnormalStoppingEvent.java │ │ │ ├── RuntimeLogJsonRpcMessenger.java │ │ │ ├── RuntimeStatusJsonRpcMessenger.java │ │ │ ├── ServerIdleEvent.java │ │ │ ├── ServerStatusJsonRpcMessenger.java │ │ │ └── WorkspaceJsonRpcMessenger.java │ │ ├── hc/ │ │ │ ├── HttpConnectionServerChecker.java │ │ │ ├── ServerChecker.java │ │ │ ├── ServersChecker.java │ │ │ ├── ServersCheckerFactory.java │ │ │ ├── TerminalHttpConnectionServerChecker.java │ │ │ └── probe/ │ │ │ ├── HttpProbe.java │ │ │ ├── HttpProbeConfig.java │ │ │ ├── HttpProbeFactory.java │ │ │ ├── Probe.java │ │ │ ├── ProbeConfig.java │ │ │ ├── ProbeFactory.java │ │ │ ├── ProbeResult.java │ │ │ ├── ProbeScheduler.java │ │ │ ├── TcpProbeConfig.java │ │ │ ├── WorkspaceProbes.java │ │ │ ├── WorkspaceProbesFactory.java │ │ │ └── server/ │ │ │ ├── ExecServerLivenessProbeConfigFactory.java │ │ │ ├── HttpProbeConfigFactory.java │ │ │ ├── TerminalServerLivenessProbeConfigFactory.java │ │ │ └── WsAgentServerLivenessProbeConfigFactory.java │ │ ├── jpa/ │ │ │ ├── JpaWorkspaceDao.java │ │ │ └── WorkspaceJpaModule.java │ │ ├── model/ │ │ │ └── impl/ │ │ │ ├── CommandImpl.java │ │ │ ├── EnvironmentImpl.java │ │ │ ├── MachineConfigImpl.java │ │ │ ├── MachineImpl.java │ │ │ ├── ProjectConfigImpl.java │ │ │ ├── RecipeImpl.java │ │ │ ├── RuntimeIdentityImpl.java │ │ │ ├── RuntimeImpl.java │ │ │ ├── ServerConfigImpl.java │ │ │ ├── ServerImpl.java │ │ │ ├── SourceStorageImpl.java │ │ │ ├── VolumeImpl.java │ │ │ ├── WarningImpl.java │ │ │ ├── WorkspaceConfigImpl.java │ │ │ ├── WorkspaceImpl.java │ │ │ └── devfile/ │ │ │ ├── ActionImpl.java │ │ │ ├── CommandImpl.java │ │ │ ├── ComponentImpl.java │ │ │ ├── DevfileImpl.java │ │ │ ├── EndpointImpl.java │ │ │ ├── EntrypointImpl.java │ │ │ ├── EnvImpl.java │ │ │ ├── MetadataImpl.java │ │ │ ├── PreviewUrlImpl.java │ │ │ ├── ProjectImpl.java │ │ │ ├── SourceImpl.java │ │ │ └── VolumeImpl.java │ │ ├── spi/ │ │ │ ├── InfrastructureException.java │ │ │ ├── InternalInfrastructureException.java │ │ │ ├── NamespaceResolutionContext.java │ │ │ ├── RuntimeStartInterruptedException.java │ │ │ ├── StateException.java │ │ │ ├── WorkspaceDao.java │ │ │ ├── environment/ │ │ │ │ ├── InternalEnvironment.java │ │ │ │ ├── InternalEnvironmentFactory.java │ │ │ │ ├── InternalMachineConfig.java │ │ │ │ ├── InternalRecipe.java │ │ │ │ ├── MachineConfigsValidator.java │ │ │ │ ├── RecipeRetriever.java │ │ │ │ └── ResourceLimitAttributesProvisioner.java │ │ │ └── provision/ │ │ │ └── env/ │ │ │ ├── AgentAuthEnableEnvVarProvider.java │ │ │ ├── CheApiEnvVarProvider.java │ │ │ ├── CheApiExternalEnvVarProvider.java │ │ │ ├── CheApiInternalEnvVarProvider.java │ │ │ ├── EnvVarProvider.java │ │ │ ├── JavaOptsEnvVariableProvider.java │ │ │ ├── LegacyEnvVarProvider.java │ │ │ ├── MachineTokenEnvVarProvider.java │ │ │ ├── MavenOptsEnvVariableProvider.java │ │ │ ├── WorkspaceIdEnvVarProvider.java │ │ │ ├── WorkspaceNameEnvVarProvider.java │ │ │ └── WorkspaceNamespaceNameEnvVarProvider.java │ │ ├── token/ │ │ │ ├── MachineAccessForbidden.java │ │ │ ├── MachineTokenException.java │ │ │ └── MachineTokenProvider.java │ │ └── wsplugins/ │ │ ├── ChePluginsApplier.java │ │ ├── PluginFQNParser.java │ │ └── model/ │ │ ├── CheContainer.java │ │ ├── CheContainerPort.java │ │ ├── ChePlugin.java │ │ ├── ChePluginEndpoint.java │ │ ├── Command.java │ │ ├── EnvVar.java │ │ ├── Exec.java │ │ ├── ExtendedPluginFQN.java │ │ ├── Handler.java │ │ ├── Lifecycle.java │ │ ├── PluginFQN.java │ │ └── Volume.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── eclipse/ │ │ └── che/ │ │ └── api/ │ │ └── workspace/ │ │ └── server/ │ │ ├── PreviewUrlLinksVariableGeneratorTest.java │ │ ├── WorkspaceEntityProviderTest.java │ │ ├── WorkspaceValidatorTest.java │ │ ├── devfile/ │ │ │ ├── DevfileEntityProviderTest.java │ │ │ ├── DevfileParserTest.java │ │ │ ├── DevfileVersionTest.java │ │ │ ├── OverridePropertiesApplierTest.java │ │ │ ├── PreferencesDeserializerTest.java │ │ │ ├── SerializableConverterTest.java │ │ │ ├── URLFetcherTest.java │ │ │ ├── URLFileContentProviderTest.java │ │ │ ├── convert/ │ │ │ │ ├── CommandConverterTest.java │ │ │ │ ├── DefaultEditorProvisionerTest.java │ │ │ │ ├── DevfileConverterTest.java │ │ │ │ ├── ProjectConverterTest.java │ │ │ │ └── component/ │ │ │ │ ├── editor/ │ │ │ │ │ └── EditorComponentToWorkspaceApplierTest.java │ │ │ │ └── plugin/ │ │ │ │ └── PluginComponentToWorkspaceApplierTest.java │ │ │ └── validator/ │ │ │ └── DevfileIntegrityValidatorTest.java │ │ ├── hc/ │ │ │ ├── HttpConnectionServerCheckerTest.java │ │ │ ├── ServerCheckerTest.java │ │ │ ├── ServersCheckerTest.java │ │ │ ├── TerminalHttpConnectionServerCheckerTest.java │ │ │ └── probe/ │ │ │ └── WorkspaceProbesFactoryTest.java │ │ ├── model/ │ │ │ └── impl/ │ │ │ └── ServerConfigImplTest.java │ │ ├── spi/ │ │ │ ├── environment/ │ │ │ │ ├── InternalEnvironmentFactoryTest.java │ │ │ │ ├── MachineConfigsValidatorTest.java │ │ │ │ └── ResourceLimitAttributesProvisionerTest.java │ │ │ └── provision/ │ │ │ └── env/ │ │ │ ├── WorkspaceNameEnvVarProviderTest.java │ │ │ └── WorkspaceNamespaceEnvVarProviderTest.java │ │ └── wsplugins/ │ │ └── PluginFQNParserTest.java │ └── resources/ │ ├── META-INF/ │ │ └── services/ │ │ └── org.eclipse.che.commons.test.tck.TckModule │ ├── devfile/ │ │ ├── devfile.json │ │ ├── devfile.yaml │ │ ├── logback-test.xml │ │ ├── petclinic.yaml │ │ ├── schema_test/ │ │ │ ├── command/ │ │ │ │ ├── devfile_action_without_commandline_and_reference.yaml │ │ │ │ ├── devfile_command_with_empty_preview_url.yaml │ │ │ │ ├── devfile_command_with_preview_url.yaml │ │ │ │ ├── devfile_command_with_preview_url_only_path.yaml │ │ │ │ ├── devfile_command_with_preview_url_only_port.yaml │ │ │ │ ├── devfile_command_with_preview_url_port_is_negative.yaml │ │ │ │ ├── devfile_command_with_preview_url_port_is_string.yaml │ │ │ │ ├── devfile_command_with_preview_url_port_is_too_high.yaml │ │ │ │ ├── devfile_missing_command_actions.yaml │ │ │ │ ├── devfile_missing_command_name.yaml │ │ │ │ └── devfile_multiple_commands_actions.yaml │ │ │ ├── component/ │ │ │ │ ├── devfile_component_with_automount_secrets.yaml │ │ │ │ ├── devfile_component_with_undeclared_field.yaml │ │ │ │ ├── devfile_missing_component_type.yaml │ │ │ │ ├── devfile_unknown_component_type.yaml │ │ │ │ └── devfile_without_any_component.yaml │ │ │ ├── devfile/ │ │ │ │ ├── devfile_empty_metadata.yaml │ │ │ │ ├── devfile_just_generatename.yaml │ │ │ │ ├── devfile_missing_api_version.yaml │ │ │ │ ├── devfile_missing_metadata.yaml │ │ │ │ ├── devfile_missing_name_and_generatename.yaml │ │ │ │ ├── devfile_name_and_generatename.yaml │ │ │ │ ├── devfile_null_metadata.yaml │ │ │ │ ├── devfile_v2-1-0_just_schemaVersion.yaml │ │ │ │ ├── devfile_v2-1-0_simple-devfile.yaml │ │ │ │ ├── devfile_v2-1-0_unsupported_schemaVersion.yaml │ │ │ │ ├── devfile_v2-1-0_with_invalid_plugin_definition.yaml │ │ │ │ ├── devfile_v2-2-0-basic-nodejs.yaml │ │ │ │ ├── devfile_v2-2-0-basic-python.yaml │ │ │ │ ├── devfile_v2-2-0-basic-quarkus.yaml │ │ │ │ ├── devfile_v2-2-0-basic-springboot.yaml │ │ │ │ ├── devfile_v2-2-0-quarkus.yaml │ │ │ │ ├── devfile_v2-2-0-simple-devfile.yaml │ │ │ │ ├── devfile_v2_invalid_schemaVersion.yaml │ │ │ │ ├── devfile_v2_just_schemaVersion.yaml │ │ │ │ ├── devfile_v2_sample-devfile.yaml │ │ │ │ ├── devfile_v2_simple-devfile.yaml │ │ │ │ ├── devfile_v2_spring-boot-http-booster-devfile.yaml │ │ │ │ ├── devfile_v2_unsupported_schemaVersion.yaml │ │ │ │ ├── devfile_with_sparse_checkout_dir.yaml │ │ │ │ └── devfile_with_undeclared_field.yaml │ │ │ ├── dockerimage_component/ │ │ │ │ ├── devfile_dockerimage_component.yaml │ │ │ │ ├── devfile_dockerimage_component_with_indistinctive_field_selector.yaml │ │ │ │ ├── devfile_dockerimage_component_with_invalid_memory_limit.yaml │ │ │ │ ├── devfile_dockerimage_component_with_missing_image.yaml │ │ │ │ ├── devfile_dockerimage_component_with_missing_memory_limit.yaml │ │ │ │ └── devfile_dockerimage_component_without_entry_point.yaml │ │ │ ├── editor_plugin_component/ │ │ │ │ ├── devfile_editor_component_with_bad_registry.yaml │ │ │ │ ├── devfile_editor_component_with_custom_registry.yaml │ │ │ │ ├── devfile_editor_component_with_id_and_reference.yaml │ │ │ │ ├── devfile_editor_component_with_indistinctive_field.yaml │ │ │ │ ├── devfile_editor_component_with_missing_id.yaml │ │ │ │ ├── devfile_editor_component_with_multiple_colons_in_id.yaml │ │ │ │ ├── devfile_editor_component_with_registry_in_id.yaml │ │ │ │ ├── devfile_editor_component_without_version.yaml │ │ │ │ ├── devfile_editor_plugins.yaml │ │ │ │ ├── devfile_editor_plugins_components_with_invalid_memory_limit.yaml │ │ │ │ ├── devfile_editor_plugins_components_with_resource_limits.yaml │ │ │ │ ├── devfile_plugin_component_with_reference.yaml │ │ │ │ └── devfile_plugin_components_with_preferences.yaml │ │ │ └── kubernetes_openshift_component/ │ │ │ ├── devfile_k8s_openshift_component_with_endpoints.yaml │ │ │ ├── devfile_k8s_openshift_component_with_env.yaml │ │ │ ├── devfile_kubernetes_component.yaml │ │ │ ├── devfile_kubernetes_component_absolute_reference.yaml │ │ │ ├── devfile_kubernetes_component_content_without_reference.yaml │ │ │ ├── devfile_kubernetes_component_reference_and_content_as_block.yaml │ │ │ ├── devfile_openshift_component.yaml │ │ │ ├── devfile_openshift_component_content_without_reference.yaml │ │ │ ├── devfile_openshift_component_reference_and_content.yaml │ │ │ ├── devfile_openshift_component_reference_and_content_as_block.yaml │ │ │ ├── devfile_openshift_component_with_indistinctive_field_id.yaml │ │ │ └── devfile_openshift_component_with_missing_reference_and_referenceContent.yaml │ │ ├── url_fetcher_test_resource.json │ │ └── workspace_config.json │ ├── invalid-json.json │ ├── invalid-recipes.json │ ├── logback-test.xml │ ├── recipes.json │ ├── ws_conf_machine_source_dockerfile_content.json │ ├── ws_conf_machine_source_dockerfile_location.json │ └── ws_conf_machine_source_dockerimage.json ├── che-core-api-workspace-shared/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── eclipse/ │ └── che/ │ └── api/ │ └── workspace/ │ └── shared/ │ ├── Constants.java │ ├── ProjectProblemImpl.java │ ├── dto/ │ │ ├── BrokerStatus.java │ │ ├── CommandDto.java │ │ ├── EnvironmentDto.java │ │ ├── MachineConfigDto.java │ │ ├── MachineDto.java │ │ ├── ProjectConfigDto.java │ │ ├── ProjectProblemDto.java │ │ ├── RecipeDto.java │ │ ├── RuntimeDto.java │ │ ├── RuntimeIdentityDto.java │ │ ├── ServerConfigDto.java │ │ ├── ServerDto.java │ │ ├── SourceStorageDto.java │ │ ├── VolumeDto.java │ │ ├── WarningDto.java │ │ ├── WorkspaceConfigDto.java │ │ ├── WorkspaceDto.java │ │ ├── devfile/ │ │ │ ├── ComponentDto.java │ │ │ ├── DevfileActionDto.java │ │ │ ├── DevfileCommandDto.java │ │ │ ├── DevfileDto.java │ │ │ ├── DevfileVolumeDto.java │ │ │ ├── EndpointDto.java │ │ │ ├── EntrypointDto.java │ │ │ ├── EnvDto.java │ │ │ ├── MetadataDto.java │ │ │ ├── PreviewUrlDto.java │ │ │ ├── ProjectDto.java │ │ │ └── SourceDto.java │ │ └── event/ │ │ ├── BootstrapperStatusEvent.java │ │ ├── BrokerLogEvent.java │ │ ├── BrokerStatusChangedEvent.java │ │ ├── MachineLogEvent.java │ │ ├── MachineStatusEvent.java │ │ ├── RuntimeLogEvent.java │ │ ├── RuntimeStatusEvent.java │ │ ├── ServerStatusEvent.java │ │ └── WorkspaceStatusEvent.java │ └── event/ │ ├── WorkspaceCreatedEvent.java │ └── WorkspaceRemovedEvent.java ├── che-core-sql-schema/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── resources/ │ └── che-schema/ │ ├── 5.0.0-M8/ │ │ ├── 1__init.sql │ │ └── mysql/ │ │ └── 1__init.sql │ ├── 5.0.0-M9/ │ │ ├── 1__add_index_on_workspace_temporary.sql │ │ ├── 2__update_local_links_in_environments.sql │ │ └── mysql/ │ │ └── 2__update_local_links_in_environments.sql │ ├── 5.11.0/ │ │ ├── 1__optimize_user_search.sql │ │ └── postgresql/ │ │ └── 1__optimize_user_search.sql │ ├── 5.2.0/ │ │ ├── 1__increase_project_attributes_values_length.sql │ │ └── mysql/ │ │ └── 1__increase_project_attributes_values_length.sql │ ├── 5.4.0/ │ │ ├── 1__drop_user_to_account_relation.sql │ │ ├── 2__create_missed_account_indexes.sql │ │ └── mysql/ │ │ └── 1__drop_user_to_account_relation.sql │ ├── 5.6.0/ │ │ └── 1__add_exec_agent_where_terminal_agent_is_present.sql │ ├── 5.7.0/ │ │ ├── 1__add_factory.sql │ │ ├── 2__remove_match_policy.sql │ │ └── mysql/ │ │ └── 1__add_factory.sql │ ├── 5.8.0/ │ │ └── 1__add_foreigh_key_indexes.sql │ ├── 6.0.0/ │ │ ├── 10__move_dockerimage_recipe_location_to_content.sql │ │ ├── 11__increase_workspace_attributes_values_length.sql │ │ ├── 12__remove_stack_sources.sql │ │ ├── 1__add_path_to_serverconf.sql │ │ ├── 2__rename_agents_to_installers.sql │ │ ├── 3__add_installer.sql │ │ ├── 4__remove_old_recipe.sql │ │ ├── 5__add_machine_env.sql │ │ ├── 6__remove_snapshots.sql │ │ ├── 7__add_machine_volumes.sql │ │ ├── 8__add_serverconf_attributes.sql │ │ ├── 9__increase_externalmachine_env_value_length.sql │ │ └── mysql/ │ │ ├── 11__increase_workspace_attributes_values_length.sql │ │ └── 9__increase_externalmachine_env_value_length.sql │ ├── 6.10.0/ │ │ ├── 1__add_workspace_cfg_attributes.sql │ │ └── 2__change_signature_key_pair_id.sql │ ├── 6.11.0/ │ │ ├── 1__add_signature_key_constraints.sql │ │ └── mysql/ │ │ └── 1__add_signature_key_constraints.sql │ ├── 6.12.0/ │ │ ├── 1__rename_project_attributes_values_field.sql │ │ └── mysql/ │ │ └── 1__rename_project_attributes_values_field.sql │ ├── 6.15.0/ │ │ ├── 1__remove_not_null_constraint_from_env_name_fields.sql │ │ ├── 2__add_commands_to_k8s_runtime.sql │ │ ├── mysql/ │ │ │ └── 1__remove_not_null_constraint_from_env_name_fields.sql │ │ └── postgresql/ │ │ └── 1__remove_not_null_constraint_from_env_name_fields.sql │ ├── 6.16.0/ │ │ ├── 1__increase_workspace_config_attributes_values_length.sql │ │ ├── 2__create_workspace_activity_table.sql │ │ ├── 3__bootstrap_ws_activity_data.sql │ │ └── mysql/ │ │ ├── 1__increase_workspace_config_attributes_values_length.sql │ │ └── 3__bootstrap_ws_activity_data.sql │ ├── 6.17.0/ │ │ └── 1__convert_enums_to_strings.sql │ ├── 6.3.0/ │ │ ├── 1__add_fk_indexes.sql │ │ └── mysql/ │ │ └── 1__add_fk_indexes.sql │ ├── 6.4.0/ │ │ ├── 1__add_workspace_expirations.sql │ │ ├── 2__add_signature_key.sql │ │ ├── 3__add_k8s_runtimes.sql │ │ └── postgresql/ │ │ └── 2__add_signature_key.sql │ ├── 7.0.0-beta4.0/ │ │ └── 1__add_devfile.sql │ ├── 7.0.0-beta5.0/ │ │ ├── 1__devfile_command_reference.sql │ │ └── mysql/ │ │ └── 1__devfile_command_reference.sql │ ├── 7.0.0-beta6.0/ │ │ └── 1__add_devfile_component_prefs.sql │ ├── 7.0.0-beta7.0/ │ │ └── 1__add_registry_url_to_devfile_component.sql │ ├── 7.0.0-beta8.0-RC2.0/ │ │ ├── 1__devfile_metadata.sql │ │ ├── 2__devfile_make_some_fields_optional.sql │ │ └── mysql/ │ │ ├── 1__devfile_metadata.sql │ │ └── 2__devfile_make_some_fields_optional.sql │ ├── 7.10.0/ │ │ ├── 1__add_devfile_plugin_editor_component_cpu_limit_request.sql │ │ └── 2__add_devfile_plugin_editor_component_ram_request.sql │ ├── 7.11.0/ │ │ └── 1__update_inconsistent_stopped_workspace_activities.sql │ ├── 7.16.0/ │ │ └── 1__add_devfile_component_automount_workspace_secrets.sql │ ├── 7.2.0/ │ │ └── 1__remove_installers.sql │ ├── 7.20.0/ │ │ └── 1__userdevfile.sql │ ├── 7.21.0/ │ │ └── 1__remove_installers.sql │ ├── 7.26.0/ │ │ ├── 1__remove_factory_button_and_image.sql │ │ └── mysql/ │ │ └── 1__remove_factory_button_and_image.sql │ ├── 7.4.0/ │ │ ├── 1__add_devfile_source_sparse_checkout_dir.sql │ │ └── 2__add_preview_url_to_devfile_command.sql │ └── 7.6.0/ │ └── 1__drop_che_workspace_expiration.sql └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .ci/openshift-ci/Dockerfile ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # Dockerfile to bootstrap build and test in openshift-ci FROM registry.ci.openshift.org/openshift/release:golang-1.20 # hadolint ignore=DL3002 USER 0 SHELL ["/bin/bash", "-c"] # Temporary workaround since mirror.centos.org is down and can be replaced with vault.centos.org RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo && \ sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo && \ sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo # hadolint ignore=DL3041 # Install yq, kubectl, chectl cli. RUN yum install --assumeyes -d1 psmisc python3-pip httpd-tools nodejs && \ pip3 install --upgrade setuptools && \ pip3 install yq && \ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \ chmod +x ./kubectl && \ mv ./kubectl /usr/local/bin && \ bash <(curl -sL https://che-incubator.github.io/chectl/install.sh) --channel=next ================================================ FILE: .ci/openshift-ci/ca.crt ================================================ -----BEGIN CERTIFICATE----- MIIDDDCCAfSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtpbmdy ZXNzLW9wZXJhdG9yQDE3MjQwNzU0NzgwHhcNMjQwODE5MTM1MTE3WhcNMjYwODE5 MTM1MTE4WjAmMSQwIgYDVQQDDBtpbmdyZXNzLW9wZXJhdG9yQDE3MjQwNzU0Nzgw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgfFNrBJSxPGNjA0S5+DUa Usqks86BMzzK7f6Fln90Aiw6eHgznvLlYLsUZUpCrbuOR2DbLy4NF4z1QMKwakY4 ClyNuxFUB823EzxdWsQx10LYeOglRWivNPln4n9TkCmet2fpSquI6LVnvFAJWWjE exAZt0JjOLfcLChNTZCdyxBp/v2eVM1uNj9F/EeQp+8IjZ4FJREuyQnFAA55DzYH qqsmscjLjWZeamZCKPufBWkHmxAfhxqSDiM9rfr9AuDQbgU85q4TKA9xpQmRHTsQ PNv7IeKAexrhin467FbIfbVcUOMxPe80i/wWW3Z9T/c9UNDkyfg26Y/u94/irk1h AgMBAAGjRTBDMA4GA1UdDwEB/wQEAwICpDASBgNVHRMBAf8ECDAGAQH/AgEAMB0G A1UdDgQWBBRUi8oPN5+mKa/wzaBztYbxY8QvFDANBgkqhkiG9w0BAQsFAAOCAQEA I5kiJUCNW/+7Pu7emoNb5H0L774AIccDmiXk4aS7Kbkhno9IuErdmYEbntjqllbt kFYSlcccW7AZm2EQ5SWOjhdMuDnF1krngv4ffHTN3RxIp0TbSLV8bqjQrwHxDf0s n8XYTrB3mIdmb0BK7SsaH5rubFCDWLxOMe9CT5SZsN258CoOm9fxOfE2SnxzLxjZ 2rTUozKXLB+xPyezZsQ2hGPbQJzf7KRBITqOJbaTRDDDxfXr/IosYvGaPGpOC/aw 4t2kbdLTJeiQAlZPwup9NXTPze+MEyAo2udooMACcxdiOEQc00bXvzKF0Qlvy6wC v2ib/BnCyVIZ3On5CwwJSA== -----END CERTIFICATE----- ================================================ FILE: .ci/openshift-ci/common.sh ================================================ #!/bin/bash # # Copyright (c) 2023-2025 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # set -e # only exit with zero if all commands of the pipeline exit successfully set -o pipefail PR_IMAGE_TAG="pr-${PULL_NUMBER}" export CHE_NAMESPACE=${CHE_NAMESPACE:-"eclipse-che"} export CHE_SERVER_IMAGE=${CHE_SERVER_IMAGE:-"quay.io/eclipse/che-server:${PR_IMAGE_TAG}"} export ARTIFACTS_DIR=${ARTIFACT_DIR:-"/tmp/artifacts"} export CHE_FORWARDED_PORT="8081" export OCP_ADMIN_USER_NAME=${OCP_ADMIN_USER_NAME:-"admin"} export OCP_NON_ADMIN_USER_NAME=${OCP_NON_ADMIN_USER_NAME:-"user"} export OCP_LOGIN_PASSWORD=${OCP_LOGIN_PASSWORD:-"passw"} export ADMIN_CHE_NAMESPACE=${OCP_ADMIN_USER_NAME}"-che" export USER_CHE_NAMESPACE=${OCP_NON_ADMIN_USER_NAME}"-che" export GIT_PROVIDER_USERNAME=${GIT_PROVIDER_USERNAME:-"chepullreq1"} export PUBLIC_REPO_WORKSPACE_NAME=${PUBLIC_REPO_WORKSPACE_NAME:-"public-repo-wksp-testname"} export PRIVATE_REPO_WORKSPACE_NAME=${PRIVATE_REPO_WORKSPACE_NAME:-"private-repo-wksp-testname"} export PUBLIC_PROJECT_NAME=${PUBLIC_PROJECT_NAME:-"public-repo"} export PRIVATE_PROJECT_NAME=${PRIVATE_PROJECT_NAME:-"private-repo"} export TEST_FILE_NAME=${TEST_FILE_NAME:-"Date.txt"} export CUSTOM_CONFIG_MAP_NAME=${CUSTOM_CONFIG_MAP_NAME:-"custom-ca-certificates"} export GIT_SSL_CONFIG_MAP_NAME=${GIT_SSL_CONFIG_MAP_NAME:-"che-self-signed-cert"} provisionOpenShiftOAuthUser() { echo "------- [INFO] Start provisioning Openshift OAuth user -------" htpasswd -c -B -b users.htpasswd ${OCP_ADMIN_USER_NAME} ${OCP_LOGIN_PASSWORD} htpasswd -b users.htpasswd ${OCP_NON_ADMIN_USER_NAME} ${OCP_LOGIN_PASSWORD} oc create secret generic htpass-secret --from-file=htpasswd="users.htpasswd" -n openshift-config oc apply -f ".ci/openshift-ci/htpasswdProvider.yaml" oc adm policy add-cluster-role-to-user cluster-admin ${OCP_ADMIN_USER_NAME} echo "------- [INFO] Waiting for htpasswd auth to be working up to 5 minutes -------" CURRENT_TIME=$(date +%s) ENDTIME=$((CURRENT_TIME + 300)) while [ "$(date +%s)" -lt $ENDTIME ]; do if oc login -u=${OCP_ADMIN_USER_NAME} -p=${OCP_LOGIN_PASSWORD} --insecure-skip-tls-verify=false; then echo "======= [INFO] OpenShift OAuth htpasswd is configured. ======= ======= [INFO] Login to OCP cluster with admin user credentials is success.=======" return 0 fi sleep 5 done echo "####### [ERROR] Error occurred while waiting OpenShift OAuth htpasswd setup. Try to rerun test. #######" exit 1 } configureGitSelfSignedCertificate() { echo "------- [INFO] Configure self-signed certificate for Git provider -------" oc adm new-project ${CHE_NAMESPACE} oc project ${CHE_NAMESPACE} echo "------- [INFO] Create ConfigMap with the required TLS certificate -------" oc create configmap ${CUSTOM_CONFIG_MAP_NAME} --from-file=.ci/openshift-ci/ca.crt oc label configmap ${CUSTOM_CONFIG_MAP_NAME} app.kubernetes.io/part-of=che.eclipse.org app.kubernetes.io/component=ca-bundle echo "======= [INFO] ConfigMap is configured =======" } createCustomResourcesFile() { echo "------- [INFO] Create custom resourses file -------" cat > custom-resources.yaml <<-END apiVersion: org.eclipse.che/v2 spec: devEnvironments: maxNumberOfRunningWorkspacesPerUser: 10000 END echo "======= [INFO] Generated custom resources file =======" cat custom-resources.yaml } deployChe() { echo "------- [INFO] Start installing Eclipse Che -------" chectl server:deploy --cheimage=$CHE_SERVER_IMAGE \ --che-operator-cr-patch-yaml=custom-resources.yaml \ --platform=openshift \ --telemetry=off \ --batch waitFinishDeploymentCheServer echo "======= [INFO] Eclipse Che is successfully installed =======" } waitFinishDeploymentCheServer() { CURRENT_TIME=$(date +%s) ENDTIME=$((CURRENT_TIME + 60)) while [ "$(date +%s)" -lt $ENDTIME ]; do podCheServerName=$(oc get pod -n ${CHE_NAMESPACE} -l component=che | grep "che" | awk '{ print $1 }') echo "Pod Che_Server: $podCheServerName" count=$(echo "$podCheServerName" | wc -l) if [ $count -eq 1 ]; then echo "------- [INFO] Only one Che Server pod is left. -------" return 0 fi echo "------- [INFO] Waiting until only one Che Server pod remains. -------" sleep 5 done echo "####### [ERROR] Error occurred while waiting for only one Che Server pod. #######" exit 1 } # this command starts port forwarding between the local machine and the che-host service in the OpenShift cluster. forwardPortToService() { echo "------- [INFO] Start forwarding between the local machine and the che-host service -------" oc port-forward service/che-host ${CHE_FORWARDED_PORT}:8080 -n ${CHE_NAMESPACE} & sleep 3s } killProcessByPort() { fuser -k ${CHE_FORWARDED_PORT}/tcp } requestFactoryResolverGitRepoUrl() { GIT_REPO_URL=$1 CLUSTER_ACCESS_TOKEN=$(oc whoami -t) curl -i -X 'POST' \ http://localhost:${CHE_FORWARDED_PORT}/api/factory/resolver \ -H 'accept: */*' \ -H "Authorization: Bearer ${CLUSTER_ACCESS_TOKEN}" \ -H 'Content-Type: application/json' \ -d '{ "url": "'${GIT_REPO_URL}'" }' } # check that factory resolver returns correct value without any PAT/OAuth setup testFactoryResolverNoPatOAuth() { echo "------- [INFO] Check factory resolver for public repository with NO PAT/OAuth setup -------" testFactoryResolverResponse $1 200 echo "------- [INFO] Check factory resolver for private repository with NO PAT/OAuth setup -------" testFactoryResolverResponse $2 500 } # check that raw devfile url factory resolver returns correct value without any PAT/OAuth setup testFactoryResolverNoPatOAuthRaw() { echo "------- [INFO] Check factory resolver for public repository with NO PAT/OAuth setup -------" testFactoryResolverResponse $1 200 echo "------- [INFO] Check factory resolver for private repository with NO PAT/OAuth setup -------" testFactoryResolverResponse $2 400 } # check that factory resolver returns correct value with PAT/OAuth setup testFactoryResolverWithPatOAuth() { echo "------- [INFO] Check factory resolver for public repository with PAT/OAuth setup -------" testFactoryResolverResponse $1 200 echo "------- [INFO] Check factory resolver for private repository with PAT/OAuth setup -------" testFactoryResolverResponse $2 200 } testFactoryResolverResponse() { URL=$1 RESPONSE_CODE=$2 if [ "$(requestFactoryResolverGitRepoUrl ${URL} | grep "HTTP/1.1 ${RESPONSE_CODE}")" ]; then echo "======= [INFO] Factory resolver returned 'HTTP/1.1 ${RESPONSE_CODE}' status code. Expected client side response. =======" else echo "####### [ERROR] Factory resolver returned wrong status code. Expected: HTTP/1.1 ${RESPONSE_CODE}. ####### ####### Cause possible: PR code regress or service is changed. Need to investigate it. #######" exit 1 fi } requestProvisionNamespace() { CLUSTER_ACCESS_TOKEN=$(oc whoami -t) curl -i -X 'POST' \ http://localhost:${CHE_FORWARDED_PORT}/api/kubernetes/namespace/provision \ -H 'accept: application/json' \ -H "Authorization: Bearer ${CLUSTER_ACCESS_TOKEN}" \ -d '' } initUserNamespace() { OCP_USER_NAME=$1 echo "------- [INFO] Initialize user namespace -------" oc login -u=${OCP_USER_NAME} -p=${OCP_LOGIN_PASSWORD} --insecure-skip-tls-verify=false request_result=$(requestProvisionNamespace) || true if [ -z "$request_result" ]; then echo "####### [ERROR] Cause possible: lost connection to pod, this is an infrastructure problem. Try to rerun the test. #######" exit 1 elif [ "$(echo "$request_result" | grep "HTTP/1.1 200")" ]; then echo "======= [INFO] Request provision user namespace returned 'HTTP/1.1 200' status code. User namespace is created. =======" else echo "####### [ERROR] Request provision user namespace returned wrong status code. Expected: HTTP/1.1 200. ####### ####### User namespace creation failed. Cause possible: PR code regression or service is changed. Need to investigate. #######" exit 1 fi } setupPersonalAccessToken() { GIT_PROVIDER_TYPE=$1 GIT_PROVIDER_URL=$2 GIT_PROVIDER_PAT=$3 echo "------- [INFO] Setup Personal Access Token Secret -------" oc project ${USER_CHE_NAMESPACE} CHE_USER_ID=$(oc get secret user-profile -o jsonpath='{.data.id}' | base64 -d) cat .ci/openshift-ci/pat-secret.yaml > pat-secret.yaml # patch the pat-secret.yaml file sed -i "s#che-user-id#${CHE_USER_ID}#g" pat-secret.yaml sed -i "s#git-provider-name#${GIT_PROVIDER_TYPE}#g" pat-secret.yaml sed -i "s#git-provider-url#${GIT_PROVIDER_URL}#g" pat-secret.yaml sed -i "s#content-access-token#${GIT_PROVIDER_PAT}#g" pat-secret.yaml if [ "${GIT_PROVIDER_TYPE}" == "azure-devops" ]; then sed -i "s#''#${GIT_PROVIDER_USERNAME}#g" pat-secret.yaml fi cat pat-secret.yaml oc apply -f pat-secret.yaml -n ${USER_CHE_NAMESPACE} echo "======= [INFO] Personal Access Token is created. =======" } setupSSHKeyPairs() { GIT_PRIVATE_KEY=$1 GIT_PUBLIC_KEY=$2 echo "------- [INFO] Setup SSH Key Pairs Secret "------- oc project ${USER_CHE_NAMESPACE} ENCODED_GIT_PRIVATE_KEY=$(echo "${GIT_PRIVATE_KEY}" | base64 -w 0) ENCODED_GIT_PUBLIC_KEY=$(echo "${GIT_PUBLIC_KEY}" | base64 -w 0) cat .ci/openshift-ci/ssh-secret.yaml > ssh-secret.yaml # patch the ssh-secret.yaml file sed -i "s#ssh_private_key#${ENCODED_GIT_PRIVATE_KEY}#g" ssh-secret.yaml sed -i "s#ssh_public_key#${ENCODED_GIT_PUBLIC_KEY}#g" ssh-secret.yaml cat ssh-secret.yaml oc apply -f ssh-secret.yaml -n ${USER_CHE_NAMESPACE} echo "======= [INFO] SSH Secret is created. =======" } # Only for GitLab server administrator users createOAuthApplicationGitLabServer() { ADMIN_ACCESS_TOKEN=$1 CHE_URL=https://$(oc get route -n ${CHE_NAMESPACE} che -o jsonpath='{.spec.host}') echo "------- [INFO] Create OAuth Application -------" response=$(curl -k -X POST \ ${GIT_PROVIDER_URL}/api/v4/applications \ -H "PRIVATE-TOKEN: ${ADMIN_ACCESS_TOKEN}" \ -d "name=${APPLICATION_NAME}" \ -d "redirect_uri=${CHE_URL}/api/oauth/callback" \ -d "scopes=api write_repository openid") echo "------- [INFO] Response of the created OAuth Application -------" OAUTH_ID=$(echo "$response" | jq -r '.id') APPLICATION_ID=$(echo "$response" | jq -r '.application_id') APPLICATION_SECRET=$(echo "$response" | jq -r '.secret') } # Only for GitLab server administrator users deleteOAuthApplicationGitLabServer() { OAUTH_ID=$1 ADMIN_ACCESS_TOKEN=$2 echo "------- [INFO] Delete OAuth Application -------" curl -i -k -X DELETE \ ${GIT_PROVIDER_URL}/api/v4/applications/${OAUTH_ID} \ -H "PRIVATE-TOKEN: ${ADMIN_ACCESS_TOKEN}" echo "======= [INFO] OAuth Application is deleted =======" } # Only for GitLab server revokeAuthorizedOAuthApplication() { APPLICATION_ID=$1 APPLICATION_SECRET=$2 echo "------- [INFO] Revoke authorized OAuth application -------" oc project ${USER_CHE_NAMESPACE} OAUTH_TOKEN_NAME=$(oc get secret | grep 'personal-access-token'| awk 'NR==1 { print $1 }') OAUTH_TOKEN=$(oc get secret $OAUTH_TOKEN_NAME -o jsonpath='{.data.token}' | base64 -d) curl -i -k -X POST \ ${GIT_PROVIDER_URL}/oauth/revoke \ -d "client_id=${APPLICATION_ID}" \ -d "client_secret=${APPLICATION_SECRET}" \ -d "token=${OAUTH_TOKEN}" echo "======= [INFO] Authorized OAuth application is revoked =======" } # Only for GitLab server setupOAuthSecret() { APPLICATION_ID=$1 APPLICATION_SECRET=$2 echo "------- [INFO] Setup OAuth Secret -------" oc login -u=${OCP_ADMIN_USER_NAME} -p=${OCP_LOGIN_PASSWORD} --insecure-skip-tls-verify=false oc project ${CHE_NAMESPACE} SERVER_POD=$(oc get pod -l component=che | grep "che" | awk 'NR==1 { print $1 }') cat .ci/openshift-ci/oauth-secret.yaml > oauth-secret.yaml # patch the oauth-secret.yaml file sed -i "s#git-provider-url#${GIT_PROVIDER_URL}#g" oauth-secret.yaml sed -i "s#application-id#${APPLICATION_ID}#g" oauth-secret.yaml sed -i "s#application-secret#${APPLICATION_SECRET}#g" oauth-secret.yaml cat oauth-secret.yaml oc apply -f oauth-secret.yaml -n ${CHE_NAMESPACE} echo "------- [INFO] Wait updating deployment after create OAuth secret -------" oc wait --for=delete pod/${SERVER_POD} --timeout=120s } runTestWorkspaceWithGitRepoUrl() { WS_NAME=$1 PROJECT_NAME=$2 GIT_REPO_URL=$3 OCP_USER_NAMESPACE=$4 oc project ${OCP_USER_NAMESPACE} cat .ci/openshift-ci/devworkspace-test.yaml > devworkspace-test.yaml echo "------- [INFO] Preparing 'devworkspace-test.yaml' and run test workspace -------" # patch the devworkspace-test.yaml file sed -i "s#ws-name#${WS_NAME}#g" devworkspace-test.yaml sed -i "s#project-name#${PROJECT_NAME}#g" devworkspace-test.yaml sed -i "s#git-repo-url#${GIT_REPO_URL}#g" devworkspace-test.yaml cat devworkspace-test.yaml oc apply -f devworkspace-test.yaml -n ${OCP_USER_NAMESPACE} oc wait -n ${OCP_USER_NAMESPACE} --for=condition=Ready dw ${WS_NAME} --timeout=420s echo "======= [INFO] Test workspace is run =======" } testProjectIsCloned() { PROJECT_NAME=$1 OCP_USER_NAMESPACE=$2 WORKSPACE_POD_NAME=$(oc get pods -n ${OCP_USER_NAMESPACE} | grep workspace | awk '{print $1}') if oc exec -it -n ${OCP_USER_NAMESPACE} ${WORKSPACE_POD_NAME} -- test -f /projects/${PROJECT_NAME}/${TEST_FILE_NAME}; then echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${TEST_FILE_NAME} exists. =======" else echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${TEST_FILE_NAME} is absent. =======" return 1 fi } testGitCredentialsData() { OCP_USER_NAMESPACE=$1 GIT_PROVIDER_PAT=$2 GIT_PROVIDER_URL=$3 echo "------- [INFO] Check the 'git credentials' is in a workspace -------" hostName="${GIT_PROVIDER_URL#https://}" if [ "${GIT_PROVIDER_TYPE}" == "azure-devops" ]; then userName="username" else userName=${GIT_PROVIDER_USERNAME} fi gitCredentials="https://${userName}:${GIT_PROVIDER_PAT}@${hostName}" WORKSPACE_POD_NAME=$(oc get pods -n ${OCP_USER_NAMESPACE} | grep workspace | awk '{print $1}') if oc exec -it -n ${OCP_USER_NAMESPACE} ${WORKSPACE_POD_NAME} -- cat /.git-credentials/credentials | grep -q ${gitCredentials}; then echo "======= [INFO] Git credentials file '/.git-credentials/credentials' exists and has the expected content. =======" else echo "####### [ERROR] Git credentials file '/.git-credentials/credentials' does not exist or has incorrect content. ###### ###### Cause possible: PR code regress or service is changed. Need to investigate it. #######" exit 1 fi } deleteTestWorkspace() { WS_NAME=$1 OCP_USER_NAMESPACE=$2 echo "------- [INFO] Delete test workspace -------" oc delete dw ${WS_NAME} -n ${OCP_USER_NAMESPACE} } startOAuthFactoryTest() { CHE_URL=https://$(oc get route -n ${CHE_NAMESPACE} che -o jsonpath='{.spec.host}') # patch oauth-factory-test.yaml cat .ci/openshift-ci/pod-oauth-factory-test.yaml > oauth-factory-test.yaml sed -i "s#CHE_URL#${CHE_URL}#g" oauth-factory-test.yaml sed -i "s#CHE-NAMESPACE#${CHE_NAMESPACE}#g" oauth-factory-test.yaml sed -i "s#OCP_USER_NAME#${OCP_NON_ADMIN_USER_NAME}#g" oauth-factory-test.yaml sed -i "s#OCP_USER_PASSWORD#${OCP_LOGIN_PASSWORD}#g" oauth-factory-test.yaml sed -i "s#FACTORY_REPO_URL#${PRIVATE_REPO_URL}#g" oauth-factory-test.yaml sed -i "s#GIT_BRANCH#${GIT_REPO_BRANCH}#g" oauth-factory-test.yaml sed -i "s#GIT_PROVIDER_TYPE#${GIT_PROVIDER_TYPE}#g" oauth-factory-test.yaml sed -i "s#GIT_PROVIDER_USER_NAME#${GIT_PROVIDER_LOGIN}#g" oauth-factory-test.yaml sed -i "s#GIT_PROVIDER_USER_PASSWORD#${GIT_PROVIDER_PASSWORD}#g" oauth-factory-test.yaml echo "------- [INFO] Applying the following patched OAuth Factory Test Pod: -------" cat oauth-factory-test.yaml echo "[INFO] --------------------------------------------------" oc apply -f oauth-factory-test.yaml # wait for the pod to start n=0 while [ $n -le 120 ] do PHASE=$(oc get pod -n ${CHE_NAMESPACE} ${TEST_POD_NAME} \ --template='{{ .status.phase }}') if [[ ${PHASE} == "Running" ]]; then echo "======= [INFO] Factory test started successfully. =======" return fi sleep 5 n=$(( n+1 )) done echo "####### [ERROR] Failed to start Factory test. ####### ###### Cause possible: an infrastructure problem, pod could not start, try to rerun the test. #######" exit 1 } startSmokeTest() { CHE_URL=https://$(oc get route -n ${CHE_NAMESPACE} che -o jsonpath='{.spec.host}') # patch che-smoke-test.yaml cat .ci/openshift-ci/pod-che-smoke-test.yaml > che-smoke-test.yaml sed -i "s#CHE_URL#${CHE_URL}#g" che-smoke-test.yaml sed -i "s#CHE-NAMESPACE#${CHE_NAMESPACE}#g" che-smoke-test.yaml sed -i "s#OCP_USER_NAME#${OCP_NON_ADMIN_USER_NAME}#g" che-smoke-test.yaml sed -i "s#OCP_USER_PASSWORD#${OCP_LOGIN_PASSWORD}#g" che-smoke-test.yaml echo "------- [INFO] Applying the following patched Smoke Test Pod: -------" cat che-smoke-test.yaml echo "[INFO] --------------------------------------------------" oc apply -f che-smoke-test.yaml # wait for the pod to start n=0 while [ $n -le 120 ] do PHASE=$(oc get pod -n ${CHE_NAMESPACE} ${TEST_POD_NAME} \ --template='{{ .status.phase }}') if [[ ${PHASE} == "Running" ]]; then echo "======= [INFO] Smoke test started successfully. =======" return fi sleep 5 n=$(( n+1 )) done echo "####### [ERROR] Failed to start Smoke test. ####### ####### Cause possible: an infrastructure problem, pod could not start, try to rerun the test. #######" exit 1 } # Catch the finish of the job and write logs in artifacts. catchFinish() { local RESULT=$? if [ "$RESULT" != "0" ]; then set +e collectEclipseCheLogs set -e fi echo "------- [INFO] Terminate the process after finish the test script. -------" set +e killProcessByPort set -e [[ "${RESULT}" != "0" ]] && echo "####### [ERROR] Job failed. #######" || echo "####### [INFO] Job completed successfully. #######" exit $RESULT } collectEclipseCheLogs() { mkdir -p ${ARTIFACTS_DIR}/che-logs # Collect all Eclipse Che logs and cluster CR oc login -u=${OCP_ADMIN_USER_NAME} -p=${OCP_LOGIN_PASSWORD} --insecure-skip-tls-verify=false oc get checluster -o yaml -n $CHE_NAMESPACE > "${ARTIFACTS_DIR}/che-cluster.yaml" chectl server:logs -n $CHE_NAMESPACE --directory ${ARTIFACTS_DIR}/che-logs --telemetry off } collectLogs() { echo "------- [INFO] Waiting until test pod finished. -------" oc logs -n ${CHE_NAMESPACE} ${TEST_POD_NAME} -c test -f sleep 3 # Download artifacts set +e echo -------" [INFO] Collect all Eclipse Che logs and cluster CR. -------" collectEclipseCheLogs echo "------- [INFO] Downloading test report. -------" mkdir -p ${ARTIFACTS_DIR}/e2e oc rsync -n ${CHE_NAMESPACE} ${TEST_POD_NAME}:/tmp/e2e/report/ ${ARTIFACTS_DIR}/e2e -c download-reports oc exec -n ${CHE_NAMESPACE} ${TEST_POD_NAME} -c download-reports -- touch /tmp/done # Revoke and delete the OAuth application if [[ ${TEST_POD_NAME} == "oauth-factory-test" ]]; then revokeAuthorizedOAuthApplication ${APPLICATION_ID} ${APPLICATION_SECRET} deleteOAuthApplicationGitLabServer ${OAUTH_ID} ${ADMIN_ACCESS_TOKEN} fi set -e EXIT_CODE=$(oc logs -n ${CHE_NAMESPACE} ${TEST_POD_NAME} -c test | grep EXIT_CODE) if [[ ${EXIT_CODE} != "+ EXIT_CODE=0" ]]; then echo "####### [ERROR] GUI test failed. Job failed. ####### ###### Cause possible: PR code regress or service is changed. Need to investigate it. #######" exit 1 fi echo "======= [INFO] Job completed successfully. =======" } testCloneGitRepoNoProjectExists() { WS_NAME=$1 PROJECT_NAME=$2 GIT_REPO_URL=$3 OCP_USER_NAMESPACE=$4 runTestWorkspaceWithGitRepoUrl ${WS_NAME} ${PROJECT_NAME} ${GIT_REPO_URL} ${OCP_USER_NAMESPACE} echo "------- [INFO] Check the private repository is NOT cloned with NO PAT/OAuth setup. -------" testProjectIsCloned ${PROJECT_NAME} ${OCP_USER_NAMESPACE} && \ { echo "####### [ERROR] Project file /projects/${PROJECT_NAME}/${TEST_FILE_NAME} should NOT be present. ####### ####### Cause possible: PR code regress or service is changed. Need to investigate it. #######" && exit 1; } echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${TEST_FILE_NAME} is NOT present. This is EXPECTED. =======" } # Verify that a public repository is cloned without requiring PAT, OAuth, or SSH configuration. # Verify that a public or private repository is cloned when PAT, OAuth, or SSH configuration is provided. testCloneGitRepoProjectShouldExists() { WS_NAME=$1 PROJECT_NAME=$2 GIT_REPO_URL=$3 OCP_USER_NAMESPACE=$4 runTestWorkspaceWithGitRepoUrl ${WS_NAME} ${PROJECT_NAME} ${GIT_REPO_URL} ${OCP_USER_NAMESPACE} echo "------- [INFO] Check the repository is cloned. -------" testProjectIsCloned ${PROJECT_NAME} ${OCP_USER_NAMESPACE} || \ { echo "####### [ERROR] Project file /projects/${PROJECT_NAME}/${TEST_FILE_NAME} should be present. ####### ###### Cause possible: PR code regress or service is changed. Need to investigate it. #######" && exit 1; } } setupTestEnvironment() { OCP_USER_NAME=$1 provisionOpenShiftOAuthUser createCustomResourcesFile deployChe forwardPortToService initUserNamespace ${OCP_USER_NAME} } setupTestEnvironmentOAuthFlow() { ADMIN_ACCESS_TOKEN=$1 APPLICATION_ID=$2 APPLICATION_SECRET=$3 provisionOpenShiftOAuthUser configureGitSelfSignedCertificate createCustomResourcesFile deployChe createOAuthApplicationGitLabServer ${ADMIN_ACCESS_TOKEN} ${APPLICATION_NAME} setupOAuthSecret ${APPLICATION_ID} ${APPLICATION_SECRET} } ================================================ FILE: .ci/openshift-ci/devworkspace-test.yaml ================================================ kind: DevWorkspace apiVersion: workspace.devfile.io/v1alpha2 metadata: name: ws-name spec: started: true routingClass: che template: projects: - name: project-name git: remotes: origin: git-repo-url contributions: - name: che-code uri: https://eclipse-che.github.io/che-plugin-registry/main/v3/plugins/che-incubator/che-code/latest/devfile.yaml components: - name: che-code-runtime-description container: env: - name: CODE_HOST value: 0.0.0.0 ================================================ FILE: .ci/openshift-ci/htpasswdProvider.yaml ================================================ apiVersion: config.openshift.io/v1 kind: OAuth metadata: name: cluster spec: identityProviders: - name: htpasswd mappingMethod: claim type: HTPasswd htpasswd: fileData: name: htpass-secret ================================================ FILE: .ci/openshift-ci/oauth-secret.yaml ================================================ kind: Secret apiVersion: v1 metadata: name: gitlab-oauth-secret namespace: eclipse-che labels: app.kubernetes.io/part-of: che.eclipse.org app.kubernetes.io/component: oauth-scm-configuration annotations: che.eclipse.org/oauth-scm-server: gitlab che.eclipse.org/scm-server-endpoint: git-provider-url type: Opaque stringData: id: application-id secret: application-secret ================================================ FILE: .ci/openshift-ci/pat-secret.yaml ================================================ kind: Secret apiVersion: v1 metadata: name: personal-access-token labels: app.kubernetes.io/component: scm-personal-access-token app.kubernetes.io/part-of: che.eclipse.org annotations: che.eclipse.org/che-userid: che-user-id che.eclipse.org/scm-personal-access-token-name: git-provider-name che.eclipse.org/scm-url: git-provider-url che.eclipse.org/scm-organization: '' stringData: token: content-access-token type: Opaque ================================================ FILE: .ci/openshift-ci/pod-che-smoke-test.yaml ================================================ apiVersion: v1 kind: Pod metadata: name: che-smoke-test namespace: CHE-NAMESPACE spec: volumes: - name: test-run-results - name: ffmpeg-video - name: dshm emptyDir: medium: Memory containers: # container containing the tests - name: test image: quay.io/eclipse/che-e2e:next imagePullPolicy: Always env: - name: VIDEO_RECORDING value: "true" - name: TEST_SUITE value: "test" - name: TS_SELENIUM_EDITOR value: "che-code" - name: USERSTORY value: "SmokeTest" - name: NODE_TLS_REJECT_UNAUTHORIZED value: "0" - name: TS_SELENIUM_BASE_URL value: "CHE_URL" - name: TS_SELENIUM_LOG_LEVEL value: "TRACE" - name: TS_SELENIUM_OCP_USERNAME value: "OCP_USER_NAME" - name: TS_SELENIUM_OCP_PASSWORD value: "OCP_USER_PASSWORD" - name: TS_SELENIUM_VALUE_OPENSHIFT_OAUTH value: "true" - name: TS_OCP_LOGIN_PAGE_PROVIDER_TITLE value: "htpasswd" - name: DELETE_WORKSPACE_ON_FAILED_TEST value: "true" - name: TS_SELENIUM_START_WORKSPACE_TIMEOUT value: "360000" - name: TS_IDE_LOAD_TIMEOUT value: "40000" volumeMounts: - name: test-run-results mountPath: /tmp/e2e/report/ - name: ffmpeg-video mountPath: /tmp/ffmpeg_report/ - name: dshm mountPath: /dev/shm resources: requests: memory: "3Gi" cpu: "2" limits: memory: "4Gi" cpu: "2" # Download results - name: download-reports image: eeacms/rsync imagePullPolicy: IfNotPresent volumeMounts: - name: test-run-results mountPath: /tmp/e2e/report/ - name: ffmpeg-video mountPath: /tmp/ffmpeg_report/ resources: command: ["sh"] args: [ "-c", "while true; if [[ -f /tmp/done ]]; then exit 0; fi; do sleep 1; done", ] restartPolicy: Never ================================================ FILE: .ci/openshift-ci/pod-oauth-factory-test.yaml ================================================ apiVersion: v1 kind: Pod metadata: name: oauth-factory-test namespace: CHE-NAMESPACE spec: volumes: - name: test-run-results - name: ffmpeg-video - name: dshm emptyDir: medium: Memory containers: # container containing the tests - name: test image: quay.io/eclipse/che-e2e:next imagePullPolicy: Always env: - name: VIDEO_RECORDING value: "true" - name: TEST_SUITE value: "test" - name: TS_SELENIUM_EDITOR value: "che-code" - name: USERSTORY value: "Factory" - name: NODE_TLS_REJECT_UNAUTHORIZED value: "0" - name: TS_SELENIUM_BASE_URL value: "CHE_URL" - name: TS_SELENIUM_LOG_LEVEL value: "TRACE" - name: TS_SELENIUM_OCP_USERNAME value: "OCP_USER_NAME" - name: TS_SELENIUM_OCP_PASSWORD value: "OCP_USER_PASSWORD" - name: TS_SELENIUM_VALUE_OPENSHIFT_OAUTH value: "true" - name: TS_SELENIUM_FACTORY_GIT_REPO_URL value: "FACTORY_REPO_URL" - name: TS_SELENIUM_FACTORY_GIT_REPO_BRANCH value: "GIT_BRANCH" - name: TS_SELENIUM_FACTORY_GIT_PROVIDER value: "GIT_PROVIDER_TYPE" - name: TS_SELENIUM_GIT_PROVIDER_OAUTH value: "true" - name: TS_SELENIUM_GIT_PROVIDER_USERNAME value: "GIT_PROVIDER_USER_NAME" - name: TS_SELENIUM_GIT_PROVIDER_PASSWORD value: "GIT_PROVIDER_USER_PASSWORD" - name: TS_OCP_LOGIN_PAGE_PROVIDER_TITLE value: "htpasswd" - name: DELETE_WORKSPACE_ON_FAILED_TEST value: "true" - name: TS_SELENIUM_START_WORKSPACE_TIMEOUT value: "360000" - name: TS_IDE_LOAD_TIMEOUT value: "40000" volumeMounts: - name: test-run-results mountPath: /tmp/e2e/report/ - name: ffmpeg-video mountPath: /tmp/ffmpeg_report/ - name: dshm mountPath: /dev/shm resources: requests: memory: "3Gi" cpu: "2" limits: memory: "4Gi" cpu: "2" # Download results - name: download-reports image: eeacms/rsync imagePullPolicy: IfNotPresent volumeMounts: - name: test-run-results mountPath: /tmp/e2e/report/ - name: ffmpeg-video mountPath: /tmp/ffmpeg_report/ resources: command: ["sh"] args: [ "-c", "while true; if [[ -f /tmp/done ]]; then exit 0; fi; do sleep 1; done", ] restartPolicy: Never ================================================ FILE: .ci/openshift-ci/ssh-secret.yaml ================================================ apiVersion: v1 kind: Secret metadata: name: git-ssh-key annotations: controller.devfile.io/mount-as: subpath controller.devfile.io/mount-path: /etc/ssh/ labels: controller.devfile.io/mount-to-devworkspace: "true" controller.devfile.io/watch-secret: "true" type: Opaque data: dwo_ssh_key: ssh_private_key dwo_ssh_key.pub: ssh_public_key ssh_config: aG9zdCAqCiAgSWRlbnRpdHlGaWxlIC9ldGMvc3NoL2R3b19zc2hfa2V5CiAgU3RyaWN0SG9zdEtleUNoZWNraW5nID0gbm8K ================================================ FILE: .ci/openshift-ci/test-azure-no-pat-oauth-flow-raw-devfile-url.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_RAW_PATH_URL=${PUBLIC_REPO_RAW_PATH_URL:-"https://dev.azure.com/chepullreq1/che-pr-public/_apis/git/repositories/public-repo/items?path=/devfile.yaml"} export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://dev.azure.com/chepullreq1/che-pr-private/_apis/git/repositories/private-repo/items?path=/devfile.yaml"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} testFactoryResolverResponse ${PUBLIC_REPO_RAW_PATH_URL} 200 testFactoryResolverResponse ${PRIVATE_REPO_RAW_PATH_URL} 400 testCloneGitRepoNoProjectExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-azure-no-pat-oauth-flow-ssh-url.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_SSH_URL=${PUBLIC_REPO_SSH_URL:-"git@ssh.dev.azure.com:v3/chepullreq1/che-pr-public/public-repo"} export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@ssh.dev.azure.com:v3/chepullreq1/che-pr-private/private-repo"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} setupSSHKeyPairs "${AZURE_PRIVATE_KEY}" "${AZURE_PUBLIC_KEY}" testFactoryResolverResponse ${PUBLIC_REPO_SSH_URL} 200 testFactoryResolverResponse ${PRIVATE_REPO_SSH_URL} 500 testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-azure-no-pat-oauth-flow.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://chepullreq1@dev.azure.com/chepullreq1/che-pr-public/_git/public-repo"} export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://dev.azure.com/chepullreq1/che-pr-private/_git/private-repo"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} testFactoryResolverNoPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL} testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-azure-with-pat-setup-flow.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://chepullreq1@dev.azure.com/chepullreq1/che-pr-public/_git/public-repo"} export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://dev.azure.com/chepullreq1/che-pr-private/_git/private-repo"} export GIT_PROVIDER_TYPE=${GIT_PROVIDER_TYPE:-"azure-devops"} export GIT_PROVIDER_URL=${GIT_PROVIDER_URL:-"https://dev.azure.com"} export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@ssh.dev.azure.com:v3/chepullreq1/che-pr-private/private-repo"} export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://dev.azure.com/chepullreq1/che-pr-private/_apis/git/repositories/private-repo/items?path=/devfile.yaml"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} setupPersonalAccessToken ${GIT_PROVIDER_TYPE} ${GIT_PROVIDER_URL} ${AZURE_PAT} requestProvisionNamespace testFactoryResolverWithPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL} echo "------- [INFO] Check clone public repository with PAT setup -------" testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE} testGitCredentialsData ${USER_CHE_NAMESPACE} ${AZURE_PAT} ${GIT_PROVIDER_URL} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} echo "------- [INFO] Check clone private repository with PAT setup -------" testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE} testGitCredentialsData ${USER_CHE_NAMESPACE} ${AZURE_PAT} ${GIT_PROVIDER_URL} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} echo "------- [INFO] Check clone private repository by raw devfile URL with PAT setup -------" testFactoryResolverResponse ${PRIVATE_REPO_RAW_PATH_URL} 200 testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} setupSSHKeyPairs "${AZURE_PRIVATE_KEY}" "${AZURE_PUBLIC_KEY}" echo "------- [INFO] Check clone private repository by SSH URL with PAT setup -------" testFactoryResolverResponse ${PRIVATE_REPO_SSH_URL} 200 testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-bitbucket-no-pat-oauth-flow-raw-devfile-url.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_RAW_PATH_URL=${PUBLIC_REPO_RAW_PATH_URL:-"https://bitbucket.org/chepullreq/public-repo/raw/746000bd63a54eaf8ea8aba9dfe6620e5c6c61d7/devfile.yaml"} export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://bitbucket.org/chepullreq/private-repo/raw/80b183d27c6c533462128b0c092208aad73b2906/devfile.yaml"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} testFactoryResolverNoPatOAuthRaw ${PUBLIC_REPO_RAW_PATH_URL} ${PRIVATE_REPO_RAW_PATH_URL} testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-bitbucket-no-pat-oauth-flow-ssh-url.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_SSH_URL=${PUBLIC_REPO_SSH_URL:-"git@bitbucket.org:chepullreq/public-repo.git"} export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@bitbucket.org:chepullreq/private-repo.git"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} setupSSHKeyPairs "${BITBUCKET_PRIVATE_KEY}" "${BITBUCKET_PUBLIC_KEY}" testFactoryResolverNoPatOAuth ${PUBLIC_REPO_SSH_URL} ${PRIVATE_REPO_SSH_URL} testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-bitbucket-no-pat-oauth-flow.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://chepullreq1@bitbucket.org/chepullreq/public-repo.git"} export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://chepullreq1@bitbucket.org/chepullreq/private-repo.git"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} testFactoryResolverNoPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL} testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-che-smoke-test.sh ================================================ #!/bin/bash # # Copyright (c) 2024 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export TEST_POD_NAME=${TEST_POD_NAME:-"che-smoke-test"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "collectLogs" EXIT SIGINT provisionOpenShiftOAuthUser createCustomResourcesFile deployChe startSmokeTest ================================================ FILE: .ci/openshift-ci/test-gitea-no-pat-oauth-flow.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_SSH_URL=${PUBLIC_REPO_SSH_URL:-"git@gitea.com:chepullreq1/public-repo.git"} export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@gitea.com:chepullreq1/private-repo.git"} export PUBLIC_REPO_RAW_PATH_URL=${PUBLIC_REPO_RAW_PATH_URL:-"https://gitea.com/chepullreq1/public-repo/raw/branch/main/devfile.yaml"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} setupSSHKeyPairs "${GITEA_PRIVATE_KEY}" "${GITEA_PUBLIC_KEY}" testFactoryResolverResponse ${PUBLIC_REPO_SSH_URL} 500 testFactoryResolverResponse ${PRIVATE_REPO_SSH_URL} 500 testFactoryResolverResponse ${PUBLIC_REPO_RAW_PATH_URL} 200 testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoNoProjectExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-gitea-with-pat-setup-flow.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_RAW_PATH_URL:-"https://${GITEA_PAT}@gitea.com/chepullreq1/private-repo/raw/branch/main/devfile.yaml"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} testFactoryResolverResponse ${PRIVATE_REPO_RAW_PATH_URL} 200 testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-github-no-pat-oauth-flow-raw-devfile-url.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_RAW_PATH_URL=${PUBLIC_REPO_RAW_PATH_URL:-"https://raw.githubusercontent.com/chepullreq1/public-repo/main/devfile.yaml"} export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://raw.githubusercontent.com/chepullreq1/private-repo/main/devfile.yaml"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} testFactoryResolverNoPatOAuthRaw ${PUBLIC_REPO_RAW_PATH_URL} ${PRIVATE_REPO_RAW_PATH_URL} testCloneGitRepoNoProjectExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-github-no-pat-oauth-flow-ssh-url.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_SSH_URL=${PUBLIC_REPO_SSH_URL:-"git@github.com:chepullreq1/public-repo.git"} export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@github.com:chepullreq1/private-repo.git"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} setupSSHKeyPairs "${GITHUB_PRIVATE_KEY}" "${GITHUB_PUBLIC_KEY}" testFactoryResolverNoPatOAuth ${PUBLIC_REPO_SSH_URL} ${PRIVATE_REPO_SSH_URL} testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-github-no-pat-oauth-flow.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://github.com/chepullreq1/public-repo.git"} export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://github.com/chepullreq1/private-repo.git"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} testFactoryResolverNoPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL} testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-github-with-pat-setup-flow.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://github.com/chepullreq1/public-repo.git"} export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://github.com/chepullreq1/private-repo.git"} export GIT_PROVIDER_TYPE=${GIT_PROVIDER_TYPE:-"github"} export GIT_PROVIDER_URL=${GIT_PROVIDER_URL:-"https://github.com"} export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://raw.githubusercontent.com/chepullreq1/private-repo/main/devfile.yaml"} export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@github.com:chepullreq1/private-repo.git"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} setupPersonalAccessToken ${GIT_PROVIDER_TYPE} ${GIT_PROVIDER_URL} ${GITHUB_PAT} requestProvisionNamespace testFactoryResolverWithPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL} echo "------- [INFO] Check clone public repository with PAT setup -------" testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE} testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITHUB_PAT} ${GIT_PROVIDER_URL} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} echo "------- [INFO] Check clone private repository with PAT setup -------" testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE} testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITHUB_PAT} ${GIT_PROVIDER_URL} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} echo "------- [INFO] Check clone private repository by raw devfile URL with PAT setup -------" testFactoryResolverResponse ${PRIVATE_REPO_RAW_PATH_URL} 200 testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} setupSSHKeyPairs "${GITHUB_PRIVATE_KEY}" "${GITHUB_PUBLIC_KEY}" echo "------- [INFO] Check clone private repository by SSH URL with PAT setup -------" testFactoryResolverResponse ${PRIVATE_REPO_SSH_URL} 200 testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-gitlab-no-pat-oauth-flow-raw-devfile-url.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_RAW_PATH_URL=${PUBLIC_REPO_RAW_PATH_URL:-"https://gitlab.com/chepullreq1/public-repo/-/raw/main/devfile.yaml"} export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://gitlab.com/chepullreq1/private-repo/-/raw/main/devfile.yaml"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} testFactoryResolverNoPatOAuth ${PUBLIC_REPO_RAW_PATH_URL} ${PRIVATE_REPO_RAW_PATH_URL} testCloneGitRepoNoProjectExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-gitlab-no-pat-oauth-flow-ssh-url.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_SSH_URL=${PUBLIC_REPO_SSH_URL:-"git@gitlab.com:chepullreq1/public-repo.git"} export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@gitlab.com:chepullreq1/private-repo.git"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} setupSSHKeyPairs "${GITLAB_PRIVATE_KEY}" "${GITLAB_PUBLIC_KEY}" testFactoryResolverNoPatOAuth ${PUBLIC_REPO_SSH_URL} ${PRIVATE_REPO_SSH_URL} testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-gitlab-no-pat-oauth-flow.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://gitlab.com/chepullreq1/public-repo.git"} export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://gitlab.com/chepullreq1/private-repo.git"} export PUBLIC_REPO_WITH_DOT_DEFILE_URL=${PUBLIC_REPO_WITH_DOT_DEFILE_URL:-"https://gitlab.com/chepullreq1/public-repo-dot-devfile.git"} export PRIVATE_REPO_WITH_DOT_DEFILE_URL=${PRIVATE_REPO_WITH_DOT_DEFILE_URL:-"https://gitlab.com/chepullreq1/private-repo-dot-devfile.git"} export NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE=${NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE:-"public-repo-dot-devfile"} export NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE=${NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE:-"private-repo-dot-devfile"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} testFactoryResolverNoPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL} testFactoryResolverNoPatOAuth ${PUBLIC_REPO_WITH_DOT_DEFILE_URL} ${PRIVATE_REPO_WITH_DOT_DEFILE_URL} echo "------- [INFO] Check clone a public repository without PAT -------" testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} echo "------- [INFO] Check clone a public repository with .devfile.yaml and without PAT -------" testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE} ${PUBLIC_REPO_WITH_DOT_DEFILE_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} echo "------- [INFO] Check clone a private repository without PAT is not available -------" testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} echo "------- [INFO] Check clone a private repository with .devfile.yaml and without PAT is not available -------" testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE} ${PRIVATE_REPO_WITH_DOT_DEFILE_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .ci/openshift-ci/test-gitlab-with-oauth-setup-flow.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export TEST_POD_NAME=${TEST_POD_NAME:-"oauth-factory-test"} export GIT_PROVIDER_TYPE=${GIT_PROVIDER_TYPE:-"gitlab"} export GIT_PROVIDER_URL=${GIT_PROVIDER_URL:-"https://gitlab-gitlab-system.apps.git.crw-qe.com"} export GIT_PROVIDER_LOGIN=${GIT_PROVIDER_LOGIN:-"admin-user"} export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://gitlab-gitlab-system.apps.git.crw-qe.com/admin-user/private-repo.git"} export GIT_REPO_BRANCH=${PRIVATE_REPO_BRANCH:-"main"} export APPLICATION_NAME=${APPLICATION_NAME:-"TestApp"} export OAUTH_ID="" export APPLICATION_ID="" export APPLICATION_SECRET="" # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "collectLogs" EXIT SIGINT setupTestEnvironmentOAuthFlow ${ADMIN_ACCESS_TOKEN} ${APPLICATION_NAME} ${APPLICATION_ID} ${APPLICATION_SECRET} startOAuthFactoryTest ================================================ FILE: .ci/openshift-ci/test-gitlab-with-pat-setup-flow.sh ================================================ #!/bin/bash # # Copyright (c) 2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # exit immediately when a command fails set -ex # only exit with zero if all commands of the pipeline exit successfully set -o pipefail echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. =======" export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://gitlab.com/chepullreq1/public-repo.git"} export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://gitlab.com/chepullreq1/private-repo.git"} export GIT_PROVIDER_TYPE=${GIT_PROVIDER_TYPE:-"gitlab"} export GIT_PROVIDER_URL=${GIT_PROVIDER_URL:-"https://gitlab.com"} export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@gitlab.com:chepullreq1/private-repo.git"} export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://gitlab.com/chepullreq1/private-repo/-/raw/main/devfile.yaml"} export PUBLIC_REPO_WITH_DOT_DEFILE_URL=${PUBLIC_REPO_WITH_DOT_DEFILE_URL:-"https://gitlab.com/chepullreq1/public-repo-dot-devfile.git"} export PRIVATE_REPO_WITH_DOT_DEFILE_URL=${PRIVATE_REPO_WITH_DOT_DEFILE_URL:-"https://gitlab.com/chepullreq1/private-repo-dot-devfile.git"} export NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE=${NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE:-"public-repo-dot-devfile"} export NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE=${NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE:-"private-repo-dot-devfile"} # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} setupPersonalAccessToken ${GIT_PROVIDER_TYPE} ${GIT_PROVIDER_URL} ${GITLAB_PAT} requestProvisionNamespace testFactoryResolverWithPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL} testFactoryResolverWithPatOAuth ${PUBLIC_REPO_WITH_DOT_DEFILE_URL} ${PRIVATE_REPO_WITH_DOT_DEFILE_URL} echo "------- [INFO] Check clone public repository with PAT setup -------" testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE} testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITLAB_PAT} ${GIT_PROVIDER_URL} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} echo "------- [INFO] Check clone public repository that has .devfile.yaml and with PAT setup -------" testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE} ${PUBLIC_REPO_WITH_DOT_DEFILE_URL} ${USER_CHE_NAMESPACE} testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITLAB_PAT} ${GIT_PROVIDER_URL} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} echo "------- [INFO] Check clone private repository with PAT setup -------" testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE} testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITLAB_PAT} ${GIT_PROVIDER_URL} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} echo "------- [INFO] Check clone private repository that has .devfile.yaml and with PAT setup -------" testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE} ${PRIVATE_REPO_WITH_DOT_DEFILE_URL} ${USER_CHE_NAMESPACE} testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITLAB_PAT} ${GIT_PROVIDER_URL} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} echo "------- [INFO] Check clone private repository by raw devfile URL with PAT setup -------" testFactoryResolverResponse ${PRIVATE_REPO_RAW_PATH_URL} 200 testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} setupSSHKeyPairs "${GITLAB_PRIVATE_KEY}" "${GITLAB_PUBLIC_KEY}" echo "------- [INFO] Check clone private repository by SSH URL with PAT setup -------" testFactoryResolverResponse ${PRIVATE_REPO_SSH_URL} 200 testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_SSH_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} ================================================ FILE: .gitattributes ================================================ *.sh text eol=lf *.ts text eol=lf ================================================ FILE: .github/CODEOWNERS ================================================ # Global Owners * @SDawley @ibuziuk @vinokurig @tolusha # che.properties file is quasi-documentation, so in future we might grant access to this # assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/** @osslate # deploy deploy/** @tolusha @dkwon17 ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ### What does this PR do? ### Screenshot/screencast of this PR ### What issues does this PR fix or reference? ### How to test this PR? ### PR Checklist [As the author of this Pull Request I made sure that:](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#pull-request-template-and-its-checklist) - [ ] [The Eclipse Contributor Agreement is valid](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#the-eclipse-contributor-agreement-is-valid) - [ ] [Code produced is complete](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#code-produced-is-complete) - [ ] [Code builds without errors](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#code-builds-without-errors) - [ ] [Tests are covering the bugfix](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#tests-are-covering-the-bugfix) - [ ] [The repository devfile is up to date and works](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#the-repository-devfile-is-up-to-date-and-works) - [ ] [Sections `What issues does this PR fix or reference` and `How to test this PR` completed](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#sections-what-issues-does-this-pr-fix-or-reference-and-how-to-test-this-pr-completed) - [ ] [Relevant user documentation updated](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#relevant-contributing-documentation-updated) - [ ] [Relevant contributing documentation updated](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#relevant-contributing-documentation-updated) - [ ] [CI/CD changes implemented, documented and communicated](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#cicd-changes-implemented-documented-and-communicated) ### Release Notes ### Reviewers Reviewers, please comment how you tested the PR when approving it. ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: maven directory: "/" schedule: interval: "weekly" day: "sunday" open-pull-requests-limit: 15 ================================================ FILE: .github/workflows/build-pr-check.yml ================================================ # # Copyright (c) 2020 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # name: build-pr-check on: [push, pull_request] env: PR_IMAGE_TAG: pr-${{ github.event.pull_request.number }} ORGANIZATION: quay.io/eclipse jobs: build: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Check all properties have description run: ./check_properties_description.sh - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '17' cache: 'maven' - name: Set up QEMU # Skip this and other docker related steps, if the PR is from a forked repo. GitHub secrets # are not available for forked repos, so the podman login step will fail. # See https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#using-secrets-in-a-workflow if: ${{ ! github.event.pull_request.head.repo.fork }} uses: docker/setup-qemu-action@v3 - name: Login to quay.io if: ${{ github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork }} uses: redhat-actions/podman-login@v1.7 with: username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_PASSWORD }} registry: quay.io - name: Build with Maven run: mvn -B clean install -U -Pintegration - name: Build and push images if: ${{ github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork }} run: ./build/build.sh --tag:${{ env.PR_IMAGE_TAG }} --build-platforms:linux/amd64,linux/ppc64le,linux/arm64,linux/s390x --builder:podman --push-image - name: Comment with image name uses: actions/github-script@v7 if: ${{ github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork }} with: script: | const { issue: { number: issue_number }, repo: { owner, repo } } = context; const cheServerImage = "${{ env.ORGANIZATION }}/che-server:${{ env.PR_IMAGE_TAG }}"; const patchCommand = `kubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{\"op\": \"replace\", \"path\": \"/spec/components/cheServer/deployment\", \"value\": {containers: [{image: \"${cheServerImage}\", name: che}]}}]"`; const text = ` Docker image build succeeded: **${cheServerImage}**
kubectl patch command \`\`\`bash ${patchCommand} \`\`\`
`; github.rest.issues.createComment({ issue_number, owner, repo, body: text }); ================================================ FILE: .github/workflows/che-properties-docs-update.yml ================================================ # # Copyright (c) 2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # name: Update Che docs variables on: workflow_dispatch: push: branches: - main - '7.[0-9]+.x' paths: - 'assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties' - 'assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/multiuser.properties' jobs: build: runs-on: ubuntu-22.04 steps: - name: Execute workflow in Che Docs id: build run: | invokeAction() { this_repo=$1 this_action_name=$2 this_workflow_id=$3 this_var=$4 this_val=$5 # can compute using GH API # workflow_id=$(curl -sSL https://api.github.com/repos/${this_repo}/actions/workflows -H "Authorization: token ${{ secrets.CHE_BOT_GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3+json" | jq --arg search_field "${this_action_name}" '.workflows[] | select(.name == $search_field).id'); # echo "workflow_id = $workflow_id" # or just pass it in workflow_id=$this_workflow_id curl -sSL https://api.github.com/repos/${this_repo}/actions/workflows/${workflow_id}/dispatches -X POST -H "Authorization: token ${{ secrets.CHE_BOT_GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3+json" -d "{\"ref\":\"master\",\"inputs\": {\"${this_var}\":\"${this_val}\"} }" echo "[INFO] Invoked '${this_action_name}' action ($workflow_id) - see https://github.com/${this_repo}/actions?query=workflow%3A%22${this_action_name// /+}%22" } branch=${GITHUB_REF%/} # invoke action from che-docs repo invokeAction eclipse/che-docs "Update Che variables in the docs" "13902929" branch "${branch}" ================================================ FILE: .github/workflows/generate-maven-sbom.yml ================================================ name: Generate Maven SBOM on: release: types: [published] workflow_dispatch: inputs: version: description: "Version" default: "main" required: true env: JAVA_VERSION: '11' JAVA_DISTRO: 'temurin' PRODUCT_PATH: './' PLUGIN_VERSION: '2.7.8' SBOM_TYPE: 'makeAggregateBom' permissions: contents: read jobs: generate-sbom: runs-on: ubuntu-latest outputs: project-version: ${{ steps.version.outputs.PROJECT_VERSION }} steps: - name: Extract version id: version run: | VERSION="${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.version }}" echo "PROJECT_VERSION=$VERSION" >> $GITHUB_OUTPUT echo "Product version: $VERSION" - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 ref: ${{ steps.version.outputs.PROJECT_VERSION }} - name: Setup Java SDK uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: java-version: ${{ env.JAVA_VERSION }} distribution: ${{ env.JAVA_DISTRO }} - name: Generate sbom run: | mvn org.cyclonedx:cyclonedx-maven-plugin:$PLUGIN_VERSION:$SBOM_TYPE -f "$PRODUCT_PATH/pom.xml" - name: Upload sbom uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: sbom path: ${{ env.PRODUCT_PATH }}/target/bom.json store-sbom-data: # stores sbom and metadata in a predefined format for otterdog to pick up needs: ['generate-sbom'] uses: eclipse-csi/workflows/.github/workflows/store-sbom-data.yml@main with: projectName: 'che-server' projectVersion: ${{ needs.generate-sbom.outputs.project-version }} bomArtifact: 'sbom' bomFilename: 'bom.json' parentProject: '1ab66138-685e-47bb-9020-feb6ca1fb40c' ================================================ FILE: .github/workflows/next-build.yml ================================================ # # Copyright (c) 2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # name: build-next on: workflow_dispatch: push: branches: - main jobs: build: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '17' cache: 'maven' - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Login to docker.io uses: redhat-actions/podman-login@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} registry: docker.io - name: Login to quay.io uses: redhat-actions/podman-login@v1 with: username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_PASSWORD }} registry: quay.io - name: Build with Maven run: mvn -B clean install -U -Pintegration - name: Build and push images id: build run: | echo "short_sha1=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT ./build/build.sh --tag:next --sha-tag --build-platforms:linux/amd64,linux/ppc64le,linux/arm64,linux/s390x --builder:podman --push-image - name: Create failure MM message if: ${{ failure() }} run: | echo "{\"text\":\":no_entry_sign: Next Che Server build has failed: https://github.com/eclipse-che/che-server/actions/workflows/next-build.yml\"}" > mattermost.json - name: Send MM message if: ${{ failure() }} uses: mattermost/action-mattermost-notify@1.1.0 env: MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }} MATTERMOST_CHANNEL: eclipse-che-ci MATTERMOST_USERNAME: che-bot ================================================ FILE: .github/workflows/release.yml ================================================ # # Copyright (c) 2020-2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # name: Release Che Server on: workflow_dispatch: inputs: version: description: 'The version that is going to be released. Should be in format 7.y.z' required: true default: '' forceRecreateTags: description: 'If true, tags will be overriden and regenerated. Use with caution.' required: false default: 'false' rebuildFromExistingTags: description: 'If true, and *forceRecreateTags* is false, a checkout to existing tags will be performed.' required: false default: 'false' buildAndPushImages: description: 'If true, Che Server images will be build.' required: false default: 'true' bumpNextVersion: description: 'If true, will update this project to the next version.' required: false default: 'true' jobs: build: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 with: path: che-server fetch-depth: 0 - name: Check existing tag run: | if [[ "$FORCE_RECREATE_TAGS" == "false" ]] && [[ $(cd che && git ls-remote --exit-code origin refs/tags/${{ github.event.inputs.version }}) ]]; then echo "cannot create release, when tag already exists" exit 1 fi - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Login to docker.io uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} registry: docker.io - name: Login to quay.io uses: docker/login-action@v2 with: username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_PASSWORD }} registry: quay.io - name: Set up Python 3.9 uses: actions/setup-python@v4 with: python-version: 3.9 - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '17' cache: 'maven' - name: Set up environment run: | sudo apt-get update -y || true # install more dependencies sudo apt-get -y -q install wget curl bash git java -version git --version gh --version # want >=5 bash --version java -version which java javac -version which javac - name: Release run: | export CHE_VERSION="${{ github.event.inputs.version }}" echo "CHE_VERSION=${CHE_VERSION}" export CHE_MAVEN_SETTINGS=${{ secrets.CHE_MAVEN_SETTINGS}} export CHE_OSS_SONATYPE_GPG_KEY=${{ secrets.CHE_OSS_SONATYPE_GPG_KEY}} export CHE_OSS_SONATYPE_PASSPHRASE=${{ secrets.CHE_OSS_SONATYPE_PASSPHRASE}} export CHE_GITHUB_SSH_KEY=${{ secrets.CHE_GITHUB_SSH_KEY}} export CHE_NPM_AUTH_TOKEN=${{ secrets.CHE_NPM_AUTH_TOKEN}} export DEPLOY_TO_NEXUS="${{ github.event.inputs.deployOnNexus }}" export AUTORELEASE_ON_NEXUS="${{ github.event.inputs.autoReleaseOnNexus }}" export REBUILD_FROM_EXISTING_TAGS="${{ github.event.inputs.rebuildFromExistingTags }}" export BUILD_AND_PUSH_IMAGES="${{ github.event.inputs.buildAndPushImages }}" export BUMP_NEXT_VERSION="${{ github.event.inputs.bumpNextVersion }}" export QUAY_ECLIPSE_CHE_USERNAME=${{ secrets.QUAY_USERNAME }} export QUAY_ECLIPSE_CHE_PASSWORD=${{ secrets.QUAY_PASSWORD }} git config --global user.name "Mykhailo Kuznietsov" git config --global user.email "mkuznets@redhat.com" export GITHUB_TOKEN=${{ secrets.CHE_BOT_GITHUB_TOKEN }} export CHE_BOT_GITHUB_TOKEN=${{ secrets.CHE_BOT_GITHUB_TOKEN }} set -e # determine which OS we're using: rhel or ubuntu cat /etc/os-release || true ./che-server/make-release.sh - name: Create GH Release id: create_release uses: ncipollo/release-action@v1 env: GITHUB_TOKEN: ${{ secrets.CHE_BOT_GITHUB_TOKEN }} with: allowUpdates: true body: Eclipse Che ${{ github.event.inputs.version }} draft: false name: Eclipse Che ${{ github.event.inputs.version }} omitBodyDuringUpdate: true omitNameDuringUpdate: true prerelease: false tag: ${{ github.event.inputs.version }} token: ${{ secrets.CHE_BOT_GITHUB_TOKEN }} #- name: Create failure MM message #if: ${{ failure() }} #run: | #echo "{\"text\":\":no_entry_sign: Che Server ${{ github.event.inputs.version }} release has failed: https://github.com/eclipse-che/che-server/actions/workflows/release.yml\"}" > mattermost.json #- name: Create success MM message #run: | #echo "{\"text\":\":white_check_mark: Che Server ${{ github.event.inputs.version }} has been released: https://quay.io/eclipse/che-server:${{ github.event.inputs.version }}\"}" > mattermost.json #- name: Send MM message #if: ${{ success() }} || ${{ failure() }} #uses: mattermost/action-mattermost-notify@1.1.0 #env: #MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }} #MATTERMOST_CHANNEL: eclipse-che-releases #MATTERMOST_USERNAME: che-bot ================================================ FILE: .github/workflows/try-in-web-ide.yaml ================================================ # Add Web IDE link on PRs name: Try in Web IDE on: pull_request_target: types: [opened, synchronize] jobs: add-link: runs-on: ubuntu-22.04 steps: - name: Web IDE Pull Request Check uses: redhat-actions/try-in-web-ide@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }} add_comment: false ================================================ FILE: .gitignore ================================================ .repository/ # Eclipse # ################### *.launch .classpath .project .settings/ target/ bin/ test-output/ maven-eclipse.xml .factorypath # Idea # ################## *.iml *.ipr *.iws .idea/ .vscode/ # Compiled source # ################### !*.com/ *.class *.dll *.exe *.o *.so # Packages # ############ # it's better to unpack these files and commit the raw source # git has its own built in compression methods *.7z *.dmg *.gz *.iso *.jar *.rar *.tar *.zip *.war *.ear # Logs and databases # ###################### *.log *.sqlite npm-debug.log.* # OS generated files # ###################### .DS_Store ehthumbs.db Icon? Thumbs.db */overlays *~ .directory assembly/**/overlays/ # Che files # ############# .che dockerfiles/che/eclipse-che.tar.gz dockerfiles/che/eclipse-che/ .unison* docs/_site docs/.sass-cache docs/.jekyll-metadata docs/assets/imgs !assembly/assembly-main/src/assembly/bin/ # NodeJs modules # ################## plugins/plugin-terminal-ui/node_modules/ plugins/plugin-terminal-ui/build/ requirements.lock tests/e2e/dist/ tests/e2e/node_modules/ tests/e2e/report/ tests/e2e/load-test-folder # Cypress modules # ################### */cypress/screenshots */cypress/videos */node_modules */pids */.pid */.seed */lib-cov */coverage */.grunt */.lock-wscript */cypress-tests/dist # typescript-dto /typescript-dto/.pnp.cjs ================================================ FILE: .mvn/jvm.config ================================================ -Xmx2048m -Xms1024m -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dgwt.compiler.localWorkers=1 --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED ================================================ FILE: CONTRIBUTING.md ================================================ ## Developing with Eclipse Che This project contains a [devfile v2](https://github.com/eclipse-che/che-server/blob/main/devfile.yaml) located in the project's root directory. With this devfile you can perform the following tasks: - Build project sources with maven. - Build a container image. - Install [checlt cli tool](https://github.com/che-incubator/chectl). - Start debug session. - Push your changes to GitHub. ### Starting a Workspace within Eclipse Che To start a new workspace within Eclipse Che, navigate to the following URL: ```sh {CHE-HOST}/#https://github.com/eclipse-che/che-server ``` ### Running Devfile Tasks for Downloading Dependencies and Building the Project For VS Code, execute tasks defined in the devfile with these steps: 1. Open the command palette (Ctrl/Cmd + Shift + P). 2. Execute the `Tasks: Run Task` command. 3. Select the `devfile` folder and select the `1. Build sources` task to build the project sources with maven. 4. Follow steps 1 and 2 again, select the `1. Build image` task in the `devfile` folder to build a container image. 5. Follow steps 1 and 2 again, select the `3. Install chectl` task in the `devfile` folder to install the chectl cli tool. 6. In the workspace terminal, tag the **che-server** image with your account: `podman tag quay.io/eclipse/che-server:next //che-server:next`. 7. Push the **che-server** image to your account: `podman push //che-server:next`. # Local build requirements - Apache Maven 3.9 or later - JDK 17 - Podman or Docker (required for running integration tests) A Che workspace environment allows to build the image internally, using the workspace terminal. # Start and debug 1. Deploy Che to a [Red Hat OpenShift](https://www.eclipse.org/che/docs/stable/administration-guide/installing-che-on-openshift-using-cli/) or [Minikube](https://www.eclipse.org/che/docs/stable/administration-guide/installing-che-on-minikube/) cluster by using a previously built image: `chectl server:deploy --platform= --cheimage=//che-server:next --debug`. 2. Enable local debugging of the Eclipse Che server: `chectl server:debug` in a terminal. 3. Hit a breakpoint in the code. 4. Open the `Run and Debug` (Ctrl/Cmd + Shift + D) panel. 5. Click the `Start Debugging` (F5) button. # Contributing an SCM provider An SCM provider support has to be provided by adding new maven modules to the wsmaster directory. ## Implementing che-core-api-oauth- module This module is responsible for Oauth requests to the SCM provider and contans next implementations: 1. `OAuthAuthenticator` contains specific implementation of Oauth token requestand authentication endpoint. 2. `OAuthAuthenticatorProvider` a provider of the `OAuthAuthenticator`. ## Implementing coresponding che-core-api-factory- module This module is responsible for API operations of the specific SCM provider. 1. `ApiClient` contains all necessary HTTP API calls like `getUser()`. 2. `AuthorisingFileContentProvider` overrides the common `AuthorisingFileContentProvider` to define the specific `isPublicRepository()` function. 3. `FactoryParameterResolver` validates SCM URL if it corresponds to the SCM provider. Also Provides specific Factory Parameters resolver for the SCM repository. 4. `PersonalAccessTokenFetcher` fetches Personal Access Token from the SCM provider by reqesting the OAuth API of the provider. Validates the token by comparing the token access scopes with the predefined scope list. 5. `FileResolver` Implementation of a resolver that can return particular file content from specified SCM repository. 6. `URLParser` Parses the string representation of the URL to an SCM specific object 7. `UserDataFetcher` Implementation of a resolver that can return particular file content from specified SCM repository. # CI There are several [GitHub Actions](https://github.com/eclipse-che/che-server/actions) workflows implemented for this repository: - [![build-next](https://github.com/eclipse-che/che-server/actions/workflows/next-build.yml/badge.svg)](https://github.com/eclipse-che/che-server/actions/workflows/next-build.yml) Builds Maven artifacts, builds container images and pushes them to [quay.io](https://quay.io/organization/eclipse) on each commit to [`main`](https://github.com/eclipse-che/che-server/tree/main) branch. - [![Release Che Server](https://github.com/eclipse-che/che-server/actions/workflows/release.yml/badge.svg)](https://github.com/eclipse-che/che-server/actions/workflows/release.yml) Builds Maven artifacts and container images. Images are public and pushed to [quay.io](https://quay.io/organization/eclipse). See [RELEASE.md](https://github.com/eclipse-che/che-server/blob/master/RELEASE.md) for more information about this workflow. - [![Release Changelog](https://github.com/eclipse-che/che-server/actions/workflows/release-changelog.yml/badge.svg)](https://github.com/eclipse-che/che-server/actions/workflows/release-changelog.yml) Creates a GitHub release which will include a generated changelog. - [![Update Che docs variables](https://github.com/eclipse-che/che-server/actions/workflows/che-properties-docs-update.yml/badge.svg)](https://github.com/eclipse-che/che-server/actions/workflows/che-properties-docs-update.yml/badge.svg) Runs on each commit to [`main`](https://github.com/eclipse-che/che-server/tree/main) branch. - [![build-pr-check](https://github.com/eclipse-che/che-server/actions/workflows/build-pr-check.yml/badge.svg)](https://github.com/eclipse-che/che-server/actions/workflows/build-pr-check.yml) Builds Maven artifacts and container images. This workflow is used as a check for all pull requests that are submitted to this project. - [![Sonar](https://github.com/eclipse-che/che-server/actions/workflows/sonar.yaml/badge.svg)](https://github.com/eclipse-che/che-server/actions/workflows/sonar.yaml) Runs Sonar against the main branch. The result can be seen [here](https://sonarcloud.io/dashboard?id=org.eclipse.che%3Ache-server). - [![Try in Web IDE](https://github.com/eclipse-che/che-server/actions/workflows/try-in-web-ide.yaml/badge.svg)](https://github.com/eclipse-che/che-server/actions/workflows/try-in-web-ide.yaml) Used as a check for pull requests that are submitted to this project. Downstream builds can be found at the link below, which is _internal to Red Hat_. Stable builds can be found by replacing the 3.x with a specific version like 3.2. - [server_3.x](https://main-jenkins-csb-crwqe.apps.ocp-c1.prod.psi.redhat.com/job/DS_CI/job/server_3.x/) # Report issues Issues are tracked on the main Eclipse Che Repository: https://github.com/eclipse/che/issues ================================================ FILE: LICENSE ================================================ Eclipse Public License - v 2.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS "Contribution" means: a) in the case of the initial Contributor, the initial content Distributed under this Agreement, and b) in the case of each subsequent Contributor: i) changes to the Program, and ii) additions to the Program; where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution "originates" from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. "Contributor" means any person or entity that Distributes the Program. "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. "Program" means the Contributions Distributed in accordance with this Agreement. "Recipient" means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. "Derivative Works" shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. "Modified Works" shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. "Distribute" means the acts of a) distributing or b) making available in any manner that enables the transfer of a copy. "Source Code" means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. "Secondary License" means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. 2. GRANT OF RIGHTS a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. e) Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). 3. REQUIREMENTS 3.1 If a Contributor Distributes the Program in any form, then: a) the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and b) the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: i) effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; ii) effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; iii) does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and iv) requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. 3.2 When the Program is Distributed as Source Code: a) it must be made available under this Agreement, or if the Program (i) is combined with other material in a separate file or files made available under a Secondary License, and (ii) the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and b) a copy of this Agreement must be included with each copy of the Program. 3.3 Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability ("notices") contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. 4. COMMERCIAL DISTRIBUTION Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 5. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. Exhibit A - Form of Secondary Licenses Notice "This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}." Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. ================================================ FILE: NUMBERING.md ================================================ ### Schema ``` a.b.c-d a = major b = feature c = bug fix d = development, numbered as `d` ``` ### Release ``` a.b.0 - initial a.b.c - bug fixing ``` ## EXAMPLE ### Before Stable Release ``` 4.0.0-rc1-SNAPSHOT - This is a build 4.0.0-rc1 - This is a release 4.0.0-rc2-SNAPSHOT - This is a build 4.0.0-rc2 - This is a release ``` ### Ship ``` 4.0.0 ``` ### Branch For Fixes ``` 4.0.x ``` ### Version For Fixes On Ship Release ``` 4.0.1 4.0.2 ``` ================================================ FILE: README.md ================================================ [![Contribute](https://www.eclipse.org/che/contribute.svg)](https://workspaces.openshift.com#https://github.com/eclipse-che/che-server) # What is Che server Che Server provides an API for managing Kubernetes namespaces and to retrieve devfile content from repositories, hosted on GitHub, GitLab, Bitbucket and Microsoft Azure Repos. # Project structure Che Server is mostly a Java web application deployed on an Apache Tomcat server in a container. Che Server uses the following modules: ### OAuth1 / OAuth2 API implementations - wsmaster/che-core-api-auth - wsmaster/che-core-api-azure-devops - wsmaster/che-core-api-bitbucket - wsmaster/che-core-api-github - wsmaster/che-core-api-gitlab ### Factory flow implementations - wsmaster/che-core-api-factory-azure-devops - wsmaster/che-core-api-factory-bitbucket - wsmaster/che-core-api-factory-bitbucket-server - wsmaster/che-core-api-factory-github - wsmaster/che-core-api-factory-gitlab - wsmaster/che-core-api-factory-shared ### Kubernetes namespace provisioning - infrastructures/kubernetes - infrastructure/openshift - infrastructures/infrastructure-factory Other modules are deprecated and will be removed in the future. # License - [Eclipse Public License 2.0](LICENSE) # Join the community The Eclipse Che community is globally reachable through public chat rooms, mailing list and weekly calls. See the Eclipse Che Documentation about [how you can join our community](https://www.eclipse.org/che/docs/stable/overview/introduction-to-eclipse-che/#_joining_the_community). ## Builds * [![release latest stable](https://github.com/eclipse-che/che-server/actions/workflows/release.yml/badge.svg)](https://github.com/eclipse-che/che-server/actions/workflows/release.yml) ## SBOM To enhance supply chain security and offer users clear insight into project components, Eclipse Che now generates a Software Bill of Materials (SBOM) for every release. These are published to the Eclipse Foundation SBOM registry, with access instructions and usage details available in this [documentation](https://eclipse-csi.github.io/security-handbook/sbom/registry.html). ================================================ FILE: RELEASE.md ================================================ # Automated release workflow Release is performed with GitHub Actions workflow [release.yml](https://github.com/eclipse/che/actions/workflows/release.yml). The release will perform the build of Maven Artifacts, build and push of all nesessary docker images, and bumping up development version. [make-release.sh](https://github.com/eclipse/che/blob/master/make-release.sh) is the script that can be used for standalone release outside of GitHub Actions. However, ensure that all environment variables are set in place before invoking `./make-release.sh`, similarly to how it is outlined in [release.yml](https://github.com/eclipse/che/actions/workflows/release.yml): REBUILD_FROM_EXISTING_TAGS - if `true`, release will not create new tag, but instead checkout to existing one. Use this to rerun failed attempts, without having to recreate the tag. BUILD_AND_PUSH_IMAGES - if `true`, will build all asociated images in [dockerfiles](https://github.com/eclipse/che/tree/master/dockerfiles) directory. Set `false`, if this step needs to be skipped. BUMP_NEXT_VERSION - if `true`, will increase the development versions in main and bugfix branches. Set false, if this step needs to be skipped ================================================ FILE: assembly/assembly-che-tomcat/pom.xml ================================================ 4.0.0 che-assembly-parent org.eclipse.che 7.118.0-SNAPSHOT assembly-che-tomcat jar Che Assembly :: Assemblies Che Tomcat assembly-che-tomcat org.apache.maven.plugins maven-assembly-plugin make-assembly package single false false ${project.basedir}/src/assembly/assembly.xml org.apache.maven.plugins maven-clean-plugin true ${project.build.directory} **/* org.apache.maven.plugins maven-dependency-plugin unpack-tomcat prepare-package unpack org.apache.tomcat tomcat zip ${org.apache.tomcat.version} false analyze true ================================================ FILE: assembly/assembly-che-tomcat/src/assembly/LICENSE-tomcat.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. APACHE TOMCAT SUBCOMPONENTS: Apache Tomcat includes a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the following licenses. For the Eclipse JDT Core Batch Compiler (ecj-x.x.x.jar) component and the following Jakarta EE Schemas: - jakartaee_9.xsd - jakarta_web-services_2_0.xsd - jakarta_web-services_client_2_0.xsd - jsp_3_0.xsd - web-app_5_0.xsd - web-commonn_5_0.xsd - web-fragment_5_0.xsd - web-jsptaglibrary_3_0.xsd Eclipse Public License - v 2.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS "Contribution" means: a) in the case of the initial Contributor, the initial content Distributed under this Agreement, and b) in the case of each subsequent Contributor: i) changes to the Program, and ii) additions to the Program; where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution "originates" from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. "Contributor" means any person or entity that Distributes the Program. "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. "Program" means the Contributions Distributed in accordance with this Agreement. "Recipient" means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. "Derivative Works" shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. "Modified Works" shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. "Distribute" means the acts of a) distributing or b) making available in any manner that enables the transfer of a copy. "Source Code" means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. "Secondary License" means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. 2. GRANT OF RIGHTS a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. e) Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). 3. REQUIREMENTS 3.1 If a Contributor Distributes the Program in any form, then: a) the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and b) the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: i) effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; ii) effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; iii) does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and iv) requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. 3.2 When the Program is Distributed as Source Code: a) it must be made available under this Agreement, or if the Program (i) is combined with other material in a separate file or files made available under a Secondary License, and (ii) the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and b) a copy of this Agreement must be included with each copy of the Program. 3.3 Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability ("notices") contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. 4. COMMERCIAL DISTRIBUTION Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 5. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. Exhibit A - Form of Secondary Licenses Notice "This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}." Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. For the Windows Installer component: * All NSIS source code, plug-ins, documentation, examples, header files and graphics, with the exception of the compression modules and where otherwise noted, are licensed under the zlib/libpng license. * The zlib compression module for NSIS is licensed under the zlib/libpng license. * The bzip2 compression module for NSIS is licensed under the bzip2 license. * The lzma compression module for NSIS is licensed under the Common Public License version 1.0. zlib/libpng license This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. bzip2 license Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 3. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 4. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Julian Seward, Cambridge, UK. jseward@acm.org Common Public License version 1.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS "Contribution" means: a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and b) in the case of each subsequent Contributor: i) changes to the Program, and ii) additions to the Program; where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. "Contributor" means any person or entity that distributes the Program. "Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. "Program" means the Contributions distributed in accordance with this Agreement. "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 2. GRANT OF RIGHTS a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 3. REQUIREMENTS A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: a) it complies with the terms and conditions of this Agreement; and b) its license agreement: i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. When the Program is made available in source code form: a) it must be made available under this Agreement; and b) a copy of this Agreement must be included with each copy of the Program. Contributors may not remove or alter any copyright notices contained within the Program. Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 4. COMMERCIAL DISTRIBUTION Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 5. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against a Contributor with respect to a patent applicable to software (including a cross-claim or counterclaim in a lawsuit), then any patent licenses granted by that Contributor to such Recipient under this Agreement shall terminate as of the date such litigation is filed. In addition, if Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. IBM is the initial Agreement Steward. IBM may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. Special exception for LZMA compression module Igor Pavlov and Amir Szekely, the authors of the LZMA compression module for NSIS, expressly permit you to statically or dynamically link your code (or bind by name) to the files from the LZMA compression module for NSIS without subjecting your linked code to the terms of the Common Public license version 1.0. Any modifications or additions to files from the LZMA compression module for NSIS, however, are subject to the terms of the Common Public License version 1.0. For the following XML Schemas for Java EE Deployment Descriptors: - javaee_5.xsd - javaee_web_services_1_2.xsd - javaee_web_services_client_1_2.xsd - javaee_6.xsd - javaee_web_services_1_3.xsd - javaee_web_services_client_1_3.xsd - jsp_2_2.xsd - web-app_3_0.xsd - web-common_3_0.xsd - web-fragment_3_0.xsd - javaee_7.xsd - javaee_web_services_1_4.xsd - javaee_web_services_client_1_4.xsd - jsp_2_3.xsd - web-app_3_1.xsd - web-common_3_1.xsd - web-fragment_3_1.xsd - javaee_8.xsd - web-app_4_0.xsd - web-common_4_0.xsd - web-fragment_4_0.xsd COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 1. Definitions. 1.1. Contributor. means each individual or entity that creates or contributes to the creation of Modifications. 1.2. Contributor Version. means the combination of the Original Software, prior Modifications used by a Contributor (if any), and the Modifications made by that particular Contributor. 1.3. Covered Software. means (a) the Original Software, or (b) Modifications, or (c) the combination of files containing Original Software with files containing Modifications, in each case including portions thereof. 1.4. Executable. means the Covered Software in any form other than Source Code. 1.5. Initial Developer. means the individual or entity that first makes Original Software available under this License. 1.6. Larger Work. means a work which combines Covered Software or portions thereof with code not governed by the terms of this License. 1.7. License. means this document. 1.8. Licensable. means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein. 1.9. Modifications. means the Source Code and Executable form of any of the following: A. Any file that results from an addition to, deletion from or modification of the contents of a file containing Original Software or previous Modifications; B. Any new file that contains any part of the Original Software or previous Modification; or C. Any new file that is contributed or otherwise made available under the terms of this License. 1.10. Original Software. means the Source Code and Executable form of computer software code that is originally released under this License. 1.11. Patent Claims. means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor. 1.12. Source Code. means (a) the common form of computer software code in which modifications are made and (b) associated documentation included in or with such code. 1.13. You. (or .Your.) means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, .You. includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, .control. means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants. 2.1. The Initial Developer Grant. Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, the Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer, to use, reproduce, modify, display, perform, sublicense and distribute the Original Software (or portions thereof), with or without Modifications, and/or as part of a Larger Work; and (b) under Patent Claims infringed by the making, using or selling of Original Software, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Software (or portions thereof). (c) The licenses granted in Sections 2.1(a) and (b) are effective on the date Initial Developer first distributes or otherwise makes the Original Software available to a third party under the terms of this License. (d) Notwithstanding Section 2.1(b) above, no patent license is granted: (1) for code that You delete from the Original Software, or (2) for infringements caused by: (i) the modification of the Original Software, or (ii) the combination of the Original Software with other software or devices. 2.2. Contributor Grant. Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by Contributor to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof), either on an unmodified basis, with other Modifications, as Covered Software and/or as part of a Larger Work; and (b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: (1) Modifications made by that Contributor (or portions thereof); and (2) the combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination). (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor first distributes or otherwise makes the Modifications available to a third party. (d) Notwithstanding Section 2.2(b) above, no patent license is granted: (1) for any code that Contributor has deleted from the Contributor Version; (2) for infringements caused by: (i) third party modifications of Contributor Version, or (ii) the combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or (3) under Patent Claims infringed by Covered Software in the absence of Modifications made by that Contributor. 3. Distribution Obligations. 3.1. Availability of Source Code. Any Covered Software that You distribute or otherwise make available in Executable form must also be made available in Source Code form and that Source Code form must be distributed only under the terms of this License. You must include a copy of this License with every copy of the Source Code form of the Covered Software You distribute or otherwise make available. You must inform recipients of any such Covered Software in Executable form as to how they can obtain such Covered Software in Source Code form in a reasonable manner on or through a medium customarily used for software exchange. 3.2. Modifications. The Modifications that You create or to which You contribute are governed by the terms of this License. You represent that You believe Your Modifications are Your original creation(s) and/or You have sufficient rights to grant the rights conveyed by this License. 3.3. Required Notices. You must include a notice in each of Your Modifications that identifies You as the Contributor of the Modification. You may not remove or alter any copyright, patent or trademark notices contained within the Covered Software, or any notices of licensing or any descriptive text giving attribution to any Contributor or the Initial Developer. 3.4. Application of Additional Terms. You may not offer or impose any terms on any Covered Software in Source Code form that alters or restricts the applicable version of this License or the recipients. rights hereunder. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, you may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear that any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer. 3.5. Distribution of Executable Versions. You may distribute the Executable form of the Covered Software under the terms of this License or under the terms of a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable form does not attempt to limit or alter the recipient.s rights in the Source Code form from the rights set forth in this License. If You distribute the Covered Software in Executable form under a different license, You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer. 3.6. Larger Works. You may create a Larger Work by combining Covered Software with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Software. 4. Versions of the License. 4.1. New Versions. Sun Microsystems, Inc. is the initial license steward and may publish revised and/or new versions of this License from time to time. Each version will be given a distinguishing version number. Except as provided in Section 4.3, no one other than the license steward has the right to modify this License. 4.2. Effect of New Versions. You may always continue to use, distribute or otherwise make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. If the Initial Developer includes a notice in the Original Software prohibiting it from being distributed or otherwise made available under any subsequent version of the License, You must distribute and make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. Otherwise, You may also choose to use, distribute or otherwise make the Covered Software available under the terms of any subsequent version of the License published by the license steward. 4.3. Modified Versions. When You are an Initial Developer and You want to create a new license for Your Original Software, You may create and use a modified version of this License if You: (a) rename the license and remove any references to the name of the license steward (except to note that the license differs from this License); and (b) otherwise make it clear that the license contains terms which differ from this License. 5. DISCLAIMER OF WARRANTY. COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN .AS IS. BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. 6. TERMINATION. 6.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive. 6.2. If You assert a patent infringement claim (excluding declaratory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You assert such claim is referred to as .Participant.) alleging that the Participant Software (meaning the Contributor Version where the Participant is a Contributor or the Original Software where the Participant is the Initial Developer) directly or indirectly infringes any patent, then any and all rights granted directly or indirectly to You by such Participant, the Initial Developer (if the Initial Developer is not the Participant) and all Contributors under Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively and automatically at the expiration of such 60 day notice period, unless if within such 60 day period You withdraw Your claim with respect to the Participant Software against such Participant either unilaterally or pursuant to a written agreement with Participant. 6.3. In the event of termination under Sections 6.1 or 6.2 above, all end user licenses that have been validly granted by You or any distributor hereunder prior to termination (excluding licenses granted to You by any distributor) shall survive termination. 7. LIMITATION OF LIABILITY. UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTY.S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. 8. U.S. GOVERNMENT END USERS. The Covered Software is a .commercial item,. as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of .commercial computer software. (as that term is defined at 48 C.F.R. ? 252.227-7014(a)(1)) and commercial computer software documentation. as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Software with only those rights set forth herein. This U.S. Government Rights clause is in lieu of, and supersedes, any other FAR, DFAR, or other clause or provision that addresses Government rights in computer software under this License. 9. MISCELLANEOUS. This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by the law of the jurisdiction specified in a notice contained within the Original Software (except to the extent applicable law, if any, provides otherwise), excluding such jurisdiction's conflict-of-law provisions. Any litigation relating to this License shall be subject to the jurisdiction of the courts located in the jurisdiction and venue specified in a notice contained within the Original Software, with the losing party responsible for costs, including, without limitation, court costs and reasonable attorneys. fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. You agree that You alone are responsible for compliance with the United States export administration regulations (and the export control laws and regulation of any other countries) when You use, distribute or otherwise make available any Covered Software. 10. RESPONSIBILITY FOR CLAIMS. As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability. NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) The code released under the CDDL shall be governed by the laws of the State of California (excluding conflict-of-law provisions). Any litigation relating to this License shall be subject to the jurisdiction of the Federal Courts of the Northern District of California and the state courts of the State of California, with venue lying in Santa Clara County, California. ================================================ FILE: assembly/assembly-che-tomcat/src/assembly/assembly.xml ================================================ tomcat-zip zip false ${project.build.directory}/dependency/apache-tomcat-${org.apache.tomcat.version} **/webapps/** **/bin/setenv.* **/bin/*.sh **/bin/catalina-tasks.xml **/bin/*.tar.gz **/conf/tomcat-users.xml **/conf/server.xml ${project.build.directory}/dependency/apache-tomcat-${org.apache.tomcat.version} **/bin/*.sh 755 ${project.basedir}/src/assembly LICENSE-tomcat.txt ${project.basedir}/src/assembly/bin bin setenv.sh 755 ${project.basedir}/src/assembly/conf conf logback-access.xml logback.xml logback-json-appenders.xml logback-plaintext-appenders.xml server.xml context.xml logging.properties ================================================ FILE: assembly/assembly-che-tomcat/src/assembly/conf/logback-access.xml ================================================ common ${che.logs.dir}/logs/localhost-access.log true utf-8 common ${che.logs.dir}/archive/localhost-access-%d{yyyyMMdd}-%i.log ${max.retention.days} 20MB ================================================ FILE: assembly/assembly-che-tomcat/src/assembly/conf/logback-json-appenders.xml ================================================ identity_id req_id ================================================ FILE: assembly/assembly-che-tomcat/src/assembly/conf/logback-plaintext-appenders.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n true true utf-8 %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ${che.logs.dir}/archive/%d{yyyy/MM/dd}/catalina.log ${max.retention.days} ================================================ FILE: assembly/assembly-che-tomcat/src/assembly/conf/logback.xml ================================================ true ================================================ FILE: assembly/assembly-che-tomcat/src/assembly/conf/server.xml ================================================ ================================================ FILE: assembly/assembly-main/pom.xml ================================================ 4.0.0 che-assembly-parent org.eclipse.che 7.118.0-SNAPSHOT assembly-main pom Che Assembly :: Assemblies Che Server Tomcat org.eclipse.che assembly-che-tomcat zip org.eclipse.che assembly-root-war war org.eclipse.che assembly-swagger-war war org.eclipse.che assembly-wsmaster-war war org.postgresql postgresql org.apache.maven.plugins maven-assembly-plugin make-assembly package single false false ${project.basedir}/src/assembly/assembly.xml eclipse-che-${project.version} posix org.apache.maven.plugins maven-dependency-plugin unpack-tomcat prepare-package unpack org.eclipse.che assembly-che-tomcat zip true ${project.build.directory}/dependency/assembly-che-tomcat ================================================ FILE: assembly/assembly-main/src/assembly/LICENSE ================================================ Eclipse Public License - v 1.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS "Contribution" means: a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and b) in the case of each subsequent Contributor: i) changes to the Program, and ii) additions to the Program; where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. "Contributor" means any person or entity that distributes the Program. "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. "Program" means the Contributions distributed in accordance with this Agreement. "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 2. GRANT OF RIGHTS a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 3. REQUIREMENTS A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: a) it complies with the terms and conditions of this Agreement; and b) its license agreement: i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. When the Program is made available in source code form: a) it must be made available under this Agreement; and b) a copy of this Agreement must be included with each copy of the Program. Contributors may not remove or alter any copyright notices contained within the Program. Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 4. COMMERCIAL DISTRIBUTION Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 5. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. ================================================ FILE: assembly/assembly-main/src/assembly/README ================================================ /******************************************************************************* * Copyright (c) 2012-2019 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation *******************************************************************************/ Eclipse Che --------------------------- Next Generation Eclipse IDE ============= Requirements ============= * Java Platform, Standard Edition 8 Development Kit. * Apache Maven version 3.3.1+. * Docker 1.8+. =============== Getting Started =============== See eclipse.org/che ============= Contributing ============= See github.com/eclipse/che ============== Documentation ============== See eclipse.org/che/docs ================================================ FILE: assembly/assembly-main/src/assembly/assembly.xml ================================================ tomcat-zip dir zip tar.gz false false tomcat/webapps ROOT.war org.eclipse.che:assembly-root-war false false tomcat/webapps api.war org.eclipse.che:assembly-wsmaster-war false false tomcat/webapps swagger.war org.eclipse.che:assembly-swagger-war false false lib org.postgresql:postgresql ${project.build.directory}/dependency/assembly-che-tomcat tomcat/ bin/*.bat ${project.basedir}/src/assembly LICENSE README ================================================ FILE: assembly/assembly-root-war/pom.xml ================================================ 4.0.0 che-assembly-parent org.eclipse.che 7.118.0-SNAPSHOT assembly-root-war war Che Assembly :: Root war Packages Che root web app UTF-8 ch.qos.logback logback-classic jakarta.inject jakarta.inject-api net.logstash.logback logstash-logback-encoder org.eclipse.che.core che-core-commons-inject org.eclipse.che.core che-core-commons-j2ee org.eclipse.che.core che-core-logback che-core-api-core org.eclipse.che.core org.everrest everrest-guice-servlet jakarta.servlet jakarta.servlet-api provided org.eclipse.che.core che-core-api-core test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test org.apache.maven.plugins maven-dependency-plugin analyze net.logstash.logback:logstash-logback-encoder ch.qos.logback:logback-classic org.eclipse.che.core:che-core-commons-j2ee org.eclipse.che.core:che-core-logback ================================================ FILE: assembly/assembly-root-war/src/main/java/org/eclipse/che/ApiAccessRejectionFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import javax.inject.Singleton; /** * Returns internal server error when request is supposed to be handled by API war. Situation occurs * when API war failed to start and all requests to it are handled by ROOT war. * * @author Alexander Garagatyi */ @Singleton public class ApiAccessRejectionFilter implements Filter { public static final String ERROR_MESSAGE = "Internal server error occurs. API is not accessible"; private static final byte[] ERROR_MESSAGE_IN_BYTES = ERROR_MESSAGE.getBytes(); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse resp = (HttpServletResponse) response; resp.setStatus(500); resp.getOutputStream().write(ERROR_MESSAGE_IN_BYTES); } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} } ================================================ FILE: assembly/assembly-root-war/src/main/java/org/eclipse/che/DashboardModule.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che; import com.google.inject.servlet.ServletModule; import org.eclipse.che.inject.DynaModule; @DynaModule public class DashboardModule extends ServletModule { @Override protected void configureServlets() { filter("/api", "/api/*").through(ApiAccessRejectionFilter.class); filter("/*").through(DashboardRedirectionFilter.class); } } ================================================ FILE: assembly/assembly-root-war/src/main/java/org/eclipse/che/DashboardRedirectionFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.regex.Pattern; import javax.inject.Singleton; /** * Redirect user to dashboard if request wasn't made to namespace/workspaceName or app resources. * * @author Max Shaposhnik */ @Singleton public class DashboardRedirectionFilter implements Filter { // Describes IDE direct URL in format namespace/workspaceName, like user123/java-mysql private static final String IDE_DIRECT_URL = "/\\w+/\\w+"; // Describes URL to app resources, like /_ide/loader.html private static final String APP_RESOURCES = "/_app/.*"; private static final Pattern EXCLUDES = Pattern.compile("^(" + APP_RESOURCES + ")|(" + IDE_DIRECT_URL + ")$"); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; if (("GET".equals(req.getMethod()) || "HEAD".equals(req.getMethod())) && !EXCLUDES.matcher(req.getRequestURI()).matches()) { resp.sendRedirect("/dashboard/"); return; } chain.doFilter(request, response); } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} } ================================================ FILE: assembly/assembly-root-war/src/main/webapp/META-INF/context.xml ================================================ ================================================ FILE: assembly/assembly-root-war/src/main/webapp/WEB-INF/rewrite.config ================================================ RewriteRule ^/factory$ /dashboard/#/load-factory/ [R,NE,QSA] RewriteRule ^/factory/$ /dashboard/#/load-factory/ [R,NE,QSA] RewriteRule ^/f/$ /dashboard/#/load-factory/ [R,NE,QSA] RewriteRule ^/f$ /dashboard/#/load-factory/ [R,NE,QSA] RewriteRule ^/(?!_app/)(\w+)/(\w+)$ /dashboard/#/ide/$1/$2 [R,NE,QSA] RewriteRule ^/(?!_app/)(\w+)/(\w+)/$ /dashboard/#/ide/$1/$2 [R,NE,QSA] ================================================ FILE: assembly/assembly-root-war/src/main/webapp/WEB-INF/web.xml ================================================ org.eclipse.che.inject.CheBootstrap cacheDisablingFilter org.eclipse.che.filter.CheCacheDisablingFilter pattern_filename ^.*\.nocache\..*$ pattern_appname ^.*/_app/.*$ cacheForcingFilter org.eclipse.che.filter.CheCacheForcingFilter pattern_filename ^.*\.cache\..*$ guiceFilter com.google.inject.servlet.GuiceFilter guiceFilter /* cacheDisablingFilter /* cacheForcingFilter /* default /* the user role developer ================================================ FILE: assembly/assembly-root-war/src/main/webapp/_app/keycloackLoader.js ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ export class KeycloakLoader { /** * Load keycloak settings */ loadKeycloakSettings() { const msg = "Cannot load keycloak settings. This is normal for single-user mode."; return new Promise((resolve, reject) => { try { const request = new XMLHttpRequest(); request.onerror = request.onabort = function() { reject(new Error(msg)); }; request.onload = () => { if (request.status == 200) { resolve(this.injectKeycloakScript(JSON.parse(request.responseText))); } else { reject(new Error(msg)); } }; const url = "/api/keycloak/settings"; request.open("GET", url, true); request.send(); } catch (e) { reject(new Error(msg + e.message)); } }); } /** * Injects keycloak javascript */ injectKeycloakScript(keycloakSettings) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.type = 'text/javascript'; script.language = 'javascript'; script.async = true; script.src = keycloakSettings['che.keycloak.js_adapter_url']; script.onload = () => { resolve(this.initKeycloak(keycloakSettings)); }; script.onerror = script.onabort = () => { reject(new Error('cannot load ' + script.src)); }; document.head.appendChild(script); }); } /** * Initialize keycloak */ initKeycloak(keycloakSettings) { return new Promise((resolve, reject) => { function keycloakConfig() { const theOidcProvider = keycloakSettings['che.keycloak.oidc_provider']; if (!theOidcProvider) { return { url: keycloakSettings['che.keycloak.auth_server_url'], realm: keycloakSettings['che.keycloak.realm'], clientId: keycloakSettings['che.keycloak.client_id'] }; } else { return { oidcProvider: theOidcProvider, clientId: keycloakSettings['che.keycloak.client_id'] }; } } const keycloak = Keycloak(keycloakConfig()); window['_keycloak'] = keycloak; var useNonce; if (typeof keycloakSettings['che.keycloak.use_nonce'] === 'string') { useNonce = keycloakSettings['che.keycloak.use_nonce'].toLowerCase() === 'true'; } window.sessionStorage.setItem('oidcIdeRedirectUrl', location.href); keycloak .init({ onLoad: 'login-required', checkLoginIframe: false, useNonce: useNonce, scope: 'openid', redirectUri: location.href }) .success(() => { resolve(keycloak); }) .error(() => { reject(new Error('[Keycloak] Failed to initialize Keycloak')); }); }); } } ================================================ FILE: assembly/assembly-root-war/src/main/webapp/_app/loader.css ================================================ /** * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ #workspace-loader { position: fixed; width: 300px; height: 45px; left: 50%; top: 30%; margin-left: -150px; transition: all 0.2s ease-in-out; } #workspace-loader-label { position: absolute; left: 0px; top: 0px; width: 300px; height: 30px; font-family: sans-serif; font-size: 14px; line-height: 30px; text-align: center; color: #a0a9b7; } #workspace-loader-reload { display: none; position: absolute; left: 0px; top: 0px; width: 300px; height: 30px; font-family: sans-serif; font-size: 14px; line-height: 30px; text-align: center; color: #a0a9b7; } #workspace-loader-reload a { color: #a0a9b7; } #workspace-loader-progress { position: absolute; width: 300px; height: 15px; left: 0px; bottom: 0px; background-color: #202325; border: 1px solid #456594; box-sizing: border-box; } #workspace-loader-progress>div { position: absolute; left: 1px; right: 1px; top: 1px; bottom: 1px; overflow: hidden; } #workspace-loader-progress-bar { box-sizing: border-box; height: 100%; width: 0%; background-color: #498fe1; transition: all 0.2s linear; animation-name: dancing; animation-duration: 3s; animation-delay: 1s; animation-timing-function: linear; animation-iteration-count: infinite; } #workspace-console { position: fixed; left: 30px; right: 30px; bottom: 25px; height: 30%; background-color: transparent; overflow: auto; color: #e6e6e6; left: 2px; right: 2px; bottom: 2px; transition: all 0.2s ease-in-out; } #workspace-console>div { position: relative; width: 100%; } #workspace-console pre { font-family: "Droid Sans Mono", monospace; font-size: 9pt; line-height: 13px; padding: 0; margin: 0; white-space: pre-wrap; word-wrap: break-word; cursor: text; } #workspace-console pre.error { color: #e22812; } @-webkit-keyframes dancing { 0% { width: 0%; margin-left: 0%; } 30% { width: 30%; margin-left: 0%; } 70% { width: 30%; margin-left: 70%; } 100% { width: 0%; margin-left: 100%; } } @keyframes dancing { 0% { width: 0%; margin-left: 0%; } 6% { width: 30%; margin-left: 0%; } 14% { width: 30%; margin-left: 70%; } 20% { width: 0%; margin-left: 100%; } 100% { width: 0%; margin-left: 100%; } } ================================================ FILE: assembly/assembly-root-war/src/main/webapp/_app/loader.html ================================================ Workspace token loader
Loading a runtime token...
Press F5 or click here to try again.
================================================ FILE: assembly/assembly-root-war/src/main/webapp/_app/loader.js ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ import { KeycloakLoader } from './keycloackLoader.js'; class Loader { constructor() { document.getElementById('workspace-loader-reload').onclick = () => this.onclickReload(); } /** * Hides progress bar and displays reloading prompt */ hideLoader() { document.getElementById('workspace-loader-label').style.display = 'none'; document.getElementById('workspace-loader-progress').style.display = 'none'; document.getElementById('workspace-loader-reload').style.display = 'block'; } /** * Displays error message * @param {string} message an error message to show */ error(message) { const container = document.getElementById("workspace-console-container"); if (container.childElementCount > 500) { container.removeChild(container.firstChild) } const element = document.createElement("pre"); element.innerHTML = message; container.appendChild(element); if (element.scrollIntoView) { element.scrollIntoView(); } element.className = "error"; return element; } /** * Reloads the page */ onclickReload() { window.location.reload(); return false; } /** * Returns query parameter value if it is present * @param {string} name a query parameter name */ getQueryParam(name) { const params = window.location.search.substr(1), paramEntries = params.split('&'); const entry = paramEntries.find(_entry => { return _entry.startsWith(name + '='); }); if (!entry) { return; } const [_, value] = entry.split('='); return decodeURIComponent(value); } /** * Fetches workspace details by ID * @param {string} workspaceId a workspace ID */ asyncGetWorkspace(workspaceId) { return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); request.open("GET", '/api/workspace/' + workspaceId); this.setAuthorizationHeader(request).then((xhr) => { xhr.send(); xhr.onreadystatechange = () => { if (xhr.readyState !== 4) { return; } if (xhr.status !== 200) { const errorMessage = 'Failed to get the workspace: "' + this.getRequestErrorMessage(xhr) + '"'; reject(new Error(errorMessage)); return; } resolve(JSON.parse(xhr.responseText)); }; }); }); } /** * Sets authorization header for a request * @param {XMLHttpRequest} xhr */ setAuthorizationHeader(xhr) { return new Promise((resolve, reject) => { if (window._keycloak && window._keycloak.token) { window._keycloak.updateToken(5).success(() => { xhr.setRequestHeader('Authorization', 'Bearer ' + window._keycloak.token); resolve(xhr); }).error(() => { window.sessionStorage.setItem('oidcIdeRedirectUrl', location.href); window._keycloak.login(); reject(new Error('Failed to refresh token')); }); } resolve(xhr); }); } /** * Finds and returns appropriate server which user request belongs to, or throw error * if none is found. * @param {*} workspace a workspace * @param {string} redirectUrl a redirect URL */ asyncGetMatchedServer(workspace, redirectUrl) { return new Promise((resolve, reject) => { if (!workspace.runtime) { reject(new Error("Can't check service link: Workspace isn't RUNNING at the moment.")); return; } var machines = Object.values(workspace.runtime.machines); var servers = machines.filter(machines => machines.servers) .map(machine => Object.values(machine.servers)) .reduce((servers, machineServers) => servers.concat(...machineServers), []); const server = servers.find(_server => { if (!_server.url) { return false; } const url = new URL(_server.url); url.search = ''; return redirectUrl.startsWith(url.href); }); if (server) { resolve(server); } else { reject(new Error("Workspace doesn't have a server which matches with URL: " + redirectUrl)); } }); } /** * Returns resolved promise if `workspace` has the `runtime` property * @param {*} workspace a workspace */ asyncGetWsToken(workspace) { return new Promise((resolve, reject) => { if (workspace.runtime) { resolve(workspace.runtime.machineToken); } else { reject(new Error("Can't get ws-token: Workspace isn't RUNNING at the moment.")); } }); } /** * Calls auth endpoint in order to accept authentication cookie. * @param {string} redirectUrl a redirect URL * @param {string} token */ asyncAuthenticate(redirectUrl, endpointOrigin, token) { redirectUrl = new URL(redirectUrl); // if endpointOrigin is just "/", we'd end up with "///jwt/auth". So we replace two or more consecutive / with a single /. const url = redirectUrl.protocol + "//" + redirectUrl.host + ("/" + endpointOrigin + "/jwt/auth").replace(/\/{2,}/g, "/"); return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); request.open('GET', url); request.setRequestHeader('Authorization', 'Bearer ' + token); request.withCredentials = true; request.send(); request.onreadystatechange = () => { if (request.readyState !== 4) { return; } if (request.status !== 204) { const errorMessage = 'Failed to authenticate: "' + this.getRequestErrorMessage(request) + '"'; reject(new Error(errorMessage)); return; } resolve(); }; }); } getRequestErrorMessage(xhr) { let errorMessage; try { const response = JSON.parse(xhr.responseText); errorMessage = response.message; } catch (e) { } if (errorMessage) { return errorMessage; } if (xhr.statusText) { return xhr.statusText; } return "Unknown error"; } } (async function() { const loader = new Loader(); const workspaceId = loader.getQueryParam('workspaceId'); const redirectUrl = loader.getQueryParam('redirectUrl'); try { if (!workspaceId) { throw new Error("Workspace ID isn't found in query parameters."); } if (!redirectUrl) { throw new Error("Redirect URL isn't found in query parameters."); } await new KeycloakLoader().loadKeycloakSettings(); const workspace = await loader.asyncGetWorkspace(workspaceId); const server = await loader.asyncGetMatchedServer(workspace, redirectUrl); const token = await loader.asyncGetWsToken(workspace); await loader.asyncAuthenticate(server.url, server.attributes.endpointOrigin, token); window.location.replace(redirectUrl); } catch (errorMessage) { console.error(errorMessage); loader.hideLoader(); loader.error(errorMessage); } })(); ================================================ FILE: assembly/assembly-root-war/src/main/webapp/_app/oauth.html ================================================ Authentication
================================================ FILE: assembly/assembly-root-war/src/main/webapp/_app/oauthLoader.js ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ import { KeycloakLoader } from './keycloackLoader.js'; /** * Displays error message * @param {string} message an error message to show */ function error(message) { const container = document.getElementById("error-console"); const element = document.createElement("pre"); element.innerHTML = message; element.style.color = 'red'; container.appendChild(element); return element; } /** * Returns an array of query parameter values if it is present * @param {string} name of the query parameter */ function getQueryParams(name) { const queryString = window.location.search; return new URLSearchParams(queryString).getAll(name); } /** * Fetches userId */ function asyncGetUserId() { return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); request.open("GET", '/api/user'); setAuthorizationHeader(request).then((xhr) => { xhr.send(); xhr.onreadystatechange = () => { if (xhr.readyState !== 4) { return; } if (xhr.status !== 200) { const errorMessage = 'Failed to get the workspace: "' + this.getRequestErrorMessage(xhr) + '"'; reject(new Error(errorMessage)); return; } resolve(JSON.parse(xhr.responseText).id); }; }); }); } /** * Sets authorization header for a request * @param {XMLHttpRequest} xhr */ function setAuthorizationHeader(xhr) { return new Promise((resolve, reject) => { if (window._keycloak && window._keycloak.token) { window._keycloak.updateToken(5).success(() => { xhr.setRequestHeader('Authorization', 'Bearer ' + window._keycloak.token); resolve(xhr); }).error(() => { window.sessionStorage.setItem('oidcIdeRedirectUrl', location.href); window._keycloak.login(); reject(new Error('Failed to refresh token')); }); } resolve(xhr); }); } function postMessage(message) { if (window.opener) { window.opener.postMessage(message, '*'); } } /** * Fetches workspace details by ID * @param {string} workspaceId a workspace id */ function getWorkspace(workspaceId) { return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); request.open("GET", '/api/workspace/' + workspaceId); setAuthorizationHeader(request).then((xhr) => { xhr.send(); xhr.onreadystatechange = () => { if (xhr.readyState !== 4) { return; } if (xhr.status !== 200) { const errorMessage = 'Failed to get the workspace: "' + this.getRequestErrorMessage(xhr) + '"'; reject(new Error(errorMessage)); return; } resolve(undefined); }; }); }); } function parseToken (token) { return JSON.parse(atob(token.split('.')[1])); } (async () => { let token; try { const keycloak = await new KeycloakLoader().loadKeycloakSettings(); token = keycloak ? keycloak.token : undefined; } catch (e) { console.log(e.message); } try { if (token) { await new Promise((resolve, reject) => { window.addEventListener('message', async data => { if (data.data.startsWith('token:')) { const machineToken = parseToken(data.data.substring(6, data.data.length)); const userToken = parseToken(token); if (machineToken.uid === userToken.sub) { try { await getWorkspace(machineToken.wsid); } catch (e) { reject(e); } } else { reject(new Error('Machine and user token mismatch')); } resolve(undefined); } }); postMessage('status:ready-to-receive-messages'); }); } const status = getQueryParams('status')[0]; { if (status && status === 'ready') { postMessage('token:' + (token ? token : '')); return; } } const oauthProvider = getQueryParams('oauth_provider')[0]; if (!oauthProvider) { postMessage('token:' + (token ? token : '')); return; } const currentUrl = window.location.href; const cheUrl = currentUrl.substring(0, currentUrl.indexOf('/_app')); const apiUrl = cheUrl + '/api'; const redirectUrl = currentUrl.substring(0, currentUrl.indexOf('?')) + '?status=ready'; let url = `${apiUrl}/oauth/authenticate?oauth_provider=${oauthProvider}&userId=${await asyncGetUserId()}`; const scope = getQueryParams('scope'); { for (const s of scope) { url += `&scope=${s}`; } } url += `&redirect_after_login=${redirectUrl}`; if (token) { url += `&token=${token}`; } window.location.replace(url); } catch (errorMessage) { error(errorMessage); console.error(errorMessage); } }) (); ================================================ FILE: assembly/assembly-root-war/src/test/java/org/eclipse/che/ApiAccessRejectionFilterTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ @Listeners(value = {MockitoTestNGListener.class}) public class ApiAccessRejectionFilterTest { @Mock private FilterChain chain; @Mock private HttpServletRequest request; @Mock private HttpServletResponse response; @Mock private ServletOutputStream outputStream; @InjectMocks private ApiAccessRejectionFilter filter; @Test public void shouldReturnError() throws Exception { // given when(response.getOutputStream()).thenReturn(outputStream); // when filter.doFilter(request, response, chain); // then verifyNoMoreInteractions(chain); verify(response).setStatus(500); verify(outputStream).write(eq(ApiAccessRejectionFilter.ERROR_MESSAGE.getBytes())); } } ================================================ FILE: assembly/assembly-root-war/src/test/java/org/eclipse/che/DashboardRedirectionFilterTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.util.Collections; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.SubjectImpl; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Max Shaposhnik (mshaposhnik@codenvy.com) */ @Listeners(value = {MockitoTestNGListener.class}) public class DashboardRedirectionFilterTest { @Mock private FilterChain chain; @Mock private HttpServletRequest request; @Mock private HttpServletResponse response; @InjectMocks private DashboardRedirectionFilter filter; @Test(dataProvider = "nonNamespacePathProvider") public void shouldRedirectIfGetRequestIsNotNamespaceWorkspaceName(String uri) throws Exception { // given when(request.getMethod()).thenReturn("GET"); when(request.getRequestURI()).thenReturn(uri); EnvironmentContext context = new EnvironmentContext(); context.setSubject( new SubjectImpl("id123", Collections.emptyList(), "name", "token123", false)); EnvironmentContext.setCurrent(context); // when filter.doFilter(request, response, chain); // then verify(response).sendRedirect(eq("/dashboard/")); } @Test(dataProvider = "nonNamespacePathProvider") public void shouldRedirectIfHEADRequestIsNotNamespaceWorkspaceName(String uri) throws Exception { // given when(request.getMethod()).thenReturn("HEAD"); when(request.getRequestURI()).thenReturn(uri); EnvironmentContext context = new EnvironmentContext(); context.setSubject( new SubjectImpl("id123", Collections.emptyList(), "name", "token123", false)); EnvironmentContext.setCurrent(context); // when filter.doFilter(request, response, chain); // then verify(response).sendRedirect(eq("/dashboard/")); } @DataProvider(name = "nonNamespacePathProvider") public Object[][] nonProjectPathProvider() { return new Object[][] { {"/"}, {"/ws-id/"}, {"/wsname"}, {"/unknown/resource/index.html"}, }; } @Test public void shouldSkipRequestToAppResources() throws Exception { // given when(request.getMethod()).thenReturn("GET"); when(request.getRequestURI()).thenReturn("/_app/loader.html"); // when filter.doFilter(request, response, chain); // then verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); } @Test(dataProvider = "notGETNorHEADMethodProvider") public void shouldSkipNotGETNorHEADRequest(String method) throws Exception { // given when(request.getMethod()).thenReturn(method); lenient().when(request.getRequestURI()).thenReturn("/namespace/workspaceName"); // when filter.doFilter(request, response, chain); // then verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); } @DataProvider(name = "notGETNorHEADMethodProvider") public Object[][] notGETNorHEADMethodProvider() { return new Object[][] {{"POST"}, {"DELETE"}, {"PUT"}}; } } ================================================ FILE: assembly/assembly-swagger-war/pom.xml ================================================ 4.0.0 che-assembly-parent org.eclipse.che 7.118.0-SNAPSHOT assembly-swagger-war war Che Assembly :: Swagger war che-swagger-ui org.apache.maven.plugins maven-war-plugin false com.mycila license-maven-plugin **/src/** ================================================ FILE: assembly/assembly-swagger-war/src/main/webapp/index.html ================================================ Swagger UI
================================================ FILE: assembly/assembly-swagger-war/src/main/webapp/oauth2-redirect.html ================================================ Swagger UI: OAuth2 Redirect ================================================ FILE: assembly/assembly-swagger-war/src/main/webapp/swagger-ui-bundle.js ================================================ /*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=543)}([function(e,t,n){"use strict";e.exports=n(132)},function(e,t,n){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function n(e){return i(e)?e:J(e)}function r(e){return s(e)?e:K(e)}function o(e){return u(e)?e:Y(e)}function a(e){return i(e)&&!c(e)?e:G(e)}function i(e){return!(!e||!e[p])}function s(e){return!(!e||!e[f])}function u(e){return!(!e||!e[h])}function c(e){return s(e)||u(e)}function l(e){return!(!e||!e[d])}t(r,n),t(o,n),t(a,n),n.isIterable=i,n.isKeyed=s,n.isIndexed=u,n.isAssociative=c,n.isOrdered=l,n.Keyed=r,n.Indexed=o,n.Set=a;var p="@@__IMMUTABLE_ITERABLE__@@",f="@@__IMMUTABLE_KEYED__@@",h="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",m="delete",v=5,g=1<>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?A(e)+t:t}function O(){return!0}function j(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function T(e,t){return P(e,t,0)}function I(e,t){return P(e,t,t)}function P(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var N=0,M=1,R=2,D="function"==typeof Symbol&&Symbol.iterator,L="@@iterator",B=D||L;function F(e){this.next=e}function U(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function q(){return{value:void 0,done:!0}}function z(e){return!!H(e)}function V(e){return e&&"function"==typeof e.next}function W(e){var t=H(e);return t&&t.call(e)}function H(e){var t=e&&(D&&e[D]||e[L]);if("function"==typeof t)return t}function $(e){return e&&"number"==typeof e.length}function J(e){return null==e?ie():i(e)?e.toSeq():ce(e)}function K(e){return null==e?ie().toKeyedSeq():i(e)?s(e)?e.toSeq():e.fromEntrySeq():se(e)}function Y(e){return null==e?ie():i(e)?s(e)?e.entrySeq():e.toIndexedSeq():ue(e)}function G(e){return(null==e?ie():i(e)?s(e)?e.entrySeq():e:ue(e)).toSetSeq()}F.prototype.toString=function(){return"[Iterator]"},F.KEYS=N,F.VALUES=M,F.ENTRIES=R,F.prototype.inspect=F.prototype.toSource=function(){return this.toString()},F.prototype[B]=function(){return this},t(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(e,t){return pe(this,e,t,!0)},J.prototype.__iterator=function(e,t){return fe(this,e,t,!0)},t(K,J),K.prototype.toKeyedSeq=function(){return this},t(Y,J),Y.of=function(){return Y(arguments)},Y.prototype.toIndexedSeq=function(){return this},Y.prototype.toString=function(){return this.__toString("Seq [","]")},Y.prototype.__iterate=function(e,t){return pe(this,e,t,!1)},Y.prototype.__iterator=function(e,t){return fe(this,e,t,!1)},t(G,J),G.of=function(){return G(arguments)},G.prototype.toSetSeq=function(){return this},J.isSeq=ae,J.Keyed=K,J.Set=G,J.Indexed=Y;var Z,X,Q,ee="@@__IMMUTABLE_SEQ__@@";function te(e){this._array=e,this.size=e.length}function ne(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function re(e){this._iterable=e,this.size=e.length||e.size}function oe(e){this._iterator=e,this._iteratorCache=[]}function ae(e){return!(!e||!e[ee])}function ie(){return Z||(Z=new te([]))}function se(e){var t=Array.isArray(e)?new te(e).fromEntrySeq():V(e)?new oe(e).fromEntrySeq():z(e)?new re(e).fromEntrySeq():"object"==typeof e?new ne(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function ue(e){var t=le(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return t}function ce(e){var t=le(e)||"object"==typeof e&&new ne(e);if(!t)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e);return t}function le(e){return $(e)?new te(e):V(e)?new oe(e):z(e)?new re(e):void 0}function pe(e,t,n,r){var o=e._cache;if(o){for(var a=o.length-1,i=0;i<=a;i++){var s=o[n?a-i:i];if(!1===t(s[1],r?s[0]:i,e))return i+1}return i}return e.__iterateUncached(t,n)}function fe(e,t,n,r){var o=e._cache;if(o){var a=o.length-1,i=0;return new F((function(){var e=o[n?a-i:i];return i++>a?q():U(t,r?e[0]:i-1,e[1])}))}return e.__iteratorUncached(t,n)}function he(e,t){return t?de(t,e,"",{"":e}):me(e)}function de(e,t,n,r){return Array.isArray(t)?e.call(r,n,Y(t).map((function(n,r){return de(e,n,r,t)}))):ve(t)?e.call(r,n,K(t).map((function(n,r){return de(e,n,r,t)}))):t}function me(e){return Array.isArray(e)?Y(e).map(me).toList():ve(e)?K(e).map(me).toMap():e}function ve(e){return e&&(e.constructor===Object||void 0===e.constructor)}function ge(e,t){if(e===t||e!=e&&t!=t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if((e=e.valueOf())===(t=t.valueOf())||e!=e&&t!=t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function ye(e,t){if(e===t)return!0;if(!i(t)||void 0!==e.size&&void 0!==t.size&&e.size!==t.size||void 0!==e.__hash&&void 0!==t.__hash&&e.__hash!==t.__hash||s(e)!==s(t)||u(e)!==u(t)||l(e)!==l(t))return!1;if(0===e.size&&0===t.size)return!0;var n=!c(e);if(l(e)){var r=e.entries();return t.every((function(e,t){var o=r.next().value;return o&&ge(o[1],e)&&(n||ge(o[0],t))}))&&r.next().done}var o=!1;if(void 0===e.size)if(void 0===t.size)"function"==typeof e.cacheResult&&e.cacheResult();else{o=!0;var a=e;e=t,t=a}var p=!0,f=t.__iterate((function(t,r){if(n?!e.has(t):o?!ge(t,e.get(r,b)):!ge(e.get(r,b),t))return p=!1,!1}));return p&&e.size===f}function be(e,t){if(!(this instanceof be))return new be(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if(X)return X;X=this}}function _e(e,t){if(!e)throw new Error(t)}function we(e,t,n){if(!(this instanceof we))return new we(e,t,n);if(_e(0!==n,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),n=void 0===n?1:Math.abs(n),tr?q():U(e,o,n[t?r-o++:o++])}))},t(ne,K),ne.prototype.get=function(e,t){return void 0===t||this.has(e)?this._object[e]:t},ne.prototype.has=function(e){return this._object.hasOwnProperty(e)},ne.prototype.__iterate=function(e,t){for(var n=this._object,r=this._keys,o=r.length-1,a=0;a<=o;a++){var i=r[t?o-a:a];if(!1===e(n[i],i,this))return a+1}return a},ne.prototype.__iterator=function(e,t){var n=this._object,r=this._keys,o=r.length-1,a=0;return new F((function(){var i=r[t?o-a:a];return a++>o?q():U(e,i,n[i])}))},ne.prototype[d]=!0,t(re,Y),re.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var n=W(this._iterable),r=0;if(V(n))for(var o;!(o=n.next()).done&&!1!==e(o.value,r++,this););return r},re.prototype.__iteratorUncached=function(e,t){if(t)return this.cacheResult().__iterator(e,t);var n=W(this._iterable);if(!V(n))return new F(q);var r=0;return new F((function(){var t=n.next();return t.done?t:U(e,r++,t.value)}))},t(oe,Y),oe.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var n,r=this._iterator,o=this._iteratorCache,a=0;a=r.length){var t=n.next();if(t.done)return t;r[o]=t.value}return U(e,o,r[o++])}))},t(be,Y),be.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},be.prototype.get=function(e,t){return this.has(e)?this._value:t},be.prototype.includes=function(e){return ge(this._value,e)},be.prototype.slice=function(e,t){var n=this.size;return j(e,t,n)?this:new be(this._value,I(t,n)-T(e,n))},be.prototype.reverse=function(){return this},be.prototype.indexOf=function(e){return ge(this._value,e)?0:-1},be.prototype.lastIndexOf=function(e){return ge(this._value,e)?this.size:-1},be.prototype.__iterate=function(e,t){for(var n=0;n=0&&t=0&&nn?q():U(e,a++,i)}))},we.prototype.equals=function(e){return e instanceof we?this._start===e._start&&this._end===e._end&&this._step===e._step:ye(this,e)},t(xe,n),t(Ee,xe),t(Se,xe),t(Ce,xe),xe.Keyed=Ee,xe.Indexed=Se,xe.Set=Ce;var Ae="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function(e,t){var n=65535&(e|=0),r=65535&(t|=0);return n*r+((e>>>16)*r+n*(t>>>16)<<16>>>0)|0};function ke(e){return e>>>1&1073741824|3221225471&e}function Oe(e){if(!1===e||null==e)return 0;if("function"==typeof e.valueOf&&(!1===(e=e.valueOf())||null==e))return 0;if(!0===e)return 1;var t=typeof e;if("number"===t){if(e!=e||e===1/0)return 0;var n=0|e;for(n!==e&&(n^=4294967295*e);e>4294967295;)n^=e/=4294967295;return ke(n)}if("string"===t)return e.length>Fe?je(e):Te(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"===t)return Ie(e);if("function"==typeof e.toString)return Te(e.toString());throw new Error("Value type "+t+" cannot be hashed.")}function je(e){var t=ze[e];return void 0===t&&(t=Te(e),qe===Ue&&(qe=0,ze={}),qe++,ze[e]=t),t}function Te(e){for(var t=0,n=0;n0)switch(e.nodeType){case 1:return e.uniqueID;case 9:return e.documentElement&&e.documentElement.uniqueID}}var Re,De="function"==typeof WeakMap;De&&(Re=new WeakMap);var Le=0,Be="__immutablehash__";"function"==typeof Symbol&&(Be=Symbol(Be));var Fe=16,Ue=255,qe=0,ze={};function Ve(e){_e(e!==1/0,"Cannot perform this action with an infinite size.")}function We(e){return null==e?ot():He(e)&&!l(e)?e:ot().withMutations((function(t){var n=r(e);Ve(n.size),n.forEach((function(e,n){return t.set(n,e)}))}))}function He(e){return!(!e||!e[Je])}t(We,Ee),We.of=function(){var t=e.call(arguments,0);return ot().withMutations((function(e){for(var n=0;n=t.length)throw new Error("Missing value for key: "+t[n]);e.set(t[n],t[n+1])}}))},We.prototype.toString=function(){return this.__toString("Map {","}")},We.prototype.get=function(e,t){return this._root?this._root.get(0,void 0,e,t):t},We.prototype.set=function(e,t){return at(this,e,t)},We.prototype.setIn=function(e,t){return this.updateIn(e,b,(function(){return t}))},We.prototype.remove=function(e){return at(this,e,b)},We.prototype.deleteIn=function(e){return this.updateIn(e,(function(){return b}))},We.prototype.update=function(e,t,n){return 1===arguments.length?e(this):this.updateIn([e],t,n)},We.prototype.updateIn=function(e,t,n){n||(n=t,t=void 0);var r=vt(this,xn(e),t,n);return r===b?void 0:r},We.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):ot()},We.prototype.merge=function(){return ft(this,void 0,arguments)},We.prototype.mergeWith=function(t){return ft(this,t,e.call(arguments,1))},We.prototype.mergeIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,ot(),(function(e){return"function"==typeof e.merge?e.merge.apply(e,n):n[n.length-1]}))},We.prototype.mergeDeep=function(){return ft(this,ht,arguments)},We.prototype.mergeDeepWith=function(t){var n=e.call(arguments,1);return ft(this,dt(t),n)},We.prototype.mergeDeepIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,ot(),(function(e){return"function"==typeof e.mergeDeep?e.mergeDeep.apply(e,n):n[n.length-1]}))},We.prototype.sort=function(e){return zt(pn(this,e))},We.prototype.sortBy=function(e,t){return zt(pn(this,t,e))},We.prototype.withMutations=function(e){var t=this.asMutable();return e(t),t.wasAltered()?t.__ensureOwner(this.__ownerID):this},We.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new S)},We.prototype.asImmutable=function(){return this.__ensureOwner()},We.prototype.wasAltered=function(){return this.__altered},We.prototype.__iterator=function(e,t){return new et(this,e,t)},We.prototype.__iterate=function(e,t){var n=this,r=0;return this._root&&this._root.iterate((function(t){return r++,e(t[1],t[0],n)}),t),r},We.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?rt(this.size,this._root,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},We.isMap=He;var $e,Je="@@__IMMUTABLE_MAP__@@",Ke=We.prototype;function Ye(e,t){this.ownerID=e,this.entries=t}function Ge(e,t,n){this.ownerID=e,this.bitmap=t,this.nodes=n}function Ze(e,t,n){this.ownerID=e,this.count=t,this.nodes=n}function Xe(e,t,n){this.ownerID=e,this.keyHash=t,this.entries=n}function Qe(e,t,n){this.ownerID=e,this.keyHash=t,this.entry=n}function et(e,t,n){this._type=t,this._reverse=n,this._stack=e._root&&nt(e._root)}function tt(e,t){return U(e,t[0],t[1])}function nt(e,t){return{node:e,index:0,__prev:t}}function rt(e,t,n,r){var o=Object.create(Ke);return o.size=e,o._root=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function ot(){return $e||($e=rt(0))}function at(e,t,n){var r,o;if(e._root){var a=x(_),i=x(w);if(r=it(e._root,e.__ownerID,0,void 0,t,n,a,i),!i.value)return e;o=e.size+(a.value?n===b?-1:1:0)}else{if(n===b)return e;o=1,r=new Ye(e.__ownerID,[[t,n]])}return e.__ownerID?(e.size=o,e._root=r,e.__hash=void 0,e.__altered=!0,e):r?rt(o,r):ot()}function it(e,t,n,r,o,a,i,s){return e?e.update(t,n,r,o,a,i,s):a===b?e:(E(s),E(i),new Qe(t,r,[o,a]))}function st(e){return e.constructor===Qe||e.constructor===Xe}function ut(e,t,n,r,o){if(e.keyHash===r)return new Xe(t,r,[e.entry,o]);var a,i=(0===n?e.keyHash:e.keyHash>>>n)&y,s=(0===n?r:r>>>n)&y;return new Ge(t,1<>>=1)i[s]=1&n?t[a++]:void 0;return i[r]=o,new Ze(e,a+1,i)}function ft(e,t,n){for(var o=[],a=0;a>1&1431655765))+(e>>2&858993459))+(e>>4)&252645135,e+=e>>8,127&(e+=e>>16)}function yt(e,t,n,r){var o=r?e:C(e);return o[t]=n,o}function bt(e,t,n,r){var o=e.length+1;if(r&&t+1===o)return e[t]=n,e;for(var a=new Array(o),i=0,s=0;s=wt)return ct(e,u,r,o);var f=e&&e===this.ownerID,h=f?u:C(u);return p?s?c===l-1?h.pop():h[c]=h.pop():h[c]=[r,o]:h.push([r,o]),f?(this.entries=h,this):new Ye(e,h)}},Ge.prototype.get=function(e,t,n,r){void 0===t&&(t=Oe(n));var o=1<<((0===e?t:t>>>e)&y),a=this.bitmap;return 0==(a&o)?r:this.nodes[gt(a&o-1)].get(e+v,t,n,r)},Ge.prototype.update=function(e,t,n,r,o,a,i){void 0===n&&(n=Oe(r));var s=(0===t?n:n>>>t)&y,u=1<=xt)return pt(e,f,c,s,d);if(l&&!d&&2===f.length&&st(f[1^p]))return f[1^p];if(l&&d&&1===f.length&&st(d))return d;var m=e&&e===this.ownerID,g=l?d?c:c^u:c|u,_=l?d?yt(f,p,d,m):_t(f,p,m):bt(f,p,d,m);return m?(this.bitmap=g,this.nodes=_,this):new Ge(e,g,_)},Ze.prototype.get=function(e,t,n,r){void 0===t&&(t=Oe(n));var o=(0===e?t:t>>>e)&y,a=this.nodes[o];return a?a.get(e+v,t,n,r):r},Ze.prototype.update=function(e,t,n,r,o,a,i){void 0===n&&(n=Oe(r));var s=(0===t?n:n>>>t)&y,u=o===b,c=this.nodes,l=c[s];if(u&&!l)return this;var p=it(l,e,t+v,n,r,o,a,i);if(p===l)return this;var f=this.count;if(l){if(!p&&--f0&&r=0&&e>>t&y;if(r>=this.array.length)return new Ot([],e);var o,a=0===r;if(t>0){var i=this.array[r];if((o=i&&i.removeBefore(e,t-v,n))===i&&a)return this}if(a&&!o)return this;var s=Lt(this,e);if(!a)for(var u=0;u>>t&y;if(o>=this.array.length)return this;if(t>0){var a=this.array[o];if((r=a&&a.removeAfter(e,t-v,n))===a&&o===this.array.length-1)return this}var i=Lt(this,e);return i.array.splice(o+1),r&&(i.array[o]=r),i};var jt,Tt,It={};function Pt(e,t){var n=e._origin,r=e._capacity,o=qt(r),a=e._tail;return i(e._root,e._level,0);function i(e,t,n){return 0===t?s(e,n):u(e,t,n)}function s(e,i){var s=i===o?a&&a.array:e&&e.array,u=i>n?0:n-i,c=r-i;return c>g&&(c=g),function(){if(u===c)return It;var e=t?--c:u++;return s&&s[e]}}function u(e,o,a){var s,u=e&&e.array,c=a>n?0:n-a>>o,l=1+(r-a>>o);return l>g&&(l=g),function(){for(;;){if(s){var e=s();if(e!==It)return e;s=null}if(c===l)return It;var n=t?--l:c++;s=i(u&&u[n],o-v,a+(n<=e.size||t<0)return e.withMutations((function(e){t<0?Ft(e,t).set(0,n):Ft(e,0,t+1).set(t,n)}));t+=e._origin;var r=e._tail,o=e._root,a=x(w);return t>=qt(e._capacity)?r=Dt(r,e.__ownerID,0,t,n,a):o=Dt(o,e.__ownerID,e._level,t,n,a),a.value?e.__ownerID?(e._root=o,e._tail=r,e.__hash=void 0,e.__altered=!0,e):Nt(e._origin,e._capacity,e._level,o,r):e}function Dt(e,t,n,r,o,a){var i,s=r>>>n&y,u=e&&s0){var c=e&&e.array[s],l=Dt(c,t,n-v,r,o,a);return l===c?e:((i=Lt(e,t)).array[s]=l,i)}return u&&e.array[s]===o?e:(E(a),i=Lt(e,t),void 0===o&&s===i.array.length-1?i.array.pop():i.array[s]=o,i)}function Lt(e,t){return t&&e&&t===e.ownerID?e:new Ot(e?e.array.slice():[],t)}function Bt(e,t){if(t>=qt(e._capacity))return e._tail;if(t<1<0;)n=n.array[t>>>r&y],r-=v;return n}}function Ft(e,t,n){void 0!==t&&(t|=0),void 0!==n&&(n|=0);var r=e.__ownerID||new S,o=e._origin,a=e._capacity,i=o+t,s=void 0===n?a:n<0?a+n:o+n;if(i===o&&s===a)return e;if(i>=s)return e.clear();for(var u=e._level,c=e._root,l=0;i+l<0;)c=new Ot(c&&c.array.length?[void 0,c]:[],r),l+=1<<(u+=v);l&&(i+=l,o+=l,s+=l,a+=l);for(var p=qt(a),f=qt(s);f>=1<p?new Ot([],r):h;if(h&&f>p&&iv;g-=v){var b=p>>>g&y;m=m.array[b]=Lt(m.array[b],r)}m.array[p>>>v&y]=h}if(s=f)i-=f,s-=f,u=v,c=null,d=d&&d.removeBefore(r,0,i);else if(i>o||f>>u&y;if(_!==f>>>u&y)break;_&&(l+=(1<o&&(c=c.removeBefore(r,u,i-l)),c&&fa&&(a=c.size),i(u)||(c=c.map((function(e){return he(e)}))),r.push(c)}return a>e.size&&(e=e.setSize(a)),mt(e,t,r)}function qt(e){return e>>v<=g&&i.size>=2*a.size?(r=(o=i.filter((function(e,t){return void 0!==e&&s!==t}))).toKeyedSeq().map((function(e){return e[0]})).flip().toMap(),e.__ownerID&&(r.__ownerID=o.__ownerID=e.__ownerID)):(r=a.remove(t),o=s===i.size-1?i.pop():i.set(s,void 0))}else if(u){if(n===i.get(s)[1])return e;r=a,o=i.set(s,[t,n])}else r=a.set(t,i.size),o=i.set(i.size,[t,n]);return e.__ownerID?(e.size=r.size,e._map=r,e._list=o,e.__hash=void 0,e):Wt(r,o)}function Jt(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function Kt(e){this._iter=e,this.size=e.size}function Yt(e){this._iter=e,this.size=e.size}function Gt(e){this._iter=e,this.size=e.size}function Zt(e){var t=bn(e);return t._iter=e,t.size=e.size,t.flip=function(){return e},t.reverse=function(){var t=e.reverse.apply(this);return t.flip=function(){return e.reverse()},t},t.has=function(t){return e.includes(t)},t.includes=function(t){return e.has(t)},t.cacheResult=_n,t.__iterateUncached=function(t,n){var r=this;return e.__iterate((function(e,n){return!1!==t(n,e,r)}),n)},t.__iteratorUncached=function(t,n){if(t===R){var r=e.__iterator(t,n);return new F((function(){var e=r.next();if(!e.done){var t=e.value[0];e.value[0]=e.value[1],e.value[1]=t}return e}))}return e.__iterator(t===M?N:M,n)},t}function Xt(e,t,n){var r=bn(e);return r.size=e.size,r.has=function(t){return e.has(t)},r.get=function(r,o){var a=e.get(r,b);return a===b?o:t.call(n,a,r,e)},r.__iterateUncached=function(r,o){var a=this;return e.__iterate((function(e,o,i){return!1!==r(t.call(n,e,o,i),o,a)}),o)},r.__iteratorUncached=function(r,o){var a=e.__iterator(R,o);return new F((function(){var o=a.next();if(o.done)return o;var i=o.value,s=i[0];return U(r,s,t.call(n,i[1],s,e),o)}))},r}function Qt(e,t){var n=bn(e);return n._iter=e,n.size=e.size,n.reverse=function(){return e},e.flip&&(n.flip=function(){var t=Zt(e);return t.reverse=function(){return e.flip()},t}),n.get=function(n,r){return e.get(t?n:-1-n,r)},n.has=function(n){return e.has(t?n:-1-n)},n.includes=function(t){return e.includes(t)},n.cacheResult=_n,n.__iterate=function(t,n){var r=this;return e.__iterate((function(e,n){return t(e,n,r)}),!n)},n.__iterator=function(t,n){return e.__iterator(t,!n)},n}function en(e,t,n,r){var o=bn(e);return r&&(o.has=function(r){var o=e.get(r,b);return o!==b&&!!t.call(n,o,r,e)},o.get=function(r,o){var a=e.get(r,b);return a!==b&&t.call(n,a,r,e)?a:o}),o.__iterateUncached=function(o,a){var i=this,s=0;return e.__iterate((function(e,a,u){if(t.call(n,e,a,u))return s++,o(e,r?a:s-1,i)}),a),s},o.__iteratorUncached=function(o,a){var i=e.__iterator(R,a),s=0;return new F((function(){for(;;){var a=i.next();if(a.done)return a;var u=a.value,c=u[0],l=u[1];if(t.call(n,l,c,e))return U(o,r?c:s++,l,a)}}))},o}function tn(e,t,n){var r=We().asMutable();return e.__iterate((function(o,a){r.update(t.call(n,o,a,e),0,(function(e){return e+1}))})),r.asImmutable()}function nn(e,t,n){var r=s(e),o=(l(e)?zt():We()).asMutable();e.__iterate((function(a,i){o.update(t.call(n,a,i,e),(function(e){return(e=e||[]).push(r?[i,a]:a),e}))}));var a=yn(e);return o.map((function(t){return mn(e,a(t))}))}function rn(e,t,n,r){var o=e.size;if(void 0!==t&&(t|=0),void 0!==n&&(n===1/0?n=o:n|=0),j(t,n,o))return e;var a=T(t,o),i=I(n,o);if(a!=a||i!=i)return rn(e.toSeq().cacheResult(),t,n,r);var s,u=i-a;u==u&&(s=u<0?0:u);var c=bn(e);return c.size=0===s?s:e.size&&s||void 0,!r&&ae(e)&&s>=0&&(c.get=function(t,n){return(t=k(this,t))>=0&&ts)return q();var e=o.next();return r||t===M?e:U(t,u-1,t===N?void 0:e.value[1],e)}))},c}function on(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var a=this;if(o)return this.cacheResult().__iterate(r,o);var i=0;return e.__iterate((function(e,o,s){return t.call(n,e,o,s)&&++i&&r(e,o,a)})),i},r.__iteratorUncached=function(r,o){var a=this;if(o)return this.cacheResult().__iterator(r,o);var i=e.__iterator(R,o),s=!0;return new F((function(){if(!s)return q();var e=i.next();if(e.done)return e;var o=e.value,u=o[0],c=o[1];return t.call(n,c,u,a)?r===R?e:U(r,u,c,e):(s=!1,q())}))},r}function an(e,t,n,r){var o=bn(e);return o.__iterateUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterate(o,a);var s=!0,u=0;return e.__iterate((function(e,a,c){if(!s||!(s=t.call(n,e,a,c)))return u++,o(e,r?a:u-1,i)})),u},o.__iteratorUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterator(o,a);var s=e.__iterator(R,a),u=!0,c=0;return new F((function(){var e,a,l;do{if((e=s.next()).done)return r||o===M?e:U(o,c++,o===N?void 0:e.value[1],e);var p=e.value;a=p[0],l=p[1],u&&(u=t.call(n,l,a,i))}while(u);return o===R?e:U(o,a,l,e)}))},o}function sn(e,t){var n=s(e),o=[e].concat(t).map((function(e){return i(e)?n&&(e=r(e)):e=n?se(e):ue(Array.isArray(e)?e:[e]),e})).filter((function(e){return 0!==e.size}));if(0===o.length)return e;if(1===o.length){var a=o[0];if(a===e||n&&s(a)||u(e)&&u(a))return a}var c=new te(o);return n?c=c.toKeyedSeq():u(e)||(c=c.toSetSeq()),(c=c.flatten(!0)).size=o.reduce((function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}}),0),c}function un(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var a=0,s=!1;function u(e,c){var l=this;e.__iterate((function(e,o){return(!t||c0}function dn(e,t,r){var o=bn(e);return o.size=new te(r).map((function(e){return e.size})).min(),o.__iterate=function(e,t){for(var n,r=this.__iterator(M,t),o=0;!(n=r.next()).done&&!1!==e(n.value,o++,this););return o},o.__iteratorUncached=function(e,o){var a=r.map((function(e){return e=n(e),W(o?e.reverse():e)})),i=0,s=!1;return new F((function(){var n;return s||(n=a.map((function(e){return e.next()})),s=n.some((function(e){return e.done}))),s?q():U(e,i++,t.apply(null,n.map((function(e){return e.value}))))}))},o}function mn(e,t){return ae(e)?t:e.constructor(t)}function vn(e){if(e!==Object(e))throw new TypeError("Expected [K, V] tuple: "+e)}function gn(e){return Ve(e.size),A(e)}function yn(e){return s(e)?r:u(e)?o:a}function bn(e){return Object.create((s(e)?K:u(e)?Y:G).prototype)}function _n(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):J.prototype.cacheResult.call(this)}function wn(e,t){return e>t?1:e=0;n--)t={value:arguments[n],next:t};return this.__ownerID?(this.size=e,this._head=t,this.__hash=void 0,this.__altered=!0,this):Kn(e,t)},Vn.prototype.pushAll=function(e){if(0===(e=o(e)).size)return this;Ve(e.size);var t=this.size,n=this._head;return e.reverse().forEach((function(e){t++,n={value:e,next:n}})),this.__ownerID?(this.size=t,this._head=n,this.__hash=void 0,this.__altered=!0,this):Kn(t,n)},Vn.prototype.pop=function(){return this.slice(1)},Vn.prototype.unshift=function(){return this.push.apply(this,arguments)},Vn.prototype.unshiftAll=function(e){return this.pushAll(e)},Vn.prototype.shift=function(){return this.pop.apply(this,arguments)},Vn.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Yn()},Vn.prototype.slice=function(e,t){if(j(e,t,this.size))return this;var n=T(e,this.size);if(I(t,this.size)!==this.size)return Se.prototype.slice.call(this,e,t);for(var r=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=r,this._head=o,this.__hash=void 0,this.__altered=!0,this):Kn(r,o)},Vn.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?Kn(this.size,this._head,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},Vn.prototype.__iterate=function(e,t){if(t)return this.reverse().__iterate(e);for(var n=0,r=this._head;r&&!1!==e(r.value,n++,this);)r=r.next;return n},Vn.prototype.__iterator=function(e,t){if(t)return this.reverse().__iterator(e);var n=0,r=this._head;return new F((function(){if(r){var t=r.value;return r=r.next,U(e,n++,t)}return q()}))},Vn.isStack=Wn;var Hn,$n="@@__IMMUTABLE_STACK__@@",Jn=Vn.prototype;function Kn(e,t,n,r){var o=Object.create(Jn);return o.size=e,o._head=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function Yn(){return Hn||(Hn=Kn(0))}function Gn(e,t){var n=function(n){e.prototype[n]=t[n]};return Object.keys(t).forEach(n),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(t).forEach(n),e}Jn[$n]=!0,Jn.withMutations=Ke.withMutations,Jn.asMutable=Ke.asMutable,Jn.asImmutable=Ke.asImmutable,Jn.wasAltered=Ke.wasAltered,n.Iterator=F,Gn(n,{toArray:function(){Ve(this.size);var e=new Array(this.size||0);return this.valueSeq().__iterate((function(t,n){e[n]=t})),e},toIndexedSeq:function(){return new Kt(this)},toJS:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJS?e.toJS():e})).__toJS()},toJSON:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJSON?e.toJSON():e})).__toJS()},toKeyedSeq:function(){return new Jt(this,!0)},toMap:function(){return We(this.toKeyedSeq())},toObject:function(){Ve(this.size);var e={};return this.__iterate((function(t,n){e[n]=t})),e},toOrderedMap:function(){return zt(this.toKeyedSeq())},toOrderedSet:function(){return Ln(s(this)?this.valueSeq():this)},toSet:function(){return jn(s(this)?this.valueSeq():this)},toSetSeq:function(){return new Yt(this)},toSeq:function(){return u(this)?this.toIndexedSeq():s(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return Vn(s(this)?this.valueSeq():this)},toList:function(){return St(s(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(e,t){return 0===this.size?e+t:e+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+t},concat:function(){return mn(this,sn(this,e.call(arguments,0)))},includes:function(e){return this.some((function(t){return ge(t,e)}))},entries:function(){return this.__iterator(R)},every:function(e,t){Ve(this.size);var n=!0;return this.__iterate((function(r,o,a){if(!e.call(t,r,o,a))return n=!1,!1})),n},filter:function(e,t){return mn(this,en(this,e,t,!0))},find:function(e,t,n){var r=this.findEntry(e,t);return r?r[1]:n},forEach:function(e,t){return Ve(this.size),this.__iterate(t?e.bind(t):e)},join:function(e){Ve(this.size),e=void 0!==e?""+e:",";var t="",n=!0;return this.__iterate((function(r){n?n=!1:t+=e,t+=null!=r?r.toString():""})),t},keys:function(){return this.__iterator(N)},map:function(e,t){return mn(this,Xt(this,e,t))},reduce:function(e,t,n){var r,o;return Ve(this.size),arguments.length<2?o=!0:r=t,this.__iterate((function(t,a,i){o?(o=!1,r=t):r=e.call(n,r,t,a,i)})),r},reduceRight:function(e,t,n){var r=this.toKeyedSeq().reverse();return r.reduce.apply(r,arguments)},reverse:function(){return mn(this,Qt(this,!0))},slice:function(e,t){return mn(this,rn(this,e,t,!0))},some:function(e,t){return!this.every(tr(e),t)},sort:function(e){return mn(this,pn(this,e))},values:function(){return this.__iterator(M)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(e,t){return A(e?this.toSeq().filter(e,t):this)},countBy:function(e,t){return tn(this,e,t)},equals:function(e){return ye(this,e)},entrySeq:function(){var e=this;if(e._cache)return new te(e._cache);var t=e.toSeq().map(er).toIndexedSeq();return t.fromEntrySeq=function(){return e.toSeq()},t},filterNot:function(e,t){return this.filter(tr(e),t)},findEntry:function(e,t,n){var r=n;return this.__iterate((function(n,o,a){if(e.call(t,n,o,a))return r=[o,n],!1})),r},findKey:function(e,t){var n=this.findEntry(e,t);return n&&n[0]},findLast:function(e,t,n){return this.toKeyedSeq().reverse().find(e,t,n)},findLastEntry:function(e,t,n){return this.toKeyedSeq().reverse().findEntry(e,t,n)},findLastKey:function(e,t){return this.toKeyedSeq().reverse().findKey(e,t)},first:function(){return this.find(O)},flatMap:function(e,t){return mn(this,cn(this,e,t))},flatten:function(e){return mn(this,un(this,e,!0))},fromEntrySeq:function(){return new Gt(this)},get:function(e,t){return this.find((function(t,n){return ge(n,e)}),void 0,t)},getIn:function(e,t){for(var n,r=this,o=xn(e);!(n=o.next()).done;){var a=n.value;if((r=r&&r.get?r.get(a,b):b)===b)return t}return r},groupBy:function(e,t){return nn(this,e,t)},has:function(e){return this.get(e,b)!==b},hasIn:function(e){return this.getIn(e,b)!==b},isSubset:function(e){return e="function"==typeof e.includes?e:n(e),this.every((function(t){return e.includes(t)}))},isSuperset:function(e){return(e="function"==typeof e.isSubset?e:n(e)).isSubset(this)},keyOf:function(e){return this.findKey((function(t){return ge(t,e)}))},keySeq:function(){return this.toSeq().map(Qn).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(e){return this.toKeyedSeq().reverse().keyOf(e)},max:function(e){return fn(this,e)},maxBy:function(e,t){return fn(this,t,e)},min:function(e){return fn(this,e?nr(e):ar)},minBy:function(e,t){return fn(this,t?nr(t):ar,e)},rest:function(){return this.slice(1)},skip:function(e){return this.slice(Math.max(0,e))},skipLast:function(e){return mn(this,this.toSeq().reverse().skip(e).reverse())},skipWhile:function(e,t){return mn(this,an(this,e,t,!0))},skipUntil:function(e,t){return this.skipWhile(tr(e),t)},sortBy:function(e,t){return mn(this,pn(this,t,e))},take:function(e){return this.slice(0,Math.max(0,e))},takeLast:function(e){return mn(this,this.toSeq().reverse().take(e).reverse())},takeWhile:function(e,t){return mn(this,on(this,e,t))},takeUntil:function(e,t){return this.takeWhile(tr(e),t)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=ir(this))}});var Zn=n.prototype;Zn[p]=!0,Zn[B]=Zn.values,Zn.__toJS=Zn.toArray,Zn.__toStringMapper=rr,Zn.inspect=Zn.toSource=function(){return this.toString()},Zn.chain=Zn.flatMap,Zn.contains=Zn.includes,Gn(r,{flip:function(){return mn(this,Zt(this))},mapEntries:function(e,t){var n=this,r=0;return mn(this,this.toSeq().map((function(o,a){return e.call(t,[a,o],r++,n)})).fromEntrySeq())},mapKeys:function(e,t){var n=this;return mn(this,this.toSeq().flip().map((function(r,o){return e.call(t,r,o,n)})).flip())}});var Xn=r.prototype;function Qn(e,t){return t}function er(e,t){return[t,e]}function tr(e){return function(){return!e.apply(this,arguments)}}function nr(e){return function(){return-e.apply(this,arguments)}}function rr(e){return"string"==typeof e?JSON.stringify(e):String(e)}function or(){return C(arguments)}function ar(e,t){return et?-1:0}function ir(e){if(e.size===1/0)return 0;var t=l(e),n=s(e),r=t?1:0;return sr(e.__iterate(n?t?function(e,t){r=31*r+ur(Oe(e),Oe(t))|0}:function(e,t){r=r+ur(Oe(e),Oe(t))|0}:t?function(e){r=31*r+Oe(e)|0}:function(e){r=r+Oe(e)|0}),r)}function sr(e,t){return t=Ae(t,3432918353),t=Ae(t<<15|t>>>-15,461845907),t=Ae(t<<13|t>>>-13,5),t=Ae((t=(t+3864292196|0)^e)^t>>>16,2246822507),t=ke((t=Ae(t^t>>>13,3266489909))^t>>>16)}function ur(e,t){return e^t+2654435769+(e<<6)+(e>>2)|0}return Xn[f]=!0,Xn[B]=Zn.entries,Xn.__toJS=Zn.toObject,Xn.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+rr(e)},Gn(o,{toKeyedSeq:function(){return new Jt(this,!1)},filter:function(e,t){return mn(this,en(this,e,t,!1))},findIndex:function(e,t){var n=this.findEntry(e,t);return n?n[0]:-1},indexOf:function(e){var t=this.keyOf(e);return void 0===t?-1:t},lastIndexOf:function(e){var t=this.lastKeyOf(e);return void 0===t?-1:t},reverse:function(){return mn(this,Qt(this,!1))},slice:function(e,t){return mn(this,rn(this,e,t,!1))},splice:function(e,t){var n=arguments.length;if(t=Math.max(0|t,0),0===n||2===n&&!t)return this;e=T(e,e<0?this.count():this.size);var r=this.slice(0,e);return mn(this,1===n?r:r.concat(C(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){var n=this.findLastEntry(e,t);return n?n[0]:-1},first:function(){return this.get(0)},flatten:function(e){return mn(this,un(this,e,!1))},get:function(e,t){return(e=k(this,e))<0||this.size===1/0||void 0!==this.size&&e>this.size?t:this.find((function(t,n){return n===e}),void 0,t)},has:function(e){return(e=k(this,e))>=0&&(void 0!==this.size?this.size===1/0||e1)try{return decodeURIComponent(t[1])}catch(e){console.error(e)}return null}function Pe(e){return t=e.replace(/\.[^./]*$/,""),Y()(J()(t));var t}function Ne(e,t,n,r,a){if(!t)return[];var s=[],u=t.get("nullable"),c=t.get("required"),p=t.get("maximum"),h=t.get("minimum"),d=t.get("type"),m=t.get("format"),g=t.get("maxLength"),b=t.get("minLength"),w=t.get("uniqueItems"),x=t.get("maxItems"),E=t.get("minItems"),S=t.get("pattern"),C=n||!0===c,A=null!=e;if(u&&null===e||!d||!(C||A&&"array"===d||!(!C&&!A)))return[];var k="string"===d&&e,O="array"===d&&l()(e)&&e.length,j="array"===d&&W.a.List.isList(e)&&e.count(),T=[k,O,j,"array"===d&&"string"==typeof e&&e,"file"===d&&e instanceof se.a.File,"boolean"===d&&(e||!1===e),"number"===d&&(e||0===e),"integer"===d&&(e||0===e),"object"===d&&"object"===i()(e)&&null!==e,"object"===d&&"string"==typeof e&&e],I=P()(T).call(T,(function(e){return!!e}));if(C&&!I&&!r)return s.push("Required field is not provided"),s;if("object"===d&&(null===a||"application/json"===a)){var N,M=e;if("string"==typeof e)try{M=JSON.parse(e)}catch(e){return s.push("Parameter string value must be valid JSON"),s}if(t&&t.has("required")&&Ee(c.isList)&&c.isList()&&y()(c).call(c,(function(e){void 0===M[e]&&s.push({propKey:e,error:"Required property not found"})})),t&&t.has("properties"))y()(N=t.get("properties")).call(N,(function(e,t){var n=Ne(M[t],e,!1,r,a);s.push.apply(s,o()(f()(n).call(n,(function(e){return{propKey:t,error:e}}))))}))}if(S){var R=function(e,t){if(!new RegExp(t).test(e))return"Value must follow pattern "+t}(e,S);R&&s.push(R)}if(E&&"array"===d){var D=function(e,t){var n;if(!e&&t>=1||e&&e.lengtht)return v()(n="Array must not contain more then ".concat(t," item")).call(n,1===t?"":"s")}(e,x);L&&s.push({needRemove:!0,error:L})}if(w&&"array"===d){var B=function(e,t){if(e&&("true"===t||!0===t)){var n=Object(V.fromJS)(e),r=n.toSet();if(e.length>r.size){var o=Object(V.Set)();if(y()(n).call(n,(function(e,t){_()(n).call(n,(function(t){return Ee(t.equals)?t.equals(e):t===e})).size>1&&(o=o.add(t))})),0!==o.size)return f()(o).call(o,(function(e){return{index:e,error:"No duplicates allowed."}})).toArray()}}}(e,w);B&&s.push.apply(s,o()(B))}if(g||0===g){var F=function(e,t){var n;if(e.length>t)return v()(n="Value must be no longer than ".concat(t," character")).call(n,1!==t?"s":"")}(e,g);F&&s.push(F)}if(b){var U=function(e,t){var n;if(e.lengtht)return"Value must be less than ".concat(t)}(e,p);q&&s.push(q)}if(h||0===h){var z=function(e,t){if(e2&&void 0!==arguments[2]?arguments[2]:{},r=n.isOAS3,o=void 0!==r&&r,a=n.bypassRequiredCheck,i=void 0!==a&&a,s=e.get("required"),u=Object(le.a)(e,{isOAS3:o}),c=u.schema,l=u.parameterContentMediaType;return Ne(t,c,s,i,l)},Re=function(e,t,n){if(e&&(!e.xml||!e.xml.name)){if(e.xml=e.xml||{},!e.$$ref)return e.type||e.items||e.properties||e.additionalProperties?'\n\x3c!-- XML example cannot be generated; root element name is undefined --\x3e':null;var r=e.$$ref.match(/\S*\/(\S+)$/);e.xml.name=r[1]}return Object(ie.memoizedCreateXMLExample)(e,t,n)},De=[{when:/json/,shouldStringifyTypes:["string"]}],Le=["object"],Be=function(e,t,n,r){var a=Object(ie.memoizedSampleFromSchema)(e,t,r),s=i()(a),u=S()(De).call(De,(function(e,t){var r;return t.when.test(n)?v()(r=[]).call(r,o()(e),o()(t.shouldStringifyTypes)):e}),Le);return te()(u,(function(e){return e===s}))?M()(a,null,2):a},Fe=function(e,t,n,r){var o,a=Be(e,t,n,r);try{"\n"===(o=me.a.dump(me.a.load(a),{lineWidth:-1}))[o.length-1]&&(o=T()(o).call(o,0,o.length-1))}catch(e){return console.error(e),"error: could not generate yaml example"}return o.replace(/\t/g," ")},Ue=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:void 0;return e&&Ee(e.toJS)&&(e=e.toJS()),r&&Ee(r.toJS)&&(r=r.toJS()),/xml/.test(t)?Re(e,n,r):/(yaml|yml)/.test(t)?Fe(e,n,t,r):Be(e,n,t,r)},qe=function(){var e={},t=se.a.location.search;if(!t)return{};if(""!=t){var n=t.substr(1).split("&");for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(r=n[r].split("="),e[decodeURIComponent(r[0])]=r[1]&&decodeURIComponent(r[1])||"")}return e},ze=function(t){return(t instanceof e?t:e.from(t.toString(),"utf-8")).toString("base64")},Ve={operationsSorter:{alpha:function(e,t){return e.get("path").localeCompare(t.get("path"))},method:function(e,t){return e.get("method").localeCompare(t.get("method"))}},tagsSorter:{alpha:function(e,t){return e.localeCompare(t)}}},We=function(e){var t=[];for(var n in e){var r=e[n];void 0!==r&&""!==r&&t.push([n,"=",encodeURIComponent(r).replace(/%20/g,"+")].join(""))}return t.join("&")},He=function(e,t,n){return!!Q()(n,(function(n){return re()(e[n],t[n])}))};function $e(e){return"string"!=typeof e||""===e?"":Object(H.sanitizeUrl)(e)}function Je(e){return!(!e||D()(e).call(e,"localhost")>=0||D()(e).call(e,"127.0.0.1")>=0||"none"===e)}function Ke(e){if(!W.a.OrderedMap.isOrderedMap(e))return null;if(!e.size)return null;var t=B()(e).call(e,(function(e,t){return U()(t).call(t,"2")&&x()(e.get("content")||{}).length>0})),n=e.get("default")||W.a.OrderedMap(),r=(n.get("content")||W.a.OrderedMap()).keySeq().toJS().length?n:null;return t||r}var Ye=function(e){return"string"==typeof e||e instanceof String?z()(e).call(e).replace(/\s/g,"%20"):""},Ge=function(e){return ce()(Ye(e).replace(/%20/g,"_"))},Ze=function(e){return _()(e).call(e,(function(e,t){return/^x-/.test(t)}))},Xe=function(e){return _()(e).call(e,(function(e,t){return/^pattern|maxLength|minLength|maximum|minimum/.test(t)}))};function Qe(e,t){var n,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){return!0};if("object"!==i()(e)||l()(e)||null===e||!t)return e;var o=A()({},e);return y()(n=x()(o)).call(n,(function(e){e===t&&r(o[e],e)?delete o[e]:o[e]=Qe(o[e],t,r)})),o}function et(e){if("string"==typeof e)return e;if(e&&e.toJS&&(e=e.toJS()),"object"===i()(e)&&null!==e)try{return M()(e,null,2)}catch(t){return String(e)}return null==e?"":e.toString()}function tt(e){return"number"==typeof e?e.toString():e}function nt(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.returnAll,r=void 0!==n&&n,o=t.allowHashes,a=void 0===o||o;if(!W.a.Map.isMap(e))throw new Error("paramToIdentifier: received a non-Im.Map parameter as input");var i,s,u,c=e.get("name"),l=e.get("in"),p=[];e&&e.hashCode&&l&&c&&a&&p.push(v()(i=v()(s="".concat(l,".")).call(s,c,".hash-")).call(i,e.hashCode()));l&&c&&p.push(v()(u="".concat(l,".")).call(u,c));return p.push(c),r?p:p[0]||""}function rt(e,t){var n,r=nt(e,{returnAll:!0});return _()(n=f()(r).call(r,(function(e){return t[e]}))).call(n,(function(e){return void 0!==e}))[0]}function ot(){return it(fe()(32).toString("base64"))}function at(e){return it(de()("sha256").update(e).digest("base64"))}function it(e){return e.replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}var st=function(e){return!e||!(!ge(e)||!e.isEmpty())}}).call(this,n(73).Buffer)},function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){var r=n(240);function o(e,t){for(var n=0;n1?t-1:0),r=1;r1&&void 0!==arguments[1]?arguments[1]:r,n=null,a=null;return function(){return o(t,n,arguments)||(a=e.apply(null,arguments)),n=arguments,a}}))},function(e,t,n){e.exports=n(668)},function(e,t,n){var r=n(175),o=n(572);function a(t){return"function"==typeof r&&"symbol"==typeof o?(e.exports=a=function(e){return typeof e},e.exports.default=e.exports,e.exports.__esModule=!0):(e.exports=a=function(e){return e&&"function"==typeof r&&e.constructor===r&&e!==r.prototype?"symbol":typeof e},e.exports.default=e.exports,e.exports.__esModule=!0),a(t)}e.exports=a,e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){e.exports=n(598)},function(e,t,n){e.exports=n(596)},function(e,t,n){"use strict";var r=n(40),o=n(127).f,a=n(362),i=n(34),s=n(107),u=n(67),c=n(55),l=function(e){var t=function(t,n,r){if(this instanceof e){switch(arguments.length){case 0:return new e;case 1:return new e(t);case 2:return new e(t,n)}return new e(t,n,r)}return e.apply(this,arguments)};return t.prototype=e.prototype,t};e.exports=function(e,t){var n,p,f,h,d,m,v,g,y=e.target,b=e.global,_=e.stat,w=e.proto,x=b?r:_?r[y]:(r[y]||{}).prototype,E=b?i:i[y]||(i[y]={}),S=E.prototype;for(f in t)n=!a(b?f:y+(_?".":"#")+f,e.forced)&&x&&c(x,f),d=E[f],n&&(m=e.noTargetGet?(g=o(x,f))&&g.value:x[f]),h=n&&m?m:t[f],n&&typeof d==typeof h||(v=e.bind&&n?s(h,r):e.wrap&&n?l(h):w&&"function"==typeof h?s(Function.call,h):h,(e.sham||h&&h.sham||d&&d.sham)&&u(v,"sham",!0),E[f]=v,w&&(c(i,p=y+"Prototype")||u(i,p,{}),i[p][f]=h,e.real&&S&&!S[f]&&u(S,f,h)))}},function(e,t,n){e.exports=n(601)},function(e,t,n){e.exports=n(401)},function(e,t,n){var r=n(448),o=n(449),a=n(854),i=n(856),s=n(860),u=n(862),c=n(867),l=n(240),p=n(3);function f(e,t){var n=r(e);if(o){var s=o(e);t&&(s=a(s).call(s,(function(t){return i(e,t).enumerable}))),n.push.apply(n,s)}return n}e.exports=function(e){for(var t=1;t>",i=function(){invariant(!1,"ImmutablePropTypes type checking code is stripped in production.")};i.isRequired=i;var s=function(){return i};function u(e){var t=typeof e;return Array.isArray(e)?"array":e instanceof RegExp?"object":e instanceof o.Iterable?"Immutable."+e.toSource().split(" ")[0]:t}function c(e){function t(t,n,r,o,i,s){for(var u=arguments.length,c=Array(u>6?u-6:0),l=6;l4)}function l(e){var t=e.get("swagger");return"string"==typeof t&&i()(t).call(t,"2.0")}function p(e){return function(t,n){return function(r){return n&&n.specSelectors&&n.specSelectors.specJson?c(n.specSelectors.specJson())?u.a.createElement(e,o()({},r,n,{Ori:t})):u.a.createElement(t,r):(console.warn("OAS3 wrapper: couldn't get spec"),null)}}}},function(e,t,n){e.exports=n(592)},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,n){"use strict";var r=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;function i(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,s,u=i(e),c=1;c0){var o=v()(n).call(n,(function(e){return console.error(e),e.line=e.fullPath?_(w,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",y()(e,"message",{enumerable:!0,value:e.message}),e}));a.newThrownErrBatch(o)}return r.updateResolved(t)}))}},Se=[],Ce=Y()(u()(f.a.mark((function e(){var t,n,r,o,a,i,s,c,l,p,h,m,g,b,w,E,C,k;return f.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(t=Se.system){e.next=4;break}return console.error("debResolveSubtrees: don't have a system to operate on, aborting."),e.abrupt("return");case 4:if(n=t.errActions,r=t.errSelectors,o=t.fn,a=o.resolveSubtree,i=o.fetch,s=o.AST,c=void 0===s?{}:s,l=t.specSelectors,p=t.specActions,a){e.next=8;break}return console.error("Error: Swagger-Client did not provide a `resolveSubtree` method, doing nothing."),e.abrupt("return");case 8:return h=c.getLineNumberForPath?c.getLineNumberForPath:function(){},m=l.specStr(),g=t.getConfigs(),b=g.modelPropertyMacro,w=g.parameterMacro,E=g.requestInterceptor,C=g.responseInterceptor,e.prev=11,e.next=14,_()(Se).call(Se,function(){var e=u()(f.a.mark((function e(t,o){var s,c,p,g,_,k,j,T,I;return f.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,t;case 2:return s=e.sent,c=s.resultMap,p=s.specWithCurrentSubtrees,e.next=7,a(p,o,{baseDoc:l.url(),modelPropertyMacro:b,parameterMacro:w,requestInterceptor:E,responseInterceptor:C});case 7:if(g=e.sent,_=g.errors,k=g.spec,r.allErrors().size&&n.clearBy((function(e){var t;return"thrown"!==e.get("type")||"resolver"!==e.get("source")||!x()(t=e.get("fullPath")).call(t,(function(e,t){return e===o[t]||void 0===o[t]}))})),d()(_)&&_.length>0&&(j=v()(_).call(_,(function(e){return e.line=e.fullPath?h(m,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",y()(e,"message",{enumerable:!0,value:e.message}),e})),n.newThrownErrBatch(j)),!k||!l.isOAS3()||"components"!==o[0]||"securitySchemes"!==o[1]){e.next=15;break}return e.next=15,S.a.all(v()(T=A()(I=O()(k)).call(I,(function(e){return"openIdConnect"===e.type}))).call(T,function(){var e=u()(f.a.mark((function e(t){var n,r;return f.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n={url:t.openIdConnectUrl,requestInterceptor:E,responseInterceptor:C},e.prev=1,e.next=4,i(n);case 4:(r=e.sent)instanceof Error||r.status>=400?console.error(r.statusText+" "+n.url):t.openIdConnectData=JSON.parse(r.text),e.next=11;break;case 8:e.prev=8,e.t0=e.catch(1),console.error(e.t0);case 11:case"end":return e.stop()}}),e,null,[[1,8]])})));return function(t){return e.apply(this,arguments)}}()));case 15:return Z()(c,o,k),Z()(p,o,k),e.abrupt("return",{resultMap:c,specWithCurrentSubtrees:p});case 18:case"end":return e.stop()}}),e)})));return function(t,n){return e.apply(this,arguments)}}(),S.a.resolve({resultMap:(l.specResolvedSubtree([])||Object(z.Map)()).toJS(),specWithCurrentSubtrees:l.specJson().toJS()}));case 14:k=e.sent,delete Se.system,Se=[],e.next=22;break;case 19:e.prev=19,e.t0=e.catch(11),console.error(e.t0);case 22:p.updateResolvedSubtree([],k.resultMap);case 23:case"end":return e.stop()}}),e,null,[[11,19]])}))),35),Ae=function(e){return function(t){var n;T()(n=v()(Se).call(Se,(function(e){return e.join("@@")}))).call(n,e.join("@@"))>-1||(Se.push(e),Se.system=t,Ce())}};function ke(e,t,n,r,o){return{type:re,payload:{path:e,value:r,paramName:t,paramIn:n,isXml:o}}}function Oe(e,t,n,r){return{type:re,payload:{path:e,param:t,value:n,isXml:r}}}var je=function(e,t){return{type:me,payload:{path:e,value:t}}},Te=function(){return{type:me,payload:{path:[],value:Object(z.Map)()}}},Ie=function(e,t){return{type:ae,payload:{pathMethod:e,isOAS3:t}}},Pe=function(e,t,n,r){return{type:oe,payload:{pathMethod:e,paramName:t,paramIn:n,includeEmptyValue:r}}};function Ne(e){return{type:fe,payload:{pathMethod:e}}}function Me(e,t){return{type:he,payload:{path:e,value:t,key:"consumes_value"}}}function Re(e,t){return{type:he,payload:{path:e,value:t,key:"produces_value"}}}var De=function(e,t,n){return{payload:{path:e,method:t,res:n},type:ie}},Le=function(e,t,n){return{payload:{path:e,method:t,req:n},type:se}},Be=function(e,t,n){return{payload:{path:e,method:t,req:n},type:ue}},Fe=function(e){return{payload:e,type:ce}},Ue=function(e){return function(t){var n,r,o=t.fn,a=t.specActions,i=t.specSelectors,s=t.getConfigs,c=t.oas3Selectors,l=e.pathName,p=e.method,h=e.operation,m=s(),g=m.requestInterceptor,y=m.responseInterceptor,b=h.toJS();h&&h.get("parameters")&&P()(n=A()(r=h.get("parameters")).call(r,(function(e){return e&&!0===e.get("allowEmptyValue")}))).call(n,(function(t){if(i.parameterInclusionSettingFor([l,p],t.get("name"),t.get("in"))){e.parameters=e.parameters||{};var n=Object(X.B)(t,e.parameters);(!n||n&&0===n.size)&&(e.parameters[t.get("name")]="")}}));if(e.contextUrl=W()(i.url()).toString(),b&&b.operationId?e.operationId=b.operationId:b&&l&&p&&(e.operationId=o.opId(b,l,p)),i.isOAS3()){var _,w=M()(_="".concat(l,":")).call(_,p);e.server=c.selectedServer(w)||c.selectedServer();var x=c.serverVariables({server:e.server,namespace:w}).toJS(),E=c.serverVariables({server:e.server}).toJS();e.serverVariables=D()(x).length?x:E,e.requestContentType=c.requestContentType(l,p),e.responseContentType=c.responseContentType(l,p)||"*/*";var S,C=c.requestBodyValue(l,p),k=c.requestBodyInclusionSetting(l,p);if(C&&C.toJS)e.requestBody=A()(S=v()(C).call(C,(function(e){return z.Map.isMap(e)?e.get("value"):e}))).call(S,(function(e,t){return(d()(e)?0!==e.length:!Object(X.q)(e))||k.get(t)})).toJS();else e.requestBody=C}var O=B()({},e);O=o.buildRequest(O),a.setRequest(e.pathName,e.method,O);var j=function(){var t=u()(f.a.mark((function t(n){var r,o;return f.a.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,g.apply(undefined,[n]);case 2:return r=t.sent,o=B()({},r),a.setMutatedRequest(e.pathName,e.method,o),t.abrupt("return",r);case 6:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}();e.requestInterceptor=j,e.responseInterceptor=y;var T=U()();return o.execute(e).then((function(t){t.duration=U()()-T,a.setResponse(e.pathName,e.method,t)})).catch((function(t){"Failed to fetch"===t.message&&(t.name="",t.message='**Failed to fetch.** \n**Possible Reasons:** \n - CORS \n - Network Failure \n - URL scheme must be "http" or "https" for CORS request.'),a.setResponse(e.pathName,e.method,{error:!0,err:Object(H.serializeError)(t)})}))}},qe=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.path,n=e.method,r=i()(e,Q);return function(e){var a=e.fn.fetch,i=e.specSelectors,s=e.specActions,u=i.specJsonWithResolvedSubtrees().toJS(),c=i.operationScheme(t,n),l=i.contentTypeValues([t,n]).toJS(),p=l.requestContentType,f=l.responseContentType,h=/xml/i.test(p),d=i.parameterValues([t,n],h).toJS();return s.executeRequest(o()(o()({},r),{},{fetch:a,spec:u,pathName:t,method:n,parameters:d,requestContentType:p,scheme:c,responseContentType:f}))}};function ze(e,t){return{type:le,payload:{path:e,method:t}}}function Ve(e,t){return{type:pe,payload:{path:e,method:t}}}function We(e,t,n){return{type:ve,payload:{scheme:e,path:t,method:n}}}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,n){var r=n(37);e.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},function(e,t,n){var r=n(175),o=n(243),a=n(242),i=n(185);e.exports=function(e,t){var n=void 0!==r&&o(e)||e["@@iterator"];if(!n){if(a(e)||(n=i(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var s=0,u=function(){};return{s:u,n:function(){return s>=e.length?{done:!0}:{done:!1,value:e[s++]}},e:function(e){throw e},f:u}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var c,l=!0,p=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return l=e.done,e},e:function(e){p=!0,c=e},f:function(){try{l||null==n.return||n.return()}finally{if(p)throw c}}}},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){var n=Array.isArray;e.exports=n},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){var r=n(47);e.exports=function(e){if(!r(e))throw TypeError(String(e)+" is not an object");return e}},function(e,t,n){var r=n(449),o=n(450),a=n(872);e.exports=function(e,t){if(null==e)return{};var n,i,s=a(e,t);if(r){var u=r(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(s[n]=e[n])}return s},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t,n){"use strict";n.r(t),n.d(t,"UPDATE_SELECTED_SERVER",(function(){return r})),n.d(t,"UPDATE_REQUEST_BODY_VALUE",(function(){return o})),n.d(t,"UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG",(function(){return a})),n.d(t,"UPDATE_REQUEST_BODY_INCLUSION",(function(){return i})),n.d(t,"UPDATE_ACTIVE_EXAMPLES_MEMBER",(function(){return s})),n.d(t,"UPDATE_REQUEST_CONTENT_TYPE",(function(){return u})),n.d(t,"UPDATE_RESPONSE_CONTENT_TYPE",(function(){return c})),n.d(t,"UPDATE_SERVER_VARIABLE_VALUE",(function(){return l})),n.d(t,"SET_REQUEST_BODY_VALIDATE_ERROR",(function(){return p})),n.d(t,"CLEAR_REQUEST_BODY_VALIDATE_ERROR",(function(){return f})),n.d(t,"CLEAR_REQUEST_BODY_VALUE",(function(){return h})),n.d(t,"setSelectedServer",(function(){return d})),n.d(t,"setRequestBodyValue",(function(){return m})),n.d(t,"setRetainRequestBodyValueFlag",(function(){return v})),n.d(t,"setRequestBodyInclusion",(function(){return g})),n.d(t,"setActiveExamplesMember",(function(){return y})),n.d(t,"setRequestContentType",(function(){return b})),n.d(t,"setResponseContentType",(function(){return _})),n.d(t,"setServerVariableValue",(function(){return w})),n.d(t,"setRequestBodyValidateError",(function(){return x})),n.d(t,"clearRequestBodyValidateError",(function(){return E})),n.d(t,"initRequestBodyValidateError",(function(){return S})),n.d(t,"clearRequestBodyValue",(function(){return C}));var r="oas3_set_servers",o="oas3_set_request_body_value",a="oas3_set_request_body_retain_flag",i="oas3_set_request_body_inclusion",s="oas3_set_active_examples_member",u="oas3_set_request_content_type",c="oas3_set_response_content_type",l="oas3_set_server_variable_value",p="oas3_set_request_body_validate_error",f="oas3_clear_request_body_validate_error",h="oas3_clear_request_body_value";function d(e,t){return{type:r,payload:{selectedServerUrl:e,namespace:t}}}function m(e){var t=e.value,n=e.pathMethod;return{type:o,payload:{value:t,pathMethod:n}}}var v=function(e){var t=e.value,n=e.pathMethod;return{type:a,payload:{value:t,pathMethod:n}}};function g(e){var t=e.value,n=e.pathMethod,r=e.name;return{type:i,payload:{value:t,pathMethod:n,name:r}}}function y(e){var t=e.name,n=e.pathMethod,r=e.contextType,o=e.contextName;return{type:s,payload:{name:t,pathMethod:n,contextType:r,contextName:o}}}function b(e){var t=e.value,n=e.pathMethod;return{type:u,payload:{value:t,pathMethod:n}}}function _(e){var t=e.value,n=e.path,r=e.method;return{type:c,payload:{value:t,path:n,method:r}}}function w(e){var t=e.server,n=e.namespace,r=e.key,o=e.val;return{type:l,payload:{server:t,namespace:n,key:r,val:o}}}var x=function(e){var t=e.path,n=e.method,r=e.validationErrors;return{type:p,payload:{path:t,method:n,validationErrors:r}}},E=function(e){var t=e.path,n=e.method;return{type:f,payload:{path:t,method:n}}},S=function(e){var t=e.pathMethod;return{type:f,payload:{path:t[0],method:t[1]}}},C=function(e){var t=e.pathMethod;return{type:h,payload:{pathMethod:t}}}},function(e,t,n){var r=n(60),o={}.hasOwnProperty;e.exports=Object.hasOwn||function(e,t){return o.call(r(e),t)}},function(e,t,n){"use strict";var r=!("undefined"==typeof window||!window.document||!window.document.createElement),o={canUseDOM:r,canUseWorkers:"undefined"!=typeof Worker,canUseEventListeners:r&&!(!window.addEventListener&&!window.attachEvent),canUseViewport:r&&!!window.screen,isInWorker:!r};e.exports=o},function(e,t){e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},function(e,t,n){"use strict";n.d(t,"b",(function(){return m})),n.d(t,"e",(function(){return v})),n.d(t,"c",(function(){return y})),n.d(t,"a",(function(){return b})),n.d(t,"d",(function(){return _}));var r=n(49),o=n.n(r),a=n(18),i=n.n(a),s=n(2),u=n.n(s),c=n(57),l=n.n(c),p=n(356),f=n.n(p),h=function(e){return String.prototype.toLowerCase.call(e)},d=function(e){return e.replace(/[^\w]/gi,"_")};function m(e){var t=e.openapi;return!!t&&f()(t,"3")}function v(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=r.v2OperationIdCompatibilityMode;if(!e||"object"!==i()(e))return null;var a=(e.operationId||"").replace(/\s/g,"");return a.length?d(e.operationId):g(t,n,{v2OperationIdCompatibilityMode:o})}function g(e,t){var n,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=r.v2OperationIdCompatibilityMode;if(o){var a,i,s=u()(a="".concat(t.toLowerCase(),"_")).call(a,e).replace(/[\s!@#$%^&*()_+=[{\]};:<>|./?,\\'""-]/g,"_");return(s=s||u()(i="".concat(e.substring(1),"_")).call(i,t)).replace(/((_){2,})/g,"_").replace(/^(_)*/g,"").replace(/([_])*$/g,"")}return u()(n="".concat(h(t))).call(n,d(e))}function y(e,t){var n;return u()(n="".concat(h(t),"-")).call(n,e)}function b(e,t){return e&&e.paths?function(e,t){return function(e,t,n){if(!e||"object"!==i()(e)||!e.paths||"object"!==i()(e.paths))return null;var r=e.paths;for(var o in r)for(var a in r[o])if("PARAMETERS"!==a.toUpperCase()){var s=r[o][a];if(s&&"object"===i()(s)){var u={spec:e,pathName:o,method:a.toUpperCase(),operation:s},c=t(u);if(n&&c)return u}}return}(e,t,!0)||null}(e,(function(e){var n=e.pathName,r=e.method,o=e.operation;if(!o||"object"!==i()(o))return!1;var a=o.operationId;return[v(o,n,r),y(n,r),a].some((function(e){return e&&e===t}))})):null}function _(e){var t=e.spec,n=t.paths,r={};if(!n||t.$$normalized)return e;for(var a in n){var i=n[a];if(l()(i)){var s=i.parameters,c=function(e){var n=i[e];if(!l()(n))return"continue";var c=v(n,a,e);if(c){r[c]?r[c].push(n):r[c]=[n];var p=r[c];if(p.length>1)p.forEach((function(e,t){var n;e.__originalOperationId=e.__originalOperationId||e.operationId,e.operationId=u()(n="".concat(c)).call(n,t+1)}));else if(void 0!==n.operationId){var f=p[0];f.__originalOperationId=f.__originalOperationId||n.operationId,f.operationId=c}}if("parameters"!==e){var h=[],d={};for(var m in t)"produces"!==m&&"consumes"!==m&&"security"!==m||(d[m]=t[m],h.push(d));if(s&&(d.parameters=s,h.push(d)),h.length){var g,y=o()(h);try{for(y.s();!(g=y.n()).done;){var b=g.value;for(var _ in b)if(n[_]){if("parameters"===_){var w,x=o()(b[_]);try{var E=function(){var e=w.value;n[_].some((function(t){return t.name&&t.name===e.name||t.$ref&&t.$ref===e.$ref||t.$$ref&&t.$$ref===e.$$ref||t===e}))||n[_].push(e)};for(x.s();!(w=x.n()).done;)E()}catch(e){x.e(e)}finally{x.f()}}}else n[_]=b[_]}}catch(e){y.e(e)}finally{y.f()}}}};for(var p in i)c(p)}}return t.$$normalized=!0,e}},function(e,t,n){"use strict";n.r(t),n.d(t,"NEW_THROWN_ERR",(function(){return o})),n.d(t,"NEW_THROWN_ERR_BATCH",(function(){return a})),n.d(t,"NEW_SPEC_ERR",(function(){return i})),n.d(t,"NEW_SPEC_ERR_BATCH",(function(){return s})),n.d(t,"NEW_AUTH_ERR",(function(){return u})),n.d(t,"CLEAR",(function(){return c})),n.d(t,"CLEAR_BY",(function(){return l})),n.d(t,"newThrownErr",(function(){return p})),n.d(t,"newThrownErrBatch",(function(){return f})),n.d(t,"newSpecErr",(function(){return h})),n.d(t,"newSpecErrBatch",(function(){return d})),n.d(t,"newAuthErr",(function(){return m})),n.d(t,"clear",(function(){return v})),n.d(t,"clearBy",(function(){return g}));var r=n(145),o="err_new_thrown_err",a="err_new_thrown_err_batch",i="err_new_spec_err",s="err_new_spec_err_batch",u="err_new_auth_err",c="err_clear",l="err_clear_by";function p(e){return{type:o,payload:Object(r.serializeError)(e)}}function f(e){return{type:a,payload:e}}function h(e){return{type:i,payload:e}}function d(e){return{type:s,payload:e}}function m(e){return{type:u,payload:e}}function v(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{type:c,payload:e}}function g(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!0};return{type:l,payload:e}}},function(e,t,n){var r=n(106);e.exports=function(e){return Object(r(e))}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){var r=n(73),o=r.Buffer;function a(e,t){for(var n in e)t[n]=e[n]}function i(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(a(r,t),t.Buffer=i),a(o,i),i.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},i.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},i.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},i.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){var r;!function(){"use strict";var n={}.hasOwnProperty;function o(){for(var e=[],t=0;t0?o(r(e),9007199254740991):0}},function(e,t,n){var r=n(34),o=n(40),a=function(e){return"function"==typeof e?e:void 0};e.exports=function(e,t){return arguments.length<2?a(r[e])||a(o[e]):r[e]&&r[e][t]||o[e]&&o[e][t]}},function(e,t,n){var r=n(407),o="object"==typeof self&&self&&self.Object===Object&&self,a=r||o||Function("return this")();e.exports=a},function(e,t,n){"use strict";e.exports={debugTool:null}},function(e,t,n){"use strict";(function(e){var r=n(588),o=n(589),a=n(376);function i(){return u.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function s(e,t){if(i()=i())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+i().toString(16)+" bytes");return 0|e}function d(e,t){if(u.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return q(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return z(e).length;default:if(r)return q(e).length;t=(""+t).toLowerCase(),r=!0}}function m(e,t,n){var r=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return T(this,t,n);case"utf8":case"utf-8":return A(this,t,n);case"ascii":return O(this,t,n);case"latin1":case"binary":return j(this,t,n);case"base64":return C(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return I(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function v(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function g(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=u.from(t,r)),u.isBuffer(t))return 0===t.length?-1:y(e,t,n,r,o);if("number"==typeof t)return t&=255,u.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):y(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function y(e,t,n,r,o){var a,i=1,s=e.length,u=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;i=2,s/=2,u/=2,n/=2}function c(e,t){return 1===i?e[t]:e.readUInt16BE(t*i)}if(o){var l=-1;for(a=n;as&&(n=s-u),a=n;a>=0;a--){for(var p=!0,f=0;fo&&(r=o):r=o;var a=t.length;if(a%2!=0)throw new TypeError("Invalid hex string");r>a/2&&(r=a/2);for(var i=0;i>8,o=n%256,a.push(o),a.push(r);return a}(t,e.length-n),e,n,r)}function C(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function A(e,t,n){n=Math.min(e.length,n);for(var r=[],o=t;o239?4:c>223?3:c>191?2:1;if(o+p<=n)switch(p){case 1:c<128&&(l=c);break;case 2:128==(192&(a=e[o+1]))&&(u=(31&c)<<6|63&a)>127&&(l=u);break;case 3:a=e[o+1],i=e[o+2],128==(192&a)&&128==(192&i)&&(u=(15&c)<<12|(63&a)<<6|63&i)>2047&&(u<55296||u>57343)&&(l=u);break;case 4:a=e[o+1],i=e[o+2],s=e[o+3],128==(192&a)&&128==(192&i)&&128==(192&s)&&(u=(15&c)<<18|(63&a)<<12|(63&i)<<6|63&s)>65535&&u<1114112&&(l=u)}null===l?(l=65533,p=1):l>65535&&(l-=65536,r.push(l>>>10&1023|55296),l=56320|1023&l),r.push(l),o+=p}return function(e){var t=e.length;if(t<=k)return String.fromCharCode.apply(String,e);var n="",r=0;for(;r0&&(e=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(e+=" ... ")),""},u.prototype.compare=function(e,t,n,r,o){if(!u.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(this===e)return 0;for(var a=(o>>>=0)-(r>>>=0),i=(n>>>=0)-(t>>>=0),s=Math.min(a,i),c=this.slice(r,o),l=e.slice(t,n),p=0;po)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var a=!1;;)switch(r){case"hex":return b(this,e,t,n);case"utf8":case"utf-8":return _(this,e,t,n);case"ascii":return w(this,e,t,n);case"latin1":case"binary":return x(this,e,t,n);case"base64":return E(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return S(this,e,t,n);default:if(a)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),a=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var k=4096;function O(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;or)&&(n=r);for(var o="",a=t;an)throw new RangeError("Trying to access beyond buffer length")}function N(e,t,n,r,o,a){if(!u.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function M(e,t,n,r){t<0&&(t=65535+t+1);for(var o=0,a=Math.min(e.length-n,2);o>>8*(r?o:1-o)}function R(e,t,n,r){t<0&&(t=4294967295+t+1);for(var o=0,a=Math.min(e.length-n,4);o>>8*(r?o:3-o)&255}function D(e,t,n,r,o,a){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function L(e,t,n,r,a){return a||D(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function B(e,t,n,r,a){return a||D(e,0,n,8),o.write(e,t,n,r,52,8),n+8}u.prototype.slice=function(e,t){var n,r=this.length;if((e=~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),(t=void 0===t?r:~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),t0&&(o*=256);)r+=this[e+--t]*o;return r},u.prototype.readUInt8=function(e,t){return t||P(e,1,this.length),this[e]},u.prototype.readUInt16LE=function(e,t){return t||P(e,2,this.length),this[e]|this[e+1]<<8},u.prototype.readUInt16BE=function(e,t){return t||P(e,2,this.length),this[e]<<8|this[e+1]},u.prototype.readUInt32LE=function(e,t){return t||P(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},u.prototype.readUInt32BE=function(e,t){return t||P(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},u.prototype.readIntLE=function(e,t,n){e|=0,t|=0,n||P(e,t,this.length);for(var r=this[e],o=1,a=0;++a=(o*=128)&&(r-=Math.pow(2,8*t)),r},u.prototype.readIntBE=function(e,t,n){e|=0,t|=0,n||P(e,t,this.length);for(var r=t,o=1,a=this[e+--r];r>0&&(o*=256);)a+=this[e+--r]*o;return a>=(o*=128)&&(a-=Math.pow(2,8*t)),a},u.prototype.readInt8=function(e,t){return t||P(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},u.prototype.readInt16LE=function(e,t){t||P(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt16BE=function(e,t){t||P(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt32LE=function(e,t){return t||P(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},u.prototype.readInt32BE=function(e,t){return t||P(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},u.prototype.readFloatLE=function(e,t){return t||P(e,4,this.length),o.read(this,e,!0,23,4)},u.prototype.readFloatBE=function(e,t){return t||P(e,4,this.length),o.read(this,e,!1,23,4)},u.prototype.readDoubleLE=function(e,t){return t||P(e,8,this.length),o.read(this,e,!0,52,8)},u.prototype.readDoubleBE=function(e,t){return t||P(e,8,this.length),o.read(this,e,!1,52,8)},u.prototype.writeUIntLE=function(e,t,n,r){(e=+e,t|=0,n|=0,r)||N(this,e,t,n,Math.pow(2,8*n)-1,0);var o=1,a=0;for(this[t]=255&e;++a=0&&(a*=256);)this[t+o]=e/a&255;return t+n},u.prototype.writeUInt8=function(e,t,n){return e=+e,t|=0,n||N(this,e,t,1,255,0),u.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},u.prototype.writeUInt16LE=function(e,t,n){return e=+e,t|=0,n||N(this,e,t,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):M(this,e,t,!0),t+2},u.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||N(this,e,t,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):M(this,e,t,!1),t+2},u.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||N(this,e,t,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):R(this,e,t,!0),t+4},u.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||N(this,e,t,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):R(this,e,t,!1),t+4},u.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);N(this,e,t,n,o-1,-o)}var a=0,i=1,s=0;for(this[t]=255&e;++a>0)-s&255;return t+n},u.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);N(this,e,t,n,o-1,-o)}var a=n-1,i=1,s=0;for(this[t+a]=255&e;--a>=0&&(i*=256);)e<0&&0===s&&0!==this[t+a+1]&&(s=1),this[t+a]=(e/i>>0)-s&255;return t+n},u.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||N(this,e,t,1,127,-128),u.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},u.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||N(this,e,t,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):M(this,e,t,!0),t+2},u.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||N(this,e,t,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):M(this,e,t,!1),t+2},u.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||N(this,e,t,4,2147483647,-2147483648),u.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):R(this,e,t,!0),t+4},u.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||N(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),u.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):R(this,e,t,!1),t+4},u.prototype.writeFloatLE=function(e,t,n){return L(this,e,t,!0,n)},u.prototype.writeFloatBE=function(e,t,n){return L(this,e,t,!1,n)},u.prototype.writeDoubleLE=function(e,t,n){return B(this,e,t,!0,n)},u.prototype.writeDoubleBE=function(e,t,n){return B(this,e,t,!1,n)},u.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t=0;--o)e[o+t]=this[o+n];else if(a<1e3||!u.TYPED_ARRAY_SUPPORT)for(o=0;o>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(a=t;a55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&a.push(239,191,189);continue}if(i+1===r){(t-=3)>-1&&a.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&a.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&a.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;a.push(n)}else if(n<2048){if((t-=2)<0)break;a.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;a.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;a.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return a}function z(e){return r.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(F,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function V(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}}).call(this,n(51))},function(e,t,n){"use strict";function r(e){return null==e}var o={isNothing:r,isObject:function(e){return"object"==typeof e&&null!==e},toArray:function(e){return Array.isArray(e)?e:r(e)?[]:[e]},repeat:function(e,t){var n,r="";for(n=0;ns&&(t=r-s+(a=" ... ").length),n-r>s&&(n=r+s-(i=" ...").length),{str:a+e.slice(t,n).replace(/\t/g,"→")+i,pos:r-t+a.length}}function c(e,t){return o.repeat(" ",t-e.length)+e}var l=function(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),"number"!=typeof t.indent&&(t.indent=1),"number"!=typeof t.linesBefore&&(t.linesBefore=3),"number"!=typeof t.linesAfter&&(t.linesAfter=2);for(var n,r=/\r?\n|\r|\0/g,a=[0],i=[],s=-1;n=r.exec(e.buffer);)i.push(n.index),a.push(n.index+n[0].length),e.position<=n.index&&s<0&&(s=a.length-2);s<0&&(s=a.length-1);var l,p,f="",h=Math.min(e.line+t.linesAfter,i.length).toString().length,d=t.maxLength-(t.indent+h+3);for(l=1;l<=t.linesBefore&&!(s-l<0);l++)p=u(e.buffer,a[s-l],i[s-l],e.position-(a[s]-a[s-l]),d),f=o.repeat(" ",t.indent)+c((e.line-l+1).toString(),h)+" | "+p.str+"\n"+f;for(p=u(e.buffer,a[s],i[s],e.position,d),f+=o.repeat(" ",t.indent)+c((e.line+1).toString(),h)+" | "+p.str+"\n",f+=o.repeat("-",t.indent+h+3+p.pos)+"^\n",l=1;l<=t.linesAfter&&!(s+l>=i.length);l++)p=u(e.buffer,a[s+l],i[s+l],e.position-(a[s]-a[s+l]),d),f+=o.repeat(" ",t.indent)+c((e.line+l+1).toString(),h)+" | "+p.str+"\n";return f.replace(/\n$/,"")},p=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],f=["scalar","sequence","mapping"];var h=function(e,t){if(t=t||{},Object.keys(t).forEach((function(t){if(-1===p.indexOf(t))throw new s('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')})),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=function(e){var t={};return null!==e&&Object.keys(e).forEach((function(n){e[n].forEach((function(e){t[String(e)]=n}))})),t}(t.styleAliases||null),-1===f.indexOf(this.kind))throw new s('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')};function d(e,t){var n=[];return e[t].forEach((function(e){var t=n.length;n.forEach((function(n,r){n.tag===e.tag&&n.kind===e.kind&&n.multi===e.multi&&(t=r)})),n[t]=e})),n}function m(e){return this.extend(e)}m.prototype.extend=function(e){var t=[],n=[];if(e instanceof h)n.push(e);else if(Array.isArray(e))n=n.concat(e);else{if(!e||!Array.isArray(e.implicit)&&!Array.isArray(e.explicit))throw new s("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })");e.implicit&&(t=t.concat(e.implicit)),e.explicit&&(n=n.concat(e.explicit))}t.forEach((function(e){if(!(e instanceof h))throw new s("Specified list of YAML types (or a single Type object) contains a non-Type object.");if(e.loadKind&&"scalar"!==e.loadKind)throw new s("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.");if(e.multi)throw new s("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.")})),n.forEach((function(e){if(!(e instanceof h))throw new s("Specified list of YAML types (or a single Type object) contains a non-Type object.")}));var r=Object.create(m.prototype);return r.implicit=(this.implicit||[]).concat(t),r.explicit=(this.explicit||[]).concat(n),r.compiledImplicit=d(r,"implicit"),r.compiledExplicit=d(r,"explicit"),r.compiledTypeMap=function(){var e,t,n={scalar:{},sequence:{},mapping:{},fallback:{},multi:{scalar:[],sequence:[],mapping:[],fallback:[]}};function r(e){e.multi?(n.multi[e.kind].push(e),n.multi.fallback.push(e)):n[e.kind][e.tag]=n.fallback[e.tag]=e}for(e=0,t=arguments.length;e=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0o"+e.toString(8):"-0o"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}}),A=new RegExp("^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");var k=/^[-+]?[0-9]+e/;var O=new h("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!A.test(e)||"_"===e[e.length-1])},construct:function(e){var t,n;return n="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===n?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:n*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||o.isNegativeZero(e))},represent:function(e,t){var n;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(o.isNegativeZero(e))return"-0.0";return n=e.toString(10),k.test(n)?n.replace("e",".e"):n},defaultStyle:"lowercase"}),j=_.extend({implicit:[w,x,C,O]}),T=j,I=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),P=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");var N=new h("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:function(e){return null!==e&&(null!==I.exec(e)||null!==P.exec(e))},construct:function(e){var t,n,r,o,a,i,s,u,c=0,l=null;if(null===(t=I.exec(e))&&(t=P.exec(e)),null===t)throw new Error("Date resolve error");if(n=+t[1],r=+t[2]-1,o=+t[3],!t[4])return new Date(Date.UTC(n,r,o));if(a=+t[4],i=+t[5],s=+t[6],t[7]){for(c=t[7].slice(0,3);c.length<3;)c+="0";c=+c}return t[9]&&(l=6e4*(60*+t[10]+ +(t[11]||0)),"-"===t[9]&&(l=-l)),u=new Date(Date.UTC(n,r,o,a,i,s,c)),l&&u.setTime(u.getTime()-l),u},instanceOf:Date,represent:function(e){return e.toISOString()}});var M=new h("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}}),R="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";var D=new h("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,r=0,o=e.length,a=R;for(n=0;n64)){if(t<0)return!1;r+=6}return r%8==0},construct:function(e){var t,n,r=e.replace(/[\r\n=]/g,""),o=r.length,a=R,i=0,s=[];for(t=0;t>16&255),s.push(i>>8&255),s.push(255&i)),i=i<<6|a.indexOf(r.charAt(t));return 0===(n=o%4*6)?(s.push(i>>16&255),s.push(i>>8&255),s.push(255&i)):18===n?(s.push(i>>10&255),s.push(i>>2&255)):12===n&&s.push(i>>4&255),new Uint8Array(s)},predicate:function(e){return"[object Uint8Array]"===Object.prototype.toString.call(e)},represent:function(e){var t,n,r="",o=0,a=e.length,i=R;for(t=0;t>18&63],r+=i[o>>12&63],r+=i[o>>6&63],r+=i[63&o]),o=(o<<8)+e[t];return 0===(n=a%3)?(r+=i[o>>18&63],r+=i[o>>12&63],r+=i[o>>6&63],r+=i[63&o]):2===n?(r+=i[o>>10&63],r+=i[o>>4&63],r+=i[o<<2&63],r+=i[64]):1===n&&(r+=i[o>>2&63],r+=i[o<<4&63],r+=i[64],r+=i[64]),r}}),L=Object.prototype.hasOwnProperty,B=Object.prototype.toString;var F=new h("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,n,r,o,a,i=[],s=e;for(t=0,n=s.length;t>10),56320+(e-65536&1023))}for(var ae=new Array(256),ie=new Array(256),se=0;se<256;se++)ae[se]=re(se)?1:0,ie[se]=re(se);function ue(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||W,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.firstTabInLine=-1,this.documents=[]}function ce(e,t){var n={name:e.filename,buffer:e.input.slice(0,-1),position:e.position,line:e.line,column:e.position-e.lineStart};return n.snippet=l(n),new s(t,n)}function le(e,t){throw ce(e,t)}function pe(e,t){e.onWarning&&e.onWarning.call(null,ce(e,t))}var fe={YAML:function(e,t,n){var r,o,a;null!==e.version&&le(e,"duplication of %YAML directive"),1!==n.length&&le(e,"YAML directive accepts exactly one argument"),null===(r=/^([0-9]+)\.([0-9]+)$/.exec(n[0]))&&le(e,"ill-formed argument of the YAML directive"),o=parseInt(r[1],10),a=parseInt(r[2],10),1!==o&&le(e,"unacceptable YAML version of the document"),e.version=n[0],e.checkLineBreaks=a<2,1!==a&&2!==a&&pe(e,"unsupported YAML version of the document")},TAG:function(e,t,n){var r,o;2!==n.length&&le(e,"TAG directive accepts exactly two arguments"),r=n[0],o=n[1],Y.test(r)||le(e,"ill-formed tag handle (first argument) of the TAG directive"),H.call(e.tagMap,r)&&le(e,'there is a previously declared suffix for "'+r+'" tag handle'),G.test(o)||le(e,"ill-formed tag prefix (second argument) of the TAG directive");try{o=decodeURIComponent(o)}catch(t){le(e,"tag prefix is malformed: "+o)}e.tagMap[r]=o}};function he(e,t,n,r){var o,a,i,s;if(t1&&(e.result+=o.repeat("\n",t-1))}function _e(e,t){var n,r,o=e.tag,a=e.anchor,i=[],s=!1;if(-1!==e.firstTabInLine)return!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=i),r=e.input.charCodeAt(e.position);0!==r&&(-1!==e.firstTabInLine&&(e.position=e.firstTabInLine,le(e,"tab characters must not be used in indentation")),45===r)&&ee(e.input.charCodeAt(e.position+1));)if(s=!0,e.position++,ge(e,!0,-1)&&e.lineIndent<=t)i.push(null),r=e.input.charCodeAt(e.position);else if(n=e.line,Ee(e,t,3,!1,!0),i.push(e.result),ge(e,!0,-1),r=e.input.charCodeAt(e.position),(e.line===n||e.lineIndent>t)&&0!==r)le(e,"bad indentation of a sequence entry");else if(e.lineIndentt?m=1:e.lineIndent===t?m=0:e.lineIndentt?m=1:e.lineIndent===t?m=0:e.lineIndentt)&&(g&&(i=e.line,s=e.lineStart,u=e.position),Ee(e,t,4,!0,o)&&(g?m=e.result:v=e.result),g||(me(e,f,h,d,m,v,i,s,u),d=m=v=null),ge(e,!0,-1),c=e.input.charCodeAt(e.position)),(e.line===a||e.lineIndent>t)&&0!==c)le(e,"bad indentation of a mapping entry");else if(e.lineIndent=0))break;0===a?le(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):l?le(e,"repeat of an indentation width identifier"):(p=t+a-1,l=!0)}if(Q(i)){do{i=e.input.charCodeAt(++e.position)}while(Q(i));if(35===i)do{i=e.input.charCodeAt(++e.position)}while(!X(i)&&0!==i)}for(;0!==i;){for(ve(e),e.lineIndent=0,i=e.input.charCodeAt(e.position);(!l||e.lineIndentp&&(p=e.lineIndent),X(i))f++;else{if(e.lineIndent0){for(o=i,a=0;o>0;o--)(i=ne(s=e.input.charCodeAt(++e.position)))>=0?a=(a<<4)+i:le(e,"expected hexadecimal character");e.result+=oe(a),e.position++}else le(e,"unknown escape sequence");n=r=e.position}else X(s)?(he(e,n,r,!0),be(e,ge(e,!1,t)),n=r=e.position):e.position===e.lineStart&&ye(e)?le(e,"unexpected end of the document within a double quoted scalar"):(e.position++,r=e.position)}le(e,"unexpected end of the stream within a double quoted scalar")}(e,h)?g=!0:!function(e){var t,n,r;if(42!==(r=e.input.charCodeAt(e.position)))return!1;for(r=e.input.charCodeAt(++e.position),t=e.position;0!==r&&!ee(r)&&!te(r);)r=e.input.charCodeAt(++e.position);return e.position===t&&le(e,"name of an alias node must contain at least one character"),n=e.input.slice(t,e.position),H.call(e.anchorMap,n)||le(e,'unidentified alias "'+n+'"'),e.result=e.anchorMap[n],ge(e,!0,-1),!0}(e)?function(e,t,n){var r,o,a,i,s,u,c,l,p=e.kind,f=e.result;if(ee(l=e.input.charCodeAt(e.position))||te(l)||35===l||38===l||42===l||33===l||124===l||62===l||39===l||34===l||37===l||64===l||96===l)return!1;if((63===l||45===l)&&(ee(r=e.input.charCodeAt(e.position+1))||n&&te(r)))return!1;for(e.kind="scalar",e.result="",o=a=e.position,i=!1;0!==l;){if(58===l){if(ee(r=e.input.charCodeAt(e.position+1))||n&&te(r))break}else if(35===l){if(ee(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&ye(e)||n&&te(l))break;if(X(l)){if(s=e.line,u=e.lineStart,c=e.lineIndent,ge(e,!1,-1),e.lineIndent>=t){i=!0,l=e.input.charCodeAt(e.position);continue}e.position=a,e.line=s,e.lineStart=u,e.lineIndent=c;break}}i&&(he(e,o,a,!1),be(e,e.line-s),o=a=e.position,i=!1),Q(l)||(a=e.position+1),l=e.input.charCodeAt(++e.position)}return he(e,o,a,!1),!!e.result||(e.kind=p,e.result=f,!1)}(e,h,1===n)&&(g=!0,null===e.tag&&(e.tag="?")):(g=!0,null===e.tag&&null===e.anchor||le(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===m&&(g=u&&_e(e,d))),null===e.tag)null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);else if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&le(e,'unacceptable node kind for ! tag; it should be "scalar", not "'+e.kind+'"'),c=0,l=e.implicitTypes.length;c"),null!==e.result&&f.kind!==e.kind&&le(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+f.kind+'", not "'+e.kind+'"'),f.resolve(e.result,e.tag)?(e.result=f.construct(e.result,e.tag),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):le(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")}return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||g}function Se(e){var t,n,r,o,a=e.position,i=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap=Object.create(null),e.anchorMap=Object.create(null);0!==(o=e.input.charCodeAt(e.position))&&(ge(e,!0,-1),o=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==o));){for(i=!0,o=e.input.charCodeAt(++e.position),t=e.position;0!==o&&!ee(o);)o=e.input.charCodeAt(++e.position);for(r=[],(n=e.input.slice(t,e.position)).length<1&&le(e,"directive name must not be less than one character in length");0!==o;){for(;Q(o);)o=e.input.charCodeAt(++e.position);if(35===o){do{o=e.input.charCodeAt(++e.position)}while(0!==o&&!X(o));break}if(X(o))break;for(t=e.position;0!==o&&!ee(o);)o=e.input.charCodeAt(++e.position);r.push(e.input.slice(t,e.position))}0!==o&&ve(e),H.call(fe,n)?fe[n](e,n,r):pe(e,'unknown document directive "'+n+'"')}ge(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,ge(e,!0,-1)):i&&le(e,"directives end mark is expected"),Ee(e,e.lineIndent-1,4,!1,!0),ge(e,!0,-1),e.checkLineBreaks&&J.test(e.input.slice(a,e.position))&&pe(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&ye(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,ge(e,!0,-1)):e.position=55296&&r<=56319&&t+1=56320&&n<=57343?1024*(r-55296)+n-56320+65536:r}function ze(e){return/^\n* /.test(e)}function Ve(e,t,n,r,o,a,i,s){var u,c,l=0,p=null,f=!1,h=!1,d=-1!==r,m=-1,v=Be(c=qe(e,0))&&c!==je&&!Le(c)&&45!==c&&63!==c&&58!==c&&44!==c&&91!==c&&93!==c&&123!==c&&125!==c&&35!==c&&38!==c&&42!==c&&33!==c&&124!==c&&61!==c&&62!==c&&39!==c&&34!==c&&37!==c&&64!==c&&96!==c&&function(e){return!Le(e)&&58!==e}(qe(e,e.length-1));if(t||i)for(u=0;u=65536?u+=2:u++){if(!Be(l=qe(e,u)))return 5;v=v&&Ue(l,p,s),p=l}else{for(u=0;u=65536?u+=2:u++){if(10===(l=qe(e,u)))f=!0,d&&(h=h||u-m-1>r&&" "!==e[m+1],m=u);else if(!Be(l))return 5;v=v&&Ue(l,p,s),p=l}h=h||d&&u-m-1>r&&" "!==e[m+1]}return f||h?n>9&&ze(e)?5:i?2===a?5:2:h?4:3:!v||i||o(e)?2===a?5:2:1}function We(e,t,n,r,o){e.dump=function(){if(0===t.length)return 2===e.quotingType?'""':"''";if(!e.noCompatMode&&(-1!==Ie.indexOf(t)||Pe.test(t)))return 2===e.quotingType?'"'+t+'"':"'"+t+"'";var a=e.indent*Math.max(1,n),i=-1===e.lineWidth?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-a),u=r||e.flowLevel>-1&&n>=e.flowLevel;switch(Ve(t,u,e.indent,i,(function(t){return function(e,t){var n,r;for(n=0,r=e.implicitTypes.length;n"+He(t,e.indent)+$e(Re(function(e,t){var n,r,o=/(\n+)([^\n]*)/g,a=(s=e.indexOf("\n"),s=-1!==s?s:e.length,o.lastIndex=s,Je(e.slice(0,s),t)),i="\n"===e[0]||" "===e[0];var s;for(;r=o.exec(e);){var u=r[1],c=r[2];n=" "===c[0],a+=u+(i||n||""===c?"":"\n")+Je(c,t),i=n}return a}(t,i),a));case 5:return'"'+function(e){for(var t,n="",r=0,o=0;o=65536?o+=2:o++)r=qe(e,o),!(t=Te[r])&&Be(r)?(n+=e[o],r>=65536&&(n+=e[o+1])):n+=t||Ne(r);return n}(t)+'"';default:throw new s("impossible error: invalid scalar style")}}()}function He(e,t){var n=ze(e)?String(t):"",r="\n"===e[e.length-1];return n+(r&&("\n"===e[e.length-2]||"\n"===e)?"+":r?"":"-")+"\n"}function $e(e){return"\n"===e[e.length-1]?e.slice(0,-1):e}function Je(e,t){if(""===e||" "===e[0])return e;for(var n,r,o=/ [^ ]/g,a=0,i=0,s=0,u="";n=o.exec(e);)(s=n.index)-a>t&&(r=i>a?i:s,u+="\n"+e.slice(a,r),a=r+1),i=s;return u+="\n",e.length-a>t&&i>a?u+=e.slice(a,i)+"\n"+e.slice(i+1):u+=e.slice(a),u.slice(1)}function Ke(e,t,n,r){var o,a,i,s="",u=e.tag;for(o=0,a=n.length;o tag resolver accepts not "'+c+'" style');r=u.represent[c](t,c)}e.dump=r}return!0}return!1}function Ge(e,t,n,r,o,a,i){e.tag=null,e.dump=n,Ye(e,n,!1)||Ye(e,n,!0);var u,c=ke.call(e.dump),l=r;r&&(r=e.flowLevel<0||e.flowLevel>t);var p,f,h="[object Object]"===c||"[object Array]"===c;if(h&&(f=-1!==(p=e.duplicates.indexOf(n))),(null!==e.tag&&"?"!==e.tag||f||2!==e.indent&&t>0)&&(o=!1),f&&e.usedDuplicates[p])e.dump="*ref_"+p;else{if(h&&f&&!e.usedDuplicates[p]&&(e.usedDuplicates[p]=!0),"[object Object]"===c)r&&0!==Object.keys(e.dump).length?(!function(e,t,n,r){var o,a,i,u,c,l,p="",f=e.tag,h=Object.keys(n);if(!0===e.sortKeys)h.sort();else if("function"==typeof e.sortKeys)h.sort(e.sortKeys);else if(e.sortKeys)throw new s("sortKeys must be a boolean or a function");for(o=0,a=h.length;o1024)&&(e.dump&&10===e.dump.charCodeAt(0)?l+="?":l+="? "),l+=e.dump,c&&(l+=De(e,t)),Ge(e,t+1,u,!0,c)&&(e.dump&&10===e.dump.charCodeAt(0)?l+=":":l+=": ",p+=l+=e.dump));e.tag=f,e.dump=p||"{}"}(e,t,e.dump,o),f&&(e.dump="&ref_"+p+e.dump)):(!function(e,t,n){var r,o,a,i,s,u="",c=e.tag,l=Object.keys(n);for(r=0,o=l.length;r1024&&(s+="? "),s+=e.dump+(e.condenseFlow?'"':"")+":"+(e.condenseFlow?"":" "),Ge(e,t,i,!1,!1)&&(u+=s+=e.dump));e.tag=c,e.dump="{"+u+"}"}(e,t,e.dump),f&&(e.dump="&ref_"+p+" "+e.dump));else if("[object Array]"===c)r&&0!==e.dump.length?(e.noArrayIndent&&!i&&t>0?Ke(e,t-1,e.dump,o):Ke(e,t,e.dump,o),f&&(e.dump="&ref_"+p+e.dump)):(!function(e,t,n){var r,o,a,i="",s=e.tag;for(r=0,o=n.length;r",e.dump=u+" "+e.dump)}return!0}function Ze(e,t){var n,r,o=[],a=[];for(Xe(e,o,a),n=0,r=a.length;n",'"',"`"," ","\r","\n","\t"]),l=["'"].concat(c),p=["%","/","?",";","#"].concat(l),f=["/","?","#"],h=/^[+a-z0-9A-Z_-]{0,63}$/,d=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,m={javascript:!0,"javascript:":!0},v={javascript:!0,"javascript:":!0},g={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},y=n(1077);function b(e,t,n){if(e&&o.isObject(e)&&e instanceof a)return e;var r=new a;return r.parse(e,t,n),r}a.prototype.parse=function(e,t,n){if(!o.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e);var a=e.indexOf("?"),s=-1!==a&&a127?N+="x":N+=P[M];if(!N.match(h)){var D=T.slice(0,k),L=T.slice(k+1),B=P.match(d);B&&(D.push(B[1]),L.unshift(B[2])),L.length&&(b="/"+L.join(".")+b),this.hostname=D.join(".");break}}}this.hostname.length>255?this.hostname="":this.hostname=this.hostname.toLowerCase(),j||(this.hostname=r.toASCII(this.hostname));var F=this.port?":"+this.port:"",U=this.hostname||"";this.host=U+F,this.href+=this.host,j&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==b[0]&&(b="/"+b))}if(!m[x])for(k=0,I=l.length;k0)&&n.host.split("@"))&&(n.auth=j.shift(),n.host=n.hostname=j.shift());return n.search=e.search,n.query=e.query,o.isNull(n.pathname)&&o.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.href=n.format(),n}if(!E.length)return n.pathname=null,n.search?n.path="/"+n.search:n.path=null,n.href=n.format(),n;for(var C=E.slice(-1)[0],A=(n.host||e.host||E.length>1)&&("."===C||".."===C)||""===C,k=0,O=E.length;O>=0;O--)"."===(C=E[O])?E.splice(O,1):".."===C?(E.splice(O,1),k++):k&&(E.splice(O,1),k--);if(!w&&!x)for(;k--;k)E.unshift("..");!w||""===E[0]||E[0]&&"/"===E[0].charAt(0)||E.unshift(""),A&&"/"!==E.join("/").substr(-1)&&E.push("");var j,T=""===E[0]||E[0]&&"/"===E[0].charAt(0);S&&(n.hostname=n.host=T?"":E.length?E.shift():"",(j=!!(n.host&&n.host.indexOf("@")>0)&&n.host.split("@"))&&(n.auth=j.shift(),n.host=n.hostname=j.shift()));return(w=w||n.host&&E.length)&&!T&&E.unshift(""),E.length?n.pathname=E.join("/"):(n.pathname=null,n.path=null),o.isNull(n.pathname)&&o.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.auth=e.auth||n.auth,n.slashes=n.slashes||e.slashes,n.href=n.format(),n},a.prototype.parseHost=function(){var e=this.host,t=s.exec(e);t&&(":"!==(t=t[0])&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)}},function(e,t,n){"use strict";n.r(t),n.d(t,"SHOW_AUTH_POPUP",(function(){return h})),n.d(t,"AUTHORIZE",(function(){return d})),n.d(t,"LOGOUT",(function(){return m})),n.d(t,"PRE_AUTHORIZE_OAUTH2",(function(){return v})),n.d(t,"AUTHORIZE_OAUTH2",(function(){return g})),n.d(t,"VALIDATE",(function(){return y})),n.d(t,"CONFIGURE_AUTH",(function(){return b})),n.d(t,"RESTORE_AUTHORIZATION",(function(){return _})),n.d(t,"showDefinitions",(function(){return w})),n.d(t,"authorize",(function(){return x})),n.d(t,"authorizeWithPersistOption",(function(){return E})),n.d(t,"logout",(function(){return S})),n.d(t,"logoutWithPersistOption",(function(){return C})),n.d(t,"preAuthorizeImplicit",(function(){return A})),n.d(t,"authorizeOauth2",(function(){return k})),n.d(t,"authorizeOauth2WithPersistOption",(function(){return O})),n.d(t,"authorizePassword",(function(){return j})),n.d(t,"authorizeApplication",(function(){return T})),n.d(t,"authorizeAccessCodeWithFormParams",(function(){return I})),n.d(t,"authorizeAccessCodeWithBasicAuthentication",(function(){return P})),n.d(t,"authorizeRequest",(function(){return N})),n.d(t,"configureAuth",(function(){return M})),n.d(t,"restoreAuthorization",(function(){return R})),n.d(t,"persistAuthorizationIfNeeded",(function(){return D}));var r=n(18),o=n.n(r),a=n(32),i=n.n(a),s=n(20),u=n.n(s),c=n(94),l=n.n(c),p=n(26),f=n(5),h="show_popup",d="authorize",m="logout",v="pre_authorize_oauth2",g="authorize_oauth2",y="validate",b="configure_auth",_="restore_authorization";function w(e){return{type:h,payload:e}}function x(e){return{type:d,payload:e}}var E=function(e){return function(t){var n=t.authActions;n.authorize(e),n.persistAuthorizationIfNeeded()}};function S(e){return{type:m,payload:e}}var C=function(e){return function(t){var n=t.authActions;n.logout(e),n.persistAuthorizationIfNeeded()}},A=function(e){return function(t){var n=t.authActions,r=t.errActions,o=e.auth,a=e.token,s=e.isValid,u=o.schema,c=o.name,l=u.get("flow");delete p.a.swaggerUIRedirectOauth2,"accessCode"===l||s||r.newAuthErr({authId:c,source:"auth",level:"warning",message:"Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"}),a.error?r.newAuthErr({authId:c,source:"auth",level:"error",message:i()(a)}):n.authorizeOauth2WithPersistOption({auth:o,token:a})}};function k(e){return{type:g,payload:e}}var O=function(e){return function(t){var n=t.authActions;n.authorizeOauth2(e),n.persistAuthorizationIfNeeded()}},j=function(e){return function(t){var n=t.authActions,r=e.schema,o=e.name,a=e.username,i=e.password,s=e.passwordType,c=e.clientId,l=e.clientSecret,p={grant_type:"password",scope:e.scopes.join(" "),username:a,password:i},h={};switch(s){case"request-body":!function(e,t,n){t&&u()(e,{client_id:t});n&&u()(e,{client_secret:n})}(p,c,l);break;case"basic":h.Authorization="Basic "+Object(f.a)(c+":"+l);break;default:console.warn("Warning: invalid passwordType ".concat(s," was passed, not including client id and secret"))}return n.authorizeRequest({body:Object(f.b)(p),url:r.get("tokenUrl"),name:o,headers:h,query:{},auth:e})}};var T=function(e){return function(t){var n=t.authActions,r=e.schema,o=e.scopes,a=e.name,i=e.clientId,s=e.clientSecret,u={Authorization:"Basic "+Object(f.a)(i+":"+s)},c={grant_type:"client_credentials",scope:o.join(" ")};return n.authorizeRequest({body:Object(f.b)(c),name:a,url:r.get("tokenUrl"),auth:e,headers:u})}},I=function(e){var t=e.auth,n=e.redirectUrl;return function(e){var r=e.authActions,o=t.schema,a=t.name,i=t.clientId,s=t.clientSecret,u=t.codeVerifier,c={grant_type:"authorization_code",code:t.code,client_id:i,client_secret:s,redirect_uri:n,code_verifier:u};return r.authorizeRequest({body:Object(f.b)(c),name:a,url:o.get("tokenUrl"),auth:t})}},P=function(e){var t=e.auth,n=e.redirectUrl;return function(e){var r=e.authActions,o=t.schema,a=t.name,i=t.clientId,s=t.clientSecret,u=t.codeVerifier,c={Authorization:"Basic "+Object(f.a)(i+":"+s)},l={grant_type:"authorization_code",code:t.code,client_id:i,redirect_uri:n,code_verifier:u};return r.authorizeRequest({body:Object(f.b)(l),name:a,url:o.get("tokenUrl"),auth:t,headers:c})}},N=function(e){return function(t){var n,r=t.fn,a=t.getConfigs,s=t.authActions,c=t.errActions,p=t.oas3Selectors,f=t.specSelectors,h=t.authSelectors,d=e.body,m=e.query,v=void 0===m?{}:m,g=e.headers,y=void 0===g?{}:g,b=e.name,_=e.url,w=e.auth,x=(h.getConfigs()||{}).additionalQueryStringParams;if(f.isOAS3()){var E=p.serverEffectiveValue(p.selectedServer());n=l()(_,E,!0)}else n=l()(_,f.url(),!0);"object"===o()(x)&&(n.query=u()({},n.query,x));var S=n.toString(),C=u()({Accept:"application/json, text/plain, */*","Content-Type":"application/x-www-form-urlencoded","X-Requested-With":"XMLHttpRequest"},y);r.fetch({url:S,method:"post",headers:C,query:v,body:d,requestInterceptor:a().requestInterceptor,responseInterceptor:a().responseInterceptor}).then((function(e){var t=JSON.parse(e.data),n=t&&(t.error||""),r=t&&(t.parseError||"");e.ok?n||r?c.newAuthErr({authId:b,level:"error",source:"auth",message:i()(t)}):s.authorizeOauth2WithPersistOption({auth:w,token:t}):c.newAuthErr({authId:b,level:"error",source:"auth",message:e.statusText})})).catch((function(e){var t=new Error(e).message;if(e.response&&e.response.data){var n=e.response.data;try{var r="string"==typeof n?JSON.parse(n):n;r.error&&(t+=", error: ".concat(r.error)),r.error_description&&(t+=", description: ".concat(r.error_description))}catch(e){}}c.newAuthErr({authId:b,level:"error",source:"auth",message:t})}))}};function M(e){return{type:b,payload:e}}function R(e){return{type:_,payload:e}}var D=function(){return function(e){var t=e.authSelectors;if((0,e.getConfigs)().persistAuthorization){var n=t.authorized();localStorage.setItem("authorized",i()(n.toJS()))}}}},function(e,t,n){var r=n(1048);e.exports=function(e){for(var t=1;tS;S++)if((h||S in w)&&(b=x(y=w[S],S,_),e))if(t)A[S]=b;else if(b)switch(e){case 3:return!0;case 5:return y;case 6:return S;case 2:u.call(A,y)}else switch(e){case 4:return!1;case 7:u.call(A,y)}return p?-1:c||l?l:A}};e.exports={forEach:c(0),map:c(1),filter:c(2),some:c(3),every:c(4),find:c(5),findIndex:c(6),filterOut:c(7)}},function(e,t,n){n(157);var r=n(576),o=n(40),a=n(99),i=n(67),s=n(131),u=n(41)("toStringTag");for(var c in r){var l=o[c],p=l&&l.prototype;p&&a(p)!==u&&i(p,u,c),s[c]=s.Array}},function(e,t,n){"use strict";e.exports={current:null}},function(e,t){e.exports=function(e){return null!=e&&"object"==typeof e}},function(e,t){var n,r,o=e.exports={};function a(){throw new Error("setTimeout has not been defined")}function i(){throw new Error("clearTimeout has not been defined")}function s(e){if(n===setTimeout)return setTimeout(e,0);if((n===a||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:a}catch(e){n=a}try{r="function"==typeof clearTimeout?clearTimeout:i}catch(e){r=i}}();var u,c=[],l=!1,p=-1;function f(){l&&u&&(l=!1,u.length?c=u.concat(c):p=-1,c.length&&h())}function h(){if(!l){var e=s(f);l=!0;for(var t=c.length;t;){for(u=c,c=[];++p1)for(var n=1;n0&&"/"!==t[0]}));function Se(e,t,n){var r;t=t||[];var o=we.apply(void 0,u()(r=[e]).call(r,i()(t))).get("parameters",Object(I.List)());return x()(o).call(o,(function(e,t){var r=n&&"body"===t.get("in")?t.get("value_xml"):t.get("value");return e.set(Object(T.A)(t,{allowHashes:!1}),r)}),Object(I.fromJS)({}))}function Ce(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(I.List.isList(e))return A()(e).call(e,(function(e){return I.Map.isMap(e)&&e.get("in")===t}))}function Ae(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(I.List.isList(e))return A()(e).call(e,(function(e){return I.Map.isMap(e)&&e.get("type")===t}))}function ke(e,t){var n,r;t=t||[];var o=z(e).getIn(u()(n=["paths"]).call(n,i()(t)),Object(I.fromJS)({})),a=e.getIn(u()(r=["meta","paths"]).call(r,i()(t)),Object(I.fromJS)({})),s=Oe(e,t),c=o.get("parameters")||new I.List,l=a.get("consumes_value")?a.get("consumes_value"):Ae(c,"file")?"multipart/form-data":Ae(c,"formData")?"application/x-www-form-urlencoded":void 0;return Object(I.fromJS)({requestContentType:l,responseContentType:s})}function Oe(e,t){var n,r;t=t||[];var o=z(e).getIn(u()(n=["paths"]).call(n,i()(t)),null);if(null!==o){var a=e.getIn(u()(r=["meta","paths"]).call(r,i()(t),["produces_value"]),null),s=o.getIn(["produces",0],null);return a||s||"application/json"}}function je(e,t){var n;t=t||[];var r=z(e),a=r.getIn(u()(n=["paths"]).call(n,i()(t)),null);if(null!==a){var s=t,c=o()(s,1)[0],l=a.get("produces",null),p=r.getIn(["paths",c,"produces"],null),f=r.getIn(["produces"],null);return l||p||f}}function Te(e,t){var n;t=t||[];var r=z(e),a=r.getIn(u()(n=["paths"]).call(n,i()(t)),null);if(null!==a){var s=t,c=o()(s,1)[0],l=a.get("consumes",null),p=r.getIn(["paths",c,"consumes"],null),f=r.getIn(["consumes"],null);return l||p||f}}var Ie=function(e,t,n){var r=e.get("url").match(/^([a-z][a-z0-9+\-.]*):/),o=O()(r)?r[1]:null;return e.getIn(["scheme",t,n])||e.getIn(["scheme","_defaultScheme"])||o||""},Pe=function(e,t,n){var r;return d()(r=["http","https"]).call(r,Ie(e,t,n))>-1},Ne=function(e,t){var n;t=t||[];var r=e.getIn(u()(n=["meta","paths"]).call(n,i()(t),["parameters"]),Object(I.fromJS)([])),o=!0;return f()(r).call(r,(function(e){var t=e.get("errors");t&&t.count()&&(o=!1)})),o},Me=function(e,t){var n,r,o={requestBody:!1,requestContentType:{}},a=e.getIn(u()(n=["resolvedSubtrees","paths"]).call(n,i()(t),["requestBody"]),Object(I.fromJS)([]));return a.size<1||(a.getIn(["required"])&&(o.requestBody=a.getIn(["required"])),f()(r=a.getIn(["content"]).entrySeq()).call(r,(function(e){var t=e[0];if(e[1].getIn(["schema","required"])){var n=e[1].getIn(["schema","required"]).toJS();o.requestContentType[t]=n}}))),o},Re=function(e,t,n,r){var o;if((n||r)&&n===r)return!0;var a=e.getIn(u()(o=["resolvedSubtrees","paths"]).call(o,i()(t),["requestBody","content"]),Object(I.fromJS)([]));if(a.size<2||!n||!r)return!1;var s=a.getIn([n,"schema","properties"],Object(I.fromJS)([])),c=a.getIn([r,"schema","properties"],Object(I.fromJS)([]));return!!s.equals(c)};function De(e){return I.Map.isMap(e)?e:new I.Map}},function(e,t,n){"use strict";(function(t){var r=n(894),o=n(895),a=/^[A-Za-z][A-Za-z0-9+-.]*:[\\/]+/,i=/^([a-z][a-z0-9.+-]*:)?([\\/]{1,})?([\S\s]*)/i,s=new RegExp("^[\\x09\\x0A\\x0B\\x0C\\x0D\\x20\\xA0\\u1680\\u180E\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200A\\u202F\\u205F\\u3000\\u2028\\u2029\\uFEFF]+");function u(e){return(e||"").toString().replace(s,"")}var c=[["#","hash"],["?","query"],function(e){return e.replace("\\","/")},["/","pathname"],["@","auth",1],[NaN,"host",void 0,1,1],[/:(\d+)$/,"port",void 0,1],[NaN,"hostname",void 0,1,1]],l={hash:1,query:1};function p(e){var n,r=("undefined"!=typeof window?window:void 0!==t?t:"undefined"!=typeof self?self:{}).location||{},o={},i=typeof(e=e||r);if("blob:"===e.protocol)o=new h(unescape(e.pathname),{});else if("string"===i)for(n in o=new h(e,{}),l)delete o[n];else if("object"===i){for(n in e)n in l||(o[n]=e[n]);void 0===o.slashes&&(o.slashes=a.test(e.href))}return o}function f(e){e=u(e);var t=i.exec(e);return{protocol:t[1]?t[1].toLowerCase():"",slashes:!!(t[2]&&t[2].length>=2),rest:t[2]&&1===t[2].length?"/"+t[3]:t[3]}}function h(e,t,n){if(e=u(e),!(this instanceof h))return new h(e,t,n);var a,i,s,l,d,m,v=c.slice(),g=typeof t,y=this,b=0;for("object"!==g&&"string"!==g&&(n=t,t=null),n&&"function"!=typeof n&&(n=o.parse),t=p(t),a=!(i=f(e||"")).protocol&&!i.slashes,y.slashes=i.slashes||a&&t.slashes,y.protocol=i.protocol||t.protocol||"",e=i.rest,i.slashes||(v[3]=[/(.*)/,"pathname"]);b=4?[t[0],t[1],t[2],t[3],"".concat(t[0],".").concat(t[1]),"".concat(t[0],".").concat(t[2]),"".concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[0]),"".concat(t[1],".").concat(t[2]),"".concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[1]),"".concat(t[2],".").concat(t[3]),"".concat(t[3],".").concat(t[0]),"".concat(t[3],".").concat(t[1]),"".concat(t[3],".").concat(t[2]),"".concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[1],".").concat(t[3]),"".concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[2],".").concat(t[3]),"".concat(t[0],".").concat(t[3],".").concat(t[1]),"".concat(t[0],".").concat(t[3],".").concat(t[2]),"".concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[1],".").concat(t[2],".").concat(t[3]),"".concat(t[1],".").concat(t[3],".").concat(t[0]),"".concat(t[1],".").concat(t[3],".").concat(t[2]),"".concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[0],".").concat(t[3]),"".concat(t[2],".").concat(t[1],".").concat(t[0]),"".concat(t[2],".").concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[3],".").concat(t[0]),"".concat(t[2],".").concat(t[3],".").concat(t[1]),"".concat(t[3],".").concat(t[0],".").concat(t[1]),"".concat(t[3],".").concat(t[0],".").concat(t[2]),"".concat(t[3],".").concat(t[1],".").concat(t[0]),"".concat(t[3],".").concat(t[1],".").concat(t[2]),"".concat(t[3],".").concat(t[2],".").concat(t[0]),"".concat(t[3],".").concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[1],".").concat(t[2],".").concat(t[3]),"".concat(t[0],".").concat(t[1],".").concat(t[3],".").concat(t[2]),"".concat(t[0],".").concat(t[2],".").concat(t[1],".").concat(t[3]),"".concat(t[0],".").concat(t[2],".").concat(t[3],".").concat(t[1]),"".concat(t[0],".").concat(t[3],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[3],".").concat(t[2],".").concat(t[1]),"".concat(t[1],".").concat(t[0],".").concat(t[2],".").concat(t[3]),"".concat(t[1],".").concat(t[0],".").concat(t[3],".").concat(t[2]),"".concat(t[1],".").concat(t[2],".").concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[2],".").concat(t[3],".").concat(t[0]),"".concat(t[1],".").concat(t[3],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[3],".").concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[0],".").concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[0],".").concat(t[3],".").concat(t[1]),"".concat(t[2],".").concat(t[1],".").concat(t[0],".").concat(t[3]),"".concat(t[2],".").concat(t[1],".").concat(t[3],".").concat(t[0]),"".concat(t[2],".").concat(t[3],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[3],".").concat(t[1],".").concat(t[0]),"".concat(t[3],".").concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[3],".").concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[3],".").concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[3],".").concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[3],".").concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[3],".").concat(t[2],".").concat(t[1],".").concat(t[0])]:void 0),g[r]}function b(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=e.filter((function(e){return"token"!==e})),o=y(r);return o.reduce((function(e,t){return f()({},e,n[t])}),t)}function _(e){return e.join(" ")}function w(e){var t=e.node,n=e.stylesheet,r=e.style,o=void 0===r?{}:r,a=e.useInlineStyles,i=e.key,s=t.properties,u=t.type,c=t.tagName,l=t.value;if("text"===u)return l;if(c){var p,h=function(e,t){var n=0;return function(r){return n+=1,r.map((function(r,o){return w({node:r,stylesheet:e,useInlineStyles:t,key:"code-segment-".concat(n,"-").concat(o)})}))}}(n,a);if(a){var m=Object.keys(n).reduce((function(e,t){return t.split(".").forEach((function(t){e.includes(t)||e.push(t)})),e}),[]),g=s.className&&s.className.includes("token")?["token"]:[],y=s.className&&g.concat(s.className.filter((function(e){return!m.includes(e)})));p=f()({},s,{className:_(y)||void 0,style:b(s.className,Object.assign({},s.style,o),n)})}else p=f()({},s,{className:_(s.className)});var x=h(t.children);return d.a.createElement(c,v()({key:i},p),x)}}var x=/\n/g;function E(e){var t=e.codeString,n=e.codeStyle,r=e.containerStyle,o=void 0===r?{float:"left",paddingRight:"10px"}:r,a=e.numberStyle,i=void 0===a?{}:a,s=e.startingLineNumber;return d.a.createElement("code",{style:Object.assign({},n,o)},function(e){var t=e.lines,n=e.startingLineNumber,r=e.style;return t.map((function(e,t){var o=t+n;return d.a.createElement("span",{key:"line-".concat(t),className:"react-syntax-highlighter-line-number",style:"function"==typeof r?r(o):r},"".concat(o,"\n"))}))}({lines:t.replace(/\n$/,"").split("\n"),style:i,startingLineNumber:s}))}function S(e,t){return{type:"element",tagName:"span",properties:{key:"line-number--".concat(e),className:["comment","linenumber","react-syntax-highlighter-line-number"],style:t},children:[{type:"text",value:e}]}}function C(e,t,n){var r,o={display:"inline-block",minWidth:(r=n,"".concat(r.toString().length,".25em")),paddingRight:"1em",textAlign:"right",userSelect:"none"},a="function"==typeof e?e(t):e;return f()({},o,a)}function A(e){var t=e.children,n=e.lineNumber,r=e.lineNumberStyle,o=e.largestLineNumber,a=e.showInlineLineNumbers,i=e.lineProps,s=void 0===i?{}:i,u=e.className,c=void 0===u?[]:u,l=e.showLineNumbers,p=e.wrapLongLines,h="function"==typeof s?s(n):s;if(h.className=c,n&&a){var d=C(r,n,o);t.unshift(S(n,d))}return p&l&&(h.style=f()({},h.style,{display:"flex"})),{type:"element",tagName:"span",properties:h,children:t}}function k(e){for(var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=0;r2&&void 0!==arguments[2]?arguments[2]:[];return A({children:e,lineNumber:t,lineNumberStyle:s,largestLineNumber:i,showInlineLineNumbers:o,lineProps:n,className:a,showLineNumbers:r,wrapLongLines:u})}function m(e,t){if(r&&t&&o){var n=C(s,t,i);e.unshift(S(t,n))}return e}function v(e,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[];return t||r.length>0?d(e,n,r):m(e,n)}for(var g=function(){var e=l[h],t=e.children[0].value;if(t.match(x)){var n=t.split("\n");n.forEach((function(t,o){var i=r&&p.length+a,s={type:"text",value:"".concat(t,"\n")};if(0===o){var u=v(l.slice(f+1,h).concat(A({children:[s],className:e.properties.className})),i);p.push(u)}else if(o===n.length-1){if(l[h+1]&&l[h+1].children&&l[h+1].children[0]){var c=A({children:[{type:"text",value:"".concat(t)}],className:e.properties.className});l.splice(h+1,0,c)}else{var d=v([s],i,e.properties.className);p.push(d)}}else{var m=v([s],i,e.properties.className);p.push(m)}})),f=h}h++};h .hljs-title":{color:"#88C0D0"},"hljs-keyword":{color:"#81A1C1"},"hljs-literal":{color:"#81A1C1"},"hljs-symbol":{color:"#81A1C1"},"hljs-number":{color:"#B48EAD"},"hljs-regexp":{color:"#EBCB8B"},"hljs-string":{color:"#A3BE8C"},"hljs-title":{color:"#8FBCBB"},"hljs-params":{color:"#D8DEE9"},"hljs-bullet":{color:"#81A1C1"},"hljs-code":{color:"#8FBCBB"},"hljs-emphasis":{fontStyle:"italic"},"hljs-formula":{color:"#8FBCBB"},"hljs-strong":{fontWeight:"bold"},"hljs-link:hover":{textDecoration:"underline"},"hljs-quote":{color:"#4C566A"},"hljs-comment":{color:"#4C566A"},"hljs-doctag":{color:"#8FBCBB"},"hljs-meta":{color:"#5E81AC"},"hljs-meta-keyword":{color:"#5E81AC"},"hljs-meta-string":{color:"#A3BE8C"},"hljs-attr":{color:"#8FBCBB"},"hljs-attribute":{color:"#D8DEE9"},"hljs-builtin-name":{color:"#81A1C1"},"hljs-name":{color:"#81A1C1"},"hljs-section":{color:"#88C0D0"},"hljs-tag":{color:"#81A1C1"},"hljs-variable":{color:"#D8DEE9"},"hljs-template-variable":{color:"#D8DEE9"},"hljs-template-tag":{color:"#5E81AC"},"abnf .hljs-attribute":{color:"#88C0D0"},"abnf .hljs-symbol":{color:"#EBCB8B"},"apache .hljs-attribute":{color:"#88C0D0"},"apache .hljs-section":{color:"#81A1C1"},"arduino .hljs-built_in":{color:"#88C0D0"},"aspectj .hljs-meta":{color:"#D08770"},"aspectj > .hljs-title":{color:"#88C0D0"},"bnf .hljs-attribute":{color:"#8FBCBB"},"clojure .hljs-name":{color:"#88C0D0"},"clojure .hljs-symbol":{color:"#EBCB8B"},"coq .hljs-built_in":{color:"#88C0D0"},"cpp .hljs-meta-string":{color:"#8FBCBB"},"css .hljs-built_in":{color:"#88C0D0"},"css .hljs-keyword":{color:"#D08770"},"diff .hljs-meta":{color:"#8FBCBB"},"ebnf .hljs-attribute":{color:"#8FBCBB"},"glsl .hljs-built_in":{color:"#88C0D0"},"groovy .hljs-meta:not(:first-child)":{color:"#D08770"},"haxe .hljs-meta":{color:"#D08770"},"java .hljs-meta":{color:"#D08770"},"ldif .hljs-attribute":{color:"#8FBCBB"},"lisp .hljs-name":{color:"#88C0D0"},"lua .hljs-built_in":{color:"#88C0D0"},"moonscript .hljs-built_in":{color:"#88C0D0"},"nginx .hljs-attribute":{color:"#88C0D0"},"nginx .hljs-section":{color:"#5E81AC"},"pf .hljs-built_in":{color:"#88C0D0"},"processing .hljs-built_in":{color:"#88C0D0"},"scss .hljs-keyword":{color:"#81A1C1"},"stylus .hljs-keyword":{color:"#81A1C1"},"swift .hljs-meta":{color:"#D08770"},"vim .hljs-built_in":{color:"#88C0D0",fontStyle:"italic"},"yaml .hljs-meta":{color:"#D08770"}},obsidian:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#282b2e",color:"#e0e2e4"},"hljs-keyword":{color:"#93c763",fontWeight:"bold"},"hljs-selector-tag":{color:"#93c763",fontWeight:"bold"},"hljs-literal":{color:"#93c763",fontWeight:"bold"},"hljs-selector-id":{color:"#93c763"},"hljs-number":{color:"#ffcd22"},"hljs-attribute":{color:"#668bb0"},"hljs-code":{color:"white"},"hljs-class .hljs-title":{color:"white"},"hljs-section":{color:"white",fontWeight:"bold"},"hljs-regexp":{color:"#d39745"},"hljs-link":{color:"#d39745"},"hljs-meta":{color:"#557182"},"hljs-tag":{color:"#8cbbad"},"hljs-name":{color:"#8cbbad",fontWeight:"bold"},"hljs-bullet":{color:"#8cbbad"},"hljs-subst":{color:"#8cbbad"},"hljs-emphasis":{color:"#8cbbad"},"hljs-type":{color:"#8cbbad",fontWeight:"bold"},"hljs-built_in":{color:"#8cbbad"},"hljs-selector-attr":{color:"#8cbbad"},"hljs-selector-pseudo":{color:"#8cbbad"},"hljs-addition":{color:"#8cbbad"},"hljs-variable":{color:"#8cbbad"},"hljs-template-tag":{color:"#8cbbad"},"hljs-template-variable":{color:"#8cbbad"},"hljs-string":{color:"#ec7600"},"hljs-symbol":{color:"#ec7600"},"hljs-comment":{color:"#818e96"},"hljs-quote":{color:"#818e96"},"hljs-deletion":{color:"#818e96"},"hljs-selector-class":{color:"#A082BD"},"hljs-doctag":{fontWeight:"bold"},"hljs-title":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"}},"tomorrow-night":{"hljs-comment":{color:"#969896"},"hljs-quote":{color:"#969896"},"hljs-variable":{color:"#cc6666"},"hljs-template-variable":{color:"#cc6666"},"hljs-tag":{color:"#cc6666"},"hljs-name":{color:"#cc6666"},"hljs-selector-id":{color:"#cc6666"},"hljs-selector-class":{color:"#cc6666"},"hljs-regexp":{color:"#cc6666"},"hljs-deletion":{color:"#cc6666"},"hljs-number":{color:"#de935f"},"hljs-built_in":{color:"#de935f"},"hljs-builtin-name":{color:"#de935f"},"hljs-literal":{color:"#de935f"},"hljs-type":{color:"#de935f"},"hljs-params":{color:"#de935f"},"hljs-meta":{color:"#de935f"},"hljs-link":{color:"#de935f"},"hljs-attribute":{color:"#f0c674"},"hljs-string":{color:"#b5bd68"},"hljs-symbol":{color:"#b5bd68"},"hljs-bullet":{color:"#b5bd68"},"hljs-addition":{color:"#b5bd68"},"hljs-title":{color:"#81a2be"},"hljs-section":{color:"#81a2be"},"hljs-keyword":{color:"#b294bb"},"hljs-selector-tag":{color:"#b294bb"},hljs:{display:"block",overflowX:"auto",background:"#1d1f21",color:"#c5c8c6",padding:"0.5em"},"hljs-emphasis":{fontStyle:"italic"},"hljs-strong":{fontWeight:"bold"}}},Q=o()(X),ee=function(e){return i()(Q).call(Q,e)?X[e]:(console.warn("Request style '".concat(e,"' is not available, returning default instead")),Z)}},function(e,t){e.exports=!0},function(e,t,n){var r=n(237),o=n(68).f,a=n(67),i=n(55),s=n(548),u=n(41)("toStringTag");e.exports=function(e,t,n,c){if(e){var l=n?e:e.prototype;i(l,u)||o(l,u,{configurable:!0,value:t}),c&&!r&&a(l,"toString",s)}}},function(e,t,n){var r=n(237),o=n(151),a=n(41)("toStringTag"),i="Arguments"==o(function(){return arguments}());e.exports=r?o:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),a))?n:i?o(t):"Object"==(r=o(t))&&"function"==typeof t.callee?"Arguments":r}},function(e,t,n){"use strict";e.exports=function(e){if("function"!=typeof e)throw new TypeError(e+" is not a function");return e}},function(e,t,n){e.exports=n(679)},function(e,t,n){"use strict";function r(e){return function(e){try{return!!JSON.parse(e)}catch(e){return null}}(e)?"json":null}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";n.r(t),n.d(t,"UPDATE_LAYOUT",(function(){return o})),n.d(t,"UPDATE_FILTER",(function(){return a})),n.d(t,"UPDATE_MODE",(function(){return i})),n.d(t,"SHOW",(function(){return s})),n.d(t,"updateLayout",(function(){return u})),n.d(t,"updateFilter",(function(){return c})),n.d(t,"show",(function(){return l})),n.d(t,"changeMode",(function(){return p}));var r=n(5),o="layout_update_layout",a="layout_update_filter",i="layout_update_mode",s="layout_show";function u(e){return{type:o,payload:e}}function c(e){return{type:a,payload:e}}function l(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return e=Object(r.v)(e),{type:s,payload:{thing:e,shown:t}}}function p(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return e=Object(r.v)(e),{type:i,payload:{thing:e,mode:t}}}},function(e,t,n){var r=n(421),o=n(161),a=n(192),i=n(50),s=n(115),u=n(193),c=n(160),l=n(249),p=Object.prototype.hasOwnProperty;e.exports=function(e){if(null==e)return!0;if(s(e)&&(i(e)||"string"==typeof e||"function"==typeof e.splice||u(e)||l(e)||a(e)))return!e.length;var t=o(e);if("[object Map]"==t||"[object Set]"==t)return!e.size;if(c(e))return!r(e).length;for(var n in e)if(p.call(e,n))return!1;return!0}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t){e.exports=function(e){if(null==e)throw TypeError("Can't call method on "+e);return e}},function(e,t,n){var r=n(77);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 0:return function(){return e.call(t)};case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,o){return e.call(t,n,r,o)}}return function(){return e.apply(t,arguments)}}},function(e,t,n){var r=n(70);e.exports=r("navigator","userAgent")||""},function(e,t,n){var r,o=n(52),a=n(230),i=n(233),s=n(156),u=n(366),c=n(225),l=n(181),p=l("IE_PROTO"),f=function(){},h=function(e){return" ================================================ FILE: assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/keycloak/oidcCallbackIde.html ================================================ oidcCallback ================================================ FILE: assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/logging.properties ================================================ handlers = org.slf4j.bridge.SLF4JBridgeHandler ================================================ FILE: assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/openapi-configuration.json ================================================ { "resourcePackages": [ "org.eclipse.che" ], "prettyPrint" : true, "cacheTTL": 0, "openAPI": { "servers": [ { "url": "/api" } ], "info": { "version": "7.X", "title": "Che Server API", "description": "Che Server API", "license": { "name": "Eclipse Public License - v 2.0", "url": "https://www.eclipse.org/legal/epl-2.0" } }, "components": { "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } } }, "security": [ { "bearerAuth": [] } ] } } ================================================ FILE: assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/web.xml ================================================ org.eclipse.che.inject.CheBootstrap guiceFilter com.google.inject.servlet.GuiceFilter guiceFilter /* jdbc/che javax.sql.DataSource org.eclipse.che.multiuser.api.authentication.commons.DestroySessionListener ================================================ FILE: assembly/assembly-wsmaster-war/src/test/java/org/eclipse/che/integration/IntegrityConfigurationTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.integration; import static java.lang.String.format; import com.google.common.io.Files; import com.google.common.io.Resources; import com.google.inject.Inject; import java.io.File; import java.io.IOException; import java.io.Reader; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Parameter; import java.nio.charset.Charset; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.inject.Named; import org.eclipse.che.commons.schedule.ScheduleDelay; import org.reflections.Reflections; import org.reflections.scanners.FieldAnnotationsScanner; import org.reflections.scanners.MethodAnnotationsScanner; import org.reflections.scanners.MethodParameterScanner; import org.testng.Assert; import org.testng.annotations.Ignore; import org.testng.annotations.Test; public class IntegrityConfigurationTest { static final Set KNOWN_UNDECLARED_PROPERTIES = new HashSet<>(); static { // created during war build time in buildinfo.properties KNOWN_UNDECLARED_PROPERTIES.add("che.product.build_info"); // Defined in MachineAuthModule KNOWN_UNDECLARED_PROPERTIES.add("che.auth.signature_key_size"); // Defined in KubernetesInfraModule KNOWN_UNDECLARED_PROPERTIES.add("kubernetesBasedEnvironments"); // Defined in KubernetesInfraModule KNOWN_UNDECLARED_PROPERTIES.add("infra.kubernetes.ingress.annotations"); // Defined in DocsModule KNOWN_UNDECLARED_PROPERTIES.add("che.json.ignored_classes"); // Aliased to che.infra.kubernetes.trusted_ca.mount_path KNOWN_UNDECLARED_PROPERTIES.add("che.infra.openshift.trusted_ca_bundles_mount_path"); // Defined in OpenShiftInfraModule KNOWN_UNDECLARED_PROPERTIES.add("multihost-exposer"); // Defined in KubernetesInfraModule KNOWN_UNDECLARED_PROPERTIES.add("allowedEnvironmentTypeUpgrades"); // Defined in WsMasterModule KNOWN_UNDECLARED_PROPERTIES.add("system.domain.actions"); // Defined in ReplicationModule KNOWN_UNDECLARED_PROPERTIES.add("jgroups.config.file"); // Defined in UserDevfileApiPermissionsModule and WorkspaceApiPermissionsModule KNOWN_UNDECLARED_PROPERTIES.add("system.super_privileged_domains"); // Defined in KubernetesInfraModule KNOWN_UNDECLARED_PROPERTIES.add("kubernetesBasedComponents"); // Defined in WsMasterModule KNOWN_UNDECLARED_PROPERTIES.add("che.agents.auth_enabled"); // Defined in che server deployment. KNOWN_UNDECLARED_PROPERTIES.add("che.host"); // Defined in MachineAuthModule KNOWN_UNDECLARED_PROPERTIES.add("che.auth.signature_key_algorithm"); // Defined in che server deployment. KNOWN_UNDECLARED_PROPERTIES.add("env.KUBERNETES_NAMESPACE"); // Defined in che server deployment. KNOWN_UNDECLARED_PROPERTIES.add("env.POD_NAMESPACE"); // Defined in WsMasterModule KNOWN_UNDECLARED_PROPERTIES.add("jndi.datasource.name"); } @Test public void shouldHaveNamedDefaults() { Reflections reflections = new Reflections( "org.eclipse.che", new MethodParameterScanner(), new FieldAnnotationsScanner()); Map configuration = getProperties(new File(Resources.getResource("che").getPath())); for (Constructor constructor : reflections.getConstructorsWithAnyParamAnnotated(Named.class)) { for (Parameter p : constructor.getParameters()) { if (p.isAnnotationPresent(Named.class)) { String key = p.getAnnotation(Named.class).value(); Assert.assertTrue( configuration.containsKey(key) || KNOWN_UNDECLARED_PROPERTIES.contains(key), "constructor `" + constructor.getName() + "` do not have default value `" + key + "` for the parameter `" + p.getName() + "` in configuration"); } } } for (Field field : reflections.getFieldsAnnotatedWith(Named.class)) { if (field.isAnnotationPresent(Named.class)) { if (field.isAnnotationPresent(Inject.class) && field.getAnnotation(Inject.class).optional()) { continue; } String key = field.getAnnotation(Named.class).value(); Assert.assertTrue( configuration.containsKey(key) || KNOWN_UNDECLARED_PROPERTIES.contains(key), "Field `" + field.getName() + "` in class " + field.getDeclaringClass().getName() + " do not have default value `" + key + "` for the parameter ` in configuration"); } } } @Test @Ignore public void shouldNotDeclareUnused() { Reflections reflections = new Reflections( "org.eclipse.che", new MethodParameterScanner(), new FieldAnnotationsScanner(), new MethodAnnotationsScanner()); Set parameters = new HashSet<>(); parameters.addAll( reflections.getConstructorsWithAnyParamAnnotated(Named.class).stream() .flatMap(c -> Stream.of(c.getParameters())) .filter(p -> p.isAnnotationPresent(Named.class)) .map(p -> p.getAnnotation(Named.class).value()) .collect(Collectors.toSet())); parameters.addAll( reflections.getFieldsAnnotatedWith(Named.class).stream() .filter(f -> f.isAnnotationPresent(Named.class)) .map(f -> f.getAnnotation(Named.class).value()) .collect(Collectors.toSet())); parameters.addAll( reflections.getMethodsAnnotatedWith(ScheduleDelay.class).stream() .filter(m -> m.isAnnotationPresent(ScheduleDelay.class)) .map(m -> m.getAnnotation(ScheduleDelay.class).delayParameterName()) .collect(Collectors.toSet())); parameters.addAll( reflections.getMethodsAnnotatedWith(ScheduleDelay.class).stream() .filter(m -> m.isAnnotationPresent(ScheduleDelay.class)) .map(m -> m.getAnnotation(ScheduleDelay.class).initialDelayParameterName()) .collect(Collectors.toSet())); Set unusedDeclaredConfigurationParameters = getProperties(new File(Resources.getResource("che").getPath())).keySet().stream() .filter(k -> !parameters.contains(k)) .collect(Collectors.toSet()); Assert.assertTrue( unusedDeclaredConfigurationParameters.isEmpty(), "Parameters declared but not used. " + unusedDeclaredConfigurationParameters); } @Test public void shouldNotHaveGuiceNamedDefaults() { Reflections reflections = new Reflections("org.eclipse.che", new MethodParameterScanner()); Set guiceNamedClasses = reflections.getConstructorsWithAnyParamAnnotated(com.google.inject.name.Named.class); Assert.assertTrue( guiceNamedClasses.isEmpty(), "Found classes that uses com.google.inject.name.Named annotation instead of javax.inject.Named"); } protected Map getProperties(File confDir) { Map values = new HashMap<>(); final File[] files = confDir.listFiles(); if (files != null) { for (File file : files) { if (!file.isDirectory()) { if ("properties".equals(Files.getFileExtension(file.getName()))) { Properties properties = new Properties(); try (Reader reader = Files.newReader(file, Charset.forName("UTF-8"))) { properties.load(reader); } catch (IOException e) { throw new IllegalStateException( format("Unable to read configuration file %s", file), e); } for (final String name : properties.stringPropertyNames()) values.put(name, properties.getProperty(name)); } } } } return values; } } ================================================ FILE: assembly/assembly-wsmaster-war/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: assembly/pom.xml ================================================ 4.0.0 che-server org.eclipse.che 7.118.0-SNAPSHOT ../pom.xml che-assembly-parent pom Che Assembly :: Parent assembly-swagger-war assembly-wsmaster-war assembly-root-war assembly-che-tomcat assembly-main ================================================ FILE: build/README.md ================================================ # Eclipse Che - Dockerfiles This `che-dockerfiles` repository is where all dockerfiles for Che Launcher, Che's CLI and Che's Stacks are hosted. # Eclipse Che [![Join the chat at https://gitter.im/eclipse/che](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/eclipse/che?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Eclipse License](http://img.shields.io/badge/license-Eclipse-brightgreen.svg)](https://github.com/codenvy/che/blob/master/LICENSE) [![Build Status](https://ci.codenvycorp.com/buildStatus/icon?job=che-ci-master)](https://ci.codenvycorp.com/job/che-ci-master) https://www.eclipse.org/che/. Next-generation Eclipse platform, developer workspace server and cloud IDE. Che defines workspaces that include their dependencies including embedded containerized runtimes, Web IDE, and project code. This makes workspaces distributed, collaborative, and portable to run anywhere on a desktop or a server ... [Read More](https://www.eclipse.org/che/features/) ![Eclipse Che](https://www.eclipse.org/che/images/banner@2x.png "Eclipse Che") ### Getting Started You can run Che in the public cloud, a private cloud, or install it on any OS. Che has been tested on Ubuntu, Linux, MacOS and Windows. The [step by step guide](https://eclipse.org/che/getting-started/) will get you going. The `che` repository is where we do development and there are many ways you can participate, for example: - [Submit bugs and feature requests](https://github.com/eclipse/che/issues) and help us verify them - Review [source code changes](https://github.com/eclipse/che/pulls) - [Improve docs](https://github.com/codenvy/che-docs) ### Contributing If you are interested in fixing issues and contributing directly to the code base, please see [How to Contribute](https://github.com/eclipse/che/wiki/How-To-Contribute). It covers: - [Submitting bugs](https://github.com/eclipse/che/wiki/Submitting-Bugs-and-Suggestions) - [Development workflow](https://github.com/eclipse/che/wiki/Development-Workflow) - [Coding guidelines](https://github.com/eclipse/che/wiki/Coding-Guidelines) - [Contributor license agreement](https://github.com/eclipse/che/wiki/Contributor-License-Agreement) ### Feedback * **Support:** You can ask questions, report bugs, and request features using [GitHub issues](https://github.com/eclipse/che/issues). * **Roadmap:** We maintain [the roadmap](https://github.com/eclipse/che/wiki/Roadmap) on the wiki. * **Weekly Meetings:** Join us on [a hangout](https://github.com/eclipse/che/wiki/Weekly-Planning-Meetings). ### License Che is open sourced under the Eclipse Public License 1.0. ================================================ FILE: build/build.sh ================================================ #!/bin/bash # # Copyright (c) 2017-2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # set -e set -u IMAGE_ALIASES=${IMAGE_ALIASES:-} ERROR=${ERROR:-} DIR=${DIR:-} SHA_TAG=${SHA_TAG:-} BUILDER=${BUILDER:-} skip_tests() { if [ $SKIP_TESTS = "true" ]; then return 0 else return 1 fi } prepare_build_args() { IFS=',' read -r -a BUILD_ARGS_ARRAY <<< "$@" for i in ${BUILD_ARGS_ARRAY[@]}; do BUILD_ARGS+="--build-arg $i " done } init() { BLUE='\033[1;34m' GREEN='\033[0;32m' RED='\033[0;31m' BROWN='\033[0;33m' PURPLE='\033[0;35m' NC='\033[0m' BOLD='\033[1m' UNDERLINE='\033[4m' ORGANIZATION="quay.io/eclipse" PREFIX="che" TAG="next" LATEST_TAG=false PUSH_IMAGE=false SKIP_TESTS=false NAME="che" ARGS="" OPTIONS="" DOCKERFILE="" BUILD_COMMAND="build" BUILD_ARGS="" BUILD_PLATFORMS="" while [ $# -gt 0 ]; do case $1 in --*) OPTIONS="${OPTIONS} ${1}" ;; *) ARGS="${ARGS} ${1}" ;; esac case $1 in --tag:*) TAG="${1#*:}" shift ;; --organization:*) ORGANIZATION="${1#*:}" shift ;; --prefix:*) PREFIX="${1#*:}" shift ;; --name:*) NAME="${1#*:}" shift ;; --skip-tests) SKIP_TESTS=true shift ;; --push-image) PUSH_IMAGE=true shift ;; --sha-tag) SHA_TAG=$(git rev-parse --short HEAD) shift ;; --latest-tag) LATEST_TAG=true shift ;; --dockerfile:*) DOCKERFILE="${1#*:}" shift ;; --build-arg*:*) BUILD_ARGS_CSV="${1#*:}" prepare_build_args $BUILD_ARGS_CSV shift ;; --build-platforms:*) BUILD_PLATFORMS="${1#*:}" shift ;; --builder:*) BUILDER="${1#*:}" shift ;; --*) printf "${RED}Unknown parameter: $1${NC}\n"; exit 2 ;; *) shift;; esac done IMAGE_NAME="$ORGANIZATION/$PREFIX-$NAME:$TAG" IMAGE_MANIFEST="$NAME-$TAG" } build() { # Compute directory if [ -z $DIR ]; then DIR=$(cd "$(dirname "$0")"; pwd) fi BUILD_COMAMAND="build" if [ -z $BUILDER ]; then echo "BUILDER is not specified, trying with podman" BUILDER=$(command -v podman || true) if [[ ! -x $BUILDER ]]; then echo "[WARNING] podman is not installed, trying with buildah" BUILDER=$(command -v buildah || true) if [[ ! -x $BUILDER ]]; then echo "[WARNING] buildah is not installed, trying with docker" BUILDER=$(command -v docker || true) if [[ ! -x $BUILDER ]]; then echo "[ERROR] This script requires podman, buildah or docker to be installed. Must abort!"; exit 1 fi else BUILD_COMMAND="bud" fi fi else if [[ ! -x $(command -v "$BUILDER" || true) ]]; then echo "Builder $BUILDER is missing. Aborting."; exit 1 fi if [[ $BUILDER =~ "docker" || $BUILDER =~ "podman" ]]; then if [[ ! $($BUILDER ps) ]]; then echo "Builder $BUILDER is not functioning. Aborting."; exit 1 fi fi if [[ $BUILDER =~ "buildah" ]]; then BUILD_COMMAND="bud" fi fi # If Dockerfile is empty, build all Dockerfiles if [ -z ${DOCKERFILE} ]; then DOCKERFILES_TO_BUILD="$(ls ${DIR}/Dockerfile*)" ORIGINAL_TAG=${TAG} # Build image for each Dockerfile for dockerfile in ${DOCKERFILES_TO_BUILD}; do dockerfile=$(basename $dockerfile) # extract TAG from Dockerfile if [ ${dockerfile} != "Dockerfile" ]; then TAG=${ORIGINAL_TAG}-$(echo ${dockerfile} | sed -e "s/^Dockerfile.//") fi IMAGE_NAME="$ORGANIZATION/$PREFIX-$NAME:$TAG" DOCKERFILE=${dockerfile} build_image done # restore variables TAG=${ORIGINAL_TAG} IMAGE_NAME="$ORGANIZATION/$PREFIX-$NAME:$TAG" else # else if specified, build only the one specified build_image fi } build_image() { printf "${BOLD}Building Docker Image ${IMAGE_NAME} from $DIR directory with tag $TAG${NC}\n" # Replace macros in Dockerfiles cat ${DIR}/${DOCKERFILE} | sed \ -e "s;\${BUILD_ORGANIZATION};${ORGANIZATION};" \ -e "s;\${BUILD_PREFIX};${PREFIX};" \ -e "s;\${BUILD_TAG};${TAG};" \ > ${DIR}/.Dockerfile cd "${DIR}" if [[ -n $BUILD_PLATFORMS ]]; then if [[ $BUILDER == "podman" ]]; then printf "${BOLD}Creating manifest ${IMAGE_MANIFEST}${NC}\n" "${BUILDER}" manifest create ${IMAGE_MANIFEST} DOCKER_STATUS=$? if [ ! $DOCKER_STATUS -eq 0 ]; then printf "${RED}Failure when creating manifest ${IMAGE_MANIFEST}${NC}\n" exit 1 fi printf "${BOLD}Building image ${IMAGE_NAME}${NC}\n" "${BUILDER}" build --platform ${BUILD_PLATFORMS} -t ${IMAGE_NAME} -f ${DIR}/.Dockerfile --manifest ${IMAGE_MANIFEST} . DOCKER_STATUS=$? if [ ! $DOCKER_STATUS -eq 0 ]; then printf "${RED}Failure when building docker image ${IMAGE_NAME}${NC}\n" exit 1 fi else printf "${RED}Multi-platform image building is only supported for podman builder${NC}\n" exit 1 fi else "${BUILDER}" "${BUILD_COMMAND}" -f ${DIR}/.Dockerfile -t ${IMAGE_NAME} ${BUILD_ARGS} . DOCKER_STATUS=$? if [ ! $DOCKER_STATUS -eq 0 ]; then printf "${RED}Failure when building docker image ${IMAGE_NAME}${NC}\n" exit 1 fi fi printf "Build of ${BLUE}${IMAGE_NAME} ${GREEN}[OK]${NC}\n" if [[ $PUSH_IMAGE == "true" ]]; then push_image ${IMAGE_NAME} ${IMAGE_NAME} if [ ! -z "${SHA_TAG}" ]; then SHA_IMAGE_NAME=${ORGANIZATION}/${PREFIX}-${NAME}:${SHA_TAG} printf "Re-tagging with SHA based tag ${BLUE}${SHA_IMAGE_NAME} ${GREEN}[OK]${NC}\n" push_image ${IMAGE_NAME} ${SHA_IMAGE_NAME} fi if [[ ${LATEST_TAG} == "true" ]]; then LATEST_IMAGE_NAME=${ORGANIZATION}/${PREFIX}-${NAME}:latest printf "Re-tagging with latest tag ${BLUE}${LATEST_IMAGE_NAME} ${GREEN}[OK]${NC}\n" push_image ${IMAGE_NAME} ${LATEST_IMAGE_NAME} fi fi if [ ! -z "${IMAGE_ALIASES}" ]; then for TMP_IMAGE_NAME in ${IMAGE_ALIASES} do "${BUILDER}" tag ${IMAGE_NAME} ${TMP_IMAGE_NAME}:${TAG} DOCKER_TAG_STATUS=$? if [ $DOCKER_TAG_STATUS -eq 0 ]; then printf " /alias ${BLUE}${TMP_IMAGE_NAME}:${TAG}${NC} ${GREEN}[OK]${NC}\n" else printf "${RED}Failure when building docker image ${IMAGE_NAME}${NC}\n" exit 1 fi done fi if [[ -n $BUILD_PLATFORMS ]] && [[ $BUILDER == "podman" ]]; then # Remove manifest list from local storage ${BUILDER} manifest rm ${IMAGE_MANIFEST} fi printf "${GREEN}Script run successfully: ${BLUE}${IMAGE_NAME}${NC}\n" } push_image() { local image=$1 local tagged_image=$2 printf "Pushing manifest ${BLUE}${image} ${NC}\n" if [[ -n $BUILD_PLATFORMS ]] && [[ $BUILDER == "podman" ]]; then ${BUILDER} manifest push ${IMAGE_MANIFEST} docker://${tagged_image} DOCKER_STATUS=$? if [ ! $DOCKER_STATUS -eq 0 ]; then printf "${RED}Failure when pushing image ${image}${NC}\n" exit 1 fi else ${BUILDER} tag ${image} ${tagged_image} DOCKER_STATUS=$? if [ ! $DOCKER_STATUS -eq 0 ]; then printf "${RED}Failure when tagging image ${tagged_image}${NC}\n" exit 1 fi ${BUILDER} push ${image} DOCKER_STATUS=$? if [ ! $DOCKER_STATUS -eq 0 ]; then printf "${RED}Failure when pushing image ${image}${NC}\n" exit 1 fi fi printf "Push of ${BLUE}${tagged_image} ${GREEN}[OK]${NC}\n" } get_full_path() { echo "$(cd "$(dirname "${1}")"; pwd)/$(basename "$1")" } convert_windows_to_posix() { echo "/"$(echo "$1" | sed 's/\\/\//g' | sed 's/://') } get_clean_path() { INPUT_PATH=$1 # \some\path => /some/path OUTPUT_PATH=$(echo ${INPUT_PATH} | tr '\\' '/') # /somepath/ => /somepath OUTPUT_PATH=${OUTPUT_PATH%/} # /some//path => /some/path OUTPUT_PATH=$(echo ${OUTPUT_PATH} | tr -s '/') # "/some/path" => /some/path OUTPUT_PATH=${OUTPUT_PATH//\"} echo ${OUTPUT_PATH} } get_mount_path() { FULL_PATH=$(get_full_path "${1}") POSIX_PATH=$(convert_windows_to_posix "${FULL_PATH}") CLEAN_PATH=$(get_clean_path "${POSIX_PATH}") echo $CLEAN_PATH } # grab assembly DIR="$(cd "$(dirname "$0")"; pwd)/dockerfiles" if [ ! -d "${DIR}/../../assembly/assembly-main/target" ]; then echo "${ERROR}Have you built assembly/assemby-main in ${DIR}/../assembly/assembly-main 'mvn clean install'?" exit 2 fi # Use of folder BUILD_ASSEMBLY_DIR=$(echo "${DIR}"/../../assembly/assembly-main/target/eclipse-che-*/eclipse-che-*/) LOCAL_ASSEMBLY_DIR="${DIR}"/eclipse-che if [ -d "${LOCAL_ASSEMBLY_DIR}" ]; then rm -r "${LOCAL_ASSEMBLY_DIR}" fi echo "Copying assembly ${BUILD_ASSEMBLY_DIR} --> ${LOCAL_ASSEMBLY_DIR}" cp -r "${BUILD_ASSEMBLY_DIR}" "${LOCAL_ASSEMBLY_DIR}" init --name:server "$@" build #cleanUp rm -rf ${DIR}/eclipse-che ================================================ FILE: build/dockerfiles/Dockerfile ================================================ # Copyright (c) 2018-2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # https://access.redhat.com/containers/?tab=tags#/registry.access.redhat.com/ubi8-minimal FROM registry.access.redhat.com/ubi8-minimal:8.10-1179 USER root ENV CHE_HOME=/home/user/eclipse-che ENV JAVA_HOME=/usr/lib/jvm/jre RUN microdnf install java-17-openjdk-headless tar gzip shadow-utils findutils && \ microdnf update -y && \ microdnf -y clean all && rm -rf /var/cache/yum && echo "Installed Packages" && rpm -qa | sort -V && echo "End Of Installed Packages" && \ adduser -G root user && mkdir -p /home/user/eclipse-che COPY entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] ADD eclipse-che /home/user/eclipse-che # this should fail if the startup script is not found in correct path /home/user/eclipse-che/tomcat/bin/catalina.sh RUN mkdir /logs /data && \ chmod 0777 /logs /data && \ chgrp -R 0 /home/user /logs /data && \ chown -R user /home/user && \ chmod -R g+rwX /home/user && \ find /home/user -type d -exec chmod 777 {} \; && \ java -version && echo -n "Server startup script in: " && \ find /home/user/eclipse-che -name catalina.sh | grep -z /home/user/eclipse-che/tomcat/bin/catalina.sh USER user # append Brew metadata here ================================================ FILE: build/dockerfiles/brew.Dockerfile ================================================ # Copyright (c) 2018-2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # # https://access.redhat.com/containers/?tab=tags#/registry.access.redhat.com/ubi8-minimal FROM ubi8-minimal:8.10-1179 USER root ENV CHE_HOME=/home/user/devspaces ENV JAVA_HOME=/usr/lib/jvm/jre RUN microdnf install java-11-openjdk-headless tar gzip shadow-utils findutils && \ microdnf update -y && \ microdnf -y clean all && rm -rf /var/cache/yum && echo "Installed Packages" && rpm -qa | sort -V && echo "End Of Installed Packages" && \ adduser -G root user && mkdir -p /home/user/devspaces COPY entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] # see fetch-artifacts-pnc.yaml COPY artifacts/assembly-main.tar.gz /tmp/assembly-main.tar.gz RUN tar xzf /tmp/assembly-main.tar.gz --strip-components=1 -C /home/user/devspaces; rm -f /tmp/assembly-main.tar.gz # this should fail if the startup script is not found in correct path /home/user/devspaces/tomcat/bin/catalina.sh RUN mkdir /logs /data && \ chmod 0777 /logs /data && \ chgrp -R 0 /home/user /logs /data && \ chown -R user /home/user && \ chmod -R g+rwX /home/user && \ find /home/user -type d -exec chmod 777 {} \; && \ java -version && echo -n "Server startup script in: " && \ find /home/user/devspaces -name catalina.sh | grep -z /home/user/devspaces/tomcat/bin/catalina.sh USER user # append Brew metadata here ================================================ FILE: build/dockerfiles/entrypoint.sh ================================================ #!/bin/bash # # Copyright (c) 2012-2020 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # init_global_variables () { # For coloring console output BLUE='\033[1;34m' GREEN='\033[0;32m' NC='\033[0m' USAGE=" Usage: che [COMMAND] start Starts server with output in the background stop Stops ${CHE_MINI_PRODUCT_NAME} server run Starts server with output in the foreground Variables: CHE_SERVER_ACTION Another way to set the [COMMAND] to [run | start | stop] CHE_PORT The port the Che server will listen on CHE_IP The IP address of the host - must be set if remote clients connecting CHE_BLOCKING_ENTROPY Starts Tomcat with blocking entropy: -Djava.security.egd=file:/dev/./urandom CHE_LAUNCH_DOCKER_REGISTRY If true, uses Docker registry to save ws snapshots instead of disk CHE_REGISTRY_HOST Hostname of Docker registry to launch, otherwise 'localhost' CHE_LOG_LEVEL [INFO | DEBUG] Sets the output level of Tomcat messages CHE_DEBUG_SERVER If true, activates Tomcat's JPDA debugging mode CHE_DEBUG_SUSPEND If true, Tomcat will start suspended waiting for debugger CHE_HOME Where the Che assembly resides - self-determining if not set " # Use blocking entropy -- needed for some servers DEFAULT_CHE_BLOCKING_ENTROPY=false CHE_BLOCKING_ENTROPY=${CHE_BLOCKING_ENTROPY:-${DEFAULT_CHE_BLOCKING_ENTROPY}} DEFAULT_CHE_SERVER_ACTION=run CHE_SERVER_ACTION=${CHE_SERVER_ACTION:-${DEFAULT_CHE_SERVER_ACTION}} DEFAULT_CHE_LAUNCH_DOCKER_REGISTRY=false CHE_LAUNCH_DOCKER_REGISTRY=${CHE_LAUNCH_DOCKER_REGISTRY:-${DEFAULT_CHE_LAUNCH_DOCKER_REGISTRY}} # Must be exported as this will be needed by Tomcat's JVM DEFAULT_CHE_REGISTRY_HOST=localhost export CHE_REGISTRY_HOST=${CHE_REGISTRY_HOST:-${DEFAULT_CHE_REGISTRY_HOST}} DEFAULT_CHE_PORT=8080 export CHE_PORT=${CHE_PORT:-${DEFAULT_CHE_PORT}} DEFAULT_CHE_IP= CHE_IP=${CHE_IP:-${DEFAULT_CHE_IP}} DEFAULT_CHE_LOG_LEVEL=INFO CHE_LOG_LEVEL=${CHE_LOG_LEVEL:-${DEFAULT_CHE_LOG_LEVEL}} DEFAULT_CHE_LOGS_DIR="${CATALINA_HOME}/logs/" export CHE_LOGS_DIR=${CHE_LOGS_DIR:-${DEFAULT_CHE_LOGS_DIR}} DEFAULT_CHE_DEBUG_SERVER=false CHE_DEBUG_SERVER=${CHE_DEBUG_SERVER:-${DEFAULT_CHE_DEBUG_SERVER}} DEFAULT_CHE_DEBUG_SUSPEND="false" CHE_DEBUG_SUSPEND=${CHE_DEBUG_SUSPEND:-${DEFAULT_CHE_DEBUG_SUSPEND}} } error () { echo echo "!!!" echo -e "!!! ${1}" echo "!!!" return 0 } usage () { echo "${USAGE}" } set_environment_variables () { ### Set value of derived environment variables. # CHE_DOCKER_IP is used internally by Che to set its IP address if [[ -z "${CHE_DOCKER_IP}" ]]; then if [[ -n "${CHE_IP}" ]]; then export CHE_DOCKER_IP="${CHE_IP}" fi fi # Convert Tomcat environment variables to POSIX format. if [[ "${JAVA_HOME}" == *":"* ]]; then JAVA_HOME=$(echo /"${JAVA_HOME}" | sed 's|\\|/|g' | sed 's|:||g') fi # Convert Che environment variables to POSIX format. if [[ "${CHE_HOME}" == *":"* ]]; then CHE_HOME=$(echo /"${CHE_HOME}" | sed 's|\\|/|g' | sed 's|:||g') fi # Sets the location of the application server and its executables # Internal property - should generally not be overridden export CATALINA_HOME="${CHE_HOME}/tomcat" # Convert windows path name to POSIX if [[ "${CATALINA_HOME}" == *":"* ]]; then CATALINA_HOME=$(echo /"${CATALINA_HOME}" | sed 's|\\|/|g' | sed 's|:||g') fi if [[ "${CHE_DEBUG_SUSPEND}" == "true" ]]; then export JPDA_SUSPEND="y" else export JPDA_SUSPEND="n" fi # Internal properties - should generally not be overridden export CATALINA_BASE="${CHE_HOME}/tomcat" export ASSEMBLY_BIN_DIR="${CATALINA_HOME}/bin" export CHE_LOGS_LEVEL="${CHE_LOG_LEVEL}" } docker_exec() { "$(which docker)" "$@" } start_che_server () { if ${CHE_LAUNCH_DOCKER_REGISTRY} ; then # Export the value of host here launch_docker_registry fi ######################################### # Launch Che natively as a tomcat server call_catalina } stop_che_server () { CHE_SERVER_ACTION="stop" echo -e "Stopping Che server running on localhost:${CHE_PORT}" call_catalina >/dev/null 2>&1 } call_catalina () { # Test to see that Che application server is where we expect it to be if [ ! -d "${ASSEMBLY_BIN_DIR}" ]; then error "Could not find Che's application server." return 1; fi ### Initialize default JVM arguments to run che if [[ "${CHE_BLOCKING_ENTROPY}" == true ]]; then [ -z "${JAVA_OPTS}" ] && JAVA_OPTS="-Xms256m -Xmx1024m" else [ -z "${JAVA_OPTS}" ] && JAVA_OPTS="-Xms256m -Xmx1024m -Djava.security.egd=file:/dev/./urandom" fi ### Cannot add this in setenv.sh. ### We do the port mapping here, and this gets inserted into server.xml when tomcat boots export JAVA_OPTS="${JAVA_OPTS} -Dport.http=${CHE_PORT} -Dche.home=${CHE_HOME}" export SERVER_PORT=${CHE_PORT} # Launch the Che application server, passing in command line parameters if [[ "${CHE_DEBUG_SERVER}" == true ]]; then "${ASSEMBLY_BIN_DIR}"/catalina.sh jpda ${CHE_SERVER_ACTION} else "${ASSEMBLY_BIN_DIR}"/catalina.sh ${CHE_SERVER_ACTION} fi } kill_and_launch_docker_registry () { echo -e "Launching Docker container named ${GREEN}registry${NC} from image ${GREEN}registry:2${NC}." docker_exec rm -f registry &> /dev/null || true docker_exec run -d -p 5000:5000 --restart=always --name registry registry:2 } launch_docker_registry () { echo "Launching a Docker registry for workspace snapshots." CREATE_NEW_CONTAINER=false # Check to see if the registry docker was not properly shut down docker_exec inspect registry &> /dev/null || DOCKER_INSPECT_EXIT=$? || true if [ "${DOCKER_INSPECT_EXIT}" != "1" ]; then # Existing container running registry is found. Let's start it. echo -e "Found a registry container named ${GREEN}registry${NC}. Attempting restart." docker_exec start registry &>/dev/null || DOCKER_EXIT=$? || true # Existing container found, but could not start it properly. if [ "${DOCKER_EXIT}" == "1" ]; then echo "Initial start of registry docker container failed... Attempting docker restart and exec." CREATE_NEW_CONTAINER=true fi echo "Successful restart of registry container." echo # No existing Che container found, we need to create a new one. else CREATE_NEW_CONTAINER=true fi if ${CREATE_NEW_CONTAINER} ; then # Container in bad state or not found, kill and launch new container. kill_and_launch_docker_registry fi } perform_database_migration() { CHE_DATA=/data if [ -f ${CHE_DATA}/db/che.mv.db ]; then echo "!!! Detected Che database, that is stored by an old path: ${CHE_DATA}/db/che.mv.db" echo "!!! In case if you want to use it, move it manually to the new path ${CHE_DATA}/storage/db/che.mv.db" echo "!!! It will be moved there automatically, if no database is present by the new path" if [ ! -f ${CHE_DATA}/storage/db/che.mv.db ]; then mkdir -p ${CHE_DATA}/storage/db mv ${CHE_DATA}/db/che.mv.db ${CHE_DATA}/storage/db/che.mv.db echo "Database has been successfully moved to the new path" fi fi } init() { ### Any variables with export is a value that native Tomcat che.sh startup script requires export CHE_IP=${CHE_IP} if [ -z "$CHE_HOME" ]; then if [ -f "/assembly/tomcat/bin/catalina.sh" ]; then export CHE_HOME="/assembly" echo "Found custom assembly in ${CHE_HOME}" else export CHE_HOME=$(echo /home/user/eclipse-che) echo "Using embedded assembly in ${CHE_HOME}." fi else export CHE_HOME=$(echo ${CHE_HOME}) echo "Using custom assembly from $CHE_HOME" fi ### We need to discover the host mount provided by the user for `/data` export CHE_DATA="/data" CHE_DATA_HOST=$(get_che_data_from_host) CHE_USER=${CHE_USER:-root} export CHE_USER=$CHE_USER if [ "$CHE_USER" != "root" ]; then if [ ! $(getent group docker) ]; then echo "!!!" echo "!!! Error: The docker group doesn't exist." echo "!!!" exit 1 fi export CHE_USER_ID=${CHE_USER} sudo chown -R ${CHE_USER} ${CHE_DATA} sudo chown -R ${CHE_USER} ${CHE_HOME} sudo chown -R ${CHE_USER} ${CHE_LOGS_DIR} fi [ -z "$CHE_DATABASE" ] && export CHE_DATABASE=${CHE_DATA}/storage perform_database_migration # CHE_DOCKER_IP_EXTERNAL must be set if you are in a VM. HOSTNAME=${CHE_DOCKER_IP_EXTERNAL:-$(get_docker_external_hostname)} if has_external_hostname; then # Internal property used by Che to set hostname. export CHE_DOCKER_IP_EXTERNAL=${HOSTNAME} fi ### Necessary to allow the container to write projects to the folder [ -z "$CHE_WORKSPACE_STORAGE__MASTER__PATH" ] && export CHE_WORKSPACE_STORAGE__MASTER__PATH=${CHE_DATA}/workspaces [ -z "$CHE_WORKSPACE_STORAGE" ] && export CHE_WORKSPACE_STORAGE="${CHE_DATA_HOST}/workspaces" [ -z "$CHE_WORKSPACE_STORAGE_CREATE_FOLDERS" ] && export CHE_WORKSPACE_STORAGE_CREATE_FOLDERS=false # Cleanup no longer in use stacks folder, accordance to a new loading policy. if [[ -d "${CHE_DATA}"/stacks ]];then rm -rf "${CHE_DATA}"/stacks fi # A che property, which names the Docker network used for che + ws to communicate if [ -z "$CHE_DOCKER_NETWORK" ]; then NETWORK_NAME="bridge" else NETWORK_NAME=$CHE_DOCKER_NETWORK fi export JAVA_OPTS="${JAVA_OPTS} -Dche.docker.network=$NETWORK_NAME" } add_cert_to_truststore() { DEFAULT_JAVA_TRUST_STORE=${JAVA_HOME}/lib/security/cacerts DEFAULT_JAVA_TRUST_STOREPASS="changeit" JAVA_TRUST_STORE=/home/user/cacerts SELF_SIGNED_CERT=/home/user/self-signed.crt MESSAGE="Found a custom cert. Adding it to java trust store $JAVA_TRUST_STORE" if [ ! -f "$JAVA_TRUST_STORE" ]; then echo "$MESSAGE based on $DEFAULT_JAVA_TRUST_STORE" cp $DEFAULT_JAVA_TRUST_STORE $JAVA_TRUST_STORE else echo "$MESSAGE" fi echo "$1" > $SELF_SIGNED_CERT # make sure that owner has permissions to write and other groups have permissions to read chmod 644 "$JAVA_TRUST_STORE" echo yes | keytool -keystore $JAVA_TRUST_STORE -importcert -alias "$2" -file $SELF_SIGNED_CERT -storepass $DEFAULT_JAVA_TRUST_STOREPASS > /dev/null # set read-only permissions on keystore file chmod 444 "$JAVA_TRUST_STORE" set_truststore_system_variables "$JAVA_TRUST_STORE" "$DEFAULT_JAVA_TRUST_STOREPASS" } set_truststore_system_variables() { KEYSTORE_PATH=$1 KEYSTORE_PASSWORD=$2 if [[ "$JAVA_OPTS" != *"-Djavax.net.ssl.trustStore"* && "$JAVA_OPTS" != *"-Djavax.net.ssl.trustStorePassword"* ]]; then export JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStore=$KEYSTORE_PATH -Djavax.net.ssl.trustStorePassword=$KEYSTORE_PASSWORD" fi } add_che_cert_to_truststore() { # Deprecated, CHE_SELF__SIGNED__CERT environment variable is not used after 7.105.0 if [ "${CHE_SELF__SIGNED__CERT}" != "" ]; then add_cert_to_truststore "${CHE_SELF__SIGNED__CERT}" "HOSTDOMAIN" fi CERT_PATH="/self-signed-cert/ca.crt" if [ -e $CERT_PATH ]; then add_cert_to_truststore "$(cat $CERT_PATH)" "HOSTDOMAIN" fi } add_public_certs_to_truststore() { JAVA_TRUST_STORE=/home/user/cacerts DEFAULT_JAVA_TRUST_STORE=${JAVA_HOME}/lib/security/cacerts DEFAULT_JAVA_TRUST_STOREPASS="changeit" if [ ! -f "$JAVA_TRUST_STORE" ]; then cp "$DEFAULT_JAVA_TRUST_STORE" "$JAVA_TRUST_STORE" fi chmod 644 "$JAVA_TRUST_STORE" CUSTOM_PUBLIC_CERTIFICATES="/public-certs" if [[ -d "$CUSTOM_PUBLIC_CERTIFICATES" && -n "$(find $CUSTOM_PUBLIC_CERTIFICATES -type f)" ]]; then FILES="$CUSTOM_PUBLIC_CERTIFICATES/*" for cert in $FILES do jks_import_ca_bundle "$cert" "$JAVA_TRUST_STORE" "$DEFAULT_JAVA_TRUST_STOREPASS" done fi chmod 444 "$JAVA_TRUST_STORE" set_truststore_system_variables "$JAVA_TRUST_STORE" "$DEFAULT_JAVA_TRUST_STOREPASS" } function jks_import_ca_bundle { CA_FILE=$1 KEYSTORE_PATH=$2 KEYSTORE_PASSWORD=$3 if [ ! -f "$CA_FILE" ]; then # CA bundle file doesn't exist, skip it echo "Failed to import CA certificates from ${CA_FILE}. File doesn't exist" return fi bundle_name=$(basename "$CA_FILE") certs_imported=0 cert_index=0 tmp_file=/tmp/cert.pem is_cert=false while IFS= read -r line; do if [ "$line" == "-----BEGIN CERTIFICATE-----" ]; then # Start copying a new certificate is_cert=true cert_index=$((cert_index+1)) # Reset destination file and add header line echo "$line" > ${tmp_file} elif [ "$line" == "-----END CERTIFICATE-----" ]; then # End of the certificate is reached, add it to trust store is_cert=false echo "$line" >> ${tmp_file} keytool -importcert -alias "${bundle_name}_${cert_index}" -keystore "$KEYSTORE_PATH" -file $tmp_file -storepass "$KEYSTORE_PASSWORD" -noprompt && \ certs_imported=$((certs_imported+1)) elif [ "$is_cert" == true ]; then # In the middle of a certificate, copy line to target file echo "$line" >> ${tmp_file} fi done < "$CA_FILE" echo "Imported ${certs_imported} certificates from ${CA_FILE}" # Clean up rm -f $tmp_file } get_che_data_from_host() { DEFAULT_DATA_HOST_PATH=/data CHE_SERVER_CONTAINER_ID=$(get_che_server_container_id) # If `docker inspect` fails $DEFAULT_DATA_HOST_PATH is returned echo $(docker inspect --format='{{(index .Volumes "/data")}}' $CHE_SERVER_CONTAINER_ID 2>/dev/null || echo $DEFAULT_DATA_HOST_PATH) } get_che_server_container_id() { # Returning `hostname` doesn't work when running Che on OpenShift/Kubernetes/Docker Cloud. # In these cases `hostname` correspond to the pod ID that is different from # the container ID echo $(basename "$(head /proc/1/cgroup || hostname)"); } is_docker_for_mac_or_windows() { if uname -r | grep -q 'linuxkit'; then return 0 elif uname -r | grep -q 'moby'; then return 0 else return 1 fi } get_docker_external_hostname() { if is_docker_for_mac_or_windows; then echo "localhost" else echo "" fi } has_external_hostname() { if [ "${HOSTNAME}" = "" ]; then return 1 else return 0 fi } # SITTERM / SIGINT responsible_shutdown() { echo "" echo "Received SIGTERM" stop_che_server & wait ${PID} exit; } set -e set +o posix # setup handlers # on callback, kill the last background process, which is `tail -f /dev/null` and execute the specified handler trap 'responsible_shutdown' SIGHUP SIGTERM SIGINT init init_global_variables set_environment_variables add_che_cert_to_truststore add_public_certs_to_truststore # run che start_che_server & PID=$! # See: http://veithen.github.io/2014/11/16/sigterm-propagation.html wait ${PID} wait ${PID} EXIT_STATUS=$? # wait forever while true do tail -f /dev/null & wait ${!} done ================================================ FILE: check_properties_description.sh ================================================ #!/bin/bash CHE_PROPERTIES_PATH="assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties" MULTIUSER_PROPERTIES_PATH="assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/multiuser.properties" HAS_DESCRIPTION=false cat $CHE_PROPERTIES_PATH $MULTIUSER_PROPERTIES_PATH | while read -r LINE do if [[ $LINE == '#'* ]]; then HAS_DESCRIPTION=true elif [[ -z $LINE ]]; then HAS_DESCRIPTION=false else if [[ $HAS_DESCRIPTION == false ]]; then echo "Property $LINE seems to be missing a description!" exit 1 fi HAS_DESCRIPTION=false fi done ================================================ FILE: core/che-core-api-core/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-core jar Che Core :: Commons :: API :: Core ${project.build.directory}/generated-sources/dto/ false aopalliance aopalliance com.google.code.gson gson com.google.guava guava com.google.inject guice com.google.inject.extensions guice-assistedinject jakarta.annotation jakarta.annotation-api jakarta.inject jakarta.inject-api jakarta.validation jakarta.validation-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-json org.eclipse.che.core che-core-commons-lang org.eclipse.che.core che-core-commons-schedule org.everrest everrest-core org.slf4j slf4j-api jakarta.servlet jakarta.servlet-api provided jakarta.websocket jakarta.websocket-api provided org.apache.tomcat tomcat-catalina provided org.apache.tomcat tomcat-coyote provided ch.qos.logback logback-classic test commons-fileupload commons-fileupload test io.rest-assured rest-assured test org.eclipse.che.core che-core-commons-test test org.everrest everrest-assured test org.hamcrest hamcrest test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} process-sources generate org.eclipse.che.core che-core-api-core ${project.version} org.eclipse.che.api.core.rest.shared.dto ${dto-generator-out-directory} org.eclipse.che.api.core.server.dto.DtoServerImpls server maven-compiler-plugin pre-compile generate-sources compile org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} nonLinux !unix org.apache.maven.plugins maven-surefire-plugin **/*Test.java org/eclipse/che/api/core/util/ProcessUtilTest.java org/eclipse/che/api/core/util/StandardLinuxShellTest.java **/it/*Test.java unix unix org.apache.maven.plugins maven-surefire-plugin **/it/*Test.java org/eclipse/che/api/core/util/ProcessUtilTest.java org/eclipse/che/api/core/util/StandardLinuxShellTest.java **/*Test.java integration false org.apache.maven.plugins maven-failsafe-plugin integration-test verify **/it/*Test.java ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/ApiException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; import org.eclipse.che.dto.server.DtoFactory; /** * Base class for all API errors. * * @author andrew00x */ @SuppressWarnings("serial") public class ApiException extends Exception { private final ServiceError serviceError; public ApiException(ServiceError serviceError) { super(serviceError.getMessage()); this.serviceError = serviceError; } public ApiException(String message) { super(message); this.serviceError = createError(message); } public ApiException(String message, Throwable cause) { super(message, cause); this.serviceError = createError(message); } public ApiException(Throwable cause) { super(cause); this.serviceError = createError(cause.getMessage()); } public ServiceError getServiceError() { return serviceError; } private ServiceError createError(String message) { return DtoFactory.getInstance().createDto(ServiceError.class).withMessage(message); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/AuthenticationException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; @SuppressWarnings("serial") public class AuthenticationException extends ApiException { /** * Response status if any exception occurs,
* Default value: 400 */ int responseStatus; public AuthenticationException() { this(400); } public AuthenticationException(String message, Throwable cause) { this(400, message, cause); } public AuthenticationException(String message) { this(400, message); } public AuthenticationException(Throwable cause) { this(400, cause); } public AuthenticationException(int responseStatus) { super("Authentication failed."); this.responseStatus = responseStatus; } public AuthenticationException(int responseStatus, String message, Throwable cause) { super(message, cause); this.responseStatus = responseStatus; } public AuthenticationException(int responseStatus, String message) { super(message); this.responseStatus = responseStatus; } public AuthenticationException(int responseStatus, Throwable cause) { super(cause); this.responseStatus = responseStatus; } public int getResponseStatus() { return responseStatus; } public void setResponseStatus(int responseStatus) { this.responseStatus = responseStatus; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/BadRequestException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; /** * A {@code BadRequestException} should be thrown when server receives invalid input parameter or * missed it. * *

Typically in REST API such errors are converted in HTTP response with status 400. * * @author Sergii Leschenko */ public class BadRequestException extends ApiException { public BadRequestException(String message) { super(message); } public BadRequestException(String message, Throwable cause) { super(message, cause); } public BadRequestException(ServiceError serviceError) { super(serviceError); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/ConflictException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; /** * A {@code ConflictException} is throws when operation could not be performed because of conflict * with prior state. For example, there is an existing resource prevents creation of a new one. * *

Typically in REST API such errors are converted in HTTP response with status 409. * * @author andrew00x */ @SuppressWarnings("serial") public class ConflictException extends ApiException { public ConflictException(String message) { super(message); } public ConflictException(ServiceError serviceError) { super(serviceError); } public ConflictException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/ErrorCodes.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; /** * Defines error codes that are used in exceptions, defined codes MUST NOT conflict, error codes * must be in range 10000-14999 inclusive. * * @author Igor Vinokur * @author Yevhenii Voevodin */ public final class ErrorCodes { public static final int LIMIT_EXCEEDED = 10000; private ErrorCodes() {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/ForbiddenException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; /** * A {@code ForbiddenException} is thrown when required operation is forbidden. For example, if * caller doesn't have rights or required operation is not allowed for some resource. * *

Typically in REST API such errors are converted in HTTP response with status 403. * * @author andrew00x */ @SuppressWarnings("serial") public class ForbiddenException extends ApiException { public ForbiddenException(String message) { super(message); } public ForbiddenException(ServiceError serviceError) { super(serviceError); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/NotFoundException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; /** * A {@code NotFoundException} is thrown if requested resource was not found. * *

Typically in REST API such errors are converted in HTTP response with status 404. * * @author andrew00x */ @SuppressWarnings("serial") public class NotFoundException extends ApiException { public NotFoundException(String message) { super(message); } public NotFoundException(ServiceError serviceError) { super(serviceError); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/Page.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.function.Function; /** * Defines paged result of data selection, it is rather dynamic data window than regular page, as it * refers to the specified selection region based on the items before and the page size, but not on * the page number and page size. * *

Examples: * *

Regular page.
* For page input arguments {@code skipItems = 3}, {@code pageSize = 3} and {@code totalCount = 10} * the page is normalized and will refer to the second page which is(item4, item5, item6): * *

 *     item1.                      <- previous page start      <- first page start
 *     item2.
 *     item3.                      <- previous page end        <- first page end
 *     item4. <- page start
 *     item5.
 *     item6. <- page end
 *     item7.                      <- next page start
 *     item8.
 *     item9.                      <- next page end
 *     item.10                                                  <- last page start/end
 * 
* *
    * Result page: *
  • Non-empty *
  • Contains 3 items(item4, item5, item6) *
  • Has the previous page(item1, item2, item3) *
  • Has the next page(item7, item8, item9) *
* *

Data window.
* For page input arguments {@code skipItems = 2}, {@code pageSize = 3} and {@code totalCount = 7} * the page will refer to the following data window: * *

 *     item1.                   <- first page start
 *     item2.
 *     item3. <- page start     <- first page end
 *     item4.
 *     item5. <- page end
 *     item6.
 *     item7.                    <- last page start/end
 * 
* *
    * Result page: *
  • Non-empty *
  • Contains 3 items(item3, item4, item5) *
  • Doesn't have the previous page(because page refers to the elements which are partially * present in the first page(item3) and the second page(item4, item5) *
  • Doesn't have the next page(the reason is the same to previous statement) *
* *

Note that all {@code Page} instances perform reference calculations based on the given {@code * itemsBefore} and {@code pageSize} values which means that implementor is responsible for * providing correct bounds and data management. * *

The instances of this class are NOT thread safe. * * @param the type of the page items * @author Yevhenii Voevodin */ public class Page { private final int pageSize; private final long itemsBefore; private final long totalCount; private final List items; /** * Creates a new page. * * @param items page items * @param itemsBefore items count before this page * @param pageSize page size * @param totalCount count of all the items * @throws NullPointerException when {@code items} collection is null * @throws IllegalArgumentException when {@code itemsBefore} is negative * @throws IllegalArgumentException when {@code pageSize} is non-positive * @throws IllegalArgumentException when {@code totalCount} is negative */ public Page(Collection items, long itemsBefore, int pageSize, long totalCount) { requireNonNull(items, "Required non-null items"); this.items = new ArrayList<>(items); checkArgument(itemsBefore >= 0, "Required non-negative value of items before"); this.itemsBefore = itemsBefore; checkArgument(pageSize > 0, "Required positive value of page size"); this.pageSize = pageSize; checkArgument(totalCount >= 0, "Required non-negative value of total items"); this.totalCount = totalCount; } /** Returns true whether this page doesn't contain items, returns false if it does. */ public boolean isEmpty() { return items.isEmpty(); } /** * Returns true when the current page has the next page, otherwise when the page is the last page * false will be returned. */ public boolean hasNextPage() { return getNumber() != -1 && itemsBefore + pageSize < totalCount; } /** * Returns true when this page has the previous page, otherwise when the page is the first page * false will be returned. */ public boolean hasPreviousPage() { return getNumber() != -1 && itemsBefore != 0; } /** * Returns a reference to the next page. * *

Note: This method was designed to be used in couple with {@link #hasNextPage()}. Returns * reference to the next page even when {@link #hasNextPage()} returns false. */ public PageRef getNextPageRef() { return new PageRef(itemsBefore + pageSize, pageSize); } /** * Returns a reference to the previous page. * *

Note: This method was designed to be used in couple with {@link #hasPreviousPage()}. Returns * reference to the first page when {@link #hasPreviousPage()} returns false. */ public PageRef getPreviousPageRef() { final long skipItems = itemsBefore <= pageSize ? 0 : itemsBefore - pageSize; return new PageRef(skipItems, pageSize); } /** Returns the reference to the last page. */ public PageRef getLastPageRef() { final long lastPageItems = totalCount % pageSize; if (lastPageItems == 0) { return new PageRef(totalCount <= pageSize ? 0 : totalCount - pageSize, pageSize); } return new PageRef(totalCount - lastPageItems, pageSize); } /** Returns the reference to the first page. */ public PageRef getFirstPageRef() { return new PageRef(0, pageSize); } /** * Returns the size of the current page. * *

Returned value is always positive and greater or equal to the value returned by the {@link * #getItemsCount()} method. */ public int getSize() { return pageSize; } /** * Returns page number starting from 1. * *

If the page is not regular page(it refers rather to the data window than to the certain * page(e.g. skip=2, pageSize=4)) then this method returns -1. */ public long getNumber() { if (itemsBefore % pageSize != 0) { return -1; } return itemsBefore / pageSize + 1; } /** * Returns the size of the page items, returned value may be equal to 0 when the page {@link * #isEmpty() is empty}, the values is the same to {@code page.getItems().size()}. */ public int getItemsCount() { return items.size(); } /** Returns the count of all the items. */ public long getTotalItemsCount() { return totalCount; } /** * Returns page items or an empty list when page doesn't contain items. * *

Note that returned instance is modifiable list and modification applied on that list will * affect the origin page result items, which allows components to modify items before propagating * page. */ public List getItems() { return items; } /** * Gets the page items and maps them with given {@code mapper}. * * @param mapper items mapper * @param the type of the result items * @return the list of mapped items */ public List getItems(Function mapper) { requireNonNull(mapper, "Required non-null mapper for page items"); return items.stream().map(mapper::apply).collect(toList()); } /** * Fills the given collection with page items. This method may be convenient when needed * collection different from the {@link List}. * *

The common example: * *

{@code
   * Set user = page.fill(new TreeSet<>(comparator));
   * }
* *

Note that this method uses {@code container.addAll(items)} so be aware of putting modifiable * collection. * * @param container collection which is used to fill result into * @param collection type * @return given collection instance {@code container} with items filled * @throws NullPointerException when {@code container} is null */ public > COL_T fill(COL_T container) { requireNonNull(container, "Required non-null items container"); container.addAll(items); return container; } /** Represents page reference as a combination of {@code skipItems & pageSize}. */ public static class PageRef { private final long skipItems; private final int pageSize; private PageRef(long skipItems, int pageSize) { this.skipItems = skipItems; this.pageSize = pageSize; } public long getItemsBefore() { return skipItems; } public int getPageSize() { return pageSize; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof PageRef)) { return false; } final PageRef that = (PageRef) obj; return skipItems == that.skipItems && pageSize == that.pageSize; } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Long.hashCode(skipItems); hash = 31 * hash + pageSize; return hash; } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/Pages.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.eclipse.che.api.core.Page.PageRef; /** * Static utility methods to interact with page suppliers. * * @author Yevhenii Voevodin */ public final class Pages { /** An experimental value used as default page size where necessary. */ public static final int DEFAULT_PAGE_SIZE = 50; /** * Defines an interface for page supplier. * * @param the type of the element held by page * @param the type of exception thrown by page supplier */ @FunctionalInterface public interface PageSupplier { /** * Gets a single page. * * @param maxItems max items to retrieve * @param skipCount how many elements to skip * @return page * @throws X exception thrown by supplier */ Page getPage(int maxItems, long skipCount) throws X; } /** * Eagerly fetches all the elements page by page and returns a stream of them. * * @param supplier page supplier * @param size how many items to retrieve per page * @param the type of the element held by page * @param the type of exception thrown by page supplier * @return stream of fetched elements * @throws X when supplier throws exception */ public static Stream stream(PageSupplier supplier, int size) throws X { return eagerFetch(supplier, size).stream(); } /** * Fetches elements like {@link #stream(PageSupplier, int)} method does using default page size * which is equal to {@value #DEFAULT_PAGE_SIZE}. */ public static Stream stream(PageSupplier supplier) throws X { return stream(supplier, DEFAULT_PAGE_SIZE); } /** * Eagerly fetches all the elements page by page and returns an iterable of them. * * @param supplier pages supplier * @param size how many items to retrieve per page * @param the type of the element held by page * @param the type of exception thrown by page supplier * @return iterable of fetched elements * @throws X when supplier throws exception */ public static Iterable iterate(PageSupplier supplier, int size) throws X { return eagerFetch(supplier, size); } /** * Fetches elements like {@link #iterate(PageSupplier, int)} method does using default page size * which is equal to {@value #DEFAULT_PAGE_SIZE}. */ public static Iterable iterate(PageSupplier supplier) throws X { return iterate(supplier, DEFAULT_PAGE_SIZE); } /** * Returns a stream which is based on lazy fetching paged iterator. * * @param supplier pages supplier * @param size how many items to retrieve per page * @param the type of the element held by page * @param exception thrown by supplier * @return stream of elements * @throws RuntimeException wraps any exception occurred during pages fetch */ public static Stream streamLazily( PageSupplier supplier, int size) { return StreamSupport.stream(new PagedIterable<>(supplier, size).spliterator(), false); } /** * Fetches elements like {@link #streamLazily(PageSupplier, int)} method does using default page * size which is equal to {@value #DEFAULT_PAGE_SIZE}. */ public static Stream streamLazily(PageSupplier supplier) { return streamLazily(supplier, DEFAULT_PAGE_SIZE); } /** * Returns an iterable which iterator lazily fetches page by page, doesn't poll the next page * until the last item from previous page is not processed. The first page is polled while * iterable is created. * * @param supplier pages supplier * @param size how many items to retrieve per page * @param the type of the element held by page * @param the type of exception thrown by page supplier * @return stream of elements * @throws RuntimeException wraps any exception occurred during pages fetch */ public static Iterable iterateLazily( PageSupplier supplier, int size) { return new PagedIterable<>(supplier, size); } /** * Returns an iterable like {@link #streamLazily(PageSupplier, int)} method does using default * page size which is equal to {@value #DEFAULT_PAGE_SIZE}. */ public static Iterable iterateLazily(PageSupplier supplier) { return iterateLazily(supplier, DEFAULT_PAGE_SIZE); } private static List eagerFetch(PageSupplier supplier, int size) throws X { Page page = supplier.getPage(size, 0); ArrayList container = new ArrayList<>(page.hasNextPage() ? page.getItemsCount() * 2 : page.getItemsCount()); while (page.hasNextPage()) { container.addAll(page.getItems()); PageRef next = page.getNextPageRef(); page = supplier.getPage(next.getPageSize(), next.getItemsBefore()); } container.addAll(page.getItems()); return container; } private static class PagedIterable implements Iterable { private final PageSupplier supplier; private final int size; private PagedIterable(PageSupplier supplier, int size) { this.supplier = supplier; this.size = size; } @Override public Iterator iterator() { return new PagedIterator<>(supplier, size); } } private static class PagedIterator implements Iterator { private final PageSupplier supplier; private final int size; private Page page; private Iterator delegate; private PagedIterator(PageSupplier supplier, int size) { this.supplier = supplier; this.size = size; fetchPage(0); } @Override public boolean hasNext() { if (delegate.hasNext()) { return true; } if (!page.hasNextPage()) { return false; } fetchPage(page.getNextPageRef().getItemsBefore()); return delegate.hasNext(); } @Override public E next() { if (!hasNext()) { throw new NoSuchElementException(); } return delegate.next(); } private void fetchPage(long skip) { try { page = supplier.getPage(size, skip); delegate = page.getItems().iterator(); } catch (Exception x) { throw new RuntimeException(x.getMessage(), x); } } } private Pages() {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/ServerException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; /** * A {@code ServerException} is thrown as a result of an error that internal server error. * *

Typically in REST API such errors are converted in HTTP response with status 500. * * @author andrew00x */ @SuppressWarnings("serial") public class ServerException extends ApiException { public ServerException(String message) { super(message); } public ServerException(ServiceError serviceError) { super(serviceError); } public ServerException(Throwable cause) { super(cause); } public ServerException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/UnauthorizedException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; import static org.eclipse.che.dto.server.DtoFactory.newDto; import java.util.Collections; import java.util.Map; import org.eclipse.che.api.core.rest.shared.dto.ExtendedError; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; /** * A {@code UnauthorizedException} is thrown when caller isn't authorized to access some resource. * *

Typically in REST API such errors are converted in HTTP response with status 401. * * @author andrew00x */ @SuppressWarnings("serial") public class UnauthorizedException extends ApiException { public UnauthorizedException(String message) { super(message); } public UnauthorizedException(ServiceError serviceError) { super(serviceError); } public UnauthorizedException(String message, int errorCode, Map attributes) { super( newDto(ExtendedError.class) .withMessage(message) .withErrorCode(errorCode) .withAttributes(attributes)); } public UnauthorizedException(String message, int errorCode) { this(message, errorCode, Collections.emptyMap()); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/ValidationException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; /** * Might be thrown by those system components that validate objects state before performing * operations with them so all conditions are met and the system is in consistent state. * * @author Yevhenii Voevodin */ public class ValidationException extends Exception { public ValidationException(String message) { super(message); } public ValidationException(String message, Throwable cause) { super(message, cause); } /** * Creates an exception with a formatted message. Please follow {@link String#format(String, * Object...)} formatting patterns. */ public ValidationException(String fmt, Object... args) { this(String.format(fmt, args)); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/cors/CheCorsFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.cors; import com.google.inject.Singleton; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import java.io.IOException; import javax.inject.Inject; import org.apache.catalina.filters.CorsFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The special filter which provides filtering requests in according to settings which are set to * {@link CorsFilter}. Uses {@link CheCorsFilterConfig} for providing configuration. * * @author Dmitry Shnurenko * @author Mykhailo Kuznietsov */ @Singleton public class CheCorsFilter implements Filter { private static final Logger LOG = LoggerFactory.getLogger(CheCorsFilter.class); private CorsFilter corsFilter; @Inject private CheCorsFilterConfig cheCorsFilterConfig; @Override public void init(FilterConfig filterConfig) throws ServletException { corsFilter = new CorsFilter(); corsFilter.init(cheCorsFilterConfig); LOG.debug( "CORS initialized with parameters: 'cors.support.credentials': '{}', 'cors.allowed.origins': '{}'", cheCorsFilterConfig.getInitParameter("cors.support.credentials"), cheCorsFilterConfig.getInitParameter("cors.allowed.origins")); } @Override public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { corsFilter.doFilter(servletRequest, servletResponse, filterChain); } @Override public void destroy() { corsFilter.destroy(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/cors/CheCorsFilterConfig.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.cors; import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_ALLOWED_HEADERS; import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_ALLOWED_METHODS; import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_ALLOWED_ORIGINS; import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_EXPOSED_HEADERS; import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_PREFLIGHT_MAXAGE; import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_SUPPORT_CREDENTIALS; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletContext; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; /** * Basic configuration for {@link CheCorsFilter}. Allowed origings and credentials support are * configurable through properties. * * @author Mykhailo Kuznietsov */ public class CheCorsFilterConfig implements FilterConfig { private final Map filterParams; @Inject public CheCorsFilterConfig( @Named("che.cors.allow_credentials") Boolean allowCredentials, @Named("che.cors.allowed_origins") String allowedOrigins) { filterParams = new HashMap<>(); filterParams.put(PARAM_CORS_ALLOWED_ORIGINS, allowedOrigins); filterParams.put( PARAM_CORS_ALLOWED_METHODS, "GET," + "POST," + "HEAD," + "OPTIONS," + "PUT," + "DELETE"); filterParams.put( PARAM_CORS_ALLOWED_HEADERS, "Content-Type," + "X-Requested-With," + "X-Oauth-Token," + "accept," + "Origin," + "Authorization," + "Access-Control-Request-Method," + "Access-Control-Request-Headers"); filterParams.put(PARAM_CORS_EXPOSED_HEADERS, "JAXRS-Body-Provided"); filterParams.put(PARAM_CORS_SUPPORT_CREDENTIALS, String.valueOf(allowCredentials)); // preflight cache is available for 10 minutes filterParams.put(PARAM_CORS_PREFLIGHT_MAXAGE, "10"); } @Override public String getFilterName() { return CheCorsFilter.class.getName(); } @Override public ServletContext getServletContext() { throw new UnsupportedOperationException( "The method is not supported in " + CheCorsFilter.class); } @Override public String getInitParameter(String key) { return filterParams.get(key); } @Override public Enumeration getInitParameterNames() { throw new UnsupportedOperationException( "The method is not supported in " + CheCorsFilter.class); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/factory/FactoryParameter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.factory; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Provide factory parameter compatibility options. * * @author Alexander Garagatyi */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface FactoryParameter { enum Obligation { MANDATORY, OPTIONAL } enum Version { // NEVER must be the last defined constant V4_0, NEVER; public static Version fromString(String v) { if (null != v) { switch (v) { case "4.0": return V4_0; } } throw new IllegalArgumentException("Unknown version " + v + "."); } @Override public String toString() { return super.name().substring(1).replace('_', '.'); } } Obligation obligation(); boolean setByServer() default false; boolean trackedOnly() default false; Version deprecatedSince() default Version.NEVER; Version ignoredSince() default Version.NEVER; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/ClientSubscriptionHandler.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import static com.google.common.collect.Sets.newConcurrentHashSet; import com.google.inject.Singleton; import java.util.Set; import javax.inject.Inject; /** A mechanism for handling subscription from the client and registered its endpointId. */ @Singleton public class ClientSubscriptionHandler { public static final String CLIENT_SUBSCRIBE_METHOD_NAME = "client/subscribe"; public static final String CLIENT_UNSUBSCRIBE_METHOD_NAME = "client/unsubscribe"; private final Set endpointIds = newConcurrentHashSet(); @Inject private void configureHandlers(RequestHandlerConfigurator configurator) { configurator .newConfiguration() .methodName(CLIENT_SUBSCRIBE_METHOD_NAME) .noParams() .noResult() .withConsumer(endpointIds::add); configurator .newConfiguration() .methodName(CLIENT_UNSUBSCRIBE_METHOD_NAME) .noParams() .noResult() .withConsumer(endpointIds::remove); } /** returns set of endpoint ids of all registered clients. */ public Set getEndpointIds() { return endpointIds; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcComposer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import java.util.List; /** * Composes objects or list of objects out of JSON RPC entities (like params or results). The * resulting objects can be of the following types: {@link String}, {@link Boolean}, {@link Double} * or DTO. */ public interface JsonRpcComposer { /** * Composes an object out of JSON RPC params entity. The resulting object can be of the following * types: {@link String}, {@link Boolean}, {@link Double} or DTO. * * @param params JSON RPC params * @param type type of resulting object * @param generic type of resulting object * @return object of class T */ T composeOne(JsonRpcParams params, Class type); /** * Composes a list of objects out of JSON RPC params entity. The resulting objects can be of the * following types: {@link String}, {@link Boolean}, {@link Double} or DTO. * * @param params JSON RPC params * @param type type of resulting objects * @param generic type of resulting objects * @return list of objects of class T */ List composeMany(JsonRpcParams params, Class type); /** * Composes an object out of JSON RPC result entity. The resulting object can be of the following * types: {@link String}, {@link Boolean}, {@link Double} or DTO. * * @param result JSON RPC result * @param type type of resulting object * @param generic type of resulting object * @return object of class T */ T composeOne(JsonRpcResult result, Class type); /** * Composes a list of objects out of JSON RPC result entity. The resulting objects can be of the * following types: {@link String}, {@link Boolean}, {@link Double} or DTO. * * @param result JSON RPC params * @param type type of resulting objects * @param generic type of resulting objects * @return list of objects of class T */ List composeMany(JsonRpcResult result, Class type); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcError.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import jakarta.validation.constraints.NotNull; /** Represents JSON RPC error object */ public class JsonRpcError { private final int code; private final String message; public JsonRpcError(int code, @NotNull String message) { this.code = code; this.message = message; } public String getMessage() { return message; } public int getCode() { return code; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcErrorTransmitter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageTransmitter; import org.slf4j.Logger; /** Transmits an instance of {@link JsonRpcException} to an endpoint */ @Singleton public class JsonRpcErrorTransmitter { private static final Logger LOGGER = getLogger(JsonRpcErrorTransmitter.class); private final WebSocketMessageTransmitter transmitter; private final JsonRpcMarshaller marshaller; @Inject public JsonRpcErrorTransmitter( WebSocketMessageTransmitter transmitter, JsonRpcMarshaller marshaller) { this.transmitter = transmitter; this.marshaller = marshaller; } public void transmit(String endpointId, JsonRpcException e) { checkNotNull(endpointId, "Endpoint ID must not be null"); checkArgument(!endpointId.isEmpty(), "Endpoint ID must not be empty"); LOGGER.debug("Transmitting a JSON RPC error: " + e.getMessage()); JsonRpcError error = new JsonRpcError(e.getCode(), e.getMessage() == null ? "Unexpected error" : e.getMessage()); JsonRpcResponse response = new JsonRpcResponse(e.getId(), null, error); String message = marshaller.marshall(response); transmitter.transmit(endpointId, message); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; /** * Specific to JSON RPC exception that is to be raised when any JSON RPC related error is met. * According to the spec there should be an error code and an error message. */ public class JsonRpcException extends RuntimeException { private final int code; private final String id; public JsonRpcException(int code, String message) { this(code, message, null); } JsonRpcException(int code, String message, String id) { super(message); this.code = code; this.id = id; } public int getCode() { return code; } public String getId() { return id; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcMarshaller.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; /** * Marshals outgoing JSON RPC requests and responses to a string representation, used to separate * the business logic and the platform/parser specific logic that is responsible for * parsing/composing json rpc entities. */ public interface JsonRpcMarshaller { /** * Serializes JSON RPC response object into a string * * @param response response * @return string representation */ String marshall(JsonRpcResponse response); /** * Serializes JSON RPC request object into a string * * @param request request * @return string representation */ String marshall(JsonRpcRequest request); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcMessageReceiver.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.eclipse.che.api.core.websocket.impl.WebsocketIdService.SEPARATOR; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageReceiver; import org.slf4j.Logger; /** * Receives and process messages coming from web socket service. Basically it validates, qualifies * and transforms a raw web socket message to a JSON RPC known structure and pass it further to * appropriate dispatchers. In case of any {@link JsonRpcException} happens during request/response * processing this class is also responsible for an error transmission. */ @Singleton public class JsonRpcMessageReceiver implements WebSocketMessageReceiver { private static final Logger LOGGER = getLogger(JsonRpcMessageReceiver.class); private final RequestDispatcher requestDispatcher; private final ResponseDispatcher responseDispatcher; private final JsonRpcErrorTransmitter errorTransmitter; private final JsonRpcQualifier jsonRpcQualifier; private final JsonRpcUnmarshaller jsonRpcUnmarshaller; private final RequestProcessor requestProcessor; @Inject public JsonRpcMessageReceiver( RequestDispatcher requestDispatcher, ResponseDispatcher responseDispatcher, JsonRpcErrorTransmitter errorTransmitter, JsonRpcQualifier jsonRpcQualifier, JsonRpcUnmarshaller jsonRpcUnmarshaller, RequestProcessor requestProcessor) { this.requestDispatcher = requestDispatcher; this.responseDispatcher = responseDispatcher; this.errorTransmitter = errorTransmitter; this.jsonRpcQualifier = jsonRpcQualifier; this.jsonRpcUnmarshaller = jsonRpcUnmarshaller; this.requestProcessor = requestProcessor; } @Override public void receive(String combinedEndpointId, String message) { checkNotNull(combinedEndpointId, "Endpoint ID must not be null"); checkArgument(!combinedEndpointId.isEmpty(), "Endpoint ID name must not be empty"); checkNotNull(message, "Message must not be null"); checkArgument(!message.isEmpty(), "Message must not be empty"); LOGGER.trace("Receiving message: {}, from endpoint: {}", message, combinedEndpointId); if (!jsonRpcQualifier.isValidJson(message)) { String error = "An error occurred on the server while parsing the JSON text"; errorTransmitter.transmit(combinedEndpointId, new JsonRpcException(-32700, error)); } List messages = jsonRpcUnmarshaller.unmarshalArray(message); for (String innerMessage : messages) { if (jsonRpcQualifier.isJsonRpcRequest(innerMessage)) { String endpointId = combinedEndpointId.split(SEPARATOR)[1]; ProcessRequestTask task = new ProcessRequestTask(combinedEndpointId, innerMessage); requestProcessor.process(endpointId, task); } else if (jsonRpcQualifier.isJsonRpcResponse(innerMessage)) { processResponse(combinedEndpointId, innerMessage); } else { processError(); } } } private void processError() { String error = "Something wen't wrong during incoming websocket message parsing"; IllegalStateException exception = new IllegalStateException(error); LOGGER.error(error, exception); throw exception; } private void processResponse(String endpointId, String innerMessage) { JsonRpcResponse response = jsonRpcUnmarshaller.unmarshalResponse(innerMessage); responseDispatcher.dispatch(endpointId, response); } private class ProcessRequestTask implements Runnable { private final String endpointId; private final String innerMessage; public ProcessRequestTask(String endpointId, String innerMessage) { this.endpointId = endpointId; this.innerMessage = innerMessage; } @Override public void run() { JsonRpcRequest request = null; try { request = jsonRpcUnmarshaller.unmarshalRequest(innerMessage); requestDispatcher.dispatch(endpointId, request); } catch (JsonRpcException e) { if (request == null || request.getId() == null) { errorTransmitter.transmit(endpointId, e); } else { errorTransmitter.transmit( endpointId, new JsonRpcException(e.getCode(), e.getMessage(), request.getId())); } } } @Override public String toString() { return "JsonRPC request `" + innerMessage + "` for " + endpointId; } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcMethodInvokerFilter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; /** * Is used to check whether Json Rpc method can be invoked. For example, can be used to check * permission before method invocation. * * @author Sergii Leshchenko */ public interface JsonRpcMethodInvokerFilter { /** * Check whether supplied method can be invoked. * * @param method method name that is going to be invoked * @param params actual method parameters * @throws JsonRpcException if method can not be invoked cause current environment context, e.g. * for current user, with current request attributes, etc. JsonRpcException should contain the * corresponding message and code. */ void accept(String method, Object... params) throws JsonRpcException; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcParams.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import static java.util.Collections.singletonList; import java.util.List; /** Represents JSON RPC params object */ public class JsonRpcParams { private List params; private boolean single; public JsonRpcParams(Object params) { this.params = singletonList(params); this.single = true; } public JsonRpcParams(List params) { this.params = params; this.single = false; } public boolean isSingle() { return single; } public List getMany() { return params; } public Object getOne() { return params.get(0); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcPromise.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Consumer; /** * Simple promise like binary consumer holder. First consumer's argument always represents endpoint * identifier, while the second can be of arbitrary type and depends on business logic. * * @param type of second argument of binary consumer */ public class JsonRpcPromise { private BiConsumer successConsumer; private BiConsumer failureConsumer; private Runnable timeoutRunnable; public Optional> getSuccessConsumer() { return Optional.ofNullable(successConsumer); } public Optional> getFailureConsumer() { return Optional.ofNullable(failureConsumer); } Optional getTimeoutRunnable() { return Optional.ofNullable(timeoutRunnable); } /** * Set timeout runnable to be called on this promise timeout. * * @param runnable timeout runnable * @return the instance of this very promise */ public JsonRpcPromise onTimeout(Runnable runnable) { checkNotNull(runnable, "JSON RPC timeout runnable argument must not be null"); checkState(this.timeoutRunnable == null, "JSON RPC timeout runnable field must not be set"); this.timeoutRunnable = runnable; return this; } /** * Set binary consumer to be called on this promise resolution. The first consumer argument is an * endpoint that the responses comes from while the second consumer argument is actually the value * of response result. * * @param biConsumer binary consumer * @return the instance of this very promise */ public JsonRpcPromise onSuccess(BiConsumer biConsumer) { checkNotNull(biConsumer, "JSON RPC success consumer argument must not be null"); checkState(this.successConsumer == null, "JSON RPC success field must not be set"); this.successConsumer = biConsumer; return this; } /** * Set consumer to be called on this promise resolution. Ths variant of promise configuration must * be used only when you need not the value of endpoint ID. The only consumer argument is the * value of response result. * * @param consumer consumer * @return the instance of this very promise */ public JsonRpcPromise onSuccess(Consumer consumer) { checkNotNull(consumer, "JSON RPC success consumer argument must not be null"); checkState(this.successConsumer == null, "JSON RPC success consumer field must not be set"); this.successConsumer = (s, r) -> consumer.accept(r); return this; } /** * Set runnable to be called on this promise resolution. Ths variant of promise configuration must * be used only when you need not both the value of endpoint ID and the value of response result. * * @param runnable runnable * @return the instance of this very promise */ public JsonRpcPromise onSuccess(Runnable runnable) { checkNotNull(runnable, "JSON RPC success runnable argument must not be null"); checkState(this.successConsumer == null, "JSON RPC success field must not be set"); this.successConsumer = (s, r) -> runnable.run(); return this; } /** * Set binary consumer to be called on promise rejection. The first consumer argument is an * endpoint that the responses comes from while the second consumer argument is actually the value * of response error. * * @param biConsumer binary consumer * @return the instance of this very promise */ public JsonRpcPromise onFailure(BiConsumer biConsumer) { checkNotNull(biConsumer, "JSON RPC failure consumer argument must not be null"); checkState(this.failureConsumer == null, "JSON RPC failure consumer field must not be set"); this.failureConsumer = biConsumer; return this; } /** * Set consumer to be called on this promise rejection. Ths variant of promise configuration must * be used only when you need not the value of endpoint ID. The only consumer argument is the * value of response result. * * @param consumer consumer * @return the instance of this very promise */ public JsonRpcPromise onFailure(Consumer consumer) { checkNotNull(consumer, "JSON RPC failure consumer argument must not be null"); checkState(this.failureConsumer == null, "JSON RPC failure consumer field must not be set"); this.failureConsumer = (s, e) -> consumer.accept(e); return this; } /** * Set runnable to be called on this promise rejection. Ths variant of promise configuration must * be used only when you need not both the value of endpoint ID and the value of response error. * * @param runnable runnable * @return the instance of this very promise */ public JsonRpcPromise onFailure(Runnable runnable) { checkNotNull(runnable, "JSON RPC success runnable argument must not be null"); checkState(this.successConsumer == null, "JSON RPC success field must not be set"); this.successConsumer = (s, e) -> runnable.run(); return this; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcQualifier.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; public interface JsonRpcQualifier { boolean isValidJson(String message); boolean isJsonRpcRequest(String message); boolean isJsonRpcResponse(String message); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcRequest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; /** Represents JSON RPC request object */ public class JsonRpcRequest { private final String id; private final String method; private final JsonRpcParams params; public JsonRpcRequest(String id, String method, JsonRpcParams params) { this.id = id; this.method = method; this.params = params; } public boolean hasParams() { return params != null; } public boolean hasId() { return id != null; } public String getMethod() { return method; } public String getId() { return id; } public JsonRpcParams getParams() { return params; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcResponse.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; /** * Represents JSON RPC response object. Can be constructed out of stringified json object or by * passing specific parameters. */ public class JsonRpcResponse { private final String id; private final JsonRpcResult result; private final JsonRpcError error; public JsonRpcResponse(String id, JsonRpcResult result, JsonRpcError error) { this.id = id; this.result = result; this.error = error; } public boolean hasId() { return id != null; } public boolean hasError() { return error != null; } public boolean hasResult() { return result != null; } public JsonRpcError getError() { return error; } public String getId() { return id; } public JsonRpcResult getResult() { return result; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcResult.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import static java.util.Collections.singletonList; import java.util.List; /** Represents JSON RPC result object */ public class JsonRpcResult { private List result; private boolean single; public JsonRpcResult(Object result) { this.result = singletonList(result); this.single = true; } public JsonRpcResult(List result) { this.result = result; this.single = false; } public boolean isSingle() { return single; } public List getMany() { return result; } public Object getOne() { return result.get(0); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcUnmarshaller.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import java.util.List; /** Transforms plain text messages into JSON RPC structures. */ public interface JsonRpcUnmarshaller { /** * Creates an array of stringified JSON RPC structures, which can further be unmarshalled * separately. * * @param message incoming message * @return array of serialized JSON RPC */ List unmarshalArray(String message); /** * Creates a request out of a plain text message * * @param message plain text message * @return JSON RPC request entity */ JsonRpcRequest unmarshalRequest(String message); /** * Creates a response out of a plain text message * * @param message plain text message * @return JSON RPC response entity */ JsonRpcResponse unmarshalResponse(String message); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcUtils.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; /** Simple utility class */ public class JsonRpcUtils { @SuppressWarnings("unchecked") public static T cast(Object object) { return (T) object; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/NotificationHandler.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; /** * Handler corresponding to processing JSON RPC requests that have no ID specified (notifications). */ public interface NotificationHandler { void handle(String endpointId, JsonRpcParams params) throws JsonRpcException; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/RequestDispatcher.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import javax.inject.Inject; import javax.inject.Singleton; import org.slf4j.Logger; /** * Dispatches incoming JSON RPC requests and notifications. If during dispatching happens any kind * of error related to JSON RPC it throws appropriate exception {@link JsonRpcException}. */ @Singleton public class RequestDispatcher { private static final Logger LOGGER = getLogger(RequestDispatcher.class); private final RequestHandlerManager requestHandlerManager; @Inject public RequestDispatcher(RequestHandlerManager requestHandlerManager) { this.requestHandlerManager = requestHandlerManager; } public void dispatch(String endpointId, JsonRpcRequest request) throws JsonRpcException { checkNotNull(endpointId, "Endpoint ID must not be null"); checkArgument(!endpointId.isEmpty(), "Endpoint ID must not be empty"); checkNotNull(request, "Request must not be null"); LOGGER.trace("Dispatching request: " + request + ", endpoint: " + endpointId); String method = request.getMethod(); JsonRpcParams params = request.getParams(); if (request.hasId()) { LOGGER.trace("Request has ID"); String requestId = request.getId(); checkRequestHandlerRegistration(method, requestId); requestHandlerManager.handle(endpointId, requestId, method, params); } else { LOGGER.trace("Request has no ID -> it is a notification"); checkNotificationHandlerRegistration(method); requestHandlerManager.handle(endpointId, method, params); } } private void checkNotificationHandlerRegistration(String method) throws JsonRpcException { if (!requestHandlerManager.isRegistered(method)) { LOGGER.error("No corresponding to method '" + method + "' handler is registered"); throw new JsonRpcException(-32601, "Method '" + method + "' not registered"); } } private void checkRequestHandlerRegistration(String method, String requestId) throws JsonRpcException { if (!requestHandlerManager.isRegistered(method)) { LOGGER.error("No corresponding to method '" + method + "' handler is registered"); throw new JsonRpcException(-32601, "Method '" + method + "' not registered", requestId); } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/RequestHandler.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; /** Handler corresponding to processing JSON RPC requests. */ public interface RequestHandler { JsonRpcResult handle(String endpointId, JsonRpcParams params) throws JsonRpcException; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/RequestHandlerConfigurator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import org.eclipse.che.api.core.jsonrpc.commons.reception.MethodNameConfigurator; /** Factory to provide facilities for dynamic configuring request handlers. */ public interface RequestHandlerConfigurator { MethodNameConfigurator newConfiguration(); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/RequestHandlerManager.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import static org.slf4j.LoggerFactory.getLogger; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageTransmitter; import org.slf4j.Logger; /** * Manages request handlers. There are nine types of such handlers that differs by the type and * number of incoming parameters and outgoing results: * *

    *
  • {@link NoneToNoneHandler} - to receive a notification w/o parameters *
  • {@link NoneToOneHandler} - to receive a request w/o parameters and a single result *
  • {@link NoneToManyHandler} - to receive a request w/o parameters and multiple results *
  • {@link OneToNoneHandler} - to receive a notification with a single parameter *
  • {@link OneToOneHandler} - to receive a request with a single parameter and a single result *
  • {@link OneToManyHandler}- to receive a request with a single parameter and multiple results *
  • {@link ManyToNoneHandler} - to receive a notification with multiple parameters *
  • {@link ManyToOneHandler} - to receive request with multiple parameters and a single result *
  • {@link ManyToManyHandler} - to receive request with multiple parameters and multiple * results *
*/ @Singleton public class RequestHandlerManager { private static final Logger LOGGER = getLogger(RequestHandlerManager.class); private final Multimap filters = ArrayListMultimap.create(); private final Map methodToCategory = new ConcurrentHashMap<>(); private final Map oneToOneHandlers = new ConcurrentHashMap<>(); private final Map oneToPromiseOneHandlers = new ConcurrentHashMap<>(); private final Map oneToManyHandlers = new ConcurrentHashMap<>(); private final Map oneToNoneHandlers = new ConcurrentHashMap<>(); private final Map manyToOneHandlers = new ConcurrentHashMap<>(); private final Map manyToManyHandlers = new ConcurrentHashMap<>(); private final Map manyToNoneHandlers = new ConcurrentHashMap<>(); private final Map noneToOneHandlers = new ConcurrentHashMap<>(); private final Map noneToManyHandlers = new ConcurrentHashMap<>(); private final Map noneToNoneHandlers = new ConcurrentHashMap<>(); private final WebSocketMessageTransmitter transmitter; private final JsonRpcComposer dtoComposer; private final JsonRpcMarshaller marshaller; @Inject public RequestHandlerManager( WebSocketMessageTransmitter transmitter, JsonRpcComposer dtoComposer, JsonRpcMarshaller marshaller) { this.transmitter = transmitter; this.dtoComposer = dtoComposer; this.marshaller = marshaller; } public synchronized void registerOneToOne( String method, Class

pClass, Class rClass, BiFunction biFunction) { mustNotBeRegistered(method); methodToCategory.put(method, Category.ONE_TO_ONE); oneToOneHandlers.put(method, new OneToOneHandler<>(pClass, rClass, biFunction)); } public synchronized void registerMethodInvokerFilter( JsonRpcMethodInvokerFilter filter, String... methods) { for (String method : methods) { filters.put(method, filter); } } public synchronized void registerOneToPromiseOne( String method, Class

pClass, Class rClass, BiFunction> function) { mustNotBeRegistered(method); methodToCategory.put(method, Category.ONE_TO_PROMISE_ONE); oneToPromiseOneHandlers.put(method, new OneToPromiseOneHandler<>(pClass, rClass, function)); } public synchronized void registerOneToMany( String method, Class

pClass, Class rClass, BiFunction> biFunction) { mustNotBeRegistered(method); methodToCategory.put(method, Category.ONE_TO_MANY); oneToManyHandlers.put(method, new OneToManyHandler<>(pClass, rClass, biFunction)); } public synchronized

void registerOneToNone( String method, Class

pClass, BiConsumer biConsumer) { mustNotBeRegistered(method); methodToCategory.put(method, Category.ONE_TO_NONE); oneToNoneHandlers.put(method, new OneToNoneHandler<>(pClass, biConsumer)); } public synchronized void registerManyToOne( String method, Class

pClass, Class rClass, BiFunction, R> biFunction) { mustNotBeRegistered(method); methodToCategory.put(method, Category.MANY_TO_ONE); manyToOneHandlers.put(method, new ManyToOneHandler<>(pClass, rClass, biFunction)); } public synchronized void registerManyToMany( String method, Class

pClass, Class rClass, BiFunction, List> function) { mustNotBeRegistered(method); methodToCategory.put(method, Category.MANY_TO_MANY); manyToManyHandlers.put(method, new ManyToManyHandler<>(pClass, rClass, function)); } public synchronized

void registerManyToNone( String method, Class

pClass, BiConsumer> biConsumer) { mustNotBeRegistered(method); methodToCategory.put(method, Category.MANY_TO_NONE); manyToNoneHandlers.put(method, new ManyToNoneHandler<>(pClass, biConsumer)); } public synchronized void registerNoneToOne( String method, Class rClass, Function function) { mustNotBeRegistered(method); methodToCategory.put(method, Category.NONE_TO_ONE); noneToOneHandlers.put(method, new NoneToOneHandler<>(rClass, function)); } public synchronized void registerNoneToMany( String method, Class rClass, Function> function) { mustNotBeRegistered(method); methodToCategory.put(method, Category.NONE_TO_MANY); noneToManyHandlers.put(method, new NoneToManyHandler<>(rClass, function)); } public synchronized void registerNoneToNone(String method, Consumer consumer) { mustNotBeRegistered(method); methodToCategory.put(method, Category.NONE_TO_NONE); noneToNoneHandlers.put(method, new NoneToNoneHandler(consumer)); } public boolean isRegistered(String method) { return methodToCategory.containsKey(method); } public synchronized boolean deregister(String method) { Category category = methodToCategory.remove(method); if (category == null) { return false; } switch (category) { case ONE_TO_ONE: oneToOneHandlers.remove(method); break; case ONE_TO_MANY: oneToManyHandlers.remove(method); break; case ONE_TO_NONE: oneToNoneHandlers.remove(method); break; case MANY_TO_ONE: manyToOneHandlers.remove(method); break; case MANY_TO_MANY: manyToManyHandlers.remove(method); break; case MANY_TO_NONE: manyToNoneHandlers.remove(method); break; case NONE_TO_ONE: noneToOneHandlers.remove(method); break; case NONE_TO_MANY: noneToManyHandlers.remove(method); break; case NONE_TO_NONE: noneToNoneHandlers.remove(method); break; case ONE_TO_PROMISE_ONE: oneToPromiseOneHandlers.remove(method); break; } return true; } public void handle( String endpointId, String requestId, String method, JsonRpcParams params) { mustBeRegistered(method); switch (methodToCategory.get(method)) { case ONE_TO_ONE: oneToOneHandlers.get(method).handle(endpointId, requestId, method, params); break; case ONE_TO_MANY: oneToManyHandlers.get(method).handle(endpointId, requestId, method, params); break; case MANY_TO_ONE: manyToOneHandlers.get(method).handle(endpointId, requestId, method, params); break; case MANY_TO_MANY: manyToManyHandlers.get(method).handle(endpointId, requestId, method, params); break; case NONE_TO_ONE: noneToOneHandlers.get(method).handle(endpointId, requestId, method, params); break; case NONE_TO_MANY: noneToManyHandlers.get(method).handle(endpointId, requestId, method, params); break; case ONE_TO_PROMISE_ONE: oneToPromiseOneHandlers.get(method).handle(endpointId, requestId, method, params); break; default: LOGGER.error("Something went wrong trying to find out handler category"); } } public void handle(String endpointId, String method, JsonRpcParams params) { mustBeRegistered(method); switch (methodToCategory.get(method)) { case ONE_TO_NONE: oneToNoneHandlers.get(method).handle(endpointId, method, params); break; case MANY_TO_NONE: manyToNoneHandlers.get(method).handle(endpointId, method, params); break; case NONE_TO_NONE: noneToNoneHandlers.get(method).handle(method, endpointId); break; default: LOGGER.error("Something went wrong trying to find out handler category"); } } private void mustBeRegistered(String method) { if (!isRegistered(method)) { String message = "Method '" + method + "' is not registered"; LOGGER.error(message); throw new IllegalStateException(message); } } private void mustNotBeRegistered(String method) { if (isRegistered(method)) { String message = "Method '" + method + "' is already registered"; LOGGER.error(message); throw new IllegalStateException(message); } } private

List

composeMany(JsonRpcParams params, Class

pClass) { return dtoComposer.composeMany(params, pClass); } private

P composeOne(JsonRpcParams params, Class

pClass) { return dtoComposer.composeOne(params, pClass); } private void filter(String method, Object... param) { for (JsonRpcMethodInvokerFilter filter : filters.get(method)) { filter.accept(method, param); } } private void transmitOne(String endpointId, String id, R result) { JsonRpcResult jsonRpcResult = new JsonRpcResult(result); JsonRpcResponse jsonRpcResponse = new JsonRpcResponse(id, jsonRpcResult, null); String message = marshaller.marshall(jsonRpcResponse); transmitter.transmit(endpointId, message); } private void transmitMany(String endpointId, String id, List result) { JsonRpcResult jsonRpcResult = new JsonRpcResult(result); JsonRpcResponse jsonRpcResponse = new JsonRpcResponse(id, jsonRpcResult, null); String message = marshaller.marshall(jsonRpcResponse); transmitter.transmit(endpointId, message); } private void transmitPromiseOne( String endpointId, String requestId, JsonRpcPromise promise) { promise.onSuccess(result -> transmitOne(endpointId, requestId, result)); promise.onFailure( jsonRpcError -> { JsonRpcResponse jsonRpcResponse = new JsonRpcResponse(requestId, null, jsonRpcError); String message = marshaller.marshall(jsonRpcResponse); transmitter.transmit(endpointId, message); }); } public enum Category { ONE_TO_ONE, ONE_TO_MANY, ONE_TO_NONE, MANY_TO_ONE, MANY_TO_MANY, MANY_TO_NONE, NONE_TO_ONE, NONE_TO_MANY, NONE_TO_NONE, ONE_TO_PROMISE_ONE } private class OneToOneHandler { private final Class

pClass; private final Class rClass; private final BiFunction biFunction; private OneToOneHandler(Class

pClass, Class rClass, BiFunction biFunction) { this.pClass = pClass; this.rClass = rClass; this.biFunction = biFunction; } private void handle(String endpointId, String requestId, String method, JsonRpcParams params) { P param = composeOne(params, pClass); for (JsonRpcMethodInvokerFilter filter : filters.get(method)) { filter.accept(method, param); } transmitOne(endpointId, requestId, biFunction.apply(endpointId, param)); } } private class OneToPromiseOneHandler { private final Class

pClass; private final Class rClass; private BiFunction> function; private OneToPromiseOneHandler( Class

pClass, Class rClass, BiFunction> function) { this.pClass = pClass; this.rClass = rClass; this.function = function; } private void handle(String endpointId, String requestId, String method, JsonRpcParams params) { P param = dtoComposer.composeOne(params, pClass); filter(method, param); transmitPromiseOne(endpointId, requestId, function.apply(endpointId, param)); } } private class OneToManyHandler { private final Class

pClass; private final Class rClass; private final BiFunction> biFunction; private OneToManyHandler( Class

pClass, Class rClass, BiFunction> biFunction) { this.pClass = pClass; this.rClass = rClass; this.biFunction = biFunction; } private void handle(String endpointId, String requestId, String method, JsonRpcParams params) { P param = dtoComposer.composeOne(params, pClass); filter(method, param); transmitMany(endpointId, requestId, biFunction.apply(endpointId, param)); } } private class OneToNoneHandler

{ private final Class

pClass; private final BiConsumer biConsumer; private OneToNoneHandler(Class

pClass, BiConsumer biConsumer) { this.pClass = pClass; this.biConsumer = biConsumer; } private void handle(String endpointId, String method, JsonRpcParams params) { P param = composeOne(params, pClass); filter(method, param); biConsumer.accept(endpointId, param); } } private class ManyToOneHandler { private final Class

pClass; private final Class rClass; private final BiFunction, R> biFunction; private ManyToOneHandler( Class

pClass, Class rClass, BiFunction, R> biFunction) { this.pClass = pClass; this.rClass = rClass; this.biFunction = biFunction; } private void handle( String endpointId, String requestId, String method, JsonRpcParams jsonParam) { List

param = dtoComposer.composeMany(jsonParam, pClass); filter(method, param); transmitOne(endpointId, requestId, biFunction.apply(endpointId, param)); } } private class ManyToManyHandler { private final Class

pClass; private final Class rClass; private final BiFunction, List> biFunction; private ManyToManyHandler( Class

pClass, Class rClass, BiFunction, List> biFunction) { this.pClass = pClass; this.rClass = rClass; this.biFunction = biFunction; } private void handle( String endpointId, String requestId, String method, JsonRpcParams jsonParams) { List

params = dtoComposer.composeMany(jsonParams, pClass); filter(method, params); transmitMany(endpointId, requestId, biFunction.apply(endpointId, params)); } } private class ManyToNoneHandler

{ private final Class

pClass; private final BiConsumer> biConsumer; private ManyToNoneHandler(Class

pClass, BiConsumer> biConsumer) { this.pClass = pClass; this.biConsumer = biConsumer; } private void handle(String endpointId, String method, JsonRpcParams params) { List

listDto = composeMany(params, pClass); filter(method, listDto); biConsumer.accept(endpointId, listDto); } public List

compose(JsonRpcParams params) { return dtoComposer.composeMany(params, pClass); } } private class NoneToOneHandler { private final Class rClass; private final Function function; private NoneToOneHandler(Class rClass, Function function) { this.rClass = rClass; this.function = function; } private void handle(String endpointId, String requestId, String method, JsonRpcParams params) { filter(method); transmitOne(endpointId, requestId, function.apply(endpointId)); } } private class NoneToManyHandler { private final Class rClass; private final Function> function; private NoneToManyHandler(Class rClass, Function> function) { this.rClass = rClass; this.function = function; } private void handle(String endpointId, String requestId, String method, JsonRpcParams params) { filter(method); transmitMany(endpointId, requestId, function.apply(endpointId)); } } private class NoneToNoneHandler { private final Consumer consumer; private NoneToNoneHandler(Consumer consumer) { this.consumer = consumer; } private void handle(String method, String endpointId) { filter(method); consumer.accept(endpointId); } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/RequestProcessor.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; /** Platfrom dependent implementation of of request handler processing algorithm. */ public interface RequestProcessor { /** * Process a runnable interface * * @param endpointId an endpoint that requested the processing * @param runnable runnable to be called for processing of a request */ void process(String endpointId, Runnable runnable); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/RequestProcessorConfigurationProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import java.util.concurrent.ExecutorService; /** Request processor configurator */ public interface RequestProcessorConfigurationProvider { /** * Get request processor configuration for specified endpoint. * * @param endpointId endpoint id */ Configuration get(String endpointId); /** Request processor configuration */ interface Configuration { String getEndpointId(); ExecutorService getExecutorService(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/RequestTransmitter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import org.eclipse.che.api.core.jsonrpc.commons.transmission.EndpointIdConfigurator; /** Simple factory that provides facilities to manually build JSON RPC requests */ public interface RequestTransmitter { EndpointIdConfigurator newRequest(); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/ResponseDispatcher.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Inject; import javax.inject.Singleton; import org.slf4j.Logger; /** Dispatches JSON RPC responses */ @Singleton public class ResponseDispatcher { private static final Logger LOGGER = getLogger(ResponseDispatcher.class); private final JsonRpcComposer composer; private final TimeoutActionRunner timeoutActionRunner; private final Map> singleTypedPromises = new ConcurrentHashMap<>(); private final Map> listTypedPromises = new ConcurrentHashMap<>(); @Inject public ResponseDispatcher(JsonRpcComposer composer, TimeoutActionRunner timeoutActionRunner) { this.composer = composer; this.timeoutActionRunner = timeoutActionRunner; } private static void checkArguments(String endpointId, String requestId, Class rClass) { checkNotNull(endpointId, "Endpoint ID must not be null"); checkArgument(!endpointId.isEmpty(), "Endpoint ID must not be empty"); checkNotNull(requestId, "Request ID must not be null"); checkArgument(!requestId.isEmpty(), "Request ID must not be empty"); checkNotNull(rClass, "Result class must not be null"); } private static String generateKey(String endpointId, String requestId) { return endpointId + '@' + requestId; } public void dispatch(String endpointId, JsonRpcResponse response) { checkNotNull(endpointId, "Endpoint ID name must not be null"); checkArgument(!endpointId.isEmpty(), "Endpoint ID name must not be empty"); checkNotNull(response, "Response name must not be null"); String responseId = response.getId(); if (responseId == null) { return; } String key = generateKey(endpointId, responseId); if (response.hasResult()) { dispatchResult(endpointId, response, key); } else if (response.hasError()) { dispatchError(endpointId, response, key); } else { LOGGER.error("Received incorrect response: no error, no result"); } } public synchronized JsonRpcPromise registerPromiseForSingleObject( String endpointId, String requestId, Class rClass, int timeoutInMillis) { checkArguments(endpointId, requestId, rClass); SingleTypedPromise promise = new SingleTypedPromise<>(rClass); String key = generateKey(endpointId, requestId); singleTypedPromises.put(key, promise); if (timeoutInMillis > 0) { timeoutActionRunner.schedule( timeoutInMillis, () -> runTimeoutConsumer(singleTypedPromises.remove(key))); } return promise; } public synchronized JsonRpcPromise> registerPromiseForListOfObjects( String endpointId, String requestId, Class rClass, int timeoutInMillis) { checkArguments(endpointId, requestId, rClass); ListTypedPromise promise = new ListTypedPromise<>(rClass); String key = generateKey(endpointId, requestId); listTypedPromises.put(key, promise); if (timeoutInMillis > 0) { timeoutActionRunner.schedule( timeoutInMillis, () -> runTimeoutConsumer(listTypedPromises.remove(key))); } return promise; } private void runTimeoutConsumer(JsonRpcPromise promise) { Optional.ofNullable(promise) .flatMap(JsonRpcPromise::getTimeoutRunnable) .ifPresent(Runnable::run); } private void dispatchResult(String endpointId, JsonRpcResponse response, String key) { Optional.ofNullable(listTypedPromises.remove(key)) .ifPresent( promise -> promise .getSuccessConsumer() .ifPresent( consumer -> promise .getType() .ifPresent( type -> consumer.accept( endpointId, composer.composeMany(response.getResult(), type))))); Optional.ofNullable(singleTypedPromises.remove(key)) .ifPresent( promise -> promise .getSuccessConsumer() .ifPresent( consumer -> promise .getType() .ifPresent( type -> consumer.accept( endpointId, composer.composeOne(response.getResult(), type))))); } private void dispatchError(String endpointId, JsonRpcResponse response, String key) { SingleTypedPromise singlePromise = singleTypedPromises.remove(key); ListTypedPromise listPromise = listTypedPromises.remove(key); JsonRpcPromise promise = singlePromise != null ? singlePromise : listPromise; promise.getFailureConsumer().ifPresent(it -> it.accept(endpointId, response.getError())); } private class ListTypedPromise extends JsonRpcPromise> { private final Class type; private ListTypedPromise(Class type) { this.type = type; } private Optional> getType() { return Optional.ofNullable(type); } } private class SingleTypedPromise extends JsonRpcPromise { private final Class type; private SingleTypedPromise(Class type) { this.type = type; } public Optional> getType() { return Optional.ofNullable(type); } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/TimeoutActionRunner.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; /** Executes operation on timeout */ public interface TimeoutActionRunner { void schedule(int timeoutInMillis, Runnable runnable); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/ConsumerConfiguratorManyToNone.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Operation configurator to define an operation to be applied when we handle incoming JSON RPC * notification with params object that is represented by a list. As it is an operation there is no * result. * * @param

type of params list items */ public class ConsumerConfiguratorManyToNone

{ private static final Logger LOGGER = getLogger(ConsumerConfiguratorManyToNone.class); private final RequestHandlerManager handlerManager; private final String method; private final Class

pClass; ConsumerConfiguratorManyToNone( RequestHandlerManager handlerManager, String method, Class

pClass) { this.handlerManager = handlerManager; this.method = method; this.pClass = pClass; } public void withBiConsumer(BiConsumer> biConsumer) { checkNotNull(biConsumer, "Notification consumer must not be null"); LOGGER.debug( "Configuring incoming request: " + "binary consumer for method: " + method + ", " + "params list items class: " + pClass); handlerManager.registerManyToNone(method, pClass, biConsumer); } public void withConsumer(Consumer> consumer) { withBiConsumer((endpointId, pValue) -> consumer.accept(pValue)); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/ConsumerConfiguratorNoneToNone.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.function.Consumer; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Operation configurator to define an operation to be applied when we handle incoming JSON RPC * notification with not params and no result value. */ public class ConsumerConfiguratorNoneToNone { private static final Logger LOGGER = getLogger(ConsumerConfiguratorNoneToNone.class); private final RequestHandlerManager handlerManager; private final String method; ConsumerConfiguratorNoneToNone(RequestHandlerManager handlerManager, String method) { this.handlerManager = handlerManager; this.method = method; } public void withConsumer(Consumer consumer) { checkNotNull(consumer, "Notification consumer must not be null"); LOGGER.debug("Configuring incoming request binary: consumer for method: " + method); handlerManager.registerNoneToNone(method, consumer); } public void withRunnable(Runnable runnable) { withConsumer(s -> runnable.run()); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/ConsumerConfiguratorOneToNone.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.function.BiConsumer; import java.util.function.Consumer; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Operation configurator to define an operation to be applied when we handle incoming JSON RPC * notification with params object that is represented by a single object. As it is an operation * there is no result. * * @param

type of params object */ public class ConsumerConfiguratorOneToNone

{ private static final Logger LOGGER = getLogger(ConsumerConfiguratorOneToNone.class); private final RequestHandlerManager handlerManager; private final String method; private final Class

pClass; ConsumerConfiguratorOneToNone( RequestHandlerManager handlerManager, String method, Class

pClass) { this.handlerManager = handlerManager; this.method = method; this.pClass = pClass; } public void withBiConsumer(BiConsumer biConsumer) { checkNotNull(biConsumer, "Notification consumer must not be null"); LOGGER.debug( "Configuring incoming request binary: " + "consumer for method: " + method + ", " + "params object class: " + pClass); handlerManager.registerOneToNone(method, pClass, biConsumer); } public void withConsumer(Consumer

consumer) { withBiConsumer((endpointId, pValue) -> consumer.accept(pValue)); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/FunctionConfiguratorManyToMany.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; import java.util.function.BiFunction; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Function configurator to define a function to be applied when we handle incoming JSON RPC request * with params object that is represented by a list. The result of a function is also a list. * * @param

type of params list items * @param type of resulting list items */ public class FunctionConfiguratorManyToMany { private static final Logger LOGGER = getLogger(FunctionConfiguratorManyToMany.class); private final RequestHandlerManager handlerManager; private final String method; private final Class

pClass; private final Class rClass; FunctionConfiguratorManyToMany( RequestHandlerManager handlerManager, String method, Class

pClass, Class rClass) { this.handlerManager = handlerManager; this.method = method; this.pClass = pClass; this.rClass = rClass; } /** * Define a function to be applied * * @param function function */ public void withFunction(BiFunction, List> function) { checkNotNull(function, "Request function must not be null"); LOGGER.debug( "Configuring incoming request: " + "binary function for method: " + method + ", " + "params list items class: " + pClass + ", " + "result list items class: " + rClass); handlerManager.registerManyToMany(method, pClass, rClass, function); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/FunctionConfiguratorManyToOne.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Function configurator to define a function to be applied when we handle incoming JSON RPC request * with params object that is represented by a list. The result of a function is a single object. * * @param

type of params list items * @param type of result object */ public class FunctionConfiguratorManyToOne { private static final Logger LOGGER = getLogger(FunctionConfiguratorManyToOne.class); private final RequestHandlerManager handlerManager; private final String method; private final Class

pClass; private final Class rClass; FunctionConfiguratorManyToOne( RequestHandlerManager handlerManager, String method, Class

pClass, Class rClass) { this.handlerManager = handlerManager; this.method = method; this.pClass = pClass; this.rClass = rClass; } /** * Define a function to be applied * * @param function function */ public void withFunction(BiFunction, R> function) { checkNotNull(function, "Request function must not be null"); LOGGER.debug( "Configuring incoming request: " + "binary function for method: " + method + ", " + "params list items class: " + pClass + ", " + "result object class: " + rClass); handlerManager.registerManyToOne(method, pClass, rClass, function); } /** * Define a function to be applied * * @param function function */ public void withFunction(Function, R> function) { withFunction((str, ps) -> function.apply(ps)); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/FunctionConfiguratorNoneToMany.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Function configurator to define a function to be applied when we handle incoming JSON RPC request * with no params object while the result of a function is a list of objects. * * @param type of result object */ public class FunctionConfiguratorNoneToMany { private static final Logger LOGGER = getLogger(FunctionConfiguratorNoneToMany.class); private final RequestHandlerManager handlerManager; private final String method; private final Class rClass; FunctionConfiguratorNoneToMany( RequestHandlerManager handlerManager, String method, Class rClass) { this.handlerManager = handlerManager; this.method = method; this.rClass = rClass; } /** * Define a function to be applied * * @param function function */ public void withFunction(Function> function) { checkNotNull(function, "Request function must not be null"); LOGGER.debug( "Configuring incoming request binary: " + "function for method: " + method + ", " + "result object class: " + rClass); handlerManager.registerNoneToMany(method, rClass, function); } /** * Define a supplier to be applied * * @param supplier supplier */ public void withSupplier(Supplier> supplier) { withFunction(s -> supplier.get()); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/FunctionConfiguratorNoneToOne.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.function.Function; import java.util.function.Supplier; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Function configurator to define a function to be applied when we handle incoming JSON RPC request * with no params object while the result of a function is a single object. * * @param type of result object */ public class FunctionConfiguratorNoneToOne { private static final Logger LOGGER = getLogger(FunctionConfiguratorNoneToOne.class); private final RequestHandlerManager handlerManager; private final String method; private final Class rClass; FunctionConfiguratorNoneToOne( RequestHandlerManager handlerManager, String method, Class rClass) { this.handlerManager = handlerManager; this.method = method; this.rClass = rClass; } /** * Define a function to be applied * * @param function function */ public void withFunction(Function function) { checkNotNull(function, "Request function must not be null"); LOGGER.debug( "Configuring incoming request binary: " + "function for method: " + method + ", " + "result object class: " + rClass); handlerManager.registerNoneToOne(method, rClass, function); } /** * Define a supplier to be applied * * @param supplier supplier */ public void withSupplier(Supplier supplier) { withFunction(s -> supplier.get()); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/FunctionConfiguratorOneToMany.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Function configurator to define a function to be applied when we handle incoming JSON RPC request * with params object that is represented by a single object while the result of a function is a * list of objects. * * @param

type of params object * @param type of result list items */ public class FunctionConfiguratorOneToMany { private static final Logger LOGGER = getLogger(FunctionConfiguratorOneToMany.class); private final RequestHandlerManager handlerManager; private final String method; private final Class

pClass; private final Class rClass; FunctionConfiguratorOneToMany( RequestHandlerManager handlerManager, String method, Class

pClass, Class rClass) { this.handlerManager = handlerManager; this.method = method; this.pClass = pClass; this.rClass = rClass; } /** * Define a binary function to be applied * * @param biFunction function */ public void withBiFunction(BiFunction> biFunction) { checkNotNull(biFunction, "Request function must not be null"); LOGGER.debug( "Configuring incoming request binary: " + "function for method: " + method + ", " + "params object class: " + pClass + ", " + "result list items class: " + rClass); handlerManager.registerOneToMany(method, pClass, rClass, biFunction); } /** * Define a function to be applied * * @param biFunction function */ public void withFunction(Function> biFunction) { withBiFunction((s, p) -> biFunction.apply(p)); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/FunctionConfiguratorOneToOne.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.function.BiFunction; import java.util.function.Function; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Function configurator to define a function to be applied when we handle incoming JSON RPC request * with params object that is represented by a single object while the result of a function is also * a single object. * * @param

type of params object * @param type of result object */ public class FunctionConfiguratorOneToOne { private static final Logger LOGGER = getLogger(FunctionConfiguratorOneToOne.class); private final RequestHandlerManager handlerManager; private final String method; private final Class

pClass; private final Class rClass; FunctionConfiguratorOneToOne( RequestHandlerManager handlerManager, String method, Class

pClass, Class rClass) { this.handlerManager = handlerManager; this.method = method; this.pClass = pClass; this.rClass = rClass; } /** * Define a binary function to be applied * * @param biFunction function */ public void withBiFunction(BiFunction biFunction) { checkNotNull(biFunction, "Request function must not be null"); LOGGER.debug( "Configuring incoming request binary: " + "function for method: " + method + ", " + "params object class: " + pClass + ", " + "result object class: " + rClass); handlerManager.registerOneToOne(method, pClass, rClass, biFunction); } /** * Define a function to be applied * * @param function function */ public void withFunction(Function function) { withBiFunction((s, p) -> function.apply(p)); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/MethodNameConfigurator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import javax.inject.Inject; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Method configurator is used to define method name that the request handler will be associated * with. */ public class MethodNameConfigurator { private static final Logger LOGGER = getLogger(MethodNameConfigurator.class); private final RequestHandlerManager requestHandlerManager; @Inject MethodNameConfigurator(RequestHandlerManager requestHandlerManager) { this.requestHandlerManager = requestHandlerManager; } public ParamsConfigurator methodName(String name) { checkNotNull(name, "Method name must not be null"); checkArgument(!name.isEmpty(), "Method name must not be empty"); LOGGER.debug("Configuring incoming request method name name: " + name); return new ParamsConfigurator(requestHandlerManager, name); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/ParamsConfigurator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Params configurator provide means to configure params type in a request that is to be handled. * Params types that are supported: {@link String}, {@link Boolean}, {@link Double}, {@link Void} * and DTO. */ public class ParamsConfigurator { private static final Logger LOGGER = getLogger(ParamsConfigurator.class); private final RequestHandlerManager requestHandlerManager; private final String method; ParamsConfigurator(RequestHandlerManager requestHandlerManager, String method) { this.requestHandlerManager = requestHandlerManager; this.method = method; } public

ResultConfiguratorFromMany

paramsAsListOfDto(Class

pClass) { checkNotNull(pClass, "Params class must not be null"); LOGGER.debug( "Configuring incoming request params: " + "method: " + method + ", " + "params list items class: " + pClass); return new ResultConfiguratorFromMany<>(requestHandlerManager, method, pClass); } public ResultConfiguratorFromMany paramsAsListOfDouble() { LOGGER.debug( "Configuring incoming request params: " + "method: " + method + ", " + "params list items class: " + Double.class); return new ResultConfiguratorFromMany<>(requestHandlerManager, method, Double.class); } public ResultConfiguratorFromMany paramsAsListOfString() { LOGGER.debug( "Configuring incoming request params: " + "method: " + method + ", " + "params list items class: " + String.class); return new ResultConfiguratorFromMany<>(requestHandlerManager, method, String.class); } public ResultConfiguratorFromMany paramsAsListOfBoolean() { LOGGER.debug( "Configuring incoming request params: " + "method: " + method + ", " + "params list items class: " + Boolean.class); return new ResultConfiguratorFromMany<>(requestHandlerManager, method, Boolean.class); } public

ResultConfiguratorFromOne

paramsAsDto(Class

pClass) { checkNotNull(pClass, "Params class must not be null"); LOGGER.debug( "Configuring incoming request params: " + "method: " + method + ", " + "params object class: " + pClass); return new ResultConfiguratorFromOne<>(requestHandlerManager, method, pClass); } public ResultConfiguratorFromNone noParams() { LOGGER.debug( "Configuring incoming request params: " + "method: " + method + ", " + "params object class: " + Void.class); return new ResultConfiguratorFromNone(requestHandlerManager, method); } public ResultConfiguratorFromOne paramsAsString() { LOGGER.debug( "Configuring incoming request params: " + "method: " + method + ", " + "params object class: " + String.class); return new ResultConfiguratorFromOne<>(requestHandlerManager, method, String.class); } public ResultConfiguratorFromOne paramsAsDouble() { LOGGER.debug( "Configuring incoming request params: " + "method: " + method + ", " + "params object class: " + Double.class); return new ResultConfiguratorFromOne<>(requestHandlerManager, method, Double.class); } public ResultConfiguratorFromOne paramsAsBoolean() { LOGGER.debug( "Configuring incoming request params: " + "method: " + method + ", " + "params object class: " + Boolean.class); return new ResultConfiguratorFromOne<>(requestHandlerManager, method, Boolean.class); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/PromiseConfigurationOneToOne.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.function.BiFunction; import java.util.function.Function; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcPromise; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Function configurator to define a function to be applied when we handle incoming JSON RPC request * with params object that is represented by a single object while the result of a function is also * a single object. * * @param

type of params object * @param type of result object */ public class PromiseConfigurationOneToOne { private static final Logger LOGGER = getLogger(PromiseConfigurationOneToOne.class); private final RequestHandlerManager handlerManager; private final String method; private final Class

pClass; private final Class rClass; PromiseConfigurationOneToOne( RequestHandlerManager handlerManager, String method, Class

pClass, Class rClass) { this.handlerManager = handlerManager; this.method = method; this.pClass = pClass; this.rClass = rClass; } /** * Define a binary function to be applied * * @param function function */ public void withPromiseBiFunction(BiFunction> function) { checkNotNull(function, "Request promise must not be null"); LOGGER.debug( "Configuring incoming request binary: " + "function for method: " + method + ", " + "params object class: " + pClass + ", " + "result object class: " + rClass); handlerManager.registerOneToPromiseOne(method, pClass, rClass, function); } /** * Define a function to be applied * * @param function function */ public void withPromise(Function> function) { withPromiseBiFunction((s, p) -> function.apply(p)); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/ResultConfiguratorFromMany.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Result configurator provide means to configure result type in a response that is to be received. * Result types that are supported: {@link String}, {@link Boolean}, {@link Double}, {@link Void} * and DTO. This configurator is used when we have defined request params as a list. */ public class ResultConfiguratorFromMany

{ private static final Logger LOGGER = getLogger(ResultConfiguratorFromMany.class); private final RequestHandlerManager requestHandlerManager; private final String method; private final Class

pClass; ResultConfiguratorFromMany( RequestHandlerManager requestHandlerManager, String method, Class

pClass) { this.requestHandlerManager = requestHandlerManager; this.method = method; this.pClass = pClass; } public FunctionConfiguratorManyToMany resultAsListOfDto(Class rClass) { checkNotNull(rClass, "Result class must not be null"); LOGGER.debug( "Configuring incoming request result: method: " + method + ", result list items class: " + rClass); return new FunctionConfiguratorManyToMany<>(requestHandlerManager, method, pClass, rClass); } public FunctionConfiguratorManyToMany resultAsListOfString() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result list items class: " + String.class); return new FunctionConfiguratorManyToMany<>( requestHandlerManager, method, pClass, String.class); } public FunctionConfiguratorManyToMany resultAsListOfDouble() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result list items class: " + Double.class); return new FunctionConfiguratorManyToMany<>( requestHandlerManager, method, pClass, Double.class); } public FunctionConfiguratorManyToMany resultAsListOfBoolean() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result list items class: " + Boolean.class); return new FunctionConfiguratorManyToMany<>( requestHandlerManager, method, pClass, Boolean.class); } public FunctionConfiguratorManyToOne resultAsDto(Class rClass) { checkNotNull(rClass, "Result class must not be null"); LOGGER.debug( "Configuring incoming request result: method: " + method + ", result object class: " + rClass); return new FunctionConfiguratorManyToOne<>(requestHandlerManager, method, pClass, rClass); } public FunctionConfiguratorManyToOne resultAsString() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result object class: " + String.class); return new FunctionConfiguratorManyToOne<>(requestHandlerManager, method, pClass, String.class); } public FunctionConfiguratorManyToOne resultAsDouble() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result object class: " + Double.class); return new FunctionConfiguratorManyToOne<>(requestHandlerManager, method, pClass, Double.class); } public FunctionConfiguratorManyToOne resultAsBoolean() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result object class: " + Double.class); return new FunctionConfiguratorManyToOne<>( requestHandlerManager, method, pClass, Boolean.class); } public ConsumerConfiguratorManyToNone

noResult() { LOGGER.debug("Configuring incoming request having no result"); return new ConsumerConfiguratorManyToNone<>(requestHandlerManager, method, pClass); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/ResultConfiguratorFromNone.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Result configurator provide means to configure result type in a response that is to be received. * Result types that are supported: {@link String}, {@link Boolean}, {@link Double}, {@link Void} * and DTO. This configurator is used when we have no defined request params. */ public class ResultConfiguratorFromNone { private static final Logger LOGGER = getLogger(ResultConfiguratorFromNone.class); private final RequestHandlerManager requestHandlerManager; private final String method; ResultConfiguratorFromNone(RequestHandlerManager requestHandlerManager, String method) { this.requestHandlerManager = requestHandlerManager; this.method = method; } public FunctionConfiguratorNoneToMany resultAsListOfDto(Class rClass) { checkNotNull(rClass, "Result class must not be null"); LOGGER.debug( "Configuring incoming request result: method: " + method + ", result list items class: " + rClass); return new FunctionConfiguratorNoneToMany<>(requestHandlerManager, method, rClass); } public FunctionConfiguratorNoneToMany resultAsListOfString() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result list items class: " + String.class); return new FunctionConfiguratorNoneToMany<>(requestHandlerManager, method, String.class); } public FunctionConfiguratorNoneToMany resultAsListOfDouble() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result list items class: " + Double.class); return new FunctionConfiguratorNoneToMany<>(requestHandlerManager, method, Double.class); } public FunctionConfiguratorNoneToMany resultAsListOfBoolean() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result list items class: " + Boolean.class); return new FunctionConfiguratorNoneToMany<>(requestHandlerManager, method, Boolean.class); } public FunctionConfiguratorNoneToOne resultAsDto(Class rClass) { checkNotNull(rClass, "Result class must not be null"); LOGGER.debug( "Configuring incoming request result: method: " + method + ", result object class: " + rClass); return new FunctionConfiguratorNoneToOne<>(requestHandlerManager, method, rClass); } public FunctionConfiguratorNoneToOne resultAsString() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result object class: " + String.class); return new FunctionConfiguratorNoneToOne<>(requestHandlerManager, method, String.class); } public FunctionConfiguratorNoneToOne resultAsDouble() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result object class: " + Double.class); return new FunctionConfiguratorNoneToOne<>(requestHandlerManager, method, Double.class); } public FunctionConfiguratorNoneToOne resultAsBoolean() { LOGGER.debug( "Configuring incoming request result: method: " + method + ", result object class: " + Boolean.class); return new FunctionConfiguratorNoneToOne<>(requestHandlerManager, method, Boolean.class); } public ConsumerConfiguratorNoneToNone noResult() { LOGGER.debug( "Configuring incoming request result: " + "method: " + method + ", no result is expected."); return new ConsumerConfiguratorNoneToNone(requestHandlerManager, method); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/reception/ResultConfiguratorFromOne.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.reception; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.slf4j.Logger; /** * Result configurator provide means to configure result type in a response that is to be received. * Result types that are supported: {@link String}, {@link Boolean}, {@link Double}, {@link Void} * and DTO. This configurator is used when we have defined request params as a single object. */ public class ResultConfiguratorFromOne

{ private static final Logger LOGGER = getLogger(ResultConfiguratorFromOne.class); private final RequestHandlerManager requestHandlerManager; private final String method; private final Class

pClass; ResultConfiguratorFromOne( RequestHandlerManager requestHandlerManager, String method, Class

pClass) { this.requestHandlerManager = requestHandlerManager; this.method = method; this.pClass = pClass; } public FunctionConfiguratorOneToMany resultAsListOfDto(Class rClass) { checkNotNull(rClass, "Result class must not be null"); LOGGER.debug( "Configuring incoming request result: " + "method: " + method + ", " + "result list items class: " + rClass); return new FunctionConfiguratorOneToMany<>(requestHandlerManager, method, pClass, rClass); } public FunctionConfiguratorOneToMany resultAsListOfString() { LOGGER.debug( "Configuring incoming request result: " + "method: " + method + ", " + "result list items class: " + String.class); return new FunctionConfiguratorOneToMany<>(requestHandlerManager, method, pClass, String.class); } public FunctionConfiguratorOneToMany resultAsListOfDouble() { LOGGER.debug( "Configuring incoming request result: " + "method: " + method + ", " + "result list items class: " + Double.class); return new FunctionConfiguratorOneToMany<>(requestHandlerManager, method, pClass, Double.class); } public FunctionConfiguratorOneToMany resultAsListOfBoolean() { LOGGER.debug( "Configuring incoming request result: " + "method: " + method + ", " + "result list items class: " + Boolean.class); return new FunctionConfiguratorOneToMany<>( requestHandlerManager, method, pClass, Boolean.class); } public FunctionConfiguratorOneToOne resultAsDto(Class rClass) { checkNotNull(rClass, "Result class must not be null"); LOGGER.debug( "Configuring incoming request result: " + "method: " + method + ", " + "result object class: " + rClass); return new FunctionConfiguratorOneToOne<>(requestHandlerManager, method, pClass, rClass); } public PromiseConfigurationOneToOne resultAsPromiseDto(Class rClass) { checkNotNull(rClass, "Result class must not be null"); LOGGER.debug( "Configuring incoming request result: " + "method: " + method + ", " + "result object class: " + rClass); return new PromiseConfigurationOneToOne<>(requestHandlerManager, method, pClass, rClass); } public FunctionConfiguratorOneToOne resultAsString() { LOGGER.debug( "Configuring incoming request result: " + "method: " + method + ", " + "result object class: " + String.class); return new FunctionConfiguratorOneToOne<>(requestHandlerManager, method, pClass, String.class); } public FunctionConfiguratorOneToOne resultAsDouble() { LOGGER.debug( "Configuring incoming request result: " + "method: " + method + ", " + "result object class: " + Double.class); return new FunctionConfiguratorOneToOne<>(requestHandlerManager, method, pClass, Double.class); } public FunctionConfiguratorOneToOne resultAsBoolean() { LOGGER.debug( "Configuring incoming request result: " + "method: " + method + ", " + "result object class: " + Boolean.class); return new FunctionConfiguratorOneToOne<>(requestHandlerManager, method, pClass, Boolean.class); } public ConsumerConfiguratorOneToNone

noResult() { LOGGER.debug("Configuring incoming request having no result"); return new ConsumerConfiguratorOneToNone<>(requestHandlerManager, method, pClass); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/transmission/EndpointIdConfigurator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.transmission; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import javax.inject.Inject; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcMarshaller; import org.eclipse.che.api.core.jsonrpc.commons.ResponseDispatcher; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageTransmitter; import org.slf4j.Logger; /** Endpoint ID configurator to defined endpoint id that the request should be addressed to. */ public class EndpointIdConfigurator { private static final Logger LOGGER = getLogger(EndpointIdConfigurator.class); private final JsonRpcMarshaller marshaller; private final ResponseDispatcher dispatcher; private final WebSocketMessageTransmitter transmitter; @Inject EndpointIdConfigurator( JsonRpcMarshaller marshaller, ResponseDispatcher dispatcher, WebSocketMessageTransmitter transmitter) { this.marshaller = marshaller; this.dispatcher = dispatcher; this.transmitter = transmitter; } public MethodNameConfigurator endpointId(String id) { checkNotNull(id, "Endpoint ID must not be null"); checkArgument(!id.isEmpty(), "Endpoint ID must not be empty"); LOGGER.debug("Configuring outgoing request endpoint ID: " + id); return new MethodNameConfigurator(marshaller, dispatcher, transmitter, id); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/transmission/MethodNameConfigurator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.transmission; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.concurrent.atomic.AtomicInteger; import javax.inject.Inject; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcMarshaller; import org.eclipse.che.api.core.jsonrpc.commons.ResponseDispatcher; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageTransmitter; import org.slf4j.Logger; /** Method name configurator to defined method name that the request will have. */ public class MethodNameConfigurator { private static final Logger LOGGER = getLogger(MethodNameConfigurator.class); public static AtomicInteger id = new AtomicInteger(0); private final JsonRpcMarshaller marshaller; private final ResponseDispatcher dispatcher; private final WebSocketMessageTransmitter transmitter; private final String endpointId; @Inject MethodNameConfigurator( JsonRpcMarshaller marshaller, ResponseDispatcher dispatcher, WebSocketMessageTransmitter transmitter, String endpointId) { this.marshaller = marshaller; this.dispatcher = dispatcher; this.transmitter = transmitter; this.endpointId = endpointId; } public ParamsConfigurator methodName(String name) { checkNotNull(name, "Method name must not be null"); checkArgument(!name.isEmpty(), "Method name must not be empty"); LOGGER.debug("Configuring outgoing request method name name: " + name); return new ParamsConfigurator(marshaller, dispatcher, transmitter, name, endpointId); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/transmission/ParamsConfigurator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.transmission; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcMarshaller; import org.eclipse.che.api.core.jsonrpc.commons.ResponseDispatcher; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageTransmitter; import org.slf4j.Logger; /** * Params configurator provide means to configure params type in a request that is to be sent. * Params types that are supported: {@link String}, {@link Boolean}, {@link Double}, {@link Void} * and DTO. */ public class ParamsConfigurator { private static final Logger LOGGER = getLogger(ParamsConfigurator.class); private final JsonRpcMarshaller marshaller; private final ResponseDispatcher dispatcher; private final WebSocketMessageTransmitter transmitter; private final String method; private final String endpointId; ParamsConfigurator( JsonRpcMarshaller marshaller, ResponseDispatcher dispatcher, WebSocketMessageTransmitter transmitter, String method, String endpointId) { this.marshaller = marshaller; this.dispatcher = dispatcher; this.transmitter = transmitter; this.method = method; this.endpointId = endpointId; } public

SendConfiguratorFromOne

paramsAsDto(P pValue) { checkNotNull(pValue, "Params value must not be null"); LOGGER.debug( "Configuring outgoing request params: " + "endpoint ID: " + endpointId + ", " + "method: " + method + ", " + "params object class: " + pValue.getClass() + ", " + "params object value: " + pValue); return new SendConfiguratorFromOne<>( marshaller, dispatcher, transmitter, method, pValue, endpointId); } public SendConfiguratorFromOne paramsAsDouble(Double pValue) { checkNotNull(pValue, "Params value must not be null"); LOGGER.debug( "Configuring outgoing request params: " + "endpoint ID: " + endpointId + ", " + "method: " + method + ", " + "params object class: " + Double.class + ", " + "params object value: " + pValue); return new SendConfiguratorFromOne<>( marshaller, dispatcher, transmitter, method, pValue, endpointId); } public SendConfiguratorFromOne paramsAsString(String pValue) { checkNotNull(pValue, "Params value must not be null"); LOGGER.debug( "Configuring outgoing request params: " + "endpoint ID: " + endpointId + ", " + "method: " + method + ", " + "params object class: " + String.class + ", " + "params object value: " + pValue); return new SendConfiguratorFromOne<>( marshaller, dispatcher, transmitter, method, pValue, endpointId); } public SendConfiguratorFromOne paramsAsBoolean(Boolean pValue) { checkNotNull(pValue, "Params value must not be null"); LOGGER.debug( "Configuring outgoing request params: " + "endpoint ID: " + endpointId + ", " + "method: " + method + ", " + "params object class: " + Boolean.class + ", " + "params object value: " + pValue); return new SendConfiguratorFromOne<>( marshaller, dispatcher, transmitter, method, pValue, endpointId); } public SendConfiguratorFromNone noParams() { LOGGER.debug( "Configuring outgoing request params: " + "endpoint ID: " + endpointId + ", " + "method: " + method + ", " + "params object class: " + Void.class + ", " + "params object value: void"); return new SendConfiguratorFromNone(marshaller, dispatcher, transmitter, method, endpointId); } public

SendConfiguratorFromMany

paramsAsListOfDto(List

pListValue) { checkNotNull(pListValue, "Params list value must not be null"); checkArgument(!pListValue.isEmpty(), "Params list value must not be empty"); LOGGER.debug( "Configuring outgoing request params: " + "endpoint ID: " + endpointId + ", " + "method: " + method + ", " + "params list items class: " + pListValue.iterator().next().getClass() + ", " + "params list value: " + pListValue); return new SendConfiguratorFromMany<>( marshaller, dispatcher, transmitter, method, pListValue, endpointId); } public SendConfiguratorFromMany paramsAsListOfString(List pListValue) { checkNotNull(pListValue, "Params list value must not be null"); checkArgument(!pListValue.isEmpty(), "Params list value must not be empty"); LOGGER.debug( "Configuring outgoing request params: " + "endpoint ID: " + endpointId + ", " + "method: " + method + ", " + "params list items class: " + String.class + ", " + "params list value: " + pListValue); return new SendConfiguratorFromMany<>( marshaller, dispatcher, transmitter, method, pListValue, endpointId); } public SendConfiguratorFromMany paramsAsListOfDouble(List pListValue) { checkNotNull(pListValue, "Params list value must not be null"); checkArgument(!pListValue.isEmpty(), "Params list value must not be empty"); LOGGER.debug( "Configuring outgoing request params: " + "endpoint ID: " + endpointId + ", " + "method: " + method + ", " + "params list items class: " + Double.class + ", " + "params list value: " + pListValue); return new SendConfiguratorFromMany<>( marshaller, dispatcher, transmitter, method, pListValue, endpointId); } public SendConfiguratorFromMany paramsAsListOfBoolean(List pListValue) { checkNotNull(pListValue, "Params list value must not be null"); checkArgument(!pListValue.isEmpty(), "Params list value must not be empty"); LOGGER.debug( "Configuring outgoing request params: " + "endpoint ID: " + endpointId + ", " + "method: " + method + ", " + "params list items class: " + Boolean.class + ", " + "params list value: " + pListValue); return new SendConfiguratorFromMany<>( marshaller, dispatcher, transmitter, method, pListValue, endpointId); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/transmission/SendConfiguratorFromMany.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.transmission; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcMarshaller; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcParams; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcPromise; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcRequest; import org.eclipse.che.api.core.jsonrpc.commons.ResponseDispatcher; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageTransmitter; import org.slf4j.Logger; /** * Configurator defines the type of a result (if present) and send a request. Result types that are * supported: {@link String}, {@link Boolean}, {@link Double}, {@link Void} and DTO. This * configurator is used when we have defined request params as a list. * * @param

type of params list items */ public class SendConfiguratorFromMany

{ private static final Logger LOGGER = getLogger(SendConfiguratorFromMany.class); private final ResponseDispatcher dispatcher; private final WebSocketMessageTransmitter transmitter; private final JsonRpcMarshaller marshaller; private final String method; private final List

pListValue; private final String endpointId; SendConfiguratorFromMany( JsonRpcMarshaller marshaller, ResponseDispatcher dispatcher, WebSocketMessageTransmitter transmitter, String method, List

pListValue, String endpointId) { this.dispatcher = dispatcher; this.transmitter = transmitter; this.marshaller = marshaller; this.method = method; this.pListValue = pListValue; this.endpointId = endpointId; } public void sendAndSkipResult() { LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "method: " + method + ", " + "params list items class: " + pListValue.iterator().next().getClass() + ", " + "params list value" + pListValue); transmitNotification(); } public JsonRpcPromise sendAndReceiveResultAsDto(Class rClass) { return sendAndReceiveResultAsDto(rClass, 0); } public JsonRpcPromise sendAndReceiveResultAsDto(Class rClass, int timeoutInMillis) { checkNotNull(rClass, "Result class value must not be null"); final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "params list items class: " + pListValue.iterator().next().getClass() + ", " + "params list value" + pListValue + ", " + "result object class: " + rClass); return dispatcher.registerPromiseForSingleObject( endpointId, requestId, rClass, timeoutInMillis); } public JsonRpcPromise sendAndReceiveResultAsString() { return sendAndReceiveResultAsString(0); } public JsonRpcPromise sendAndReceiveResultAsString(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "params list items class: " + pListValue.iterator().next().getClass() + ", " + "params list value" + pListValue + ", " + "result object class: " + String.class); return dispatcher.registerPromiseForSingleObject( endpointId, requestId, String.class, timeoutInMillis); } public JsonRpcPromise sendAndReceiveResultAsBoolean() { return sendAndReceiveResultAsBoolean(0); } public JsonRpcPromise sendAndReceiveResultAsBoolean(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "params list items class: " + pListValue.iterator().next().getClass() + ", " + "params list value" + pListValue + ", " + "result object class: " + Boolean.class); return dispatcher.registerPromiseForSingleObject( endpointId, requestId, Boolean.class, timeoutInMillis); } public JsonRpcPromise sendAndReceiveResultAsDouble() { return sendAndReceiveResultAsDouble(0); } public JsonRpcPromise sendAndReceiveResultAsDouble(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "params list items class: " + pListValue.iterator().next().getClass() + ", " + "params list value" + pListValue + ", " + "result object class: " + Double.class); return dispatcher.registerPromiseForSingleObject( endpointId, requestId, Double.class, timeoutInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfDto(Class rClass) { return sendAndReceiveResultAsListOfDto(rClass, 0); } public JsonRpcPromise> sendAndReceiveResultAsListOfDto( Class rClass, int timeoutInMillis) { checkNotNull(rClass, "Result class value must not be null"); final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "params list items class: " + pListValue.iterator().next().getClass() + ", " + "params list value" + pListValue + ", " + "result list items class: " + rClass); return dispatcher.registerPromiseForListOfObjects( endpointId, requestId, rClass, timeoutInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfString() { return sendAndReceiveResultAsListOfString(0); } public JsonRpcPromise> sendAndReceiveResultAsListOfString(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "params list items class: " + pListValue.iterator().next().getClass() + ", " + "params list value" + pListValue + ", " + "result list items class: " + String.class); return dispatcher.registerPromiseForListOfObjects( endpointId, requestId, String.class, timeoutInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfBoolean() { return sendAndReceiveResultAsListOfBoolean(0); } public JsonRpcPromise> sendAndReceiveResultAsListOfBoolean(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "params list items class: " + pListValue.iterator().next().getClass() + ", " + "params list value" + pListValue + ", " + "result list items class: " + Boolean.class); return dispatcher.registerPromiseForListOfObjects( endpointId, requestId, Boolean.class, timeoutInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfDouble() { return sendAndReceiveResultAsListOfDouble(0); } public JsonRpcPromise> sendAndReceiveResultAsListOfDouble(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "params list items class: " + pListValue.iterator().next().getClass() + ", " + "params list value" + pListValue + ", " + "result list items class: " + Double.class); return dispatcher.registerPromiseForListOfObjects( endpointId, requestId, Double.class, timeoutInMillis); } private void transmitNotification() { JsonRpcParams params = new JsonRpcParams(pListValue); JsonRpcRequest request = new JsonRpcRequest(null, method, params); String message = marshaller.marshall(request); transmitter.transmit(endpointId, message); } private String transmitRequest() { Integer id = MethodNameConfigurator.id.incrementAndGet(); String requestId = id.toString(); JsonRpcParams params = new JsonRpcParams(pListValue); JsonRpcRequest request = new JsonRpcRequest(requestId, method, params); String message = marshaller.marshall(request); transmitter.transmit(endpointId, message); return requestId; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/transmission/SendConfiguratorFromNone.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.transmission; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcMarshaller; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcPromise; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcRequest; import org.eclipse.che.api.core.jsonrpc.commons.ResponseDispatcher; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageTransmitter; import org.slf4j.Logger; /** * Configurator defines the type of a result (if present) and send a request. Result types that are * supported: {@link String}, {@link Boolean}, {@link Double}, {@link Void} and DTO. This * configurator is used when we have not set request params. */ public class SendConfiguratorFromNone { private static final Logger LOGGER = getLogger(SendConfiguratorFromNone.class); private final ResponseDispatcher dispatcher; private final WebSocketMessageTransmitter transmitter; private final JsonRpcMarshaller marshaller; private final String method; private final String endpointId; SendConfiguratorFromNone( JsonRpcMarshaller marshaller, ResponseDispatcher dispatcher, WebSocketMessageTransmitter transmitter, String method, String endpointId) { this.marshaller = marshaller; this.dispatcher = dispatcher; this.transmitter = transmitter; this.method = method; this.endpointId = endpointId; } public void sendAndSkipResult() { LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "method: " + method); transmitNotification(); } public JsonRpcPromise sendAndReceiveResultAsDto(final Class rClass) { return sendAndReceiveResultAsDto(rClass, 0); } public JsonRpcPromise sendAndReceiveResultAsDto(final Class rClass, int timeInMillis) { checkNotNull(rClass, "Result class value must not be null"); final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "result object class: " + rClass); return dispatcher.registerPromiseForSingleObject(endpointId, requestId, rClass, timeInMillis); } public JsonRpcPromise sendAndReceiveResultAsString() { return sendAndReceiveResultAsString(0); } public JsonRpcPromise sendAndReceiveResultAsString(int timeInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "result object class: " + String.class); return dispatcher.registerPromiseForSingleObject( endpointId, requestId, String.class, timeInMillis); } public JsonRpcPromise sendAndReceiveResultAsDouble() { return sendAndReceiveResultAsDouble(0); } public JsonRpcPromise sendAndReceiveResultAsDouble(int timeInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "result object class: " + Double.class); return dispatcher.registerPromiseForSingleObject( endpointId, requestId, Double.class, timeInMillis); } public JsonRpcPromise sendAndReceiveResultAsBoolean() { return sendAndReceiveResultAsBoolean(0); } public JsonRpcPromise sendAndReceiveResultAsBoolean(int timeInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "result object class: " + Boolean.class); return dispatcher.registerPromiseForSingleObject( endpointId, requestId, Boolean.class, timeInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfDto(final Class rClass) { return sendAndReceiveResultAsListOfDto(rClass, 0); } public JsonRpcPromise> sendAndReceiveResultAsListOfDto( final Class rClass, int timeInMillis) { checkNotNull(rClass, "Result class value must not be null"); final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "result list items class: " + rClass); return dispatcher.registerPromiseForListOfObjects(endpointId, requestId, rClass, timeInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfString() { return sendAndReceiveResultAsListOfString(0); } public JsonRpcPromise> sendAndReceiveResultAsListOfString(int timeInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "result list items class: " + String.class); return dispatcher.registerPromiseForListOfObjects( endpointId, requestId, String.class, timeInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfBoolean() { return sendAndReceiveResultAsListOfBoolean(0); } public JsonRpcPromise> sendAndReceiveResultAsListOfBoolean(int timeInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "result list items class: " + Boolean.class); return dispatcher.registerPromiseForListOfObjects( endpointId, requestId, Boolean.class, timeInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfDouble() { return sendAndReceiveResultAsListOfDouble(0); } public JsonRpcPromise> sendAndReceiveResultAsListOfDouble(int timeInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + "result list items class: " + Double.class); return dispatcher.registerPromiseForListOfObjects( endpointId, requestId, Double.class, timeInMillis); } private void transmitNotification() { JsonRpcRequest request = new JsonRpcRequest(null, method, null); String message = marshaller.marshall(request); transmitter.transmit(endpointId, message); } private String transmitRequest() { Integer id = MethodNameConfigurator.id.incrementAndGet(); String requestId = id.toString(); JsonRpcRequest request = new JsonRpcRequest(requestId, method, null); String message = marshaller.marshall(request); transmitter.transmit(endpointId, message); return requestId; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/commons/transmission/SendConfiguratorFromOne.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons.transmission; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcMarshaller; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcParams; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcPromise; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcRequest; import org.eclipse.che.api.core.jsonrpc.commons.ResponseDispatcher; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageTransmitter; import org.slf4j.Logger; /** * Configurator defines the type of a result (if present) and send a request. Result types that are * supported: {@link String}, {@link Boolean}, {@link Double}, {@link Void} and DTO. This * configurator is used when we have defined request params as a single object. * * @param

type of params objects */ public class SendConfiguratorFromOne

{ private static final Logger LOGGER = getLogger(SendConfiguratorFromOne.class); private final ResponseDispatcher dispatcher; private final WebSocketMessageTransmitter transmitter; private final JsonRpcMarshaller marshaller; private final String method; private final P pValue; private final String endpointId; SendConfiguratorFromOne( JsonRpcMarshaller marshaller, ResponseDispatcher dispatcher, WebSocketMessageTransmitter transmitter, String method, P pValue, String endpointId) { this.marshaller = marshaller; this.dispatcher = dispatcher; this.transmitter = transmitter; this.method = method; this.pValue = pValue; this.endpointId = endpointId; } public void sendAndSkipResult() { LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "method: " + method + ", " + (pValue != null ? "params object class: " + pValue.getClass() + ", " : "") + "params list value" + pValue); transmitNotification(); } public JsonRpcPromise sendAndReceiveResultAsDto(final Class rClass) { return sendAndReceiveResultAsDto(rClass, 0); } public JsonRpcPromise sendAndReceiveResultAsDto( final Class rClass, int timeoutInMillis) { checkNotNull(rClass, "Result class value must not be null"); final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + (pValue != null ? "params object class: " + pValue.getClass() + ", " : "") + "params list value" + pValue + ", " + "result object class: " + rClass); return dispatcher.registerPromiseForSingleObject( endpointId, requestId, rClass, timeoutInMillis); } public JsonRpcPromise sendAndReceiveResultAsString() { return sendAndReceiveResultAsString(0); } public JsonRpcPromise sendAndReceiveResultAsString(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + (pValue != null ? "params object class: " + pValue.getClass() + ", " : "") + "params list value" + pValue + ", " + "result object class: " + String.class); return dispatcher.registerPromiseForSingleObject( endpointId, requestId, String.class, timeoutInMillis); } public JsonRpcPromise sendAndReceiveResultAsDouble() { return sendAndReceiveResultAsDouble(0); } public JsonRpcPromise sendAndReceiveResultAsDouble(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + (pValue != null ? "params object class: " + pValue.getClass() + ", " : "") + "params list value" + pValue + ", " + "result object class: " + Double.class); return dispatcher.registerPromiseForSingleObject( endpointId, requestId, Double.class, timeoutInMillis); } public JsonRpcPromise sendAndReceiveResultAsBoolean() { return sendAndReceiveResultAsBoolean(0); } public JsonRpcPromise sendAndReceiveResultAsBoolean(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + (pValue != null ? "params object class: " + pValue.getClass() + ", " : "") + "params list value" + pValue + ", " + "result object class: " + Boolean.class); return dispatcher.registerPromiseForSingleObject( endpointId, requestId, Boolean.class, timeoutInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfDto(Class rClass) { return sendAndReceiveResultAsListOfDto(rClass, 0); } public JsonRpcPromise> sendAndReceiveResultAsListOfDto( Class rClass, int timeoutInMillis) { checkNotNull(rClass, "Result class value must not be null"); final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + (pValue != null ? "params object class: " + pValue.getClass() + ", " : "") + "params list value" + pValue + ", " + "result list items class: " + rClass); return dispatcher.registerPromiseForListOfObjects( endpointId, requestId, rClass, timeoutInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfString() { return sendAndReceiveResultAsListOfString(0); } public JsonRpcPromise> sendAndReceiveResultAsListOfString(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + (pValue != null ? "params object class: " + pValue.getClass() + ", " : "") + "params list value" + pValue + ", " + "result list items class: " + String.class); return dispatcher.registerPromiseForListOfObjects( endpointId, requestId, String.class, timeoutInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfBoolean() { return sendAndReceiveResultAsListOfBoolean(0); } public JsonRpcPromise> sendAndReceiveResultAsListOfBoolean(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + (pValue != null ? "params object class: " + pValue.getClass() + ", " : "") + "params list value" + pValue + ", " + "result list items class: " + Boolean.class); return dispatcher.registerPromiseForListOfObjects( endpointId, requestId, Boolean.class, timeoutInMillis); } public JsonRpcPromise> sendAndReceiveResultAsListOfDouble() { return sendAndReceiveResultAsListOfDouble(0); } public JsonRpcPromise> sendAndReceiveResultAsListOfDouble(int timeoutInMillis) { final String requestId = transmitRequest(); LOGGER.debug( "Transmitting request: " + "endpoint ID: " + endpointId + ", " + "request ID: " + requestId + ", " + "method: " + method + ", " + (pValue != null ? "params object class: " + pValue.getClass() + ", " : "") + "params list value" + pValue + ", " + "result list items class: " + Double.class); return dispatcher.registerPromiseForListOfObjects( endpointId, requestId, Double.class, timeoutInMillis); } private void transmitNotification() { JsonRpcParams params = new JsonRpcParams(pValue); JsonRpcRequest request = new JsonRpcRequest(null, method, params); String message = marshaller.marshall(request); transmitter.transmit(endpointId, message); } private String transmitRequest() { Integer id = MethodNameConfigurator.id.incrementAndGet(); String requestId = id.toString(); JsonRpcParams params = new JsonRpcParams(pValue); JsonRpcRequest request = new JsonRpcRequest(requestId, method, params); String message = marshaller.marshall(request); transmitter.transmit(endpointId, message); return requestId; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/impl/GsonJsonRpcComposer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.impl; import static java.util.Collections.emptyList; import static org.eclipse.che.api.core.jsonrpc.commons.JsonRpcUtils.cast; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.inject.Singleton; import java.util.List; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcComposer; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcParams; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcResult; import org.eclipse.che.dto.server.DtoFactory; @Singleton public class GsonJsonRpcComposer implements JsonRpcComposer { @Override public T composeOne(JsonRpcParams params, Class type) { return composeOne(type, params.getOne()); } @Override public List composeMany(JsonRpcParams params, Class type) { return composeMany(type, params.getMany()); } @Override public T composeOne(JsonRpcResult result, Class type) { return composeOne(type, result.getOne()); } @Override public List composeMany(JsonRpcResult result, Class type) { return composeMany(type, result.getMany()); } private T composeOne(Class type, Object paramObject) { if (paramObject instanceof JsonElement) { JsonElement jsonElement = (JsonElement) paramObject; return DtoFactory.getInstance().createDtoFromJson(jsonElement.toString(), type); } return cast(paramObject); } private List composeMany(Class type, List paramsList) { if (paramsList.isEmpty()) { return emptyList(); } if (paramsList.get(0) instanceof JsonElement) { JsonArray jsonArray = new JsonArray(); for (int i = 0; i < paramsList.size(); i++) { JsonElement jsonElement = (JsonElement) paramsList.get(i); jsonArray.set(i, jsonElement); } return DtoFactory.getInstance().createListDtoFromJson(jsonArray.toString(), type); } return cast(paramsList); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/impl/GsonJsonRpcMarshaller.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.impl; import static org.eclipse.che.api.core.jsonrpc.commons.JsonRpcUtils.cast; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.inject.Inject; import java.util.List; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcError; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcMarshaller; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcParams; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcRequest; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcResponse; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcResult; import org.eclipse.che.dto.server.DtoFactory; public class GsonJsonRpcMarshaller implements JsonRpcMarshaller { private final JsonParser jsonParser; private final Gson gson; @Inject public GsonJsonRpcMarshaller(JsonParser jsonParser, Gson gson) { this.jsonParser = jsonParser; this.gson = gson; } @Override public String marshall(JsonRpcResponse response) { JsonElement jsonId = getId(response); JsonElement jsonResult = getResult(response); JsonElement jsonError = getError(response); return getResponse(jsonId, jsonResult, jsonError).toString(); } @Override public String marshall(JsonRpcRequest request) { JsonElement method = getMethod(request); JsonElement id = getId(request); JsonElement params = getParams(request); return getRequest(method, id, params).toString(); } private JsonObject getRequest( JsonElement jsonMethod, JsonElement jsonId, JsonElement jsonParams) { JsonObject jsonRequest = new JsonObject(); jsonRequest.addProperty("jsonrpc", "2.0"); jsonRequest.add("method", jsonMethod); if (jsonId != null) { jsonRequest.add("id", jsonId); } if (jsonParams != null) { jsonRequest.add("params", jsonParams); } return jsonRequest; } private JsonElement getParams(JsonRpcRequest request) { if (!request.hasParams()) { return null; } JsonRpcParams params = request.getParams(); return params.isSingle() ? getElement(params.getOne()) : getElements(params.getMany()); } private JsonElement getId(JsonRpcRequest request) { return request.hasId() ? new JsonPrimitive(request.getId()) : null; } private JsonPrimitive getMethod(JsonRpcRequest request) { return new JsonPrimitive(request.getMethod()); } private JsonObject getResponse( JsonElement jsonId, JsonElement jsonResult, JsonElement jsonError) { JsonObject jsonResponse = new JsonObject(); jsonResponse.addProperty("jsonrpc", "2.0"); if (jsonId != null) { jsonResponse.add("id", jsonId); } if (jsonResult != null) { jsonResponse.add("result", jsonResult); } else { jsonResponse.add("error", jsonError); } return jsonResponse; } private JsonObject getError(JsonRpcResponse response) { if (!response.hasError()) { return null; } JsonObject jsonError = new JsonObject(); JsonRpcError error = response.getError(); jsonError.add("code", new JsonPrimitive(error.getCode())); jsonError.add("message", new JsonPrimitive(error.getMessage())); return jsonError; } private JsonElement getResult(JsonRpcResponse response) { if (!response.hasResult()) { return null; } JsonRpcResult result = response.getResult(); return result.isSingle() ? getElement(result.getOne()) : getElements(result.getMany()); } private JsonElement getId(JsonRpcResponse response) { return response.hasId() ? new JsonPrimitive(response.getId()) : null; } private JsonElement getElements(List params) { JsonArray elements = new JsonArray(); params.forEach(param -> elements.add(getJsonElement(param))); return elements; } private JsonElement getElement(Object param) { JsonElement jsonElement = getJsonElement(param); if (jsonElement.isJsonObject()) { return jsonElement; } JsonArray array = new JsonArray(); array.add(jsonElement); return array; } private JsonElement getJsonElement(Object param) { if (param == null) { return JsonNull.INSTANCE; } if (param instanceof JsonElement) { return cast(param); } if (param instanceof String) { return new JsonPrimitive((String) param); } if (param instanceof Boolean) { return new JsonPrimitive((Boolean) param); } if (param instanceof Double) { return new JsonPrimitive((Double) param); } try { return jsonParser.parse(DtoFactory.getInstance().toJson(param)); } catch (IllegalArgumentException e) { return gson.toJsonTree(param); } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/impl/GsonJsonRpcQualifier.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.impl; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.Map; import java.util.stream.Collectors; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcQualifier; import org.slf4j.Logger; @Singleton public class GsonJsonRpcQualifier implements JsonRpcQualifier { private static final Logger LOGGER = getLogger(GsonJsonRpcQualifier.class); private final JsonParser jsonParser; @Inject public GsonJsonRpcQualifier(JsonParser jsonParser) { this.jsonParser = jsonParser; } @Override public boolean isValidJson(String message) { checkNotNull(message, "Message must not be null"); checkArgument(!message.isEmpty(), "Message must not be empty"); LOGGER.trace("Validating message: {}", message); try { JsonElement unused = jsonParser.parse(message); LOGGER.trace("Validation successful"); return true; } catch (JsonParseException e) { LOGGER.warn("Validation failed: {}", e.getMessage(), e); return false; } } @Override public boolean isJsonRpcRequest(String message) { checkNotNull(message, "Message must not be null"); checkArgument(!message.isEmpty(), "Message must not be empty"); LOGGER.trace("Qualifying message: " + message); JsonObject jsonObject = jsonParser.parse(message).getAsJsonObject(); LOGGER.trace( "Json keys: " + jsonObject.entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toSet())); if (jsonObject.has("method")) { LOGGER.trace("Qualified to request"); return true; } else { LOGGER.trace("Qualified to response"); return false; } } @Override public boolean isJsonRpcResponse(String message) { checkNotNull(message, "Message must not be null"); checkArgument(!message.isEmpty(), "Message must not be empty"); LOGGER.trace("Qualifying message: " + message); JsonObject jsonObject = jsonParser.parse(message).getAsJsonObject(); LOGGER.trace( "Json keys: " + jsonObject.entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toSet())); if (jsonObject.has("error") != jsonObject.has("result")) { LOGGER.trace("Qualified to response"); return true; } return false; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/impl/GsonJsonRpcUnmarshaller.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.impl; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.Collections.singletonList; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.ArrayList; import java.util.List; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcError; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcParams; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcRequest; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcResponse; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcResult; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcUnmarshaller; @Singleton public class GsonJsonRpcUnmarshaller implements JsonRpcUnmarshaller { private final JsonParser jsonParser; @Inject public GsonJsonRpcUnmarshaller(JsonParser jsonParser) { this.jsonParser = jsonParser; } @Override public List unmarshalArray(String message) { return getArray(message, jsonParser.parse(message).isJsonArray()); } @Override public JsonRpcRequest unmarshalRequest(String message) { checkNotNull(message, "Message must not be null"); checkArgument(!message.isEmpty(), "Message must not be empty"); JsonObject request = jsonParser.parse(message).getAsJsonObject(); String method = getMethod(request); String id = getId(request); JsonRpcParams params = getParams(request); return new JsonRpcRequest(id, method, params); } @Override public JsonRpcResponse unmarshalResponse(String message) { checkNotNull(message, "Message must not be null"); checkArgument(!message.isEmpty(), "Message must not be empty"); JsonObject response = jsonParser.parse(message).getAsJsonObject(); String id = getId(response); JsonRpcResult result = getResult(response); JsonRpcError error = getError(response); return new JsonRpcResponse(id, result, error); } private JsonRpcError getError(JsonObject response) { if (!response.has("error")) { return null; } int code = response.get("error").getAsJsonObject().get("code").getAsInt(); String errorMessage = response.get("error").getAsJsonObject().get("message").getAsString(); return new JsonRpcError(code, errorMessage); } private JsonRpcResult getResult(JsonObject response) { if (!response.has("result")) { return null; } JsonElement jsonElement = response.get("result"); if (!jsonElement.isJsonArray()) { return new JsonRpcResult(getInnerItem(jsonElement)); } JsonArray jsonArray = jsonElement.getAsJsonArray(); int size = jsonArray.size(); List innerResults = new ArrayList<>(size); for (int i = 0; i < size; i++) { JsonElement innerJsonElement = jsonArray.get(i); innerResults.add(getInnerItem(innerJsonElement)); } return new JsonRpcResult(innerResults); } private JsonRpcParams getParams(JsonObject jsonObject) { if (!jsonObject.has("params")) { return null; } JsonElement jsonElement = jsonObject.get("params"); if (!jsonElement.isJsonArray()) { return new JsonRpcParams(getInnerItem(jsonElement)); } JsonArray jsonArray = jsonElement.getAsJsonArray(); int size = jsonArray.size(); List innerParameters = new ArrayList<>(size); for (int i = 0; i < size; i++) { JsonElement innerJsonElement = jsonArray.get(i); innerParameters.add(getInnerItem(innerJsonElement)); } return new JsonRpcParams(innerParameters); } private String getId(JsonObject jsonObject) { return jsonObject.has("id") ? jsonObject.get("id").getAsString() : null; } private String getMethod(JsonObject jsonObject) { return jsonObject.get("method").getAsString(); } private List getArray(String message, boolean isArray) { if (!isArray) { return singletonList(message); } JsonArray jsonArray = jsonParser.parse(message).getAsJsonArray(); int size = jsonArray.size(); List result = new ArrayList<>(size); for (int i = 0; i < size; i++) { JsonElement jsonElement = jsonArray.get(i); result.add(jsonElement.toString()); } return result; } private Object getInnerItem(JsonElement jsonElement) { if (jsonElement.isJsonNull()) { return null; } if (jsonElement.isJsonObject()) { return jsonElement.getAsJsonObject(); } if (jsonElement.isJsonPrimitive()) { JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive(); if (jsonPrimitive.isNumber()) { return jsonPrimitive.getAsDouble(); } else if (jsonPrimitive.isString()) { return jsonPrimitive.getAsString(); } else { return jsonPrimitive.getAsBoolean(); } } throw new IllegalStateException("Unexpected json element type"); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/impl/JsonRpcModule.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.impl; import com.google.gson.Gson; import com.google.gson.JsonParser; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.assistedinject.FactoryModuleBuilder; import javax.inject.Singleton; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcComposer; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcMarshaller; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcQualifier; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcUnmarshaller; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; import org.eclipse.che.api.core.jsonrpc.commons.RequestProcessor; import org.eclipse.che.api.core.jsonrpc.commons.RequestProcessorConfigurationProvider; import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; import org.eclipse.che.api.core.jsonrpc.commons.TimeoutActionRunner; import org.eclipse.che.dto.server.DtoFactory; public class JsonRpcModule extends AbstractModule { @Override protected void configure() { install(new FactoryModuleBuilder().build(RequestHandlerConfigurator.class)); install(new FactoryModuleBuilder().build(RequestTransmitter.class)); bind(JsonRpcMarshaller.class).to(GsonJsonRpcMarshaller.class); bind(JsonRpcUnmarshaller.class).to(GsonJsonRpcUnmarshaller.class); bind(JsonRpcQualifier.class).to(GsonJsonRpcQualifier.class); bind(JsonRpcComposer.class).to(GsonJsonRpcComposer.class); bind(RequestProcessor.class).to(ServerSideRequestProcessor.class); bind(RequestProcessorConfigurationProvider.class) .to(ServerSideRequestProcessorConfigurator.class); bind(TimeoutActionRunner.class).to(ServerSideTimeoutActionRunner.class); } @Provides @Singleton public JsonParser jsonParser() { return new JsonParser(); } @Provides @Singleton protected Gson gson() { return DtoFactory.getInstance().getGson(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/impl/ServerSideRequestProcessor.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.impl; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.concurrent.ExecutorService; import org.eclipse.che.api.core.jsonrpc.commons.RequestProcessor; import org.eclipse.che.api.core.jsonrpc.commons.RequestProcessorConfigurationProvider; import org.eclipse.che.api.core.jsonrpc.commons.RequestProcessorConfigurationProvider.Configuration; import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext; @Singleton public class ServerSideRequestProcessor implements RequestProcessor { private final RequestProcessorConfigurationProvider requestProcessorConfigurator; @Inject public ServerSideRequestProcessor( RequestProcessorConfigurationProvider requestProcessorConfigurator) { this.requestProcessorConfigurator = requestProcessorConfigurator; } @Override public void process(String endpointId, Runnable runnable) { Configuration configuration = requestProcessorConfigurator.get(endpointId); ExecutorService executionService = configuration.getExecutorService(); executionService.execute(ThreadLocalPropagateContext.wrap(runnable)); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/impl/ServerSideRequestProcessorConfigurator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.impl; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import org.eclipse.che.api.core.jsonrpc.commons.RequestProcessorConfigurationProvider; @Singleton public class ServerSideRequestProcessorConfigurator implements RequestProcessorConfigurationProvider { private final Map configurations; @Inject public ServerSideRequestProcessorConfigurator( Set configurations) { this.configurations = configurations.stream() .collect(Collectors.toMap(Configuration::getEndpointId, Function.identity())); } @Override public Configuration get(String endpointId) { return configurations.get(endpointId); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/jsonrpc/impl/ServerSideTimeoutActionRunner.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.impl; import com.google.inject.Singleton; import java.util.Timer; import java.util.TimerTask; import org.eclipse.che.api.core.jsonrpc.commons.TimeoutActionRunner; @Singleton public class ServerSideTimeoutActionRunner implements TimeoutActionRunner { @Override public void schedule(int timeoutInMillis, Runnable runnable) { new Timer() .schedule( new TimerTask() { @Override public void run() { runnable.run(); } }, timeoutInMillis); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/notification/EventOrigin.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.notification; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author andrew00x */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface EventOrigin { String value(); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/notification/EventService.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.notification; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Dispatchers events to listeners. Usage example: * *
 *     EventService bus = new EventService();
 *     bus.subscribe(new EventSubscriber<MyEvent>() {
 *         @Override
 *         public void onEvent(MyEvent event) {
 *             // do something with event
 *         }
 *     });
 *     bus.publish(new MyEvent());
 * 
* * @author andrew00x */ @Singleton public class EventService { private static final Logger LOG = LoggerFactory.getLogger(EventService.class); private static final int CACHE_NUM = 1 << 2; private static final int CACHE_MASK = CACHE_NUM - 1; private static final int SEG_SIZE = 32; private final LoadingCache, Set>>[] typeCache; private final ConcurrentMap, Set> subscribersByEventType; @SuppressWarnings("unchecked") public EventService() { subscribersByEventType = new ConcurrentHashMap<>(); typeCache = new LoadingCache[CACHE_NUM]; for (int i = 0; i < CACHE_NUM; i++) { typeCache[i] = CacheBuilder.newBuilder() .concurrencyLevel(SEG_SIZE) .build( new CacheLoader, Set>>() { @Override public Set> load(Class eventClass) { LinkedList> parents = new LinkedList<>(); Set> classes = new HashSet<>(); parents.add(eventClass); while (!parents.isEmpty()) { Class clazz = parents.pop(); classes.add(clazz); Class parent = clazz.getSuperclass(); if (parent != null) { parents.add(parent); } Class[] interfaces = clazz.getInterfaces(); if (interfaces.length > 0) { Collections.addAll(parents, interfaces); } } return classes; } }); } } /** * Publish event {@code event}. * * @param event event * @return published event */ @SuppressWarnings("unchecked") public T publish(T event) { if (event == null) { throw new IllegalArgumentException("Null event."); } final Class eventClass = event.getClass(); for (Class clazz : typeCache[eventClass.hashCode() & CACHE_MASK].getUnchecked(eventClass)) { final Set eventSubscribers = subscribersByEventType.get(clazz); if (eventSubscribers != null && !eventSubscribers.isEmpty()) { for (EventSubscriber eventSubscriber : eventSubscribers) { try { LOG.debug("Publish event {} for {}", event, eventSubscriber); eventSubscriber.onEvent(event); } catch (RuntimeException e) { LOG.error(e.getMessage(), e); } } } } return event; } /** * Subscribe event listener. The event to subscribe to is inferred by checking the generic type * arguments of the given subscriber. * * @param subscriber event subscriber */ public void subscribe(EventSubscriber subscriber) { final Class eventType = getEventType(subscriber); doSubscribe(subscriber, eventType); } /** * Subscribe to an event. The given subscriber will be called whenever an instance of the * specified event is published. * * @param subscriber The subscriber to call when an event is published. * @param eventType The event to subscribe to. */ public void subscribe(EventSubscriber subscriber, Class eventType) { doSubscribe(subscriber, eventType); } private void doSubscribe(EventSubscriber subscriber, Class eventType) { Set entries = subscribersByEventType.get(eventType); if (entries == null) { Set newEntries = new CopyOnWriteArraySet<>(); entries = subscribersByEventType.putIfAbsent(eventType, newEntries); if (entries == null) { entries = newEntries; } } entries.add(subscriber); } /** * Unsubscribe event listener. * * @param subscriber event subscriber */ public void unsubscribe(EventSubscriber subscriber) { final Class eventType = getEventType(subscriber); doUnsubscribe(subscriber, eventType); } public void unsubscribe(EventSubscriber subscriber, Class eventType) { doUnsubscribe(subscriber, eventType); } private void doUnsubscribe(EventSubscriber subscriber, Class eventType) { final Set entries = subscribersByEventType.get(eventType); if (entries != null && !entries.isEmpty()) { boolean changed = entries.remove(subscriber); if (changed) { if (entries.isEmpty()) { subscribersByEventType.remove(eventType); } } } } private Class getEventType(EventSubscriber subscriber) { Class eventType = null; Class clazz = subscriber.getClass(); while (clazz != null && eventType == null) { for (Type type : clazz.getGenericInterfaces()) { if (type instanceof ParameterizedType) { final ParameterizedType parameterizedType = (ParameterizedType) type; final Type rawType = parameterizedType.getRawType(); if (EventSubscriber.class == rawType) { final Type[] typeArguments = parameterizedType.getActualTypeArguments(); if (typeArguments.length == 1) { if (typeArguments[0] instanceof Class) { eventType = (Class) typeArguments[0]; } } } } } clazz = clazz.getSuperclass(); } if (eventType == null) { throw new IllegalArgumentException( String.format("Unable determine type of events processed by %s", subscriber)); } return eventType; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/notification/EventSubscriber.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.notification; /** * Receives notification events from EventService. * * @author andrew00x * @see EventService */ public interface EventSubscriber { /** * Receives notification that an event has been published to the EventService. If the method * throws an unchecked exception it is ignored. */ void onEvent(T event); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/notification/InmemoryRemoteSubscriptionStorage.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.notification; import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Singleton; /** * Imnemory implementation of {@link RemoteSubscriptionStorage} * * @author Max Shaposhnik (mshaposh@redhat.com) */ @Singleton public class InmemoryRemoteSubscriptionStorage implements RemoteSubscriptionStorage { private final Map> subscriptions = new ConcurrentHashMap<>(); @Override public Set getByMethod(String method) { return subscriptions.getOrDefault(method, Collections.emptySet()); } @Override public void addSubscription(String method, RemoteSubscriptionContext remoteSubscriptionContext) { subscriptions .computeIfAbsent(method, k -> ConcurrentHashMap.newKeySet(1)) .add(remoteSubscriptionContext); } @Override public void removeSubscription(String method, String endpointId) { subscriptions .getOrDefault(method, Collections.emptySet()) .removeIf( remoteSubscriptionContext -> Objects.equals(remoteSubscriptionContext.getEndpointId(), endpointId)); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/notification/RemoteSubscriptionContext.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.notification; import java.io.Serializable; import java.util.Map; /** * Describes single event subscription with limiting scope. * * @author Max Shaposhnik (mshaposh@redhat.com) */ public class RemoteSubscriptionContext implements Serializable { private final String endpointId; private final Map scope; RemoteSubscriptionContext(String endpointId, Map scope) { this.endpointId = endpointId; this.scope = scope; } public String getEndpointId() { return endpointId; } public Map getScope() { return scope; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/notification/RemoteSubscriptionManager.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.notification; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.Map; import java.util.function.BiPredicate; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; import org.eclipse.che.api.core.notification.dto.EventSubscription; @Singleton public class RemoteSubscriptionManager { public static final String SUBSCRIBE_JSON_RPC_METHOD = "subscribe"; public static final String UNSUBSCRIBE_JSON_RPC_METHOD = "unSubscribe"; private final EventService eventService; private final RequestTransmitter requestTransmitter; private final RemoteSubscriptionStorage remoteSubscriptionStorage; @Inject public RemoteSubscriptionManager( EventService eventService, RequestTransmitter requestTransmitter, RemoteSubscriptionStorage remoteSubscriptionStorage) { this.eventService = eventService; this.requestTransmitter = requestTransmitter; this.remoteSubscriptionStorage = remoteSubscriptionStorage; } @Inject private void configureSubscription(RequestHandlerConfigurator requestHandlerConfigurator) { requestHandlerConfigurator .newConfiguration() .methodName(SUBSCRIBE_JSON_RPC_METHOD) .paramsAsDto(EventSubscription.class) .noResult() .withBiConsumer(this::consumeSubscriptionRequest); requestHandlerConfigurator .newConfiguration() .methodName(UNSUBSCRIBE_JSON_RPC_METHOD) .paramsAsDto(EventSubscription.class) .noResult() .withBiConsumer(this::consumeUnSubscriptionRequest); } public void register( String method, Class eventType, BiPredicate> biPredicate) { eventService.subscribe( event -> remoteSubscriptionStorage.getByMethod(method).stream() .filter(context -> biPredicate.test(event, context.getScope())) .forEach(context -> transmit(context.getEndpointId(), method, event)), eventType); } private void consumeSubscriptionRequest(String endpointId, EventSubscription eventSubscription) { remoteSubscriptionStorage.addSubscription( eventSubscription.getMethod(), new RemoteSubscriptionContext(endpointId, eventSubscription.getScope())); } private void consumeUnSubscriptionRequest( String endpointId, EventSubscription eventSubscription) { remoteSubscriptionStorage.removeSubscription(eventSubscription.getMethod(), endpointId); } private void transmit(String endpointId, String method, T event) { requestTransmitter .newRequest() .endpointId(endpointId) .methodName(method) .paramsAsDto(event) .sendAndSkipResult(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/notification/RemoteSubscriptionStorage.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.notification; import java.util.Set; /** * Method - based storage of event subscriptions. * * @author Max Shaposhnik (mshaposh@redhat.com) */ public interface RemoteSubscriptionStorage { /** * Returns all active subscriptions for the given method. It is recommended for implementations to * return copy of the stored set, so this method should not be used for modifying operations. * * @param method Method name * @return active subscriptions to this method */ Set getByMethod(String method); /** * Adds new subscription to the given method subscriptions list * * @param method Method name * @param remoteSubscriptionContext new subscription */ void addSubscription(String method, RemoteSubscriptionContext remoteSubscriptionContext); /** * Removes particular subscription from the given method subscriptions list * * @param method Method name * @param endpointId id of endpoint to remove */ void removeSubscription(String method, String endpointId); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/notification/dto/EventSubscription.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.notification.dto; import java.util.Map; import org.eclipse.che.dto.shared.DTO; @DTO public interface EventSubscription { String getMethod(); EventSubscription withMethod(String method); Map getScope(); EventSubscription withScope(Map scope); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/ApiExceptionMapper.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.dto.server.DtoFactory; /** * @author andrew00x * @author gazarenkov */ @Provider @Singleton public class ApiExceptionMapper implements ExceptionMapper { @Override public Response toResponse(ApiException exception) { if (exception instanceof ForbiddenException) return Response.status(Response.Status.FORBIDDEN) .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); else if (exception instanceof NotFoundException) return Response.status(Response.Status.NOT_FOUND) .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); else if (exception instanceof UnauthorizedException) return Response.status(Response.Status.UNAUTHORIZED) .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); else if (exception instanceof BadRequestException) return Response.status(Response.Status.BAD_REQUEST) .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); else if (exception instanceof ConflictException) return Response.status(Response.Status.CONFLICT) .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); else if (exception instanceof ServerException) return Response.serverError() .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); else return Response.serverError() .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/ApiInfoProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import java.io.InputStream; import java.net.URL; import java.util.jar.Attributes; import java.util.jar.Manifest; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.eclipse.che.api.core.rest.shared.dto.ApiInfo; import org.eclipse.che.dto.server.DtoFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides api info by reading it from war manifest. * * @author Max Shaposhnyk */ @Singleton public class ApiInfoProvider implements Provider { private static final Logger LOG = LoggerFactory.getLogger(ApiInfoProvider.class); private ApiInfo apiInfo; @Inject public ApiInfoProvider(@Named("che.product.build_info") String buildInfo) { this.apiInfo = readApiInfo(buildInfo); } @Override public ApiInfo get() { return apiInfo; } private ApiInfo readApiInfo(String buildInfo) { try { // calculate path to MANIFEST.MF in the jar with ApiInfo.class Class clazz = ApiInfo.class; String classPath = clazz.getResource(clazz.getSimpleName() + ".class").toString(); String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF"; try (InputStream manifestInputStream = new URL(manifestPath).openStream()) { final Manifest manifest = new Manifest(manifestInputStream); final Attributes mainAttributes = manifest.getMainAttributes(); final DtoFactory dtoFactory = DtoFactory.getInstance(); return dtoFactory .createDto(ApiInfo.class) .withSpecificationVendor(mainAttributes.getValue("Specification-Vendor")) .withImplementationVendor(mainAttributes.getValue("Implementation-Vendor")) .withSpecificationTitle("Che REST API") .withSpecificationVersion(mainAttributes.getValue("Specification-Version")) .withImplementationVersion(mainAttributes.getValue("Implementation-Version")) .withScmRevision(mainAttributes.getValue("SCM-Revision")) .withBuildInfo(buildInfo); } } catch (Exception e) { LOG.error("Unable to read API information. Error: " + e.getMessage(), e); throw new RuntimeException("Unable to read API information", e); } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/ApiInfoService.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static java.util.stream.Collectors.toList; import jakarta.servlet.ServletContext; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import java.util.function.Function; import javax.inject.Inject; import org.eclipse.che.api.core.rest.annotations.OPTIONS; import org.eclipse.che.api.core.rest.shared.dto.ApiInfo; import org.eclipse.che.commons.annotation.Nullable; import org.everrest.core.ObjectFactory; import org.everrest.core.ResourceBinder; import org.everrest.core.resource.ResourceDescriptor; import org.everrest.services.RestServicesList.RootResource; import org.everrest.services.RestServicesList.RootResourcesList; /** * @author andrew00x */ @Path("/") public class ApiInfoService { @Inject private ApiInfo apiInfo; @OPTIONS public ApiInfo info() { return apiInfo; } @GET @Produces({MediaType.APPLICATION_JSON}) public RootResourcesList listJSON(@Context ServletContext context) { ResourceBinder binder = (ResourceBinder) context.getAttribute(ResourceBinder.class.getName()); return new RootResourcesList( binder.getResources().stream() .map( new Function, RootResource>() { @Nullable @Override public RootResource apply(ObjectFactory input) { ResourceDescriptor descriptor = input.getObjectModel(); return new RootResource( descriptor.getObjectClass().getName(), // descriptor.getPathValue().getPath(), // descriptor.getUriPattern().getRegex()); } }) .collect(toList())); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/AuthenticationExceptionMapper.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; import javax.inject.Singleton; import org.eclipse.che.api.core.AuthenticationException; import org.eclipse.che.dto.server.DtoFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * jakarta.ws.rs.ext.ExceptionMapper for AuthenticationException * * @author Alexander Garagatyi */ @Provider @Singleton public class AuthenticationExceptionMapper implements ExceptionMapper { private static final Logger LOG = LoggerFactory.getLogger(AuthenticationExceptionMapper.class); @Override public Response toResponse(AuthenticationException exception) { LOG.debug(exception.getLocalizedMessage()); int responseStatus = exception.getResponseStatus(); String message = exception.getMessage(); if (message != null) { return Response.status(responseStatus) .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); } return Response.status(responseStatus).build(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/CheJsonProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import com.google.gson.reflect.TypeToken; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Produces; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.MessageBodyReader; import jakarta.ws.rs.ext.MessageBodyWriter; import jakarta.ws.rs.ext.Provider; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.dto.server.JsonSerializable; import org.eclipse.che.dto.shared.DTO; import org.everrest.core.impl.provider.JsonEntityProvider; /** * Implementation of {@link MessageBodyReader} and {@link MessageBodyWriter} needed for binding JSON * content to and from Java Objects. * * @author andrew00x * @see DTO * @see DtoFactory */ @Singleton @Provider @Produces({MediaType.APPLICATION_JSON}) @Consumes({MediaType.APPLICATION_JSON}) public class CheJsonProvider implements MessageBodyReader, MessageBodyWriter { private Set ignoredClasses; private final JsonEntityProvider delegate = new JsonEntityProvider<>(); private final Type listOfJsonSerializableType = new TypeToken>() {}.getType(); @Inject public CheJsonProvider(@Nullable @Named("che.json.ignored_classes") Set ignoredClasses) { this.ignoredClasses = ignoredClasses == null ? new LinkedHashSet() : new LinkedHashSet<>(ignoredClasses); } @SuppressWarnings("unchecked") @Override public boolean isWriteable( Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return !ignoredClasses.contains(type) && (type.isAnnotationPresent(DTO.class) || delegate.isWriteable(type, genericType, annotations, mediaType)); } @Override public long getSize( T t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } @SuppressWarnings("unchecked") @Override public void writeTo( T t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { // Add Cache-Control before start write body. httpHeaders.putSingle(HttpHeaders.CACHE_CONTROL, "public, no-cache, no-store, no-transform"); if (t instanceof JsonSerializable) { try (Writer w = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8)) { ((JsonSerializable) t).toJson(w); } } else if (isDtoList(type, genericType, t)) { try (Writer w = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8)) { DtoFactory.getInstance().getGson().toJson(t, listOfJsonSerializableType, w); } } else { delegate.writeTo(t, type, genericType, annotations, mediaType, httpHeaders, entityStream); } } @SuppressWarnings("unchecked") @Override public boolean isReadable( Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return !ignoredClasses.contains(type) && (type.isAnnotationPresent(DTO.class) || delegate.isReadable(type, genericType, annotations, mediaType)); } @SuppressWarnings("unchecked") @Override public T readFrom( Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { if (type.isAnnotationPresent(DTO.class)) { return DtoFactory.getInstance().createDtoFromJson(entityStream, type); } else if (isDtoList(type, genericType, null)) { ParameterizedType parameterizedType = (ParameterizedType) genericType; Type elementType = parameterizedType.getActualTypeArguments()[0]; return (T) DtoFactory.getInstance().createListDtoFromJson(entityStream, (Class) elementType); } return (T) delegate.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream); } /** * Get Set of classes that we never try to serialize or deserialize. Returned Set is mutable and * new classes may be added in ignored Set. */ public Set getIgnoredClasses() { return ignoredClasses; } /** Checks if provided object is a list of DTO or serializable objects. */ private static boolean isDtoList(Class type, Type genericType, T t) { if (!List.class.isAssignableFrom(type)) { return false; } if (genericType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) genericType; Type elementType = parameterizedType.getActualTypeArguments()[0]; return elementType instanceof Class && ((Class) elementType).isAnnotationPresent(DTO.class); } else if (t instanceof List && type.equals(genericType)) { List list = (List) t; return !list.isEmpty() && list.iterator().next() instanceof JsonSerializable; } return false; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/Constants.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; /** * @author andrew00x */ public final class Constants { public static final String API_VERSION = "1.0"; private Constants() {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/CoreRestModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; import org.eclipse.che.api.core.rest.shared.dto.ApiInfo; import org.eclipse.che.api.core.util.ApiInfoLogInformer; /** * @author andrew00x */ public class CoreRestModule extends AbstractModule { @Override protected void configure() { bind(CheJsonProvider.class); bind(ApiExceptionMapper.class); bind(RuntimeExceptionMapper.class); bind(ApiInfo.class).toProvider(ApiInfoProvider.class); bind(ApiInfoLogInformer.class).asEagerSingleton(); Multibinder.newSetBinder(binder(), Class.class, Names.named("che.json.ignored_classes")); bind(WebApplicationExceptionMapper.class); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/DefaultHttpJsonRequest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static com.google.common.base.Strings.isNullOrEmpty; import static java.util.Objects.requireNonNull; import com.google.common.io.CharStreams; import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.dto.server.JsonArrayImpl; import org.eclipse.che.dto.server.JsonSerializable; import org.eclipse.che.dto.server.JsonStringMapImpl; /** * Simple implementation of {@link HttpJsonRequest} based on {@link HttpURLConnection}. * *

The implementation is not thread-safe, instance of this class must be created each time when * it's needed. * *

The instance of this request is reusable, which means that it is possible to call {@link * #request()} method more than one time per instance * * @author Yevhenii Voevodin * @see DefaultHttpJsonRequestFactory */ public class DefaultHttpJsonRequest implements HttpJsonRequest { private static final int DEFAULT_QUERY_PARAMS_LIST_SIZE = 5; private static final Object[] EMPTY_ARRAY = new Object[0]; private final String url; private int timeout; private String method; private Object body; private List> queryParams; private List> headers; private String authorizationHeaderValue; protected DefaultHttpJsonRequest(String url, String method) { this.url = requireNonNull(url, "Required non-null url"); this.method = method; } protected DefaultHttpJsonRequest(String url) { this(url, HttpMethod.GET); } protected DefaultHttpJsonRequest(Link link) { this(requireNonNull(link, "Required non-null link").getHref(), link.getMethod()); } @Override public HttpJsonRequest setMethod(@NotNull String method) { this.method = requireNonNull(method, "Required non-null http method"); return this; } @Override public HttpJsonRequest setBody(@NotNull Object body) { this.body = requireNonNull(body, "Required non-null body"); return this; } @Override public HttpJsonRequest setBody(@NotNull Map map) { this.body = new JsonStringMapImpl<>(requireNonNull(map, "Required non-null body")); return this; } @Override public HttpJsonRequest setBody(@NotNull List list) { this.body = new JsonArrayImpl<>(requireNonNull(list, "Required non-null body")); return this; } @Override public HttpJsonRequest addQueryParam(@NotNull String name, @NotNull Object value) { requireNonNull(name, "Required non-null query parameter name"); requireNonNull(value, "Required non-null query parameter value"); if (queryParams == null) { queryParams = new ArrayList<>(DEFAULT_QUERY_PARAMS_LIST_SIZE); } queryParams.add(Pair.of(name, value)); return this; } public HttpJsonRequest addHeader(@NotNull String name, @NotNull String value) { requireNonNull(name, "Required non-null header name"); requireNonNull(value, "Required non-null header value"); if (headers == null) { headers = new ArrayList<>(); } headers.add(Pair.of(name, value)); return this; } @Override public HttpJsonRequest setAuthorizationHeader(@NotNull String value) { requireNonNull(value, "Required non-null header value"); authorizationHeaderValue = value; return this; } @Override public HttpJsonRequest setTimeout(int timeout) { this.timeout = timeout; return this; } @Override public String getUrl() { final UriBuilder ub = UriBuilder.fromUri(url); if (queryParams != null) { for (Pair parameter : queryParams) { ub.queryParam(parameter.first, parameter.second); } } return ub.build().toString(); } @Override public HttpJsonResponse request() throws IOException, ServerException, UnauthorizedException, ForbiddenException, NotFoundException, ConflictException, BadRequestException { if (method == null) { throw new IllegalStateException("Could not perform request, request method was not set."); } return doRequest(timeout, url, method, body, queryParams, authorizationHeaderValue, headers); } /** * Makes this request using {@link HttpURLConnection}. * *

Uses {@link HttpHeaders#AUTHORIZATION} header with value from {@link EnvironmentContext}. *
* uses {@link HttpHeaders#ACCEPT} header with "application/json" value.
* Encodes query parameters in "UTF-8". * * @param timeout request timeout, used only if it is greater than 0 * @param url request url * @param method request method * @param body request body, must be instance of {@link JsonSerializable} * @param parameters query parameters, may be null * @param authorizationHeaderValue value of authorization header, may be null * @return response to this request * @throws IOException when connection content type is not "application/json" * @throws ServerException when response code is 500 or it is different from 400, 401, 403, 404, * 409 * @throws ForbiddenException when response code is 403 * @throws NotFoundException when response code is 404 * @throws UnauthorizedException when response code is 401 * @throws ConflictException when response code is 409 * @throws BadRequestException when response code is 400 */ protected DefaultHttpJsonResponse doRequest( int timeout, String url, String method, Object body, List> parameters, String authorizationHeaderValue, List> headers) throws IOException, ServerException, ForbiddenException, NotFoundException, UnauthorizedException, ConflictException, BadRequestException { final String authToken = EnvironmentContext.getCurrent().getSubject().getToken(); final boolean hasQueryParams = parameters != null && !parameters.isEmpty(); if (hasQueryParams || authToken != null) { final UriBuilder ub = UriBuilder.fromUri(url); // remove sensitive information from url. ub.replaceQueryParam("token", EMPTY_ARRAY); if (hasQueryParams) { for (Pair parameter : parameters) { ub.queryParam(parameter.first, parameter.second); } } url = ub.build().toString(); } final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setConnectTimeout(timeout > 0 ? timeout : 60000); conn.setReadTimeout(timeout > 0 ? timeout : 60000); final boolean hasHeaders = headers != null && !headers.isEmpty(); if (hasHeaders) { for (Pair header : headers) { conn.setRequestProperty(header.first, header.second); } } try { conn.setRequestMethod(method); // drop a hint for server side that we want to receive application/json conn.addRequestProperty(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); if (!isNullOrEmpty(authorizationHeaderValue)) { conn.setRequestProperty(HttpHeaders.AUTHORIZATION, authorizationHeaderValue); } else if (authToken != null) { conn.setRequestProperty(HttpHeaders.AUTHORIZATION, authToken); } if (body != null) { conn.addRequestProperty(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); conn.setDoOutput(true); if (HttpMethod.DELETE.equals(method)) { // to avoid jdk bug described here // http://bugs.java.com/view_bug.do?bug_id=7157360 conn.setRequestMethod(HttpMethod.POST); conn.setRequestProperty("X-HTTP-Method-Override", HttpMethod.DELETE); } try (OutputStream output = conn.getOutputStream()) { output.write(DtoFactory.getInstance().toJson(body).getBytes()); } } final int responseCode = conn.getResponseCode(); if ((responseCode / 100) != 2) { InputStream in = conn.getErrorStream(); if (in == null) { in = conn.getInputStream(); } final String str; try (Reader reader = new InputStreamReader(in)) { str = CharStreams.toString(reader); } final String contentType = conn.getContentType(); if (contentType != null && (contentType.startsWith(MediaType.APPLICATION_JSON) || contentType.startsWith("application/vnd.api+json"))) { final ServiceError serviceError = DtoFactory.getInstance().createDtoFromJson(str, ServiceError.class); if (serviceError.getMessage() != null) { if (responseCode == Response.Status.FORBIDDEN.getStatusCode()) { throw new ForbiddenException(serviceError); } else if (responseCode == Response.Status.NOT_FOUND.getStatusCode()) { throw new NotFoundException(serviceError); } else if (responseCode == Response.Status.UNAUTHORIZED.getStatusCode()) { throw new UnauthorizedException(serviceError); } else if (responseCode == Response.Status.CONFLICT.getStatusCode()) { throw new ConflictException(serviceError); } else if (responseCode == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) { throw new ServerException(serviceError); } else if (responseCode == Response.Status.BAD_REQUEST.getStatusCode()) { throw new BadRequestException(serviceError); } throw new ServerException(serviceError); } } // Can't parse content as json or content has format other we expect for error. throw new IOException( String.format( "Failed access: %s, method: %s, response code: %d, message: %s", UriBuilder.fromUri(url).replaceQuery("token").build(), method, responseCode, str)); } final String contentType = conn.getContentType(); if (responseCode != HttpURLConnection.HTTP_NO_CONTENT && contentType != null && !(contentType.startsWith(MediaType.APPLICATION_JSON) || contentType.startsWith("application/vnd.api+json"))) { throw new IOException(conn.getResponseMessage()); } try (Reader reader = new InputStreamReader(conn.getInputStream())) { return new DefaultHttpJsonResponse( CharStreams.toString(reader), responseCode, conn.getHeaderFields()); } } finally { conn.disconnect(); } } @Override public String toString() { return "DefaultHttpJsonRequest{" + "url='" + url + '\'' + ", timeout=" + timeout + ", method='" + method + '\'' + ", body=" + body + ", queryParams=" + queryParams + '}'; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/DefaultHttpJsonRequestFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import jakarta.validation.constraints.NotNull; import javax.inject.Singleton; import org.eclipse.che.api.core.rest.shared.dto.Link; /** * Creates {@link DefaultHttpJsonRequest} instances. * * @author Yevhenii Voevodin */ @Singleton public class DefaultHttpJsonRequestFactory implements HttpJsonRequestFactory { @Override public HttpJsonRequest fromUrl(@NotNull String url) { return new DefaultHttpJsonRequest(url); } @Override public HttpJsonRequest fromLink(@NotNull Link link) { return new DefaultHttpJsonRequest(link); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/DefaultHttpJsonResponse.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableMap; import static java.util.Objects.requireNonNull; import com.google.gson.reflect.TypeToken; import java.io.IOException; import java.lang.reflect.Type; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.eclipse.che.commons.json.JsonHelper; import org.eclipse.che.commons.json.JsonParseException; import org.eclipse.che.dto.server.DtoFactory; /** * Default implementation of {@link HttpJsonResponse}. * * @author Yevhenii Voevodin */ public class DefaultHttpJsonResponse implements HttpJsonResponse { private static final Type STRING_MAP_TYPE = new TypeToken>() {}.getType(); private final String responseBody; private final int responseCode; private final Map> headers; protected DefaultHttpJsonResponse(String response, int responseCode) { this.responseBody = response; this.responseCode = responseCode; this.headers = Collections.emptyMap(); } protected DefaultHttpJsonResponse( String response, int responseCode, Map> headers) { this.responseBody = response; this.responseCode = responseCode; this.headers = unmodifiableMap( headers.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> unmodifiableList(e.getValue())))); } @Override public String asString() { return responseBody; } @Override public T asDto(Class dtoInterface) { requireNonNull(dtoInterface, "Required non-null dto interface"); return DtoFactory.getInstance().createDtoFromJson(responseBody, dtoInterface); } @Override public List asList(Class dtoInterface) { requireNonNull(dtoInterface, "Required non-null dto interface"); return DtoFactory.getInstance().createListDtoFromJson(responseBody, dtoInterface); } @Override @SuppressWarnings("unchecked") public Map asProperties() throws IOException { return as(Map.class, STRING_MAP_TYPE); } @Override public T as(Class clazz, Type genericType) throws IOException { requireNonNull(clazz, "Required non-null class"); try { return JsonHelper.fromJson(responseBody, clazz, genericType); } catch (JsonParseException jsonEx) { throw new IOException(jsonEx.getLocalizedMessage(), jsonEx); } } @Override public Map> getHeaders() { return headers; } @Override public int getResponseCode() { return responseCode; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/DownloadFileResponseFilter.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static org.eclipse.che.api.core.rest.DownloadFileResponseFilter.EntityType.JSON_SERIALIZABLE; import static org.eclipse.che.api.core.rest.DownloadFileResponseFilter.EntityType.STRING; import static org.eclipse.che.api.core.rest.DownloadFileResponseFilter.EntityType.UNKNOWN; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Request; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; import java.util.List; import org.eclipse.che.dto.server.JsonSerializable; /** * Abstract Filter used to provide the json result as a file download operation. It is applying on * GET method and JSON content type only. * * @author Florent Benoit */ public abstract class DownloadFileResponseFilter { /** Entity type that we will be able to handle. */ public enum EntityType { JSON_SERIALIZABLE, STRING, UNKNOWN } /** * Query parameter used to ask to specify headers that will propose JSON object to be downloaded. */ public static final String QUERY_DOWNLOAD_PARAMETER = "downloadAsFile"; /** * Check if we need to apply a filter * * @param request * @return */ protected String getFileName( Request request, MediaType mediaType, UriInfo uriInfo, int responseStatus) { // manage only GET requests if (!HttpMethod.GET.equals(request.getMethod())) { return null; } // manage only OK code if (Response.Status.OK.getStatusCode() != responseStatus) { return null; } // Only handle JSON content if (!MediaType.APPLICATION_JSON_TYPE.equals(mediaType)) { return null; } // check if parameter filename is given MultivaluedMap queryParameters = uriInfo.getQueryParameters(); return queryParameters.getFirst(QUERY_DOWNLOAD_PARAMETER); } /** * Check if entity is compliant with our filter * * @param entity the entity embedded in response. * @return true if it's a type that we can handle */ protected boolean hasCompliantEntity(Object entity) { // no entity, skip if (entity == null) { return false; } // Check entity type if (entity instanceof List) { List entities = (List) entity; for (Object simpleEntity : entities) { if (getElementType(simpleEntity) == UNKNOWN) { return false; } } } else if (getElementType(entity) == UNKNOWN) { // unknown entity type, will not configure it as a download return false; } return true; } /** * Helper method for getting the type of the JSON entity * * @param entity the entity object * @return the type of the element */ protected EntityType getElementType(Object entity) { if (JsonSerializable.class.isAssignableFrom(entity.getClass())) { return JSON_SERIALIZABLE; } if (String.class.isAssignableFrom(entity.getClass())) { return STRING; } return UNKNOWN; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/HttpJsonRequest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import com.google.common.annotations.Beta; import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.HttpMethod; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Objects; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.dto.server.JsonSerializable; /** * Defines simple set of methods for requesting json objects. * *

Unlike {@link HttpRequestHelper} - provides builder-like style for building requests * and getting responses. * *

Simple use-cases: * *

{@code
 *  // starting new workspace
 *  requestFactory.fromUri(apiEndpoint + "/workspace/" + id + "/runtime")
 *                .setMethod("POST")
 *                .addQueryParam("envName", envName)
 *                .addQueryParam("accountId", accountId)
 *                .request();
 *
 *  // getting user preferences
 *  Map prefs = requestFactory.fromUri(apiEndpoint + "/profile/prefs")
 *                                            .setMethod("GET")
 *                                            .request()
 *                                            .asProperties();
 *
 * // getting workspace
 * UsersWorkspaceDto workspace = requestFactory.fromLink(getWorkspaceLink)
 *                                             .request()
 *                                             .asDto(UsersWorkspaceDto.class);
 * }
* *

Do not use this class for requesting content different from "application/json". * * @author Yevhenii Voevodin * @see HttpJsonRequestFactory */ @Beta public interface HttpJsonRequest { /** * Sets http method to use in this request(e.g. {@link jakarta.ws.rs.HttpMethod#GET GET}). * * @param method http method * @return this request instance * @throws NullPointerException when {@code method} is null */ HttpJsonRequest setMethod(@NotNull String method); /** * Sets request body. * * @param body should be instance of {@link JsonSerializable} * @return this request instance * @throws NullPointerException when {@code body} is null */ HttpJsonRequest setBody(@NotNull Object body); /** * Sets given string map as request body. * * @param map request body * @return this request instance * @throws NullPointerException when {@code body} is null */ HttpJsonRequest setBody(@NotNull Map map); /** * Sets given list as request body. * *

List should contain only {@link JsonSerializable} elements * * @param list list of {@link JsonSerializable} * @return this request instance * @throws NullPointerException when {@code body} is null */ HttpJsonRequest setBody(@NotNull List list); /** * Adds query parameter to the request. * * @param name query parameter name * @param value query parameter value * @return this request instance * @throws NullPointerException when either name or value is null */ HttpJsonRequest addQueryParam(@NotNull String name, @NotNull Object value); /** * Adds header to the request. * * @param name header name * @param value header value * @return this request instance * @throws NullPointerException when either header name or value is null */ HttpJsonRequest addHeader(@NotNull String name, @NotNull String value); /** * Adds authorization header to the request. * * @param value authorization header value * @return this request instance * @throws NullPointerException when value is null */ HttpJsonRequest setAuthorizationHeader(@NotNull String value); /** * Sets request timeout in milliseconds. * * @param timeoutMs request timeout in milliseconds * @return this request instance */ HttpJsonRequest setTimeout(int timeoutMs); /** Returns HTTP URL built from source URL and query parameters added to this request. */ String getUrl(); /** * Makes http request with content type "application/json" and authorization headers based on * current {@link EnvironmentContext#getCurrent() context}. * * @return {@link HttpJsonResponse} instance which represents response of this request * @throws IOException when server response content type is different from "application/json"(Not * acceptable) * @throws IOException when any io error occurs * @throws ServerException when response code is 500 or it is different from 400, 401, 403, 404, * 409 * @throws ForbiddenException when response code is 403 * @throws NotFoundException when response code is 404 * @throws UnauthorizedException when response code is 401 * @throws ConflictException when response code is 409 * @throws BadRequestException when response code is 400 */ HttpJsonResponse request() throws IOException, ServerException, UnauthorizedException, ForbiddenException, NotFoundException, ConflictException, BadRequestException; /** * Uses {@link HttpMethod#GET} as a request method. * * @return this request instance */ default HttpJsonRequest useGetMethod() { return setMethod(HttpMethod.GET); } /** * Uses {@link HttpMethod#OPTIONS} as a request method. * * @return this request instance */ default HttpJsonRequest useOptionsMethod() { return setMethod(HttpMethod.OPTIONS); } /** * Uses {@link HttpMethod#POST} as a request method. * * @return this request instance */ default HttpJsonRequest usePostMethod() { return setMethod(HttpMethod.POST); } /** * Uses {@link HttpMethod#DELETE} as a request method. * * @return this request instance */ default HttpJsonRequest useDeleteMethod() { return setMethod(HttpMethod.DELETE); } /** * Uses {@link HttpMethod#PUT} as a request method. * * @return this request instance */ default HttpJsonRequest usePutMethod() { return setMethod(HttpMethod.PUT); } /** * Adds set of query parameters to this request. * * @param params query parameters map * @return this request instance */ default HttpJsonRequest addQueryParams(@NotNull Map params) { Objects.requireNonNull(params, "Non-null query parameters required"); params.forEach(this::addQueryParam); return this; } /** * Adds set of headers to this request. * * @param headers map with headers * @return this request instance */ default HttpJsonRequest addHeaders(@NotNull Map headers) { Objects.requireNonNull(headers, "Required non-null headers"); headers.forEach(this::addHeader); return this; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/HttpJsonRequestFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import com.google.common.annotations.Beta; import com.google.inject.ImplementedBy; import jakarta.validation.constraints.NotNull; import org.eclipse.che.api.core.rest.shared.dto.Link; /** * Factory for {@link HttpJsonRequest} instances. * * @author Yevhenii Voevodin */ @Beta @ImplementedBy(DefaultHttpJsonRequestFactory.class) public interface HttpJsonRequestFactory { /** * Creates {@link HttpJsonRequest} based on {@code url}. * * @param url request url * @return new instance of {@link HttpJsonRequest} * @throws NullPointerException when url is null */ HttpJsonRequest fromUrl(@NotNull String url); /** * Crates {@link HttpJsonRequest} based on {@code link}. * * @param link request link * @return new instance of {@link HttpJsonRequest} * @throws NullPointerException when link is null */ HttpJsonRequest fromLink(@NotNull Link link); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/HttpJsonResponse.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import com.google.common.annotations.Beta; import jakarta.validation.constraints.NotNull; import java.io.IOException; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.dto.shared.DTO; /** * Defines response of {@link HttpJsonRequest}. * * @author Yevhenii Voevodin */ @Beta public interface HttpJsonResponse { /** Returns a response code. */ int getResponseCode(); /** * Returns {@link HttpJsonRequest} response body as a string, if response doesn't contain body - * empty line will be returned. * *

Example: * *

{@code
   * String recipeContent = requestFactory.fromLink(getRecipeContentLink).requestString();
   * }
*/ String asString(); /** * Returns response body as instance of {@link DTO} object. * *

Example: * *

{@code
   * UserDto user = requestFactory.fromUri(apiEndpoint + "/user")
   *                              .useGetMethod()
   *                              .request()
   *                              .asDto(UserDto.class);
   * }
* * @param dtoInterface dto interface class * @return response as a dto instance */ T asDto(@NotNull Class dtoInterface); /** * Returns result as a list of {@link DTO} objects. * *

Example: * *

{@code
   * List workspaces = requestFactory.fromUri(apiEndpoint + "/workspace/config")
   *                                                    .useGetMethod()
   *                                                    .request()
   *                                                    .asList(UsersWorkspaceDto.class);
   * }
* * @param dtoInterface dto interface class * @return response as list of dto instances */ List asList(@NotNull Class dtoInterface); /** * Returns response body as a string map. * *

Example: * *

{@code
   * Map prefs = requestFactory.fromUri(apiEndpoint + "/profile/prefs")
   *                                           .useGetRequest()
   *                                           .addQueryParam("filter", ".*che.*")
   *                                           .request()
   *                                           .asProperties();
   * }
* * @return response as a {@code Map} * @throws IOException when response body is not valid json */ Map asProperties() throws IOException; /** * Returns response as a given type. * *

Example: * *

{@code
   * Set workspaces = requestFactory.fromUri(apiEndpoint + "/workspace/config")
   *                                                   .useGetMethod()
   *                                                   .request()
   *                                                   .as(Set.class, new TypeToken>() {}.getType());
   * }
* * @param raw type of the response * @param clazz response class * @param genericType generic type of the response, if needed * @return response parsed to the given type * @throws IOException when response body is not valid json */ T as(@NotNull Class clazz, @Nullable Type genericType) throws IOException; /** * Returns an unmodifiable Map of the header fields. The Map keys are Strings that represent the * response-header field names. Each Map value is an unmodifiable List of Strings that represents * the corresponding field values. */ Map> getHeaders(); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/HttpOutputMessage.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; /** * HTTP output message. * * @author andrew00x */ public interface HttpOutputMessage extends OutputProvider { /** Set HTTP status. */ void setStatus(int status); /** * Shortcut to set content-type header. The same may be none with method {@link * #setHttpHeader(String, String)}. */ void setContentType(String contentType); /** * Add HTTP header. * * @param name name of header * @param value value of header */ void addHttpHeader(String name, String value); /** * Set HTTP header. If the header had already been set, the new value overwrites the previous one. * * @param name name of header * @param value value of header */ void setHttpHeader(String name, String value); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/HttpRequestHelper.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import com.google.common.io.CharStreams; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.net.HttpURLConnection; import java.net.URL; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.dto.server.DtoFactory; /** * Provides helper method to send HTTP requests. * * @author andrew00x */ public class HttpRequestHelper { private HttpRequestHelper() {} public static HttpJsonRequest createJsonRequest(String url) { return new DefaultHttpJsonRequest(url); } public static String requestString( String url, String method, Object body, Pair... parameters) throws IOException, ServerException, ForbiddenException, NotFoundException, UnauthorizedException, ConflictException { return requestString(-1, url, method, body, parameters); } public static String requestString( int timeout, String url, String method, Object body, Pair... parameters) throws IOException, ServerException, ForbiddenException, NotFoundException, UnauthorizedException, ConflictException { final String authToken = EnvironmentContext.getCurrent().getSubject().getToken(); if ((parameters != null && parameters.length > 0) || authToken != null) { final UriBuilder ub = UriBuilder.fromUri(url); // remove sensitive information from url. ub.replaceQueryParam("token", null); if (parameters != null && parameters.length > 0) { for (Pair parameter : parameters) { ub.queryParam(parameter.first, parameter.second); } } url = ub.build().toString(); } final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setConnectTimeout(timeout > 0 ? timeout : 60000); conn.setReadTimeout(timeout > 0 ? timeout : 60000); try { conn.setRequestMethod(method); // drop a hint for server side that we want to receive application/json // conn.addRequestProperty(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); if (authToken != null) { conn.setRequestProperty(HttpHeaders.AUTHORIZATION, authToken); } if (body != null) { // conn.addRequestProperty(HttpHeaders.CONTENT_TYPE, // MediaType.APPLICATION_JSON); conn.setDoOutput(true); if (HttpMethod.DELETE.equals(method)) { // to avoid jdk bug described here // http://bugs.java.com/view_bug.do?bug_id=7157360 conn.setRequestMethod(HttpMethod.POST); conn.setRequestProperty("X-HTTP-Method-Override", HttpMethod.DELETE); } try (OutputStream output = conn.getOutputStream()) { output.write(DtoFactory.getInstance().toJson(body).getBytes()); } } final int responseCode = conn.getResponseCode(); if ((responseCode / 100) != 2) { InputStream in = conn.getErrorStream(); if (in == null) { in = conn.getInputStream(); } final String str; try (Reader reader = new InputStreamReader(in)) { str = CharStreams.toString(reader); } final String contentType = conn.getContentType(); if (contentType != null && contentType.startsWith(MediaType.APPLICATION_JSON)) { final ServiceError serviceError = DtoFactory.getInstance().createDtoFromJson(str, ServiceError.class); if (serviceError.getMessage() != null) { if (responseCode == Response.Status.FORBIDDEN.getStatusCode()) { throw new ForbiddenException(serviceError); } else if (responseCode == Response.Status.NOT_FOUND.getStatusCode()) { throw new NotFoundException(serviceError); } else if (responseCode == Response.Status.UNAUTHORIZED.getStatusCode()) { throw new UnauthorizedException(serviceError); } else if (responseCode == Response.Status.CONFLICT.getStatusCode()) { throw new ConflictException(serviceError); } else if (responseCode == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) { throw new ServerException(serviceError); } throw new ServerException(serviceError); } } // Can't parse content as json or content has format other we expect for error. throw new IOException( String.format( "Failed access: %s, method: %s, response code: %d, message: %s", UriBuilder.fromUri(url).replaceQuery("token").build(), method, responseCode, str)); } // final String contentType = conn.getContentType(); // if (!(contentType == null || // contentType.startsWith(MediaType.APPLICATION_JSON))) { // throw new IOException(conn.getResponseMessage()); // } try (Reader reader = new InputStreamReader(conn.getInputStream())) { return CharStreams.toString(reader); } } finally { conn.disconnect(); } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/HttpServletProxyResponse.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import jakarta.servlet.http.HttpServletResponse; import jakarta.ws.rs.core.HttpHeaders; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.che.commons.lang.Pair; /** * @author andrew00x */ public final class HttpServletProxyResponse implements HttpOutputMessage { private final HttpServletResponse httpServletResponse; private String contentType; private Map>> rewriteMap; public HttpServletProxyResponse(HttpServletResponse httpServletResponse) { this.httpServletResponse = httpServletResponse; } public HttpServletProxyResponse( HttpServletResponse httpServletResponse, Map>> rewriteMap) { this.httpServletResponse = httpServletResponse; this.rewriteMap = rewriteMap; } @Override public void setStatus(int status) { httpServletResponse.setStatus(status); } @Override public void setContentType(String contentType) { setHttpHeader(HttpHeaders.CONTENT_TYPE, contentType); } @Override public void addHttpHeader(String name, String value) { if ("content-type".equals(name.toLowerCase())) { contentType = value; } httpServletResponse.addHeader(name, value); } @Override public void setHttpHeader(String name, String value) { if ("content-type".equals(name.toLowerCase())) { contentType = value; } httpServletResponse.setHeader(name, value); } @Override public OutputStream getOutputStream() throws IOException { if (contentType != null && rewriteMap != null) { for (Map.Entry>> rewriteMapEntry : rewriteMap.entrySet()) { Matcher matcher = rewriteMapEntry.getKey().matcher(contentType); if (matcher.matches()) { return new RewriteOutputStream(httpServletResponse, rewriteMapEntry.getValue()); } } } return httpServletResponse.getOutputStream(); } public void setRewriteMap(Map>> rewriteMap) { this.rewriteMap = rewriteMap; } private class RewriteOutputStream extends OutputStream { final HttpServletResponse httpServletResponse; final List> rewriteRules; ByteArrayOutputStream cache; Writer writer; RewriteOutputStream( HttpServletResponse httpServletResponse, List> rewriteRules) { this.httpServletResponse = httpServletResponse; this.rewriteRules = rewriteRules; } @Override public void write(int b) throws IOException { if (cache == null) { cache = new ByteArrayOutputStream(); } cache.write(b); if (b == '\n' || b == '\r') { final String translatedLine = translateLine(); getWriter().write(translatedLine); cache.reset(); } } String translateLine() { String translatedLine = cache.toString(); for (Pair rewriteRule : rewriteRules) { translatedLine = translatedLine.replaceAll(rewriteRule.first, rewriteRule.second); } return translatedLine; } Writer getWriter() throws IOException { if (writer == null) { writer = httpServletResponse.getWriter(); } return writer; } @Override public void flush() throws IOException { if (cache != null) { final String translatedLine = translateLine(); final Writer myWriter = getWriter(); myWriter.write(translatedLine); writer.flush(); cache.reset(); } else if (writer != null) { writer.flush(); } } @Override public void close() throws IOException { // Define variable which is unused in the case of correctly closed resources // BTW it may be simplified with JDK9 see // https://blogs.oracle.com/darcy/entry/concise_twr_jdk9 try (@SuppressWarnings("unused") Writer writer = this.writer) { flush(); } } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/JAXRSDownloadFileResponseFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static jakarta.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerResponseContext; import jakarta.ws.rs.container.ContainerResponseFilter; import jakarta.ws.rs.core.Request; import java.io.IOException; /** * JAX-RS implementation of download filter. * * @author Florent Benoit */ public class JAXRSDownloadFileResponseFilter extends DownloadFileResponseFilter implements ContainerResponseFilter { /** * JAX-RS Filter method called after a response has been provided for a request * *

Filters in the filter chain are ordered according to their {@code * jakarta.annotation.Priority} class-level annotation value. * * @param requestContext request context. * @param responseContext response context. * @throws IOException if an I/O exception occurs. */ @Override public void filter( ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { // Apply header if all if correct Request request = requestContext.getRequest(); String filename = getFileName( request, responseContext.getMediaType(), requestContext.getUriInfo(), responseContext.getStatus()); if (filename != null) { if (hasCompliantEntity(responseContext.getEntity())) { responseContext .getHeaders() .putSingle(CONTENT_DISPOSITION, "attachment; filename=" + filename); } } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/LivenessProbeService.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.core.Response; import javax.inject.Singleton; /** * Endpoint for the liveness checks. * * @author Max Shaposhnik (mshaposh@redhat.com) */ @Singleton @Path("/liveness") public class LivenessProbeService { @GET public Response checkAlive() { return Response.ok().build(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/MessageBodyAdapter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import com.google.common.annotations.Beta; import jakarta.ws.rs.WebApplicationException; import java.io.IOException; import java.io.InputStream; import java.util.Set; /** * Adapts an entity stream in an implementation specific way. * *

To bind custom adapter: * *

 *  Multibinder adaptersBinder = Multibinder.newSetBinder(binder(), MessageBodyAdapter.class);
 *  adaptersBinder.addBinding().to(CustomMessageBodyAdapter.class);
 * 
* * @author Yevhenii Voevodin */ @Beta public interface MessageBodyAdapter { /** Returns classes for which adaption will be triggered. */ Set> getTriggers(); /** * Adapts entity stream to a new one, if necessary. * * @param entityStream an entity stream * @return a new stream with an adapted data or the same {@code entityStream} if there is nothing * to adapt */ InputStream adapt(InputStream entityStream) throws WebApplicationException, IOException; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/MessageBodyAdapterInterceptor.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import com.google.common.annotations.Beta; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.MessageBodyReader; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.inject.Inject; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * This interceptor must be bound for the method {@link MessageBodyReader#readFrom(Class, Type, * Annotation[], MediaType, MultivaluedMap, InputStream)} * * @author Yevhenii Voevodin */ @Beta public class MessageBodyAdapterInterceptor implements MethodInterceptor { private final Map, MessageBodyAdapter> adapters = new HashMap<>(); @Inject public void init(Set adapters) { for (MessageBodyAdapter adapter : adapters) { for (Class trigger : adapter.getTriggers()) { this.adapters.put(trigger, adapter); } } } @Override public Object invoke(MethodInvocation invocation) throws Throwable { final Object[] args = invocation.getArguments(); final MessageBodyAdapter adapter = adapters.get((Class) args[0]); if (adapter != null) { args[args.length - 1] = adapter.adapt((InputStream) args[args.length - 1]); } return invocation.proceed(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/OutputProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import java.io.IOException; import java.io.OutputStream; /** * Factory for writable output. * * @author andrew00x */ public interface OutputProvider { /** * Get writable output. * * @return writable output * @throws IOException if an i/o error occurs */ OutputStream getOutputStream() throws IOException; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/RemoteServiceDescriptor.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.ServiceDescriptor; import org.eclipse.che.dto.server.DtoFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides basic functionality to access remote {@link Service Service}. Basically provides next * information about {@code Service}: * *
    *
  • URL of {@code Service} *
  • Version of API *
  • Optional description of {@code Service} *
  • Set of {@link org.eclipse.che.api.core.rest.shared.dto.Link Link} to access {@code Service} * functionality *
* * @author andrew00x * @see Service * @see #getLinks() */ public class RemoteServiceDescriptor { private static final Logger LOG = LoggerFactory.getLogger(RemoteServiceDescriptor.class); protected final String baseUrl; private final HttpJsonRequestFactory requestFactory; // will be initialized when it is needed private volatile ServiceDescriptor serviceDescriptor; /** * Creates new descriptor of remote RESTful service. * * @throws java.lang.IllegalArgumentException if URL is invalid */ public RemoteServiceDescriptor(String baseUrl, HttpJsonRequestFactory requestFactory) throws IllegalArgumentException { this.baseUrl = baseUrl; this.requestFactory = requestFactory; try { final URL baseUrlURL = new URL(baseUrl); final String protocol = baseUrlURL.getProtocol(); if (!(protocol.equals("http") || protocol.equals("https"))) { throw new IllegalArgumentException(String.format("Invalid URL: %s", baseUrl)); } } catch (MalformedURLException e) { throw new IllegalArgumentException(String.format("Invalid URL: %s", baseUrl)); } } public String getBaseUrl() { return baseUrl; } /** * @see ServiceDescriptor#getLinks() */ public List getLinks() throws ServerException, IOException { final List links = getServiceDescriptor().getLinks(); // always copy list and links itself! final List copy = new ArrayList<>(links.size()); for (Link link : links) { copy.add(DtoFactory.getInstance().clone(link)); } return copy; } public Link getLink(String rel) throws ServerException, IOException { final Link link = getServiceDescriptor().getLink(rel); return link == null ? null : DtoFactory.getInstance().clone(link); } public ServiceDescriptor getServiceDescriptor() throws IOException, ServerException { if (serviceDescriptor == null) { synchronized (this) { if (serviceDescriptor == null) { try { serviceDescriptor = requestFactory .fromUrl(baseUrl) .useOptionsMethod() .request() .as(getServiceDescriptorClass(), null); } catch (NotFoundException | ConflictException | UnauthorizedException | BadRequestException | ForbiddenException e) { throw new ServerException(e.getServiceError()); } } } } return serviceDescriptor; } protected Class getServiceDescriptorClass() { return ServiceDescriptor.class; } /** Checks service availability. */ public boolean isAvailable() { LOG.debug("Testing availability {}", baseUrl); try { return (requestFactory .fromUrl(baseUrl) .useOptionsMethod() .request() .as(getServiceDescriptorClass(), null) != null); } catch (Exception e) { LOG.warn(e.getLocalizedMessage()); return false; } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/RuntimeExceptionMapper.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toList; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.TimeZone; import javax.inject.Singleton; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; import org.eclipse.che.dto.server.DtoFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Exception mapper that provide error message with error time. * * @author Roman Nikitenko * @author Sergii Kabashniuk */ @Provider @Singleton public class RuntimeExceptionMapper implements ExceptionMapper { private static final Logger LOG = LoggerFactory.getLogger(RuntimeExceptionMapper.class); @Override public Response toResponse(RuntimeException exception) { final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); final String utcTime = dateFormat.format(new Date()); String message = exception.getMessage(); final String errorMessage = isNullOrEmpty(message) ? format("Internal Server Error occurred, error time: %s", utcTime) : message; LOG.error(errorMessage, exception); List trace = stream(exception.getStackTrace()).map(StackTraceElement::toString).collect(toList()); ServiceError serviceError = DtoFactory.newDto(ServiceError.class).withMessage(errorMessage).withTrace(trace); return Response.serverError() .entity(DtoFactory.getInstance().toJson(serviceError)) .type(MediaType.APPLICATION_JSON) .build(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/Service.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static java.lang.String.format; import static java.util.Collections.emptyMap; import com.google.common.collect.ListMultimap; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.CookieParam; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.FormParam; import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.MatrixParam; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriInfo; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.rest.annotations.Description; import org.eclipse.che.api.core.rest.annotations.GenerateLink; import org.eclipse.che.api.core.rest.annotations.OPTIONS; import org.eclipse.che.api.core.rest.annotations.Required; import org.eclipse.che.api.core.rest.annotations.Valid; import org.eclipse.che.api.core.rest.shared.ParameterType; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.LinkParameter; import org.eclipse.che.api.core.rest.shared.dto.RequestBodyDescriptor; import org.eclipse.che.api.core.rest.shared.dto.ServiceDescriptor; import org.eclipse.che.api.core.util.PagingUtil; import org.eclipse.che.dto.server.DtoFactory; /** * Base class for all API services. * * @author andrew00x */ public abstract class Service { @Context protected UriInfo uriInfo; @OPTIONS @Produces(MediaType.APPLICATION_JSON) public ServiceDescriptor getServiceDescriptor() { return generateServiceDescriptor(uriInfo, getClass()); } public ServiceContext getServiceContext() { return new ServiceContextImpl(uriInfo.getBaseUriBuilder(), getClass()); } protected ServiceDescriptor createServiceDescriptor() { return DtoFactory.getInstance().createDto(ServiceDescriptor.class); } /** * Generates link header value based on given {@code page} and uri returned by {@code * uriInfo.getRequestUri()}. * * @param page page to create link header * @return link header value */ protected String createLinkHeader(Page page) { return PagingUtil.createLinkHeader(page, uriInfo.getRequestUri()); } /** * Creates uri from the given parameters and delegates execution to the {@link * PagingUtil#createLinkHeader(Page, URI)} method. * * @param page page to create link header * @param method rest service method name like {@link UriBuilder#path(Class, String) path} * argument * @param queryParams query parameters map, if multiple query params needed then {@link * #createLinkHeader(Page, String, ListMultimap, Object...)} method should be used instead * @param pathParams path param values like {f@link UriBuilder#build(Object...)} method arguments */ protected String createLinkHeader( Page page, String method, Map queryParams, Object... pathParams) { final UriBuilder ub = getServiceContext().getServiceUriBuilder().path(getClass(), method); for (Map.Entry queryParam : queryParams.entrySet()) { ub.queryParam(queryParam.getKey(), queryParam.getValue()); } return PagingUtil.createLinkHeader(page, ub.build(pathParams)); } /** * This method is the same to the {@link #createLinkHeader(Page, String, Map, Object...)} except * of receiving query parameters. */ protected String createLinkHeader(Page page, String method, Object... pathParams) { return createLinkHeader(page, method, emptyMap(), pathParams); } /** * This method is the same to {@link #createLinkHeader(Page, String, Map, Object...)} except of * receiving multivalued query parameters. */ protected String createLinkHeader( Page page, String method, ListMultimap queryParams, Object... pathParams) { final UriBuilder ub = getServiceContext().getServiceUriBuilder().path(getClass(), method); for (Map.Entry queryParam : queryParams.entries()) { ub.queryParam(queryParam.getKey(), queryParam.getValue()); } return PagingUtil.createLinkHeader(page, ub.build(pathParams)); } private static final Set JAX_RS_ANNOTATIONS; static { List tmp = new ArrayList<>(8); tmp.add(CookieParam.class.getName()); tmp.add(Context.class.getName()); tmp.add(HeaderParam.class.getName()); tmp.add(MatrixParam.class.getName()); tmp.add(PathParam.class.getName()); tmp.add(QueryParam.class.getName()); tmp.add(FormParam.class.getName()); tmp.add("org.everrest.core.Property"); JAX_RS_ANNOTATIONS = new HashSet<>(tmp); } private ServiceDescriptor generateServiceDescriptor( UriInfo uriInfo, Class service) { final List links = new ArrayList<>(); for (Method method : service.getMethods()) { final GenerateLink generateLink = method.getAnnotation(GenerateLink.class); if (generateLink != null) { try { links.add(generateLinkForMethod(uriInfo, generateLink.rel(), method)); } catch (RuntimeException ignored) { } } } final Description description = service.getAnnotation(Description.class); final ServiceDescriptor dto = createServiceDescriptor() .withHref(uriInfo.getRequestUriBuilder().replaceQuery(null).build().toString()) .withLinks(links) .withVersion(Constants.API_VERSION); if (description != null) { dto.setDescription(description.value()); } return dto; } private Link generateLinkForMethod( UriInfo uriInfo, String linkRel, Method method, Object... pathParameters) { String httpMethod = null; final HttpMethod httpMethodAnnotation = getMetaAnnotation(method, HttpMethod.class); if (httpMethodAnnotation != null) { httpMethod = httpMethodAnnotation.value(); } if (httpMethod == null) { throw new IllegalArgumentException( format( "Method '%s' has not any HTTP method annotation and may not be used to produce link.", method.getName())); } final Consumes consumes = getAnnotation(method, Consumes.class); final Produces produces = getAnnotation(method, Produces.class); final UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); final LinkedList matchedURIs = new LinkedList<>(uriInfo.getMatchedURIs()); // Get path to the root resource. if (uriInfo.getMatchedResources().size() < matchedURIs.size()) { matchedURIs.remove(); } while (!matchedURIs.isEmpty()) { baseUriBuilder.path(matchedURIs.pollLast()); } final Path path = method.getAnnotation(Path.class); if (path != null) { baseUriBuilder.path(path.value()); } final Link link = DtoFactory.getInstance() .createDto(Link.class) .withRel(linkRel) .withHref(baseUriBuilder.build(pathParameters).toString()) .withMethod(httpMethod); if (consumes != null) { link.setConsumes(consumes.value()[0]); } if (produces != null) { link.setProduces(produces.value()[0]); } Class[] parameterClasses = method.getParameterTypes(); if (parameterClasses.length > 0) { Annotation[][] annotations = method.getParameterAnnotations(); for (int i = 0; i < parameterClasses.length; i++) { if (annotations[i].length > 0) { boolean isBodyParameter = false; QueryParam queryParam = null; Description description = null; Required required = null; Valid valid = null; DefaultValue defaultValue = null; for (int j = 0; j < annotations[i].length; j++) { Annotation annotation = annotations[i][j]; isBodyParameter |= !JAX_RS_ANNOTATIONS.contains(annotation.annotationType().getName()); Class annotationType = annotation.annotationType(); if (annotationType == QueryParam.class) { queryParam = (QueryParam) annotation; } else if (annotationType == Description.class) { description = (Description) annotation; } else if (annotationType == Required.class) { required = (Required) annotation; } else if (annotationType == Valid.class) { valid = (Valid) annotation; } else if (annotationType == DefaultValue.class) { defaultValue = (DefaultValue) annotation; } } if (queryParam != null) { LinkParameter parameter = DtoFactory.getInstance() .createDto(LinkParameter.class) .withName(queryParam.value()) .withRequired(required != null) .withType(getParameterType(parameterClasses[i])); if (defaultValue != null) { parameter.setDefaultValue(defaultValue.value()); } if (description != null) { parameter.setDescription(description.value()); } if (valid != null) { parameter.setValid(Arrays.asList(valid.value())); } link.getParameters().add(parameter); } else if (isBodyParameter) { if (description != null) { link.setRequestBody( DtoFactory.getInstance() .createDto(RequestBodyDescriptor.class) .withDescription(description.value())); } } } } } return link; } private T getAnnotation(Method method, Class annotationClass) { T annotation = method.getAnnotation(annotationClass); if (annotation == null) { for (Class c = method.getDeclaringClass().getSuperclass(); annotation == null && c != null && c != Object.class; c = c.getSuperclass()) { Method inherited = null; try { inherited = c.getMethod(method.getName(), method.getParameterTypes()); } catch (NoSuchMethodException ignored) { } if (inherited != null) { annotation = inherited.getAnnotation(annotationClass); } } } return annotation; } private T getMetaAnnotation(Method method, Class metaAnnotationClass) { T annotation = null; for (Annotation a : method.getAnnotations()) { annotation = a.annotationType().getAnnotation(metaAnnotationClass); if (annotation != null) { break; } } if (annotation == null) { for (Class c = method.getDeclaringClass().getSuperclass(); annotation == null && c != null && c != Object.class; c = c.getSuperclass()) { Method inherited = null; try { inherited = c.getMethod(method.getName(), method.getParameterTypes()); } catch (NoSuchMethodException ignored) { } if (inherited != null) { for (Annotation a : inherited.getAnnotations()) { annotation = a.annotationType().getAnnotation(metaAnnotationClass); if (annotation != null) { break; } } } } } return annotation; } private ParameterType getParameterType(Class clazz) { if (clazz == String.class) { return ParameterType.String; } // restriction for collections which allowed for QueryParam annotation if (clazz == List.class || clazz == Set.class || clazz == SortedSet.class) { return ParameterType.Array; } if (clazz == Boolean.class || clazz == boolean.class) { return ParameterType.Boolean; } if (clazz == short.class || clazz == int.class || clazz == long.class || clazz == float.class || clazz == double.class || clazz == Short.class || clazz == Integer.class || clazz == Long.class || clazz == Float.class || clazz == Double.class) { return ParameterType.Number; } return ParameterType.Object; } private static class ServiceContextImpl implements ServiceContext { final UriBuilder uriBuilder; final Class serviceClass; ServiceContextImpl(UriBuilder uriBuilder, Class serviceClass) { this.uriBuilder = uriBuilder; this.serviceClass = serviceClass; } @Override public UriBuilder getServiceUriBuilder() { return uriBuilder.clone().path(serviceClass); } @Override public UriBuilder getBaseUriBuilder() { return uriBuilder.clone(); } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/ServiceContext.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import jakarta.ws.rs.core.UriBuilder; /** * Helps to deliver context of RESTful request to components. * * @author Andrey Parfonov */ public interface ServiceContext { /** * Get UriBuilder which already contains base URI of RESTful application and URL pattern of * RESTful service that produces this instance. */ UriBuilder getServiceUriBuilder(); /** Get UriBuilder which already contains base URI of RESTful application. */ UriBuilder getBaseUriBuilder(); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/WebApplicationExceptionMapper.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static org.eclipse.che.dto.server.DtoFactory.newDto; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.ForbiddenException; import jakarta.ws.rs.NotAcceptableException; import jakarta.ws.rs.NotAllowedException; import jakarta.ws.rs.NotAuthorizedException; import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.NotSupportedException; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; import javax.inject.Singleton; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; import org.eclipse.che.dto.server.DtoFactory; /** * Mapper for the {@link WebApplicationException} exceptions. * * @author Max Shaposhnyk */ @Provider @Singleton public class WebApplicationExceptionMapper implements ExceptionMapper { @Override public Response toResponse(WebApplicationException exception) { ServiceError error = newDto(ServiceError.class).withMessage(exception.getMessage()); if (exception instanceof BadRequestException) { return Response.status(Response.Status.BAD_REQUEST) .entity(DtoFactory.getInstance().toJson(error)) .type(MediaType.APPLICATION_JSON) .build(); } else if (exception instanceof ForbiddenException) { return Response.status(Response.Status.FORBIDDEN) .entity(DtoFactory.getInstance().toJson(error)) .type(MediaType.APPLICATION_JSON) .build(); } else if (exception instanceof NotFoundException) { return Response.status(Response.Status.NOT_FOUND) .entity(DtoFactory.getInstance().toJson(error)) .type(MediaType.APPLICATION_JSON) .build(); } else if (exception instanceof NotAuthorizedException) { return Response.status(Response.Status.UNAUTHORIZED) .entity(DtoFactory.getInstance().toJson(error)) .type(MediaType.APPLICATION_JSON) .build(); } else if (exception instanceof NotAcceptableException) { return Response.status(Status.NOT_ACCEPTABLE) .entity(DtoFactory.getInstance().toJson(error)) .type(MediaType.APPLICATION_JSON) .build(); } else if (exception instanceof NotAllowedException) { return Response.status(Status.METHOD_NOT_ALLOWED) .entity(DtoFactory.getInstance().toJson(error)) .type(MediaType.APPLICATION_JSON) .build(); } else if (exception instanceof NotSupportedException) { return Response.status(Status.UNSUPPORTED_MEDIA_TYPE) .entity(DtoFactory.getInstance().toJson(error)) .type(MediaType.APPLICATION_JSON) .build(); } else { return Response.serverError() .entity(DtoFactory.getInstance().toJson(error)) .type(MediaType.APPLICATION_JSON) .build(); } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/annotations/Description.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Description of service or its parameters. * *

It may be applied to: * *

    *
  • sub-classes of {@link org.eclipse.che.api.core.rest.Service Service}. In this case value of * this annotation is copied to the field {@link * org.eclipse.che.api.core.rest.shared.dto.ServiceDescriptor#getDescription()} *
  • parameter of RESTful method annotated with {@link jakarta.ws.rs.QueryParam * @QueryParam}. In this case value of this annotation is copied to the field {@link * org.eclipse.che.api.core.rest.shared.dto.LinkParameter#getDescription()} *
  • entity parameter (not annotated with JAX-RS annotations) of RESTful method. Entity * parameters are described in section 3.3.2.1 of JAX-RS specification 1.0. In this case value * of this annotation is copied to the filed of {@link * org.eclipse.che.api.core.rest.shared.dto.RequestBodyDescriptor#getDescription()} *
* *

For example: There is EchoService. Let's see on the values of Description annotations. Here we * have two: at class and at method's parameter. * *

 * @Path("echo")
 * @Description("echo service")
 * public class EchoService extends Service {
 *
 *     @GenerateLink(rel = "message")
 *     @GET
 *     @Path("say")
 *     @Produces("plain/text")
 *     public String echo1(@Required @Description("echo message") @QueryParam("message") String message) {
 *         return message;
 *     }
 * }
 * 
* *

Request to URL '${base_uri}/echo' gets next output: * *

* *

 * {
 *   "description":"echo service",
 *   "version":"1.0",
 *   "href":"${base_uri}/echo",
 *   "links":[
 *     {
 *       "href":"${base_uri}/echo/say",
 *       "produces":"plain/text",
 *       "rel":"message",
 *       "method":"GET",
 *       "parameters":[
 *         {
 *           "name":"message",
 *           "type":"String",
 *           "required":true,
 *           "description":"echo message"
 *         }
 *       ]
 *     }
 *   ]
 * }
 * 
* * See two descriptions in JSON output. * * @author Andrey Parfonov * @see org.eclipse.che.api.core.rest.shared.dto.ServiceDescriptor * @see org.eclipse.che.api.core.rest.shared.dto.LinkParameter * @see org.eclipse.che.api.core.rest.shared.dto.RequestBodyDescriptor */ @Target({ElementType.TYPE, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface Description { /** * @return the description */ String value(); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/annotations/GenerateLink.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * This annotation may be applied to methods of sub-class of {@link * org.eclipse.che.api.core.rest.Service}. When client accesses method {@link * org.eclipse.che.api.core.rest.Service#getServiceDescriptor()} all methods with this annotation * are analyzed to provide descriptor of particular service. In instance of {@link * org.eclipse.che.api.core.rest.shared.dto.ServiceDescriptor ServiceDescriptor} methods with this * annotation are represented as set of links. It should help client to understand capabilities of * particular RESTful service. * * @author Andrey Parfonov * @see org.eclipse.che.api.core.rest.Service * @see org.eclipse.che.api.core.rest.shared.dto.ServiceDescriptor */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface GenerateLink { String rel(); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/annotations/OPTIONS.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.annotations; import jakarta.ws.rs.HttpMethod; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author andrew00x */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @HttpMethod(HttpMethod.OPTIONS) public @interface OPTIONS {} ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/annotations/Required.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Helps to inform client about mandatory parameters of request. * *

This annotation may be applied to parameter of RESTful method annotated with {@link * jakarta.ws.rs.QueryParam @QueryParam}. In this case field of {@link * org.eclipse.che.api.core.rest.shared.dto.LinkParameter#isRequired()} is set to {@code true}. * * @author Andrey Parfonov * @see org.eclipse.che.api.core.rest.shared.dto.LinkParameter * @see org.eclipse.che.api.core.rest.shared.dto.RequestBodyDescriptor */ @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface Required {} ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/annotations/Valid.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Helps to inform client about valid values of request parameters. * *

This annotation may be applied to parameter of RESTful method annotated with {@link * jakarta.ws.rs.QueryParam @QueryParam}. In this case value of this annotation is copied to * field {@link org.eclipse.che.api.core.rest.shared.dto.LinkParameter#getValid()} * *

For example: There is EchoService. Let's see on the value of Valid annotation, it is {"hello", * "goodbye"}. * *

 * @Path("echo")
 * @Description("echo service")
 * public class EchoService extends Service {
 *
 *     @GenerateLink(rel = "message")
 *     @GET
 *     @Path("say")
 *     @Produces("plain/text")
 *     public String echo1(@Required @Valid({"hello", "goodbye"}) @QueryParam("message") String message) {
 *         return message;
 *     }
 * }
 * 
* *

Request to URL '${base_uri}/echo' gets next output: * *

* *

 * {
 *   "description":"echo service",
 *   "version":"1.0",
 *   "href":"${base_uri}/echo",
 *   "links":[
 *     {
 *       "href":"${base_uri}/echo/say",
 *       "produces":"plain/text",
 *       "rel":"message",
 *       "method":HttpMethod.GET,
 *       "parameters":[
 *         {
 *           "name":"message",
 *           "type":"String",
 *           "required":true,
 *           "valid":[
 *             "hello",
 *             "goodbye"
 *           ]
 *         }
 *       ]
 *     }
 *   ]
 * }
 * 
* * There are two "valid" values for parameter "message": ["hello", "goodbye"] in JSON output. * * @author Andrey Parfonov * @see org.eclipse.che.api.core.rest.shared.dto.LinkParameter * @see org.eclipse.che.api.core.rest.shared.dto.RequestBodyDescriptor */ @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface Valid { String[] value(); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/shared/Links.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.shared; import java.util.LinkedList; import java.util.List; import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; import org.eclipse.che.api.core.rest.shared.dto.Link; /** * Helper class for working with links. * * @author andrew00x */ public class Links { /** * Find first link in the specified list by its relation. * * @param rel link's relation * @param links list of links * @return found link or {@code null} */ public static Link getLink(String rel, List links) { for (Link link : links) { if (rel.equals(link.getRel())) { return link; } } return null; } /** * Find all links in the specified list by its relation. * * @param rel link's relation * @param links list of links * @return found link or {@code null} */ public static List getLinks(String rel, List links) { final List result = new LinkedList<>(); for (Link link : links) { if (rel.equals(link.getRel())) { result.add(link); } } return result; } public static Link getLink(Hyperlinks links, String rel) { return getLink(rel, links.getLinks()); } public static List getLinks(Hyperlinks links, String rel) { return getLinks(rel, links.getLinks()); } private Links() {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/shared/ParameterType.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.shared; /** * @author Andrey Parfonov */ public enum ParameterType { String, Number, Boolean, Array, Object } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/shared/dto/ApiInfo.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.shared.dto; import org.eclipse.che.dto.shared.DTO; /** * @author andrew00x */ @DTO public interface ApiInfo { String getSpecificationVendor(); ApiInfo withSpecificationVendor(String specificationVendor); void setSpecificationVendor(String specificationVendor); String getImplementationVendor(); ApiInfo withImplementationVendor(String implementationVendor); void setImplementationVendor(String implementationVendor); String getSpecificationTitle(); ApiInfo withSpecificationTitle(String specificationTitle); void setSpecificationTitle(String specificationTitle); String getSpecificationVersion(); ApiInfo withSpecificationVersion(String specificationVersion); void setSpecificationVersion(String specificationVersion); String getImplementationVersion(); ApiInfo withImplementationVersion(String implementationVersion); void setImplementationVersion(String implementationVersion); String getScmRevision(); ApiInfo withScmRevision(String scmRevision); void setScmRevision(String scmRevision); String getIdeVersion(); ApiInfo withIdeVersion(String ideVersion); void setIdeVersion(String ideVersion); String getBuildInfo(); ApiInfo withBuildInfo(String buildInfo); void setBuildInfo(String buildInfo); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/shared/dto/ExtendedError.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.shared.dto; import java.util.Map; import org.eclipse.che.dto.shared.DTO; /** * Extended error which contains error code and optional attributes. * * @author Max Shaposhnik (mshaposhnik@codenvy.com) */ @DTO public interface ExtendedError extends ServiceError { /** * Get error code. * * @return error code */ int getErrorCode(); /** * Set error code. * * @param errorCode error code */ void setErrorCode(int errorCode); ExtendedError withErrorCode(int errorCode); /** * Get error attributes. * * @return attributes of the error if any */ Map getAttributes(); /** * Set error attributes. * * @param attributes error attributes */ void setAttributes(Map attributes); ExtendedError withAttributes(Map attributes); @Override ExtendedError withMessage(String message); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/shared/dto/Hyperlinks.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.shared.dto; import java.util.List; import org.eclipse.che.api.core.rest.shared.Links; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.dto.shared.DelegateRule; import org.eclipse.che.dto.shared.DelegateTo; /** * @author andrew00x */ @DTO public interface Hyperlinks { List getLinks(); Hyperlinks withLinks(List links); void setLinks(List links); @DelegateTo( client = @DelegateRule(type = Links.class, method = "getLinks"), server = @DelegateRule(type = Links.class, method = "getLinks")) List getLinks(String rel); @DelegateTo( client = @DelegateRule(type = Links.class, method = "getLink"), server = @DelegateRule(type = Links.class, method = "getLink")) Link getLink(String rel); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/shared/dto/Link.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.shared.dto; import java.util.List; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.dto.shared.DelegateRule; import org.eclipse.che.dto.shared.DelegateTo; /** * Describes resource. * * @author andrew00x */ @DTO public interface Link { /** * Get URL of resource link. * * @return URL of resource link. */ String getHref(); Link withHref(String href); /** * Set URL of resource link. * * @param href URL of resource link. */ void setHref(String href); /** * Get short description of resource link. * * @return short description of resource link */ String getRel(); Link withRel(String rel); /** * Set short description of resource link. * * @param rel short description of resource link */ void setRel(String rel); /** * Get HTTP method to use with resource. * * @return HTTP method to use with resource */ String getMethod(); Link withMethod(String method); /** * Set HTTP method to use with resource. * * @param method HTTP method to use with resource */ void setMethod(String method); /** * Get media type produced by resource. * * @return media type produced by resource */ String getProduces(); Link withProduces(String produces); /** * Set media type produced by resource. * * @param produces media type produced by resource */ void setProduces(String produces); /** * Get media type consumed by resource. * * @return media type consumed by resource */ String getConsumes(); Link withConsumes(String consumes); /** * Set media type consumed by resource. * * @param consumes media type consumed by resource */ void setConsumes(String consumes); /** * Get description of the query parameters (if any) of request. * * @return description of the query parameters (if any) of request */ List getParameters(); @DelegateTo( client = @DelegateRule(type = LinkParameterResolver.class, method = "getParameter"), server = @DelegateRule(type = LinkParameterResolver.class, method = "getParameter")) LinkParameter getParameter(String parameterName); Link withParameters(List parameters); /** * Set description of the query parameters (if any) of request. * * @param parameters description of the query parameters (if any) of request */ void setParameters(List parameters); /** * Get request body description. * * @return request body description */ RequestBodyDescriptor getRequestBody(); Link withRequestBody(RequestBodyDescriptor requestBody); /** * Set request body description. * * @param requestBody request body description */ void setRequestBody(RequestBodyDescriptor requestBody); class LinkParameterResolver { public static LinkParameter getParameter(Link link, String parameterName) { for (LinkParameter linkParameter : link.getParameters()) { if (linkParameter.getName().equals(parameterName)) { return linkParameter; } } return null; } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/shared/dto/LinkParameter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.shared.dto; import java.util.List; import org.eclipse.che.api.core.rest.shared.ParameterType; import org.eclipse.che.dto.shared.DTO; /** * Describes one query parameter of the request. * * @author Andrey Parfonov * @see org.eclipse.che.api.core.rest.annotations.Description * @see org.eclipse.che.api.core.rest.annotations.Required * @see org.eclipse.che.api.core.rest.annotations.Valid */ @DTO public interface LinkParameter { /** * Get name of parameter. * * @return name of parameter */ String getName(); LinkParameter withName(String name); /** * Set name of parameter. * * @param name name of parameter */ void setName(String name); /** * Get defaultValue of parameter. * * @return defaultValue of parameter */ String getDefaultValue(); LinkParameter withDefaultValue(String defaultValue); /** * Set defaultValue of parameter. * * @param defaultValue defaultValue of parameter */ void setDefaultValue(String defaultValue); /** * Get optional description of parameter. * * @return optional description of parameter * @see org.eclipse.che.api.core.rest.annotations.Description */ String getDescription(); LinkParameter withDescription(String description); /** * Set optional description of parameter. * * @param description optional description of parameter * @see org.eclipse.che.api.core.rest.annotations.Description */ void setDescription(String description); /** * Get type of parameter. * * @return type of parameter * @see org.eclipse.che.api.core.rest.shared.ParameterType */ ParameterType getType(); LinkParameter withType(ParameterType type); /** * Set type of parameter. * * @param type type of parameter * @see ParameterType */ void setType(ParameterType type); /** * Reports whether the parameter is mandatory. * * @return {@code true} if parameter is required and {@code false} otherwise * @see org.eclipse.che.api.core.rest.annotations.Required */ boolean isRequired(); LinkParameter withRequired(boolean required); /** * @param required {@code true} if parameter is required and {@code false} otherwise * @see org.eclipse.che.api.core.rest.annotations.Required */ void setRequired(boolean required); /** * Get optional list of constraint strings. * * @return optional list of constraint strings * @see org.eclipse.che.api.core.rest.annotations.Valid */ List getValid(); LinkParameter withValid(List valid); /** * Set optional list of constraint strings. * * @param valid optional list of constraint strings * @see org.eclipse.che.api.core.rest.annotations.Valid */ void setValid(List valid); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/shared/dto/RequestBodyDescriptor.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.shared.dto; import org.eclipse.che.dto.shared.DTO; /** * Describes body of the request. * * @author Andrey Parfonov */ @DTO public interface RequestBodyDescriptor { /** * Get optional description of request body. * * @return optional description of request body */ String getDescription(); RequestBodyDescriptor withDescription(String description); /** * Set optional description of request body. * * @param description optional description of request body */ void setDescription(String description); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/shared/dto/ServiceDescriptor.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.shared.dto; import java.util.List; import org.eclipse.che.dto.shared.DTO; /** * Describes capabilities of the Service. * * @author andrew00x * @see org.eclipse.che.api.core.rest.Service */ @DTO public interface ServiceDescriptor extends Hyperlinks { /** * Get location to get this service descriptor in JSON format. * * @return location to get this service descriptor in JSON format */ String getHref(); ServiceDescriptor withHref(String href); /** * Set location to get this service descriptor in JSON format. * * @param href location to get this service descriptor in JSON format */ void setHref(String href); /** * Get description of the Service. * * @return description of the Service */ String getDescription(); ServiceDescriptor withDescription(String description); /** * Set description of the Service. * * @param description description of the Service */ void setDescription(String description); /** * Get API version. * * @return API version */ String getVersion(); ServiceDescriptor withVersion(String version); /** * Get API version. * * @param version API version */ void setVersion(String version); ServiceDescriptor withLinks(List links); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/rest/shared/dto/ServiceError.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.shared.dto; import java.util.List; import org.eclipse.che.dto.shared.DTO; /** * Describes error which may be serialized to JSON format with {@link * org.eclipse.che.api.core.rest.ApiExceptionMapper} * * @author Andrey Parfonov * @see org.eclipse.che.api.core.ApiException * @see org.eclipse.che.api.core.rest.ApiExceptionMapper */ @DTO public interface ServiceError { /** * Get error message. * * @return error message */ String getMessage(); List getTrace(); ServiceError withMessage(String message); ServiceError withTrace(List trace); /** * Set error message. * * @param message error message */ void setMessage(String message); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/AbstractLineConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.io.IOException; /** * No-op implementation of {@link LineConsumer} * * @author Alexander Garagatyi */ public abstract class AbstractLineConsumer implements LineConsumer { @Override public void writeLine(String line) throws IOException {} @Override public void close() throws IOException {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/AbstractMessageConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.io.IOException; /** * No-op implementation of {@link MessageConsumer} * * @author Alexander Garagatyi */ public class AbstractMessageConsumer implements MessageConsumer { @Override public void consume(T message) throws IOException {} @Override public void close() throws IOException {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/ApiInfoLogInformer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import jakarta.annotation.PostConstruct; import javax.inject.Inject; import org.eclipse.che.api.core.rest.shared.dto.ApiInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Prints information about Eclipse Che Core implementation, such as: version, scm revision, build * info. */ public class ApiInfoLogInformer { private static final Logger LOG = LoggerFactory.getLogger(ApiInfoLogInformer.class); private final ApiInfo apiInfo; @Inject public ApiInfoLogInformer(ApiInfo apiInfo) { this.apiInfo = apiInfo; } @PostConstruct public void printApiInfoOnStart() { LOG.info( "Eclipse Che Api Core: Build info '{}' scmRevision '{}' implementationVersion '{}'", apiInfo.getBuildInfo(), apiInfo.getScmRevision(), apiInfo.getImplementationVersion()); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/Cancellable.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; /** * Implementation of this interface may be used with {@link Watchdog} to make possible terminate * task by timeout. * * @author andrew00x */ public interface Cancellable { interface Callback { /** * Notified when Cancellable cancelled. * * @param cancellable Cancellable */ void cancelled(Cancellable cancellable); } /** * Attempts to cancel execution of this {@code Cancellable}. * * @throws Exception if cancellation is failed */ void cancel() throws Exception; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/CommandLine.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * Container for system command arguments. * * @author Andrey Parfonov */ public class CommandLine { private final List line; public CommandLine(CommandLine other) { line = new ArrayList<>(other.line); } public CommandLine(String... args) { line = new ArrayList<>(); if (args != null && args.length > 0) { Collections.addAll(line, args); } } public CommandLine() { line = new ArrayList<>(); } /** * Adds list of arguments in command line. * * @param args arguments * @return this {@code CommandLine} */ public CommandLine add(String... args) { if (args != null && args.length > 0) { Collections.addAll(line, args); } return this; } /** * Adds list of arguments in command line. * * @param args arguments * @return this {@code CommandLine} */ public CommandLine add(List args) { if (args != null && !args.isEmpty()) { line.addAll(args); } return this; } /** * Adds set of options in command line. * * @param options options * @return this {@code CommandLine} * @see #addPair(String, String) */ public CommandLine add(Map options) { if (options != null && !options.isEmpty()) { for (Map.Entry entry : options.entrySet()) { addPair(entry.getKey(), entry.getValue()); } } return this; } /** * Adds option in command line. If {@code value != null} then adds {@code name=value} in command * line. If {@code value} is {@code null} adds only {@code name} in command line. * * @param name option's name * @param value option's value * @return this {@code CommandLine} */ public CommandLine addPair(String name, String value) { if (name != null) { if (value != null) { line.add(name + '=' + value); } else { line.add(name); } } return this; } /** * Removes all command line arguments. * * @return this {@code CommandLine} */ public CommandLine clear() { line.clear(); return this; } public String[] asArray() { return line.toArray(new String[line.size()]); } /** Create shell command. */ public String[] toShellCommand() { return ShellFactory.getShell().createShellCommand(this); } @Override public String toString() { final String[] str = asArray(); final StringBuilder sb = new StringBuilder(); for (String s : str) { if (sb.length() > 1) { sb.append(' '); } sb.append(s); } return sb.toString(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/CompositeLineConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.io.IOException; import java.nio.channels.ClosedByInterruptException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.che.api.core.util.lineconsumer.ConsumerAlreadyClosedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This line consumer consists of several consumers and copies each consumed line into all * subconsumers. Is used when lines should be written into two or more consumers.
* This class is not thread safe. Also see multithreaded implementation {@link * org.eclipse.che.api.core.util.lineconsumer.ConcurrentCompositeLineConsumer} * * @author andrew00x * @author Mykola Morhun */ public class CompositeLineConsumer implements LineConsumer { private static final Logger LOG = LoggerFactory.getLogger(CompositeLineConsumer.class); private final List lineConsumers; private boolean isOpen; public CompositeLineConsumer(LineConsumer... lineConsumers) { this.lineConsumers = new CopyOnWriteArrayList<>(lineConsumers); this.isOpen = true; } /** Closes all unclosed subconsumers. */ @Override public void close() { if (isOpen) { isOpen = false; for (LineConsumer lineConsumer : lineConsumers) { try { lineConsumer.close(); } catch (IOException e) { LOG.error( String.format("An error occurred while closing the line consumer %s", lineConsumer), e); } } } } /** * Writes given line to each subconsumer. Do nothing if this consumer is closed or all * subconsumers are closed. * * @param line line to write */ @Override public void writeLine(String line) { if (isOpen) { for (LineConsumer lineConsumer : lineConsumers) { try { lineConsumer.writeLine(line); } catch (ClosedByInterruptException interrupted) { Thread.currentThread().interrupt(); isOpen = false; return; } catch (ConsumerAlreadyClosedException e) { lineConsumers.remove( lineConsumer); // consumer is already closed, so we cannot write into it any more if (lineConsumers.size() == 0) { // if all consumers are closed then we can close this one isOpen = false; } } catch (IOException e) { LOG.error( String.format( "An error occurred while writing line to the line consumer %s", lineConsumer), e); } } } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/ContentTypeGuesser.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import jakarta.ws.rs.core.MediaType; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Helps getting content type of file. * * @author andrew00x */ public class ContentTypeGuesser { private static final Logger LOG = LoggerFactory.getLogger(ContentTypeGuesser.class); private static String defaultContentType = MediaType.APPLICATION_OCTET_STREAM; public static synchronized void setDefaultContentType(String myDefaultContentType) { defaultContentType = myDefaultContentType; } public static synchronized String getDefaultContentType() { return defaultContentType; } private static Properties properties = new Properties(); static { final String filePath = System.getProperty("org.eclipse.che.content-types"); URL resource = null; if (filePath != null) { resource = Thread.currentThread().getContextClassLoader().getResource(filePath); } if (resource == null) { resource = Thread.currentThread().getContextClassLoader().getResource("content-types.properties"); } if (resource != null) { try (InputStream stream = resource.openStream()) { properties.load(stream); LOG.info("Successfully loaded content types from {}", resource); } catch (IOException e) { LOG.error(e.getMessage(), e); } } } public static String guessContentType(java.io.File file) { String contentType = null; final String name = file.getName(); final int dot = name.lastIndexOf('.'); if (dot > 0) { final String ext = name.substring(dot + 1); if (!ext.isEmpty()) { // by file extensions contentType = properties.getProperty(ext); } } if (contentType == null) { // by full file name. contentType = properties.getProperty(name); } /* Commented due to on Amazon infra with power instances. JVM crashes with multi-thread access (no problem with single thread). *** glibc detected *** /usr/local/jdk/bin/java: double free or corruption (out): 0x00007f22f0007820 *** ======= Backtrace: ========= /lib64/libc.so.6[0x3da68760e6] /lib64/libc.so.6[0x3da6878c13] /lib64/libglib-2.0.so.0(g_array_free+0x7a)[0x3da7c13dda] /lib64/libgio-2.0.so.0[0x3da9c352f4] /lib64/libgobject-2.0.so.0(g_object_unref+0x15f)[0x3da8c0dacf] /usr/local/jdk1.7.0_17/jre/lib/amd64/libnio.so(Java_sun_nio_fs_GnomeFileTypeDetector_probeUsingGio+0xa2)[0x7f24aa7c7232] [0x7f24d3186db1] if (contentType == null) { try { // if content type is null use JDK. contentType = java.nio.file.Files.probeContentType(file.toPath()); } catch (IOException e) { LOG.warn(e.getMessage(), e); } }*/ return contentType == null ? getDefaultContentType() : contentType; } public static String guessContentType(String name) { String contentType = null; final int dot = name.lastIndexOf('.'); if (dot > 0) { final String ext = name.substring(dot + 1); if (!ext.isEmpty()) { // by file extensions contentType = properties.getProperty(ext); } } if (contentType == null) { // by full file name. contentType = properties.getProperty(name); } return contentType == null ? getDefaultContentType() : contentType; } private ContentTypeGuesser() {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/DownloadPlugin.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.io.IOException; /** * Downloads remote file. * * @author andrew00x */ public interface DownloadPlugin { interface Callback { /** * Notified when file downloaded. * * @param downloaded downloaded file */ void done(java.io.File downloaded); /** * Notified when error occurs. * * @param e error */ void error(IOException e); } /** * Download file from specified location to local directory {@code downloadTo}. * * @param downloadUrl download URL * @param downloadTo local directory for download * @param callback notified when download is done or an error occurs */ void download(String downloadUrl, java.io.File downloadTo, Callback callback); /** * Download file from specified location to local directory {@code downloadTo} and save it in file * {@code fileName}. * * @param downloadUrl download URL * @param downloadTo local directory for download * @param fileName name of local file to save download result * @param replaceExisting replace existed file with the same name * @throws IOException if i/o error occurs when try download file or save it on local filesystem */ void download( String downloadUrl, java.io.File downloadTo, String fileName, boolean replaceExisting) throws IOException; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/ErrorFilteredConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import static org.eclipse.che.api.core.util.ErrorFilteredConsumer.ErrorIndicator.DEFAULT_ERROR_INDICATOR; import java.io.IOException; /** * Consumes all output and redirect to another consumer only those lines which contain errors. * * @author Anatolii Bazko */ public class ErrorFilteredConsumer implements LineConsumer { private final ErrorIndicator errorIndicator; private final LineConsumer lineConsumer; public ErrorFilteredConsumer(ErrorIndicator errorIndicator, LineConsumer lineConsumer) { this.errorIndicator = errorIndicator; this.lineConsumer = lineConsumer; } public ErrorFilteredConsumer(LineConsumer lineConsumer) { this(DEFAULT_ERROR_INDICATOR, lineConsumer); } @Override public void writeLine(String line) throws IOException { if (errorIndicator.isError(line)) { lineConsumer.writeLine(line); } } @Override public void close() throws IOException { lineConsumer.close(); } /** Indicates if line contains a error message. */ @FunctionalInterface public interface ErrorIndicator { boolean isError(String line); ErrorIndicator DEFAULT_ERROR_INDICATOR = line -> line.startsWith("[STDERR]"); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/FileCleaner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.inject.AbstractModule; import jakarta.annotation.PreDestroy; import java.io.File; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.eclipse.che.commons.lang.IoUtil; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Delete files and directories added with method {@link @addFile}. In environments with multiple * class loaders, {@code FileCleaner} should be stopped if it isn't needed. In Codenvy environment * {@code FileCleaner} is stopped automatically by {@link FileCleanerModule} otherwise need call * method {@link #stop()} manually. * * @author andrew00x */ public class FileCleaner { private static final Logger LOG = LoggerFactory.getLogger(FileCleaner.class); /** Number of attempts to delete file or directory before start write log error messages. */ static int logAfterAttempts = 10; private static ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor( new ThreadFactoryBuilder() .setNameFormat("FileCleaner") .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setDaemon(true) .build()); static { exec.scheduleAtFixedRate( new Runnable() { @Override public void run() { clean(); } }, 30, 30, TimeUnit.SECONDS); } private static ConcurrentLinkedQueue> files = new ConcurrentLinkedQueue<>(); /** Registers new file or directory in FileCleaner. */ public static void addFile(File file) { files.offer(Pair.of(file, 0)); } /** Stops FileCleaner. */ public static void stop() { exec.shutdownNow(); try { exec.awaitTermination(3, TimeUnit.SECONDS); } catch (InterruptedException ignored) { } clean(); files.clear(); LOG.info("File cleaner is stopped"); } private static void clean() { Pair pair; final Set> failToDelete = new HashSet<>(); while ((pair = files.poll()) != null) { final File file = pair.first; int deleteAttempts = pair.second; if (file.exists()) { if (!IoUtil.deleteRecursive(file)) { failToDelete.add(Pair.of(file, ++deleteAttempts)); if (deleteAttempts > logAfterAttempts) { LOG.error("Unable to delete file '{}' after {} tries", file, deleteAttempts); } } else if (LOG.isDebugEnabled()) { LOG.debug("Deleted file '{}'", file); } } } if (!failToDelete.isEmpty()) { files.addAll(failToDelete); } } /** Guice module that stops FileCleaner when Guice container destroyed. */ public static class FileCleanerModule extends AbstractModule { @Override protected void configure() { bind(Finalizer.class).asEagerSingleton(); } } /** Helper component that stops FileCleaner. */ static class Finalizer { @PreDestroy void stop() { FileCleaner.stop(); } } private FileCleaner() {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/FileLineConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.io.File; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import java.nio.file.Files; import org.eclipse.che.api.core.util.lineconsumer.ConsumerAlreadyClosedException; /** * Consumes logs and writes them into file.
* This class is not thread safe. Also see multithreaded implementation {@link * org.eclipse.che.api.core.util.lineconsumer.ConcurrentFileLineConsumer} * * @author andrew00x * @author Mykola Morhun */ public class FileLineConsumer implements LineConsumer { private final File file; private final Writer writer; private boolean isOpen; public FileLineConsumer(File file) throws IOException { this.file = file; writer = Files.newBufferedWriter(file.toPath(), Charset.defaultCharset()); isOpen = true; } public File getFile() { return file; } @Override public void writeLine(String line) throws IOException { if (isOpen) { try { if (line != null) { writer.write(line); } writer.write('\n'); writer.flush(); } catch (IOException e) { if ("Stream closed".equals(e.getMessage())) { throw new ConsumerAlreadyClosedException(e.getMessage()); } throw e; } } } @Override public void close() throws IOException { if (isOpen) { isOpen = false; writer.close(); } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/HttpDownloadPlugin.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import jakarta.ws.rs.core.HttpHeaders; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.concurrent.TimeUnit; import org.eclipse.che.commons.lang.NameGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * DownloadPlugin that downloads single file. * * @author andrew00x */ public final class HttpDownloadPlugin implements DownloadPlugin { private static final Logger LOG = LoggerFactory.getLogger(HttpDownloadPlugin.class); private static final int CONNECT_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(3); private static final int READ_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(3); @Override public void download(String downloadUrl, java.io.File downloadTo, Callback callback) { HttpURLConnection conn = null; try { conn = (HttpURLConnection) new URL(downloadUrl).openConnection(); conn.setConnectTimeout(CONNECT_TIMEOUT); conn.setReadTimeout(READ_TIMEOUT); final int responseCode = conn.getResponseCode(); if (responseCode != 200) { throw new IOException( String.format("Invalid response status %d from remote server. ", responseCode)); } final String contentDisposition = conn.getHeaderField(HttpHeaders.CONTENT_DISPOSITION); String fileName = null; if (contentDisposition != null) { int fNameStart = contentDisposition.indexOf("filename="); if (fNameStart > 0) { int fNameEnd = contentDisposition.indexOf(';', fNameStart + 1); if (fNameEnd < 0) { fNameEnd = contentDisposition.length(); } fileName = contentDisposition.substring(fNameStart, fNameEnd).split("=")[1]; if (fileName.charAt(0) == '"' && fileName.charAt(fileName.length() - 1) == '"') { fileName = fileName.substring(1, fileName.length() - 1); } } } final java.io.File downloadFile = new java.io.File( downloadTo, fileName == null ? NameGenerator.generate("downloaded.file", 4) : fileName); try (InputStream in = conn.getInputStream()) { Files.copy(in, downloadFile.toPath()); } callback.done(downloadFile); } catch (IOException e) { LOG.debug(String.format("Failed access: %s, error: %s", downloadUrl, e.getMessage()), e); callback.error(e); } finally { if (conn != null) { conn.disconnect(); } } } @Override public void download( String downloadUrl, java.io.File downloadTo, String fileName, boolean replaceExisting) throws IOException { HttpURLConnection conn = null; try { conn = (HttpURLConnection) new URL(downloadUrl).openConnection(); conn.setConnectTimeout(CONNECT_TIMEOUT); conn.setReadTimeout(READ_TIMEOUT); final int responseCode = conn.getResponseCode(); if (responseCode != 200) { throw new IOException( String.format("Invalid response status %d from remote server. ", responseCode)); } final java.io.File downloadFile = new java.io.File(downloadTo, fileName); try (InputStream in = conn.getInputStream()) { if (replaceExisting) { Files.copy(in, downloadFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } else { Files.copy(in, downloadFile.toPath()); } } } finally { if (conn != null) { conn.disconnect(); } } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/IndentWrapperLineConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.io.IOException; /** * Line consumer for adding system indent in the end of the line * * @author Andrienko Alexander */ public class IndentWrapperLineConsumer implements LineConsumer { private final LineConsumer lineConsumer; public IndentWrapperLineConsumer(LineConsumer lineConsumer) { this.lineConsumer = lineConsumer; } /** {@inheritDoc} */ @Override public void writeLine(String line) throws IOException { lineConsumer.writeLine(line + System.lineSeparator()); } /** {@inheritDoc} */ @Override public void close() throws IOException { lineConsumer.close(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/JsonRpcEndpointIdProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.util.Set; public interface JsonRpcEndpointIdProvider { Set get(); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/JsonRpcEndpointIdsHolder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import static com.google.common.collect.Sets.newConcurrentHashSet; import static java.util.Collections.emptySet; import static java.util.stream.Collectors.toSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; @Singleton public class JsonRpcEndpointIdsHolder { private final Map> endpointIds = new ConcurrentHashMap<>(); @Inject private void configureSubscribeHandler(RequestHandlerConfigurator configurator) { configurator .newConfiguration() .methodName("event:ws-agent-output:subscribe") .paramsAsString() .noResult() .withBiConsumer( (endpointId, workspaceId) -> { endpointIds.putIfAbsent(endpointId, newConcurrentHashSet()); endpointIds.get(endpointId).add(workspaceId); }); } @Inject private void configureUnSubscribeHandler(RequestHandlerConfigurator configurator) { configurator .newConfiguration() .methodName("event:ws-agent-output:un-subscribe") .paramsAsString() .noResult() .withBiConsumer( (endpointId, workspaceId) -> { endpointIds.getOrDefault(endpointId, emptySet()).remove(workspaceId); endpointIds.entrySet().removeIf(entry -> entry.getValue().isEmpty()); }); } public Set getEndpointIdsByWorkspaceId(String workspaceId) { return endpointIds.entrySet().stream() .filter(it -> it.getValue().contains(workspaceId)) .map(Map.Entry::getKey) .collect(toSet()); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/JsonRpcEndpointToMachineNameHolder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import static com.google.common.collect.Sets.newConcurrentHashSet; import static java.util.Collections.emptySet; import static java.util.stream.Collectors.toSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; @Singleton public class JsonRpcEndpointToMachineNameHolder { private final Map> endpointIds = new ConcurrentHashMap<>(); @Inject private void configureSubscribeHandler(RequestHandlerConfigurator configurator) { configurator .newConfiguration() .methodName("event:environment-output:subscribe-by-machine-name") .paramsAsString() .noResult() .withBiConsumer( (endpointId, workspaceIdPlusMachineName) -> { endpointIds.putIfAbsent(endpointId, newConcurrentHashSet()); endpointIds.get(endpointId).add(workspaceIdPlusMachineName); }); } @Inject private void configureUnSubscribeHandler(RequestHandlerConfigurator configurator) { configurator .newConfiguration() .methodName("event:environment-output:un-subscribe-by-machine-name") .paramsAsString() .noResult() .withBiConsumer( (endpointId, workspaceIdPlusMachineName) -> { endpointIds.getOrDefault(endpointId, emptySet()).remove(workspaceIdPlusMachineName); endpointIds.entrySet().removeIf(entry -> entry.getValue().isEmpty()); }); } public Set getEndpointIdsByWorkspaceIdPlusMachineName(String workspaceIdPlusMachineName) { return endpointIds.entrySet().stream() .filter(it -> it.getValue().contains(workspaceIdPlusMachineName)) .map(Map.Entry::getKey) .collect(toSet()); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/JsonRpcLineConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import static org.slf4j.LoggerFactory.getLogger; import java.io.IOException; import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; import org.slf4j.Logger; public class JsonRpcLineConsumer implements LineConsumer { private static final Logger LOG = getLogger(JsonRpcLineConsumer.class); private final String method; private final RequestTransmitter transmitter; private final JsonRpcEndpointIdProvider jsonRpcEndpointIdProvider; public JsonRpcLineConsumer( RequestTransmitter transmitter, String method, JsonRpcEndpointIdProvider jsonRpcEndpointIdProvider) { this.method = method; this.transmitter = transmitter; this.jsonRpcEndpointIdProvider = jsonRpcEndpointIdProvider; } @Override public void writeLine(String line) throws IOException { try { jsonRpcEndpointIdProvider .get() .forEach( it -> transmitter .newRequest() .endpointId(it) .methodName(method) .paramsAsString(line) .sendAndSkipResult()); } catch (IllegalStateException e) { LOG.error("Error trying to send a line: {}", line); } } @Override public void close() throws IOException {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/JsonRpcMessageConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import static org.slf4j.LoggerFactory.getLogger; import java.io.IOException; import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; import org.slf4j.Logger; public class JsonRpcMessageConsumer implements MessageConsumer { private static final Logger LOG = getLogger(JsonRpcMessageConsumer.class); private final String method; private final RequestTransmitter transmitter; private final JsonRpcEndpointIdProvider jsonRpcEndpointIdProvider; public JsonRpcMessageConsumer( String method, RequestTransmitter transmitter, JsonRpcEndpointIdProvider jsonRpcEndpointIdProvider) { this.method = method; this.transmitter = transmitter; this.jsonRpcEndpointIdProvider = jsonRpcEndpointIdProvider; } @Override public void consume(T message) throws IOException { try { jsonRpcEndpointIdProvider .get() .forEach( it -> transmitter .newRequest() .endpointId(it) .methodName(method) .paramsAsDto(message) .sendAndSkipResult()); } catch (IllegalStateException e) { LOG.error("Error trying send line {}", message); } } @Override public void close() throws IOException {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/LineConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.io.Closeable; import java.io.IOException; /** * Consumes text line by line for analysing, writing, storing, etc. * * @author andrew00x * @see AbstractLineConsumer */ public interface LineConsumer extends Closeable { /** Consumes single line. */ void writeLine(String line) throws IOException; LineConsumer DEV_NULL = new AbstractLineConsumer() {}; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/LineConsumerFactory.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; /** A factory for {@link LineConsumer} */ @FunctionalInterface public interface LineConsumerFactory { LineConsumer newLineConsumer(); LineConsumerFactory NULL = () -> LineConsumer.DEV_NULL; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/LinksHelper.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.util.LinkedList; import java.util.List; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.LinkParameter; import org.eclipse.che.dto.server.DtoFactory; /** * @author andrew00x */ public class LinksHelper { public static Link createLink( String method, String href, String consumes, String produces, String rel, LinkParameter... params) { List l = null; if (params != null && params.length > 0) { l = new LinkedList<>(); java.util.Collections.addAll(l, params); } return createLink(method, href, consumes, produces, rel, l); } public static Link createLink( String method, String href, String consumes, String produces, String rel, List params) { return DtoFactory.getInstance() .createDto(Link.class) .withMethod(method) .withHref(href) .withConsumes(consumes) .withProduces(produces) .withRel(rel) .withParameters(params); } public static Link createLink( String method, String href, String consumes, String produces, String rel) { return DtoFactory.getInstance() .createDto(Link.class) .withMethod(method) .withHref(href) .withConsumes(consumes) .withProduces(produces) .withRel(rel); } public static Link createLink(String method, String href, String produces, String rel) { return DtoFactory.getInstance() .createDto(Link.class) .withMethod(method) .withHref(href) .withProduces(produces) .withRel(rel); } public static Link createLink(String method, String href, String rel) { return DtoFactory.getInstance() .createDto(Link.class) .withMethod(method) .withHref(href) .withRel(rel); } public static Link createLink( String method, String href, String rel, List params) { return DtoFactory.getInstance() .createDto(Link.class) .withMethod(method) .withHref(href) .withRel(rel) .withParameters(params); } private LinksHelper() {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/ListLineConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.util.LinkedList; import java.util.List; /** * Implementation of line consumer that stores in the list strings that are passed with method * {@link #writeLine}. * *

Implementation is not threadsafe and requires external synchronization if is used in * multi-thread environment. * * @author andrew00x */ public class ListLineConsumer implements LineConsumer { protected final LinkedList lines; public ListLineConsumer() { lines = new LinkedList<>(); } @Override public void writeLine(String line) { lines.add(line); } @Override public void close() {} public void clear() { lines.clear(); } public List getLines() { return new LinkedList<>(lines); } public String getText() { if (lines.isEmpty()) { return ""; } final StringBuilder output = new StringBuilder(); int n = 0; for (String line : lines) { if (n > 0) { output.append('\n'); } output.append(line); n++; } return output.toString(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/MessageConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.io.Closeable; import java.io.IOException; /** * Consumes messages one by one for analysing, writing, storing, etc. * * @author Alexander Garagatyi */ public interface MessageConsumer extends Closeable { void consume(T message) throws IOException; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/PagingUtil.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.Collections.emptyMap; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; import jakarta.ws.rs.core.UriBuilder; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.che.api.core.Page; import org.eclipse.che.commons.lang.Pair; /** * Provides useful methods for working with {@link Page} instances and pageable uris management. * * @author Yevhenii Voevodin */ public final class PagingUtil { private static final String LINK_HEADER_SEPARATOR = ", "; /** * Helps to retrieve href along with rel from link header value part. Value format is {@literal * ; rel="next"}, so the first group is href while the second * group is rel. */ private static final Pattern LINK_HEADER_REGEX = Pattern.compile("<(?.+)>;.*rel=\"(?.+)\".*"); /** * Generates link header value from the page object and base uri. The Link header spec * * @param page the page used to generate link * @param uri the uri which is used for adding {@code skipCount} & {@code maxItems} query * parameters * @return 'Link' header value * @throws NullPointerException when either {@code page} or {@code uri} is null */ public static String createLinkHeader(Page page, URI uri) { requireNonNull(page, "Required non-null page"); requireNonNull(uri, "Required non-null uri"); final ArrayList> pageRefs = new ArrayList<>(4); pageRefs.add(Pair.of("first", page.getFirstPageRef())); pageRefs.add(Pair.of("last", page.getLastPageRef())); if (page.hasPreviousPage()) { pageRefs.add(Pair.of("prev", page.getPreviousPageRef())); } if (page.hasNextPage()) { pageRefs.add(Pair.of("next", page.getNextPageRef())); } final UriBuilder ub = UriBuilder.fromUri(uri); return pageRefs.stream() .map( refPair -> format( "<%s>; rel=\"%s\"", ub.clone() .replaceQueryParam("skipCount", refPair.second.getItemsBefore()) .replaceQueryParam("maxItems", refPair.second.getPageSize()) .build() .toString(), refPair.first)) .collect(joining(LINK_HEADER_SEPARATOR)); } /** * Returns REL to URI map based on the given {@code linkHeader} value. If the {@code linkHeader} * is null or empty then an empty map will be returned. * *

Note that link header is parsed due to the {@link #createLinkHeader(Page, URI)} method * strategy. * * @param linkHeader link header value */ public static Map parseLinkHeader(String linkHeader) { if (isNullOrEmpty(linkHeader)) { return emptyMap(); } final Map res = new HashMap<>(); for (String part : linkHeader.split(LINK_HEADER_SEPARATOR)) { final Matcher matcher = LINK_HEADER_REGEX.matcher(part); if (matcher.matches()) { res.put(matcher.group("rel"), matcher.group("href")); } } return res; } private PagingUtil() {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/RateExceedDetector.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.util.concurrent.TimeUnit; /** * Rate exceed detector. It used to detect exceeding of rate of some operation. If rate is exceeded * then method {@link #updateAndCheckRate()} returns {@code true}. A {@code RateExceedDetector} gets * max allowed permits per seconds in constructor, then it checks that required time have elapsed * between two calls of method {@link #updateAndCheckRate()}. * *

Implementation is not threadsafe and required external synchronization if it's used in * multi-thread environment. An example that prints on stdout if rate exceed 5 per seconds limit: * *

{@code
 * final RateExceedDetector r = new RateExceedDetector(5);
 *
 * void doSomething() {
 *     if (r.updateAndCheckRate()) {
 *         // do something useful
 *     } else {
 *         System.out.printf("Max rate exceeded, rate: %f 1/s%n", r.getRate());
 *     }
 * }
 *
 * }
*/ public class RateExceedDetector { private final long intervalMicros; private final long reportTimeMicros; private final long rateTime; private long count; private long lastMicros; private double rate; private long threshold; public RateExceedDetector(double permits) { intervalMicros = (long) (TimeUnit.SECONDS.toMicros(1L) / permits); reportTimeMicros = TimeUnit.SECONDS.toMicros(1); lastMicros = TimeUnit.NANOSECONDS.toMicros(System.nanoTime()); rateTime = 1L; } /** * Update rate and check is max allowed rate exceeded. * * @return {@code true} if max allowed rate is exceeded and {@code false} otherwise. If this * method return {@code true} typically need check average rate with method {@link * #getRate()}. In some cases it is possible to have smaller then expected interval between * two calls of this method but average rate may be under allowed limit. */ public boolean updateAndCheckRate() { final long nowTimeMicros = TimeUnit.NANOSECONDS.toMicros(System.nanoTime()); final boolean exceed = (nowTimeMicros - threshold) < 0; threshold = nowTimeMicros + intervalMicros; countRate(nowTimeMicros); return exceed; } private void countRate(long nowTimeMicros) { if (lastMicros + reportTimeMicros < nowTimeMicros) { rate = count / rateTime; lastMicros = nowTimeMicros; count = 0; } count++; } /** Get average rate of calls method {@link #updateAndCheckRate()}. */ public double getRate() { return rate; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/ShellFactory.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; /** * @author andrew00x */ public class ShellFactory { public static Shell getShell() { if (SystemInfo.isUnix()) { return new StandardLinuxShell(); } if (SystemInfo.isWindows()) { return new StandardWindowsShell(); } throw new IllegalStateException("Unsupported OS"); } /** Creates command line to run system command from set of arguments. */ public static interface Shell { String[] createShellCommand(CommandLine args); } /** Creates command line for standard command language interpreter. */ public static class StandardLinuxShell implements Shell { @Override public String[] createShellCommand(CommandLine args) { final String[] array = args.asArray(); if (array.length == 0) { throw new IllegalArgumentException("Command line is empty"); } StringBuilder buff = new StringBuilder(); for (String str : array) { if (!str.isEmpty()) { if (buff.length() > 0) { buff.append(' '); } for (int i = 0, len = str.length(); i < len; i++) { char c = str.charAt(i); switch (c) { case ' ': case '|': case '>': case '$': case '"': case '\'': case '&': case '(': case ')': case '~': case '@': case '#': case '%': case '!': case '^': case '*': buff.append('\\'); buff.append(c); break; case '\n': buff.append('\\'); buff.append('n'); break; case '\r': buff.append('\\'); buff.append('r'); break; case '\t': buff.append('\\'); buff.append('t'); break; case '\b': buff.append('\\'); buff.append('b'); break; case '\f': buff.append('\\'); buff.append('f'); break; default: buff.append(c); break; } } } } final String[] line = new String[3]; line[0] = "/bin/bash"; line[1] = "-cl"; line[2] = buff.toString(); return line; } } public static class StandardWindowsShell implements Shell { @Override public String[] createShellCommand(CommandLine args) { final String[] array = args.asArray(); if (array.length == 0) { throw new IllegalArgumentException("Command line is empty"); } StringBuilder buff = new StringBuilder(); for (String str : array) { if (!str.isEmpty()) { if (buff.length() > 0) { buff.append(' '); } for (int i = 0, len = str.length(); i < len; i++) { char c = str.charAt(i); switch (c) { case '|': case '\'': case '`': case '&': case ',': case ';': case '(': case ')': case '^': case '*': buff.append('^'); buff.append(c); break; default: buff.append(c); break; } } } } final String[] line = new String[3]; line[0] = "cmd.exe"; line[1] = "/c"; line[2] = buff.toString(); return line; } } private ShellFactory() {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/StreamPump.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @author andrew00x */ public final class StreamPump implements Runnable { private BufferedReader bufferedReader; private LineConsumer lineConsumer; private Exception exception; private boolean done; public synchronized void start(Process process, LineConsumer lineConsumer) { this.lineConsumer = lineConsumer; bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); final Thread t = new Thread(this, "StreamPump"); t.setDaemon(true); t.start(); } public synchronized void stop() { // Not clear do we need close original stream, but since it was wrapped by BufferedReader close // it anyway. try { bufferedReader.close(); } catch (IOException ignored) { } } public synchronized void await() throws InterruptedException { while (!done) { wait(); } } public synchronized boolean isDone() { return done; } public boolean hasError() { return null != exception; } public Exception getException() { return exception; } @Override public void run() { String line; try { while ((line = bufferedReader.readLine()) != null) { lineConsumer.writeLine(line); } } catch (IOException e) { exception = e; } finally { synchronized (this) { done = true; notifyAll(); } } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/SystemInfo.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides information about operating system. * * @author Andrey Parfonov */ public class SystemInfo { private static final Logger LOG = LoggerFactory.getLogger(SystemInfo.class); public static final String OS = System.getProperty("os.name").toLowerCase(); private static final boolean linux = OS.startsWith("linux"); private static final boolean mac = OS.startsWith("mac"); private static final boolean windows = OS.startsWith("windows"); private static final boolean unix = !windows; public static boolean isLinux() { return linux; } public static boolean isWindows() { return windows; } public static boolean isMacOS() { return mac; } public static boolean isUnix() { return unix; } private SystemInfo() {} } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/ValueHolder.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; /** * Holder for a value of type T. * * @author andrew00x */ public final class ValueHolder { private T value; public ValueHolder(T value) { this.value = value; } public ValueHolder() {} public synchronized T get() { return value; } public synchronized void set(T value) { this.value = value; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/Watchdog.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * It controls the time of {@code Cancellable} invocation and if time if greater than timeout it * terminates such {@code Cancellable}. * * @author Andrey Parfonov */ public final class Watchdog implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(Watchdog.class); private final String name; private final long timeout; private boolean watch; private Cancellable cancellable; /** * Create new {@code Watchdog}. * * @param name name for background {@code Thread}. It helps to identify out threads. This * parameter is optional and may be {@code null}. * @param timeout timeout * @param unit timeout unit */ public Watchdog(String name, long timeout, TimeUnit unit) { this.name = name; if (timeout < 1) { throw new IllegalArgumentException(String.format("Invalid timeout: %d", timeout)); } this.timeout = unit.toMillis(timeout); } public Watchdog(long timeout, TimeUnit unit) { this(null, timeout, unit); } /** * Start watching {@code Cancellable}. * * @param cancellable Cancellable */ public synchronized void start(Cancellable cancellable) { this.cancellable = cancellable; this.watch = true; final Thread t = name == null ? new Thread(this) : new Thread(this, name); t.setDaemon(true); t.start(); } /** Stop watching. */ public synchronized void stop() { watch = false; notify(); } /** NOTE: Not expected to call directly by regular users of this class. */ public synchronized void run() { final long end = System.currentTimeMillis() + timeout; long now; while (watch && (end > (now = System.currentTimeMillis()))) { try { wait(end - now); } catch (InterruptedException ignored) { // Not expected to be thrown } } if (watch) { try { cancellable.cancel(); } catch (Exception e) { LOG.error(e.getMessage(), e); } watch = false; } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/WritableLineConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.io.IOException; import java.io.Writer; /** * Line consumer that consume lines to provided Writer. * * @author Sergii Kabashniuk */ public class WritableLineConsumer implements LineConsumer { private final Writer writer; public WritableLineConsumer(Writer writer) { this.writer = writer; } @Override public void writeLine(String line) throws IOException { writer.write(line); } @Override public void close() throws IOException { writer.close(); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/lineconsumer/ConcurrentCompositeLineConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util.lineconsumer; import java.io.IOException; import java.nio.channels.ClosedByInterruptException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.eclipse.che.api.core.util.LineConsumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This line consumer consists of several consumers and copies each consumed line into all * subconsumers. Is used when lines should be written into two or more consumers. This * implementation is thread safe. * * @author andrew00x * @author Mykola Morhun */ public class ConcurrentCompositeLineConsumer implements LineConsumer { private static final Logger LOG = LoggerFactory.getLogger(ConcurrentCompositeLineConsumer.class); private final List lineConsumers; private final ReentrantReadWriteLock lock; private volatile boolean isOpen; public ConcurrentCompositeLineConsumer(LineConsumer... lineConsumers) { this.lineConsumers = new CopyOnWriteArrayList<>(lineConsumers); this.lock = new ReentrantReadWriteLock(); this.isOpen = true; } public boolean isOpen() { return isOpen; } /** Closes all unclosed subconsumers. */ @Override public void close() { if (isOpen) { lock.writeLock().lock(); try { isOpen = false; for (LineConsumer lineConsumer : lineConsumers) { try { lineConsumer.close(); } catch (IOException e) { LOG.error( String.format("An error occurred while closing the line consumer %s", lineConsumer), e); } } } finally { lock.writeLock().unlock(); } } } /** * Writes given line to each subconsumer. Do nothing if this consumer is closed or all * subconsumers are closed. * * @param line line to write */ @Override public void writeLine(String line) { if (isOpen && lock.readLock().tryLock()) { try { for (LineConsumer lineConsumer : lineConsumers) { try { lineConsumer.writeLine(line); } catch (ConsumerAlreadyClosedException | ClosedByInterruptException e) { lineConsumers.remove( lineConsumer); // consumer is already closed, so we cannot write into it any more if (lineConsumers.size() == 0) { // if all consumers are closed then we can close this one isOpen = false; } } catch (IOException e) { LOG.error( String.format( "An error occurred while writing line to the line consumer %s", lineConsumer), e); } } } finally { lock.readLock().unlock(); } } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/lineconsumer/ConcurrentFileLineConsumer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util.lineconsumer; import java.io.File; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.eclipse.che.api.core.util.LineConsumer; /** * Consumes logs and writes them into file. This implementation is thread safe. * * @author andrew00x * @author Mykola Morhun */ public class ConcurrentFileLineConsumer implements LineConsumer { private final File file; private final Writer writer; private final ReentrantReadWriteLock lock; private volatile boolean isOpen; public ConcurrentFileLineConsumer(File file) throws IOException { this.file = file; writer = Files.newBufferedWriter(file.toPath(), Charset.defaultCharset()); isOpen = true; lock = new ReentrantReadWriteLock(); } public File getFile() { return file; } public boolean isOpen() { return isOpen; } @Override public void writeLine(String line) throws IOException { if (isOpen && lock.readLock().tryLock()) { try { if (line != null) { writer.write(line); } writer.write('\n'); writer.flush(); } catch (IOException e) { if ("Stream closed".equals(e.getMessage())) { throw new ConsumerAlreadyClosedException(e.getMessage()); } throw e; } finally { lock.readLock().unlock(); } } } @Override public void close() throws IOException { if (isOpen) { lock.writeLock().lock(); try { isOpen = false; writer.close(); } finally { lock.writeLock().unlock(); } } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/util/lineconsumer/ConsumerAlreadyClosedException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util.lineconsumer; import java.io.IOException; /** * Is thrown as result of attempt to write a line into closed line consumer. * * @author Mykola Morhun */ public class ConsumerAlreadyClosedException extends IOException { public ConsumerAlreadyClosedException(String message) { super(message); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/websocket/commons/WebSocketMessageReceiver.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.commons; /** * Used as entry point for a web socket protocol message consumers. * * @author Dmitry Kuleshov */ public interface WebSocketMessageReceiver { /** * Receives a message by a a web socket protocol. * * @param endpointId identifier of an endpoint known to an transmitter implementation * @param message plain text message */ void receive(String endpointId, String message); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/websocket/commons/WebSocketMessageTransmitter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.commons; /** * Plain text transmitter over a web socket protocol. In current specification it is not required * from and the implementor to fulfill strict ordering of sequential transmissions along with * message delivery notification. The only guarantee that is required is to send a text message to * predefined endpoint. * * @author Dmitry Kuleshov */ public interface WebSocketMessageTransmitter { /** * Transmit a string message to an endpoint over wer socket protocol. The connection should be * considered to be opened at the moment of calling, however the some of implementation may * provide ability to cache messages until the connection is opened. * * @param endpointId identifier of an endpoint known to an transmitter implementation * @param message plain text message */ void transmit(String endpointId, String message); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/websocket/impl/BasicWebSocketEndpoint.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.impl; import static java.util.Collections.emptyMap; import static java.util.Collections.unmodifiableMap; import static org.eclipse.che.api.core.websocket.impl.WebsocketIdService.randomClientId; import jakarta.websocket.CloseReason; import jakarta.websocket.OnClose; import jakarta.websocket.OnError; import jakarta.websocket.OnMessage; import jakarta.websocket.OnOpen; import jakarta.websocket.Session; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageReceiver; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Duplex WEB SOCKET endpoint, handles messages, errors, session open/close events. * * @author Dmitry Kuleshov */ public abstract class BasicWebSocketEndpoint { private static final Logger LOG = LoggerFactory.getLogger(BasicWebSocketEndpoint.class); private final WebSocketSessionRegistry registry; private final MessagesReSender reSender; private final WebSocketMessageReceiver receiver; private final WebsocketIdService identificationService; private final Map sessionMessagesBuffer = new ConcurrentHashMap<>(); public BasicWebSocketEndpoint( WebSocketSessionRegistry registry, MessagesReSender reSender, WebSocketMessageReceiver receiver, WebsocketIdService identificationService) { this.registry = registry; this.reSender = reSender; this.receiver = receiver; this.identificationService = identificationService; } @OnOpen public void onOpen(Session session) { String combinedEndpointId = getOrGenerateCombinedEndpointId(session); LOG.debug("Web socket session opened"); LOG.debug("Endpoint: {}", combinedEndpointId); session.setMaxIdleTimeout(0); registry.add(combinedEndpointId, session); reSender.resend(combinedEndpointId); sessionMessagesBuffer.put(session, new StringBuffer()); } @OnMessage public void onMessage(String messagePart, boolean last, Session session) { try { EnvironmentContext.getCurrent() .setSubject((Subject) session.getUserProperties().get("che_subject")); StringBuffer buffer = sessionMessagesBuffer.get(session); buffer.append(messagePart); if (last) { try { onMessage(buffer.toString(), session); } finally { buffer.setLength(0); } } } finally { EnvironmentContext.reset(); } } public void onMessage(String message, Session session) { Optional endpointIdOptional = registry.get(session); String combinedEndpointId; if (endpointIdOptional.isPresent()) { combinedEndpointId = endpointIdOptional.get(); LOG.trace("Receiving a web socket message."); LOG.trace("Endpoint: {}", combinedEndpointId); LOG.trace("Message: {}", message); } else { combinedEndpointId = getOrGenerateCombinedEndpointId(session); LOG.warn("Process interferes with an unidentified session"); } receiver.receive(combinedEndpointId, message); } @OnClose public void onClose(CloseReason closeReason, Session session) { Optional endpointIdOptional = registry.get(session); String combinedEndpointId; if (endpointIdOptional.isPresent()) { combinedEndpointId = endpointIdOptional.get(); LOG.debug("Web socket session closed"); LOG.debug("Endpoint: {}", combinedEndpointId); LOG.debug("Close reason: {}:{}", closeReason.getReasonPhrase(), closeReason.getCloseCode()); registry.remove(combinedEndpointId); sessionMessagesBuffer.remove(session); } else { LOG.warn("Closing unidentified session"); } } @OnError public void onError(Throwable t, Session session) { Optional endpointIdOptional = registry.get(session); if (endpointIdOptional.isPresent()) { LOG.debug("Web socket session error"); LOG.debug("Endpoint: {}", endpointIdOptional.get()); } else { LOG.warn("Web socket session error"); LOG.debug("Unidentified session"); } LOG.debug("Error: ", t); } protected abstract String getEndpointId(); private String getOrGenerateCombinedEndpointId(Session session) { Map queryParamsMap = getQueryParamsMap(session.getQueryString()); String clientId = queryParamsMap.getOrDefault("clientId", randomClientId()); return registry .get(session) .orElse(identificationService.getCombinedId(getEndpointId(), clientId)); } private Map getQueryParamsMap(String queryParamsString) { Map queryParamsMap = new HashMap<>(); for (String queryParamsPair : Optional.ofNullable(queryParamsString).orElse("").split("&")) { String[] pair = queryParamsPair.split("="); if (pair.length == 2) { queryParamsMap.put(pair[0], pair[1]); } } return queryParamsMap.isEmpty() ? emptyMap() : unmodifiableMap(queryParamsMap); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/websocket/impl/BasicWebSocketMessageTransmitter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.impl; import static org.slf4j.LoggerFactory.getLogger; import jakarta.websocket.Session; import java.io.IOException; import java.util.Optional; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageTransmitter; import org.slf4j.Logger; /** * Transmits messages over WEB SOCKET to a specific endpoint or broadcasts them. If WEB SOCKET * session is not opened adds messages to re-sender to try to send them when session will be opened * again. * * @author Dmitry Kuleshov */ @Singleton public class BasicWebSocketMessageTransmitter implements WebSocketMessageTransmitter { private static final Logger LOG = getLogger(BasicWebSocketMessageTransmitter.class); private final WebSocketSessionRegistry registry; private final MessagesReSender reSender; @Inject public BasicWebSocketMessageTransmitter( WebSocketSessionRegistry registry, MessagesReSender reSender) { this.registry = registry; this.reSender = reSender; } @Override public synchronized void transmit(String endpointId, String message) { Optional sessionOptional = registry.get(endpointId); if (!sessionOptional.isPresent()) { sessionOptional = registry.getByPartialMatch(endpointId).stream().findFirst(); } if (!sessionOptional.isPresent() || !sessionOptional.get().isOpen()) { LOG.trace("Session is not registered or closed, adding message to pending"); reSender.add(endpointId, message); } else { LOG.trace("Session registered and open, sending message"); try { sessionOptional.get().getBasicRemote().sendText(message); } catch (IOException e) { LOG.error("Error while trying to send a message to a basic websocket remote endpoint", e); } } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/websocket/impl/GuiceInjectorEndpointConfigurator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.impl; import com.google.inject.Injector; import jakarta.servlet.http.HttpSession; import jakarta.websocket.HandshakeResponse; import jakarta.websocket.server.HandshakeRequest; import jakarta.websocket.server.ServerEndpointConfig; import javax.inject.Inject; /** * Allows inject Guice instances on WEB SOCKET endpoint creation. * * @author Dmitry Kuleshov */ public class GuiceInjectorEndpointConfigurator extends ServerEndpointConfig.Configurator { @Inject private static Injector injector; public T getEndpointInstance(Class endpointClass) { return injector.getInstance(endpointClass); } @Override public void modifyHandshake( ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { HttpSession httpSession = (HttpSession) request.getHttpSession(); if (httpSession != null) { Object sessionSubject = httpSession.getAttribute("che_subject"); if (sessionSubject != null) { sec.getUserProperties().put("che_subject", sessionSubject); } } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/websocket/impl/MessagesReSender.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.impl; import com.google.common.collect.EvictingQueue; import jakarta.websocket.Session; import java.util.Map; import java.util.Optional; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.commons.schedule.ScheduleDelay; /** * Instance is responsible for re-sending messages that were not sent during the period when WEB * SOCKET session was closed. If session is closed during re-send process it stops and left messages * will be re-sent as WEB SOCKET session becomes open again. * * @author Dmitry Kuleshov */ @Singleton public class MessagesReSender { private static final int MAX_MESSAGES = 100; private final WebSocketSessionRegistry registry; private final Map> delayedMessageRegistry = new ConcurrentHashMap<>(); @Inject public MessagesReSender(WebSocketSessionRegistry registry) { this.registry = registry; } @ScheduleDelay(initialDelay = 60, delay = 60) void cleanStaleMessages() { long currentTimeMillis = System.currentTimeMillis(); delayedMessageRegistry .values() .forEach(it -> it.removeIf(m -> currentTimeMillis - m.timeMillis > 60_000)); delayedMessageRegistry.values().removeIf(Queue::isEmpty); } public void add(String endpointId, String message) { delayedMessageRegistry .computeIfAbsent(endpointId, k -> EvictingQueue.create(MAX_MESSAGES)) .offer(new DelayedMessage(message)); } public void resend(String endpointId) { Queue delayedMessages = delayedMessageRegistry.remove(endpointId); if (delayedMessages == null || delayedMessages.isEmpty()) { return; } Optional sessionOptional = registry.get(endpointId); if (!sessionOptional.isPresent()) { return; } Queue backingQueue = EvictingQueue.create(delayedMessages.size()); while (!delayedMessages.isEmpty()) { backingQueue.offer(delayedMessages.poll()); } Session session = sessionOptional.get(); for (DelayedMessage delayedMessage : backingQueue) { if (session.isOpen()) { session.getAsyncRemote().sendText(delayedMessage.message); } else { delayedMessages.add(delayedMessage); } } if (!delayedMessages.isEmpty()) { delayedMessageRegistry.put(endpointId, delayedMessages); } } private static class DelayedMessage { private final long timeMillis; private final String message; private DelayedMessage(String message) { this.message = message; this.timeMillis = System.currentTimeMillis(); } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/websocket/impl/WebSocketModule.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.impl; import com.google.inject.AbstractModule; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcMessageReceiver; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageReceiver; import org.eclipse.che.api.core.websocket.commons.WebSocketMessageTransmitter; public class WebSocketModule extends AbstractModule { @Override protected void configure() { requestStaticInjection(GuiceInjectorEndpointConfigurator.class); bind(WebSocketMessageReceiver.class).to(JsonRpcMessageReceiver.class); bind(WebSocketMessageTransmitter.class).to(BasicWebSocketMessageTransmitter.class); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/websocket/impl/WebSocketSessionRegistry.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.impl; import static java.util.stream.Collectors.toSet; import static org.slf4j.LoggerFactory.getLogger; import jakarta.websocket.Session; import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Singleton; import org.slf4j.Logger; /** * Binds WEB SOCKET session to a specific endpoint form which it was opened. * * @author Dmitry Kuleshov */ @Singleton public class WebSocketSessionRegistry { private static final Logger LOG = getLogger(WebSocketSessionRegistry.class); private final Map sessionsMap = new ConcurrentHashMap<>(); public void add(String endpointId, Session session) { LOG.debug("Registering session with endpoint {}", session.getId(), endpointId); sessionsMap.put(endpointId, session); } public Optional remove(String endpointId) { LOG.debug("Cancelling registration for session with endpoint {}", endpointId); return Optional.ofNullable(sessionsMap.remove(endpointId)); } public Optional remove(Session session) { return get(session).flatMap(id -> Optional.ofNullable(sessionsMap.remove(id))); } public Optional get(String endpointId) { return Optional.ofNullable(sessionsMap.get(endpointId)); } public Set getByPartialMatch(String partialEndpointId) { return sessionsMap.entrySet().stream() .filter(it -> it.getKey().contains(partialEndpointId)) .map(Map.Entry::getValue) .collect(toSet()); } public Optional get(Session session) { return sessionsMap.entrySet().stream() .filter(entry -> entry.getValue().equals(session)) .map(Map.Entry::getKey) .findAny(); } public Set getSessions() { return new HashSet<>(sessionsMap.values()); } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/api/core/websocket/impl/WebsocketIdService.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.impl; import java.util.Random; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; /** * Identification service provide means to set and get unique identifiers for websocket clients. * There are several identifier elements to distinguish: clientId, endpointId, combinedId. Client id * is the identifier of a client that is passed over websocket to the client and back. EndpointId is * called to identify a websocket endpoint client connects through. CombinedId is a combination of * client and endpoint identifiers separated by a sequence of special charaters, it is used * internally. */ @Singleton public class WebsocketIdService { public static final String SEPARATOR = "<-:->"; private static final Random GENERATOR = new Random(); public static String randomClientId() { return String.valueOf(GENERATOR.nextInt(Integer.MAX_VALUE)); } @Inject private void configureHandler(RequestHandlerConfigurator requestHandlerConfigurator) { requestHandlerConfigurator .newConfiguration() .methodName("websocketIdService/getId") .noParams() .resultAsString() .withFunction(this::extractClientId); } public String getCombinedId(String endpointId, String clientId) { return clientId + SEPARATOR + endpointId; } public String extractClientId(String combinedId) { return combinedId.split(SEPARATOR)[0]; } public String extractEndpointId(String combinedId) { return combinedId.split(SEPARATOR)[1]; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/commons/env/EnvironmentContext.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.env; import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext; import org.eclipse.che.commons.subject.Subject; /** * Defines a component that holds variables of type {@link ThreadLocal} whose value is required by * the component to work normally and cannot be recovered. This component is mainly used when we * want to do a task asynchronously, in that case to ensure that the task will be executed in the * same conditions as if it would be executed synchronously we need to transfer the thread context * from the original thread to the executor thread. */ public class EnvironmentContext { /** ThreadLocal keeper for EnvironmentContext. */ private static ThreadLocal current = ThreadLocal.withInitial(EnvironmentContext::new); static { ThreadLocalPropagateContext.addThreadLocal(current); } public static EnvironmentContext getCurrent() { return current.get(); } public static void setCurrent(EnvironmentContext environment) { current.set(environment); } public static void reset() { current.remove(); } private Subject subject; public EnvironmentContext() {} public EnvironmentContext(EnvironmentContext other) { setSubject(other.getSubject()); } /** Returns subject or {@link Subject#ANONYMOUS} in case when subject is null. */ public Subject getSubject() { return subject == null ? Subject.ANONYMOUS : subject; } /** Sets subject. */ public void setSubject(Subject subject) { this.subject = subject; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/commons/proxy/ProxyAuthenticator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.proxy; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import java.net.Authenticator; import java.net.PasswordAuthentication; /** * Set default java.net.Authenticator for requesting through the http(s) proxy. Proxy credentials * are read from the system properties: http.proxyUser http.proxyPassword https.proxyUser * https.proxyPassword * *

Usage: ProxyAuthenticator.initAuthenticator(url) ... making http(s) request by url ... * ProxyAuthenticator.resetAuthenticator() * * @author Dmytro Nochevnov */ public class ProxyAuthenticator extends Authenticator { private static ThreadLocal currentProtocolHolder = new ThreadLocal<>(); static { Authenticator.setDefault(new ProxyAuthenticator()); } public static void initAuthenticator(String remoteUrl) { if (remoteUrl != null && remoteUrl.toUpperCase().startsWith(Protocol.HTTPS.toString())) { currentProtocolHolder.set(Protocol.HTTPS); } else { currentProtocolHolder.set(Protocol.HTTP); } } public static void resetAuthenticator() { currentProtocolHolder.remove(); } private enum Protocol { HTTP, HTTPS; private PasswordAuthentication passwordAuthentication = createPasswordAuthentication(); private PasswordAuthentication createPasswordAuthentication() { if (!(isNullOrEmpty(getProxyUserSystemProperty()) || isNullOrEmpty(getProxyPasswordSystemProperty()))) { return new PasswordAuthentication( getProxyUserSystemProperty(), getProxyPasswordSystemProperty().toCharArray()); } else { return null; } } private String getProxyUserSystemProperty() { String propertyName = format("%s.proxyUser", this.toString().toLowerCase()); return System.getProperty(propertyName); } private String getProxyPasswordSystemProperty() { String propertyName = format("%s.proxyPassword", this.toString().toLowerCase()); return System.getProperty(propertyName); } private PasswordAuthentication getPasswordAuthentication() { return passwordAuthentication; } } protected PasswordAuthentication getPasswordAuthentication() { Protocol protocol = currentProtocolHolder.get(); if (protocol != null) { return protocol.getPasswordAuthentication(); } else { return null; } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/commons/subject/Subject.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.subject; import java.util.List; import org.eclipse.che.api.core.ForbiddenException; /** * Subject represents authenticated user * * @author andrew00x * @author Sergii Leschenko */ public interface Subject { /** Unidentified subject */ Subject ANONYMOUS = new Subject() { @Override public String getUserId() { return "0000-00-0000"; } @Override public String getUserName() { return "Anonymous"; } @Override public List getGroups() { return java.util.Collections.emptyList(); } @Override public boolean hasPermission(String domain, String instance, String action) { return false; } @Override public void checkPermission(String domain, String instance, String action) throws ForbiddenException { throw new ForbiddenException( "User is not authorized to perform " + action + " of " + domain + " with id '" + instance + "'"); } @Override public String getToken() { return null; } @Override public boolean isAnonymous() { return true; } @Override public boolean isTemporary() { return false; } }; /** * Get user unique identifier. * *

Note: In comparison with name id never changes for the given user. * * @return unique identifier of user. */ String getUserId(); /** * @return name of user */ String getUserName(); /** * @return list of groups the user belongs to */ List getGroups(); /** * Checks does subject have specified permission. * * @return {@code true} if subject has permission to perform given action and {@code false} * otherwise */ boolean hasPermission(String domain, String instance, String action); /** * Ensures this Subject has specified permission. * * @throws ForbiddenException if subject doesn't have specified permission */ void checkPermission(String domain, String instance, String action) throws ForbiddenException; /** * @return subject auth token to be able to execute request as subject */ String getToken(); /** * Return {@code true} if subject is anonymous, {@code false} if this is a real authenticated * subject. */ default boolean isAnonymous() { return false; } /** * @return - true if subject is temporary, false if this is a real persistent subject. */ boolean isTemporary(); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/commons/subject/SubjectImpl.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.subject; import java.util.Collections; import java.util.List; import java.util.Objects; import org.eclipse.che.api.core.ForbiddenException; /** * Base implementation of {@link Subject}. * * @author andrew00x */ public class SubjectImpl implements Subject { private final String id; private final String name; private final List groups; private final String token; private final boolean isTemporary; public SubjectImpl( String name, List groups, String id, String token, boolean isTemporary) { this.name = name; this.groups = Collections.unmodifiableList(groups); this.id = id; this.token = token; this.isTemporary = isTemporary; } @Override public String getUserName() { return name; } @Override public List getGroups() { return groups; } @Override public boolean hasPermission(String domain, String instance, String action) { return false; } @Override public void checkPermission(String domain, String instance, String action) throws ForbiddenException { throw new ForbiddenException("User is not authorized to perform operation"); } @Override public String getToken() { return token; } @Override public String getUserId() { return id; } @Override public boolean isTemporary() { return isTemporary; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof SubjectImpl)) return false; SubjectImpl other = (SubjectImpl) obj; return Objects.equals(id, other.id) && Objects.equals(name, other.name) && Objects.equals(token, other.token) && Objects.equals(groups, other.groups) && isTemporary == other.isTemporary; } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(name); hash = 31 * hash + Objects.hashCode(token); hash = 31 * hash + Boolean.hashCode(isTemporary); hash = 31 * hash + Objects.hashCode(groups); return hash; } @Override public String toString() { return "SubjectImpl{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", groups=" + groups + ", token='" + token + '\'' + ", isTemporary=" + isTemporary + '}'; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/everrest/CheMethodInvokerFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.everrest; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.dto.server.DtoFactory; import org.everrest.core.method.MethodInvokerFilter; import org.everrest.core.resource.GenericResourceMethod; /** * Abstract implementation of {@link MethodInvokerFilter} which allow to throw instances of {@link * ApiException} which will be mapped to corresponded response * * @author Sergii Leschenko */ public abstract class CheMethodInvokerFilter implements MethodInvokerFilter { @Override public void accept(GenericResourceMethod genericMethodResource, Object[] arguments) throws WebApplicationException { try { filter(genericMethodResource, arguments); } catch (ApiException exception) { Response response; if (exception instanceof ForbiddenException) { response = Response.status(Response.Status.FORBIDDEN) .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); } else if (exception instanceof NotFoundException) { response = Response.status(Response.Status.NOT_FOUND) .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); } else if (exception instanceof UnauthorizedException) response = Response.status(Response.Status.UNAUTHORIZED) .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); else if (exception instanceof BadRequestException) { response = Response.status(Response.Status.BAD_REQUEST) .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); } else if (exception instanceof ConflictException) { response = Response.status(Response.Status.CONFLICT) .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); } else if (exception instanceof ServerException) { response = Response.serverError() .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); } else { response = Response.serverError() .entity(DtoFactory.getInstance().toJson(exception.getServiceError())) .type(MediaType.APPLICATION_JSON) .build(); } throw new WebApplicationException(response); } } /** * Check does supplied method can be invoked. * * @param genericMethodResource See {@link GenericResourceMethod} * @param arguments actual method arguments that were created from request * @throws ApiException if method can not be invoked cause current environment context, e.g. for * current user, with current request attributes, etc. */ protected abstract void filter(GenericResourceMethod genericMethodResource, Object[] arguments) throws ApiException; } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/everrest/ETagResponseFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.everrest; import static org.eclipse.che.everrest.ETagResponseFilter.EntityType.JSON_SERIALIZABLE; import static org.eclipse.che.everrest.ETagResponseFilter.EntityType.STRING; import static org.eclipse.che.everrest.ETagResponseFilter.EntityType.UNKNOWN; import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.core.EntityTag; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Request; import jakarta.ws.rs.core.Response; import java.nio.charset.Charset; import java.util.List; import org.eclipse.che.dto.server.JsonSerializable; import org.everrest.core.ApplicationContext; import org.everrest.core.Filter; import org.everrest.core.GenericContainerResponse; import org.everrest.core.ResponseFilter; /** * Filter implementing {@link org.everrest.core.ResponseFilter} in order to generate ETag for * clients that want to use conditional requests. It is applying on GET method and JSON content type * only. * * @author Florent Benoit */ @Filter public class ETagResponseFilter implements ResponseFilter { public enum EntityType { JSON_SERIALIZABLE, STRING, UNKNOWN } /** * Filter the given container response * * @param containerResponse the response to use */ public void doFilter(GenericContainerResponse containerResponse) { // get entity of the response Object entity = containerResponse.getEntity(); // no entity, skip if (entity == null) { return; } // Only handle JSON content if (!MediaType.APPLICATION_JSON_TYPE.equals(containerResponse.getContentType())) { return; } // Get the request ApplicationContext applicationContext = ApplicationContext.getCurrent(); Request request = applicationContext.getRequest(); // manage only GET requests if (!HttpMethod.GET.equals(request.getMethod())) { return; } // calculate hash with MD5 HashFunction hashFunction = Hashing.md5(); Hasher hasher = hashFunction.newHasher(); boolean hashingSuccess = true; // Manage a list if (entity instanceof List) { List entities = (List) entity; for (Object simpleEntity : entities) { hashingSuccess = addHash(simpleEntity, hasher); if (!hashingSuccess) { break; } } } else { hashingSuccess = addHash(entity, hasher); } // if we're able to handle the hash if (hashingSuccess) { // get result of the hash HashCode hashCode = hasher.hash(); // Create the entity tag EntityTag entityTag = new EntityTag(hashCode.toString()); // Check the etag Response.ResponseBuilder builder = request.evaluatePreconditions(entityTag); // not modified ? if (builder != null) { containerResponse.setResponse(builder.tag(entityTag).build()); } else { // it has been changed, so send response with new ETag and entity Response.ResponseBuilder responseBuilder = Response.fromResponse(containerResponse.getResponse()).tag(entityTag); containerResponse.setResponse(responseBuilder.build()); } } } /** * Helper method to add entity to hash. If there is an invalid entity type it will return false * * @param entity the entity object to analyze and extract JSON for hashing it * @param hasher the hasher used to add the hashes */ protected boolean addHash(Object entity, Hasher hasher) { // get entity type EntityType entityType = getElementType(entity); // check if (entityType == UNKNOWN) { // unknown entity type, cannot perform hash return false; } // add hash if all is OK try { hasher.putString(getJson(entity, entityType), Charset.defaultCharset()); } catch (RuntimeException e) { return false; } return true; } /** * Helper method to retrieving the JSON content based on the entity type * * @param entity the object to analyze * @return the JSON string or null if it's an unknown type */ protected String getJson(Object entity, EntityType entityType) { switch (entityType) { case JSON_SERIALIZABLE: return ((JsonSerializable) entity).toJson(); case STRING: return (String) entity; default: return null; } } /** * Helper method for getting the type of the JSON entity * * @param entity the entity object * @return the type of the element */ protected EntityType getElementType(Object entity) { if (JsonSerializable.class.isAssignableFrom(entity.getClass())) { return JSON_SERIALIZABLE; } if (String.class.isAssignableFrom(entity.getClass())) { return STRING; } return UNKNOWN; } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/everrest/EverrestDownloadFileResponseFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.everrest; import static jakarta.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION; import jakarta.ws.rs.core.Request; import jakarta.ws.rs.core.Response; import org.eclipse.che.api.core.rest.DownloadFileResponseFilter; import org.everrest.core.ApplicationContext; import org.everrest.core.Filter; import org.everrest.core.GenericContainerResponse; import org.everrest.core.ResponseFilter; /** * JAX-RS implementation of download filter. * * @author Florent Benoit */ @Filter public class EverrestDownloadFileResponseFilter extends DownloadFileResponseFilter implements ResponseFilter { /** * Filter the given container response. * * @param containerResponse the response to use */ public void doFilter(GenericContainerResponse containerResponse) { containerResponse.getResponse(); // Get the request ApplicationContext applicationContext = ApplicationContext.getCurrent(); Request request = applicationContext.getRequest(); // Apply header if all if correct String filename = getFileName( request, containerResponse.getContentType(), applicationContext, containerResponse.getStatus()); if (filename != null) { if (hasCompliantEntity(containerResponse.getEntity())) { // it has been changed, so send response with updated header Response.ResponseBuilder responseBuilder = Response.fromResponse(containerResponse.getResponse()) .header(CONTENT_DISPOSITION, "attachment; filename=" + filename); containerResponse.setResponse(responseBuilder.build()); } } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/security/PBKDF2PasswordEncryptor.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security; import static com.google.common.primitives.Ints.tryParse; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import com.google.common.hash.HashCode; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; /** * Encrypts password using Password-based-Key-Derivative-Function with * SHA512 as pseudorandom function. See rfc2898. * * @author Yevhenii Voevodin */ public class PBKDF2PasswordEncryptor implements PasswordEncryptor { private static final String PWD_FMT = "%s:%s:%d"; private static final Pattern PWD_REGEX = Pattern.compile("(?\\w+):(?\\w+):(?[0-9]+)"); private static final String SECRET_KEY_FACTORY_NAME = "PBKDF2WithHmacSHA512"; private static final SecureRandom SECURE_RANDOM = new SecureRandom(); /** * Minimum number of iterations required is 1_000(rfc2898), pick greater as potentially safer in * the case of brute-force attacks . */ private static final int ITERATIONS_COUNT = 10_000; /** 64bit salt length based on the rfc2898 spec . */ private static final int SALT_LENGTH = 64 / 8; @Override public String encrypt(String password) { requireNonNull(password, "Required non-null password"); final byte[] salt = new byte[SALT_LENGTH]; SECURE_RANDOM.nextBytes(salt); final HashCode hash = computeHash(password.toCharArray(), salt, ITERATIONS_COUNT); final HashCode saltHash = HashCode.fromBytes(salt); return format(PWD_FMT, hash, saltHash, ITERATIONS_COUNT); } @Override public boolean test(String password, String encryptedPassword) { requireNonNull(password, "Required non-null password"); requireNonNull(password, "Required non-null encrypted password"); final Matcher matcher = PWD_REGEX.matcher(encryptedPassword); if (!matcher.matches()) { return false; } // retrieve salt, password hash and iterations count from hash final Integer iterations = tryParse(matcher.group("iterations")); final String salt = matcher.group("saltHash"); final String pwdHash = matcher.group("pwdHash"); // compute password's hash and test whether it matches to given hash final HashCode hash = computeHash(password.toCharArray(), HashCode.fromString(salt).asBytes(), iterations); return hash.toString().equals(pwdHash); } private HashCode computeHash(char[] password, byte[] salt, int iterations) { try { final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY_NAME); final KeySpec keySpec = new PBEKeySpec(password, salt, iterations, 512); return HashCode.fromBytes(keyFactory.generateSecret(keySpec).getEncoded()); } catch (NoSuchAlgorithmException | InvalidKeySpecException x) { throw new RuntimeException(x.getMessage(), x); } } } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/security/PasswordEncryptor.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security; /** * Encrypts password in implementation specific way. * * @author Yevhenii Voevodin */ public interface PasswordEncryptor { /** * Encrypts the given {@code password}. * * @param password the plain password to be encrypted * @return the encrypted password * @throws NullPointerException when the password is null * @throws RuntimeException when any error occurs during password encryption */ String encrypt(String password); /** * Tests whether given {@code password} is {@code encryptedPassword}. * * @param encryptedPassword encrypted password * @param password the password to check * @return true if given {@code password} is {@code encryptedPassword} * @throws NullPointerException when either of arguments is null * @throws RuntimeException when any error occurs during test */ boolean test(String password, String encryptedPassword); } ================================================ FILE: core/che-core-api-core/src/main/java/org/eclipse/che/security/SHA512PasswordEncryptor.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security; import static java.util.Objects.requireNonNull; import com.google.common.hash.HashCode; import com.google.common.hash.Hashing; import com.google.common.primitives.Bytes; import java.nio.charset.Charset; import java.security.SecureRandom; /** * SHA-512 based encryptor {@code hash = sha512(password + salt) + salt}. * * @author Yevhenii Voevodin */ public class SHA512PasswordEncryptor implements PasswordEncryptor { /** * 64 bit salt length is based on the source. */ private static final int SALT_BYTES_LENGTH = 64 / 8; /** SHA-512 produces 512 bits. */ private static final int ENCRYPTED_PASSWORD_BYTES_LENGTH = 512 / 8; private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private static final Charset PWD_CHARSET = Charset.forName("UTF-8"); @Override public String encrypt(String password) { requireNonNull(password, "Required non-null password"); // generate salt final byte[] salt = new byte[SALT_BYTES_LENGTH]; SECURE_RANDOM.nextBytes(salt); // sha512(password + salt) final HashCode hash = Hashing.sha512().hashBytes(Bytes.concat(password.getBytes(PWD_CHARSET), salt)); final HashCode saltHash = HashCode.fromBytes(salt); // add salt to the hash, result length (512 / 8) * 2 + (64 / 8) * 2 = 144 return hash.toString() + saltHash.toString(); } @Override public boolean test(String password, String passwordHash) { requireNonNull(password, "Required non-null password"); requireNonNull(passwordHash, "Required non-null password's hash"); // retrieve salt from the hash final int passwordHashLength = ENCRYPTED_PASSWORD_BYTES_LENGTH * 2; if (passwordHash.length() < passwordHashLength + SALT_BYTES_LENGTH * 2) { return false; } final HashCode saltHash = HashCode.fromString(passwordHash.substring(passwordHashLength)); // sha1(password + salt) final HashCode hash = Hashing.sha512() .hashBytes(Bytes.concat(password.getBytes(PWD_CHARSET), saltHash.asBytes())); // test sha1(password + salt) + salt == passwordHash return (hash.toString() + saltHash.toString()).equals(passwordHash); } } ================================================ FILE: core/che-core-api-core/src/main/resources/content-types.properties ================================================ # # Copyright (c) 2012-2018 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # ai=application/postscript aif=audio/x-aiff aifc=audio/x-aiff aiff=audio/x-aiff any=text/any asc=text/plain au=audio/basic avi=video/x-msvideo bcpio=application/x-bcpio bin=application/octet-stream bmp=image/bmp bz2=application/x-bzip2 cdf=application/x-netcdf class=application/octet-stream cpio=application/x-cpio C=text/x-c++src c=text/x-c cc=text/x-c++src cpp=text/x-c++src cxx=text/x-c++src c++=text/x-c++src cpt=application/mac-compactpro cq=application/cq-durboser cmtc=application/x-chromattic+groovy csh=application/x-csh css=text/css Dockerfile=text/x-docker dcr=application/x-director dir=application/x-director dms=application/octet-stream doc=application/msword dvi=application/x-dvi dxr=application/x-director ear=application/java-archive ecma=text/qhtml erb=application/x-ruby+html eps=application/postscript esp=text/qhtml etx=text/x-setext exe=application/octet-stream ez=application/andrew-inset gadget=application/x-google-gadget gif=image/gif groovy=application/x-groovy grs=application/x-jaxrs+groovy gtar=application/x-gtar gtmpl=application/x-groovy+html gz=application/x-gzip h=text/x-chdr hdf=application/x-hdf hh=text/x-c++hdr hqx=application/mac-binhex40 htm=text/html html=text/html ice=x-conference/x-cooltalk ief=image/ief iges=model/iges igs=model/iges jar=application/java-archive java=text/x-java jpeg=image/jpeg jpe=image/jpeg jpg=image/jpeg jsp=application/jsp js=application/x-javascript json=application/json kar=audio/midi latex=application/x-latex less=text/css lha=application/octet-stream lzh=application/octet-stream man=application/x-troff-man manifest=text/plain me=application/x-troff-me mesh=model/mesh md=text/markdown mid=audio/midi midi=audio/midi mif=application/vnd=mif mov=video/quicktime m4v=video/x-m4v m4a=audio/x-m4a movie=video/x-sgi-movie mp2=audio/mp2 mp3=audio/mp3 mpe=video/mpe mpeg=video/mpeg mpg=video/mpg mpga=audio/mpga ms=application/x-troff-ms msg=application/vnd.ms-outlook msh=model/mesh nc=application/x-netcdf oda=application/oda pbm=image/x-portable-bitmap pdb=chemical/x-pdb pdf=application/pdf pgm=image/x-portable-graymap pgn=application/x-chess-pgn php=application/x-php png=image/png pnm=image/x-portable-anymap ppm=image/x-portable-pixmap ppt=application/ppt pptx=application/vnd.openxmlformats-officedocument.presentationml.presentation properties=text/plain ps=application/postscript py=text/x-python qhtml=text/qhtml qt=video/quicktime ra=audio/x-realaudio rar=application/rar ram=audio/x-pn-realaudio rm=audio/x-pn-realaudio ras=image/x-cmu-raster rb=application/x-ruby rgb=image/x-rgb roff=application/x-troff rpm=application/x-rpm rtf=text/rtf rtx=text/richtext ru=application/x-ruby sgm=text/sgml sgml=text/sgml sh=application/x-sh shar=application/x-shar silo=model/mesh sit=application/x-stuffit skd=application/x-koan skm=application/x-koan skp=application/x-koan skt=application/x-koan smi=application/smil smil=application/smil snd=audio/basic spl=application/x-futuresplash src=application/x-wais-source sv4cpio=application/x-sv4cpio sv4crc=application/x-sv4crc svg=image/svg+xml swf=application/x-shockwave-flash t=application/x-troff tar=application/x-tar tcl=application/x-tcl tex=application/x-tex texi=application/x-texinfo texinfo=application/x-texinfo tgz=application/x-gzip tif=image/tiff tiff=image/tiff tr=application/x-troff tsv=text/tab-separated-values txt=text/plain odt=application/vnd.oasis.opendocument.text ods=application/vnd.oasis.opendocument.spreadsheet odp=application/vnd.oasis.opendocument.presentation odb=application/vnd.oasis.opendocument.database ustar=application/x-ustar vcd=application/x-cdlink vm=text/plain vrml=model/vrml war=application/java-archive wav=audio/x-wav wrl=model/vrml xbm=image/x-xbitmap xls=application/xls xlsx=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xml=text/xml xpdl=text/xml xpm=image/x-xpixmap xwd=image/x-xwindowdump xyz=chemical/x-pdb yaml=text/yaml yml=text/yaml zip=application/zip ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/PageTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.reverseOrder; import static java.util.Collections.singleton; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.util.ArrayList; import java.util.TreeSet; import org.testng.annotations.Test; /** * Tests for {@link Page}. * * @author Yevhenii Voevodin */ public class PageTest { @Test( expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Required non-negative value of items before") public void shouldThrowIllegalArgumentWhenItemsBeforeIsNegative() throws Exception { new Page<>(emptyList(), -1, 1, 10); } @Test( expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Required positive value of page size") public void shouldThrowIllegalArgumentWhenPageSizeIsNotPositive() throws Exception { new Page<>(emptyList(), 1, 0, 10); } @Test( expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Required non-negative value of total items") public void shouldThrowIllegalArgumentWhenTotalCountIsNegative() throws Exception { new Page<>(emptyList(), 1, 1, -1); } @Test( expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "Required non-null items") public void shouldThrownNPEWhenItemsListIsNull() throws Exception { new Page<>(null, 1, 1, 1); } @Test public void pageShouldBeEmptyWhenItIsCreatedWithAneEmptyItemsCollection() throws Exception { assertTrue(new Page<>(emptyList(), 1, 1, 1).isEmpty()); } @Test public void testMiddleDataWindowPage() throws Exception { // item1. <= skipped <- first page start // item2. <= skipped // item3. <- page start <- first page end // item4. // item5. <- page end <- last page start // item6. // item7. <- last page end final Page page = new Page<>(asList("item3", "item4", "item5"), 2, 3, 7); assertFalse(page.isEmpty(), "non-empty page"); assertEquals(page.getItemsCount(), 3, "items size "); assertEquals(page.getSize(), 3, "page size"); assertEquals(page.getTotalItemsCount(), 7, "total elements count"); final Page.PageRef firstRef = page.getFirstPageRef(); assertEquals(firstRef.getPageSize(), 3, "first page size"); assertEquals(firstRef.getItemsBefore(), 0, "first page skip items"); final Page.PageRef lastRef = page.getLastPageRef(); assertEquals(lastRef.getPageSize(), 3, "last page size"); assertEquals(lastRef.getItemsBefore(), 6, "last page skip items"); assertEquals(page.getNumber(), -1, "page number"); assertFalse(page.hasPreviousPage(), "has previous page"); assertFalse(page.hasNextPage(), "page has next page"); assertEquals(page.getItems(), asList("item3", "item4", "item5")); assertEquals(page.getItems(i -> i.substring(4)), asList("3", "4", "5")); assertEquals( new ArrayList<>(page.fill(new TreeSet<>(reverseOrder()))), asList("item5", "item4", "item3")); } @Test public void testMiddlePage() throws Exception { // item1. <- previous page start <- first page start // item2. // item3. <- previous page end <- first page end // item4. <- page start // item5. // item6. <- page end // item7. <- next page start // item8. // item9. <- next page end // item.10 <- last page start/end final Page page = new Page<>(asList("item4", "item5", "item6"), 3, 3, 10); assertFalse(page.isEmpty(), "non-empty page"); assertEquals(page.getItemsCount(), 3, "items size"); assertEquals(page.getSize(), 3, "page size"); assertEquals(page.getTotalItemsCount(), 10, "total elements count"); final Page.PageRef firstRef = page.getFirstPageRef(); assertEquals(firstRef.getPageSize(), 3, "first page size"); assertEquals(firstRef.getItemsBefore(), 0, "first page skip items"); final Page.PageRef lastRef = page.getLastPageRef(); assertEquals(lastRef.getPageSize(), 3, "last page size"); assertEquals(lastRef.getItemsBefore(), 9, "last page skip items"); assertEquals(page.getNumber(), 2, "page number"); assertTrue(page.hasPreviousPage(), "has previous page"); final Page.PageRef prevRef = page.getPreviousPageRef(); assertEquals(prevRef.getItemsBefore(), 0, "items before prev page"); assertEquals(prevRef.getPageSize(), 3, "prev page size"); assertTrue(page.hasNextPage(), "page has next page"); final Page.PageRef nextRef = page.getNextPageRef(); assertEquals(nextRef.getItemsBefore(), 6, "items before next page"); assertEquals(nextRef.getPageSize(), 3, "next page size"); assertEquals(page.getItems(), asList("item4", "item5", "item6")); assertEquals(page.getItems(i -> i.substring(4)), asList("4", "5", "6")); assertEquals( new ArrayList<>(page.fill(new TreeSet<>(reverseOrder()))), asList("item6", "item5", "item4")); } @Test public void testFirstPage() throws Exception { // item1. <- page start // item2. // item3. // item4. // item5. <- page end // item6. <- last page start // item7. <- last page end final Page page = new Page<>(asList("item1", "item2", "item3", "item4", "item5"), 0, 5, 7); assertFalse(page.isEmpty(), "page is empty"); assertEquals(page.getItemsCount(), 5, "items items count"); assertEquals(page.getSize(), 5, "page size"); assertEquals(page.getTotalItemsCount(), 7, "total items"); final Page.PageRef firstRef = page.getFirstPageRef(); assertEquals(firstRef.getPageSize(), 5, "first page size"); assertEquals(firstRef.getItemsBefore(), 0, "first page skip items"); final Page.PageRef lastRef = page.getLastPageRef(); assertEquals(lastRef.getPageSize(), 5, "last page size"); assertEquals(lastRef.getItemsBefore(), 5, "last page skip items"); assertEquals(page.getNumber(), 1, "page number"); assertFalse(page.hasPreviousPage(), "has previous page"); assertTrue(page.hasNextPage(), "page has next page"); final Page.PageRef nextRef = page.getNextPageRef(); assertEquals(nextRef.getPageSize(), 5, "next page size"); assertEquals(nextRef.getItemsBefore(), 5, "next page skip items"); assertEquals(page.getItems(), asList("item1", "item2", "item3", "item4", "item5")); } @Test public void testLastPage() throws Exception { // item1. <- first page start // item2. // item3. <- first page end // item4. <- prev page start // item5. // item6. <- prev page end // item7. <- page start // item8. <- page end final Page page = new Page<>(asList("item7", "item8"), 6, 3, 8); assertFalse(page.isEmpty(), "page is empty"); assertEquals(page.getItemsCount(), 2, "items count"); assertEquals(page.getSize(), 3, "page size"); assertEquals(page.getTotalItemsCount(), 8, "total items"); final Page.PageRef firstRef = page.getFirstPageRef(); assertEquals(firstRef.getPageSize(), 3, "first page size"); assertEquals(firstRef.getItemsBefore(), 0, "first page skip items"); final Page.PageRef lastRef = page.getLastPageRef(); assertEquals(lastRef.getPageSize(), 3, "last page size"); assertEquals(lastRef.getItemsBefore(), 6, "last page skip items"); assertEquals(page.getNumber(), 3, "page number"); assertTrue(page.hasPreviousPage(), "has previous page"); final Page.PageRef prevRef = page.getPreviousPageRef(); assertEquals(prevRef.getPageSize(), 3, "prev page size"); assertEquals(prevRef.getItemsBefore(), 3, "prev page skip items"); assertFalse(page.hasNextPage(), "has next page"); assertEquals(page.getItems(), asList("item7", "item8")); } @Test public void testSmallPage() throws Exception { final Page page = new Page<>(singleton("item1"), 0, 1, 1); assertFalse(page.isEmpty(), "page is empty"); assertEquals(page.getItemsCount(), 1, "items count"); assertEquals(page.getSize(), 1, "page size"); assertEquals(page.getTotalItemsCount(), 1, "total items"); final Page.PageRef firstRef = page.getFirstPageRef(); assertEquals(firstRef.getPageSize(), 1, "first page size"); assertEquals(firstRef.getItemsBefore(), 0, "first page skip items"); final Page.PageRef lastRef = page.getLastPageRef(); assertEquals(lastRef.getPageSize(), 1, "last page size"); assertEquals(lastRef.getItemsBefore(), 0, "last page skip items"); assertEquals(page.getNumber(), 1, "page number"); assertFalse(page.hasPreviousPage(), "has previous page"); assertFalse(page.hasNextPage(), "page has next page"); assertEquals(page.getItems(), singleton("item1")); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/PagesTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.testng.annotations.BeforeSuite; import org.testng.annotations.Test; /** * Tests {@link Pages}. * * @author Yevhenii Voevodin */ public class PagesTest { private TestPagesSupplier testSource; @BeforeSuite private void setUp() { String[] strings = new String[10]; for (int i = 0; i < strings.length; i++) { strings[i] = "test-string-" + i; } testSource = new TestPagesSupplier(strings); } @Test public void eagerlyStreamsAllElements() { List result = Pages.stream(testSource::getStrings, 2).collect(Collectors.toList()); assertEquals(result, testSource.strings); } @Test public void eagerlyIteratesAllElements() throws Exception { ArrayList result = Lists.newArrayList(Pages.iterate(testSource::getStrings, 2)); assertEquals(result, testSource.strings); } @Test public void lazyStreamsAllElements() { List result = Pages.streamLazily(testSource::getStrings, 2).collect(Collectors.toList()); assertEquals(result, testSource.strings); } @Test public void lazyIteratesAllElements() { ArrayList result = Lists.newArrayList(Pages.iterateLazily(testSource::getStrings, 2)); assertEquals(result, testSource.strings); } @Test public void lazyStreamingDoesNotPollNextPageUntilNeeded() { TestPagesSupplier src = spy(new TestPagesSupplier("string1", "string2", "string3")); assertTrue(Pages.streamLazily(src::getStrings, 1).anyMatch(s -> s.equals("string2"))); verify(src, times(2)).getStrings(anyInt(), anyLong()); verify(src).getStrings(1, 0); verify(src).getStrings(1, 1); } @Test public void lazyIteratingDoesNotPollNextPageUntilNeeded() { TestPagesSupplier src = spy(new TestPagesSupplier("string1", "string2", "string3")); Iterator it = Pages.iterateLazily(src::getStrings, 1).iterator(); it.next(); it.next(); verify(src, times(2)).getStrings(anyInt(), anyLong()); verify(src).getStrings(1, 0); verify(src).getStrings(1, 1); } @Test public void returnsEmptyStreamWhenFetchingEagerly() { Stream stream = Pages.stream(new TestPagesSupplier()::getStrings); assertFalse(stream.findAny().isPresent()); } @Test public void returnsIterableWithNoElementsWhileFetchingEagerly() { Iterator it = Pages.iterate(new TestPagesSupplier()::getStrings).iterator(); assertFalse(it.hasNext()); } @Test public void returnsEmptyStreamWhenFetchingLazily() { Stream stream = Pages.streamLazily(new TestPagesSupplier()::getStrings); assertFalse(stream.findAny().isPresent()); } @Test public void returnsIterableWithNoeElementsWhileFetchingLazily() { Iterator it = Pages.iterateLazily(new TestPagesSupplier()::getStrings).iterator(); assertFalse(it.hasNext()); } private static class TestPagesSupplier { private final List strings; private TestPagesSupplier(String... strings) { this.strings = Arrays.asList(strings); } public Page getStrings(int max, long skip) { List items = strings.stream().skip(skip).limit(max).collect(Collectors.toList()); return new Page<>(items, skip, max, strings.size()); } } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/jsonrpc/commons/JsonRpcMessageReceiverTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import static java.util.Collections.singletonList; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.eclipse.che.api.core.websocket.impl.WebsocketIdService; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** Tests for {@link JsonRpcMessageReceiver} */ @Listeners(MockitoTestNGListener.class) public class JsonRpcMessageReceiverTest { static final String MESSAGE = "message"; static final String ENDPOINT_ID = "client-id" + WebsocketIdService.SEPARATOR + "endpoint-id"; @Mock RequestDispatcher requestDispatcher; @Mock ResponseDispatcher responseDispatcher; @Mock JsonRpcErrorTransmitter errorTransmitter; @Mock JsonRpcQualifier jsonRpcQualifier; @Mock JsonRpcUnmarshaller jsonRpcUnmarshaller; @Mock RequestProcessor requestProcessor; @InjectMocks JsonRpcMessageReceiver jsonRpcMessageReceiver; @Test public void shouldValidateMessage() throws Exception { jsonRpcMessageReceiver.receive(ENDPOINT_ID, MESSAGE); verify(jsonRpcQualifier).isValidJson(MESSAGE); } @Test public void shouldTransmitErrorWhenValidationFailed() throws Exception { when(jsonRpcQualifier.isValidJson(MESSAGE)).thenReturn(false); jsonRpcMessageReceiver.receive(ENDPOINT_ID, MESSAGE); verify(errorTransmitter).transmit(eq(ENDPOINT_ID), any(JsonRpcException.class)); } @Test public void shouldNotTransmitErrorWhenValidationSucceeded() throws Exception { when(jsonRpcQualifier.isValidJson(MESSAGE)).thenReturn(true); jsonRpcMessageReceiver.receive(ENDPOINT_ID, MESSAGE); verify(errorTransmitter, never()).transmit(eq(ENDPOINT_ID), any(JsonRpcException.class)); } @Test public void shouldUnmarshalArray() throws Exception { jsonRpcMessageReceiver.receive(ENDPOINT_ID, MESSAGE); verify(jsonRpcUnmarshaller).unmarshalArray(MESSAGE); } @Test public void shouldDispatchResponseIfResponseReceived() throws Exception { when(jsonRpcQualifier.isJsonRpcResponse(MESSAGE)).thenReturn(true); when(jsonRpcQualifier.isJsonRpcRequest(MESSAGE)).thenReturn(false); when(jsonRpcUnmarshaller.unmarshalArray(any())).thenReturn(singletonList(MESSAGE)); JsonRpcResponse jsonRpcResponse = Mockito.mock(JsonRpcResponse.class); when(jsonRpcUnmarshaller.unmarshalResponse(any())).thenReturn(jsonRpcResponse); jsonRpcMessageReceiver.receive(ENDPOINT_ID, MESSAGE); verify(responseDispatcher).dispatch(eq(ENDPOINT_ID), any(JsonRpcResponse.class)); } @Test public void shouldDispatchRequestIfRequestReceived() throws Exception { when(jsonRpcQualifier.isJsonRpcRequest(MESSAGE)).thenReturn(true); when(jsonRpcUnmarshaller.unmarshalArray(any())).thenReturn(singletonList(MESSAGE)); jsonRpcMessageReceiver.receive(ENDPOINT_ID, MESSAGE); verify(requestProcessor).process(any(), any()); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/jsonrpc/commons/RequestDispatcherTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.jsonrpc.commons; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** Tests for {@link RequestDispatcher} */ @Listeners(MockitoTestNGListener.class) public class RequestDispatcherTest { static final String ENDPOINT_ID = "endpoint-id"; static final String REQUEST_ID = "request-id"; static final String REQUEST_METHOD = "request-method"; @Mock RequestHandlerManager requestHandlerManager; @InjectMocks RequestDispatcher requestDispatcher; @Mock JsonRpcRequest request; @Mock JsonRpcParams params; @BeforeMethod public void setUp() throws Exception { lenient().when(request.getId()).thenReturn(REQUEST_ID); when(request.getMethod()).thenReturn(REQUEST_METHOD); when(request.getParams()).thenReturn(params); when(requestHandlerManager.isRegistered(REQUEST_METHOD)).thenReturn(true); } @Test public void shouldHandleRequest() throws Exception { when(request.hasId()).thenReturn(true); requestDispatcher.dispatch(ENDPOINT_ID, request); verify(requestHandlerManager).handle(ENDPOINT_ID, REQUEST_ID, REQUEST_METHOD, params); } @Test public void shouldHandleNotification() throws Exception { when(request.hasId()).thenReturn(false); requestDispatcher.dispatch(ENDPOINT_ID, request); verify(requestHandlerManager).handle(ENDPOINT_ID, REQUEST_METHOD, params); } @Test(expectedExceptions = JsonRpcException.class) public void shouldThrowExceptionOnNotRegisteredRequestHandler() throws Exception { when(requestHandlerManager.isRegistered(REQUEST_METHOD)).thenReturn(false); requestDispatcher.dispatch(ENDPOINT_ID, request); } @Test(expectedExceptions = JsonRpcException.class) public void shouldThrowExceptionOnNotRegisteredNotificationHandler() throws Exception { when(requestHandlerManager.isRegistered(REQUEST_METHOD)).thenReturn(false); requestDispatcher.dispatch(ENDPOINT_ID, request); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/notification/EventServiceTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.notification; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * @author andrew00x */ public class EventServiceTest { private EventService bus; @BeforeMethod public void setUp() { bus = new EventService(); } @Test public void testSimpleEvent() { final List events = new ArrayList<>(); bus.subscribe( new EventSubscriber() { @Override public void onEvent(Date event) { events.add(event); } }); bus.subscribe( new EventSubscriber() { @Override public void onEvent(String event) { events.add(event); } }); bus.subscribe( new EventSubscriber() { @Override public void onEvent(Long event) { events.add(event); } }); Date date = new Date(); bus.publish(date); bus.publish("hello"); bus.publish(123L); Assert.assertEquals(events.size(), 3); Assert.assertTrue(events.contains(date)); Assert.assertTrue(events.contains("hello")); Assert.assertTrue(events.contains(123L)); // ignored bus.publish(new Object()); Assert.assertEquals(events.size(), 3); } static interface I {} static class Listener implements I, EventSubscriber { final List events = new ArrayList<>(); @Override public void onEvent(String event) { events.add(event); } } static class ExtListener extends Listener {} @Test public void testRegisterHierarchicalListener() { ExtListener listener = new ExtListener(); bus.subscribe(listener); bus.publish("hello"); Assert.assertEquals(listener.events.size(), 1); Assert.assertEquals(listener.events.get(0), "hello"); } static class Event { String data; Event() { this("event"); } Event(String data) { this.data = data; } @Override public String toString() { return data; } } static class ExtEvent extends Event { ExtEvent() { super("ext_event"); } } @Test public void testHierarchicalEvent() { final List events = new ArrayList<>(); // register two listeners. // 1. Accept only Event type. bus.subscribe( new EventSubscriber() { @Override public void onEvent(Event event) { events.add(String.format("1:%s", event)); } }); // 2. Accept Event and ExtEvent types. bus.subscribe( new EventSubscriber() { @Override public void onEvent(ExtEvent event) { events.add(String.format("2:%s", event)); } }); bus.publish(new Event()); Assert.assertEquals(events.size(), 1); Assert.assertEquals(events.get(0), "1:event"); events.clear(); bus.publish(new ExtEvent()); Assert.assertEquals(events.size(), 2); Assert.assertTrue(events.contains("1:ext_event")); Assert.assertTrue(events.contains("2:ext_event")); } @Test public void testUnsubscribe() { final List events = new ArrayList<>(); EventSubscriber l = new EventSubscriber() { @Override public void onEvent(Event event) { events.add(event.data); } }; bus.subscribe(l); bus.publish(new Event()); Assert.assertEquals(events.size(), 1); bus.unsubscribe(l); events.clear(); bus.publish(new Event()); Assert.assertEquals(events.size(), 0); } @Test(expectedExceptions = IllegalArgumentException.class) public void shouldNotDetermineTheTypeOfEventOnSubscribe() { bus.subscribe(new CustomEventSubscriber<>()); } @Test(expectedExceptions = IllegalArgumentException.class) public void shouldNotDetermineTheTypeOfEventOnUnsubscribe() { final CustomEventSubscriber sb = new CustomEventSubscriber<>(); bus.subscribe(sb, CustomEventImpl.class); bus.unsubscribe(sb); } @Test public void shouldSubscribeOnCustomEventSubscriber() { bus.subscribe(new CustomEventSubscriber<>(), CustomEventImpl.class); } @Test public void shouldUnsubscribeOnCustomEventSubscriber() { final CustomEventSubscriber sb = new CustomEventSubscriber<>(); bus.subscribe(sb, CustomEventImpl.class); bus.unsubscribe(sb, CustomEventImpl.class); } @Test public void shouldSendEventsThroughCustomEventSubscriber() { final CustomEventSubscriber sb = new CustomEventSubscriber<>(); bus.subscribe(sb, CustomEventImpl.class); bus.publish(new CustomEventImpl()); Assert.assertEquals(sb.events.size(), 1); bus.unsubscribe(sb, CustomEventImpl.class); } static class CustomEventSubscriber implements EventSubscriber { final List events = new ArrayList<>(); @Override public void onEvent(T event) { events.add(event.getMessage()); } } abstract static class CustomEvent { private final String message; public CustomEvent(String message) { this.message = message; } public String getMessage() { return message; } } static class CustomEventImpl extends CustomEvent { public CustomEventImpl() { super("message"); } } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/DefaultHttpJsonRequestTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.eclipse.che.api.core.util.LinksHelper.createLink; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; import jakarta.ws.rs.HttpMethod; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.util.LinksHelper; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.dto.server.JsonArrayImpl; import org.eclipse.che.dto.shared.JsonArray; import org.eclipse.che.dto.shared.JsonStringMap; import org.everrest.assured.EverrestJetty; import org.everrest.core.Filter; import org.everrest.core.GenericContainerRequest; import org.everrest.core.RequestFilter; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.testng.MockitoTestNGListener; import org.testng.ITestContext; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests of {@link DefaultHttpJsonRequest}. * * @author Yevhenii Voevodin */ @Listeners({MockitoTestNGListener.class, EverrestJetty.class}) public class DefaultHttpJsonRequestTest { @SuppressWarnings("unused") // used by EverrestJetty private static final EnvironmentFilter FILTER = new EnvironmentFilter(); @SuppressWarnings("unused") // used by EverrestJetty private static final ApiExceptionMapper EXCEPTION_MAPPER = new ApiExceptionMapper(); @SuppressWarnings("unused") // used by EverrestJetty private static final TestService TEST_SERVICE = new TestService(); private static final Subject TEST_SUBJECT = new SubjectImpl("name", Collections.emptyList(), "id", "token", false); private static final String DEFAULT_URL = "http://localhost:8080"; @Captor private ArgumentCaptor> mapCaptor; @Captor private ArgumentCaptor> listCaptor; private DefaultHttpJsonRequest request; @BeforeMethod private void prepareDefaultResponse() throws Exception { request = spy(new DefaultHttpJsonRequest(DEFAULT_URL)); request.setMethod("GET"); prepareResponse(""); } @Test public void shouldUseUrlAndMethodFromTheLinks() throws Exception { final Link link = createLink("POST", DEFAULT_URL, "rel"); final DefaultHttpJsonRequest request = spy(new DefaultHttpJsonRequest(link)); doReturn(new DefaultHttpJsonResponse("", 200)) .when(request) .doRequest( anyInt(), anyString(), anyString(), nullable(Object.class), nullable(List.class), nullable(String.class), nullable(List.class)); request.request(); verify(request).doRequest(0, DEFAULT_URL, "POST", null, null, null, null); } @Test public void shouldBeAbleToMakeRequest() throws Exception { final Object body = new JsonArrayImpl<>(singletonList("element")); request .setMethod("PUT") .setBody(body) .setTimeout(10_000_000) .addQueryParam("name", "value") .addQueryParam("name2", "value2") .addHeader("Connection", "close") .request(); verify(request) .doRequest( 10_000_000, "http://localhost:8080", "PUT", body, asList(Pair.of("name", "value"), Pair.of("name2", "value2")), null, asList(Pair.of("Connection", "close"))); } @Test public void shouldBeAbleToUseStringMapAsRequestBody() throws Exception { final Map body = new HashMap<>(); body.put("name", "value"); body.put("name2", "value2"); request.setMethod("POST").setBody(body).request(); verify(request) .doRequest( eq(0), eq("http://localhost:8080"), eq("POST"), mapCaptor.capture(), eq(null), eq(null), eq(null)); assertTrue(mapCaptor.getValue() instanceof JsonStringMap); assertEquals(mapCaptor.getValue(), body); } @Test public void shouldBeAbleToUseListOfJsonSerializableElementsAsRequestBody() throws Exception { final List body = singletonList(createLink("POST", "http://localhost:8080", "rel")); request.setMethod("POST").setBody(body).request(); verify(request) .doRequest( eq(0), eq("http://localhost:8080"), eq("POST"), listCaptor.capture(), eq(null), eq(null), eq(null)); assertTrue(listCaptor.getValue() instanceof JsonArray); assertEquals(listCaptor.getValue(), body); } @Test public void defaultMethodIsGet() throws Exception { request.request(); verify(request).doRequest(0, DEFAULT_URL, HttpMethod.GET, null, null, null, null); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionIfSetMethodArgumentIsNull() throws Exception { new DefaultHttpJsonRequest("http://localhost:8080").setMethod(null); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenObjectBodyIsNull() throws Exception { final Object obj = null; new DefaultHttpJsonRequest("http://localhost:8080").setBody(obj); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenListBodyIsNull() throws Exception { final List list = null; new DefaultHttpJsonRequest("http://localhost:8080").setBody(list); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenMapBodyIsNull() throws Exception { final Map map = null; new DefaultHttpJsonRequest("http://localhost:8080").setBody(map); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenQueryParamNameIsNull() throws Exception { new DefaultHttpJsonRequest("http://localhost:8080").addQueryParam(null, "value"); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenQueryParamValueIsNull() throws Exception { new DefaultHttpJsonRequest("http://localhost:8080").addQueryParam("name", null); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenQueryParamsAreNull() throws Exception { new DefaultHttpJsonRequest("http://localhost:8080").addQueryParams(null); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenHeaderNameIsNull() throws Exception { new DefaultHttpJsonRequest("http://localhost:8080").addHeader(null, "close"); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenHeaderValueIsNull() throws Exception { new DefaultHttpJsonRequest("http://localhost:8080").addHeader("Connection", null); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenHeadersAreNull() throws Exception { new DefaultHttpJsonRequest("http://localhost:8080").addHeaders(null); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenLinkHrefIsNull() throws Exception { new DefaultHttpJsonRequest(createLink("GET", null, null)); } @Test(expectedExceptions = BadRequestException.class) public void shouldThrowBadRequestExceptionWhenResponseCodeIs400(ITestContext ctx) throws Exception { new DefaultHttpJsonRequest(getUrl(ctx) + "/400/response-code-test").useGetMethod().request(); } @Test(expectedExceptions = UnauthorizedException.class) public void shouldThrowUnauthorizedExceptionWhenResponseCodeIs401(ITestContext ctx) throws Exception { new DefaultHttpJsonRequest(getUrl(ctx) + "/401/response-code-test").useGetMethod().request(); } @Test(expectedExceptions = ForbiddenException.class) public void shouldThrowForbiddenExceptionWhenResponseCodeIs403(ITestContext ctx) throws Exception { new DefaultHttpJsonRequest(getUrl(ctx) + "/403/response-code-test").useGetMethod().request(); } @Test(expectedExceptions = NotFoundException.class) public void shouldThrowNotFoundExceptionWhenResponseCodeIs404(ITestContext ctx) throws Exception { new DefaultHttpJsonRequest(getUrl(ctx) + "/404/response-code-test").useGetMethod().request(); } @Test(expectedExceptions = ConflictException.class) public void shouldThrowConflictExceptionWhenResponseCodeIs409(ITestContext ctx) throws Exception { new DefaultHttpJsonRequest(getUrl(ctx) + "/409/response-code-test").useGetMethod().request(); } @Test(expectedExceptions = ServerException.class) public void shouldThrowServerExceptionWhenResponseCodeIs500(ITestContext ctx) throws Exception { new DefaultHttpJsonRequest(getUrl(ctx) + "/500/response-code-test").useGetMethod().request(); } @Test(expectedExceptions = ServerException.class) public void shouldThrowServerExceptionWhenResponseWithAnyOtherResponseCode(ITestContext ctx) throws Exception { new DefaultHttpJsonRequest(getUrl(ctx) + "/501/response-code-test").useGetMethod().request(); } @Test(expectedExceptions = IOException.class) public void shouldThrowIOExceptionIfServerReturnsTypeDifferentFromApplicationJson( ITestContext ctx) throws Exception { new DefaultHttpJsonRequest(getUrl(ctx) + "/text-plain").useGetMethod().request(); } @Test public void shouldThrowIOExceptionIfServerDoesNotReturnContentTypeOnNoContentResponse( ITestContext ctx) throws Exception { new DefaultHttpJsonRequest(getUrl(ctx) + "/no-content").useDeleteMethod().request(); } @Test public void shouldReadJsonObjectBodyAsString(ITestContext ctx) throws Exception { final DefaultHttpJsonRequest request = new DefaultHttpJsonRequest(getUrl(ctx) + "/application-json"); request.useGetMethod(); assertEquals(request.request().asString(), TestService.JSON_OBJECT); } @Test public void shouldEncodeRequestUrlInDefaultHttpJsonRequestAndDecodeInService(ITestContext ctx) throws Exception { final String base = getUrl(ctx) + "/decode"; final HttpJsonResponse response = new DefaultHttpJsonRequest(base) .addQueryParam("query", "some white spaces !!") .useGetMethod() .request(); final String url = base + "?query=some white spaces !!"; assertEquals(url, response.asString()); } @Test public void shouldSendJsonObjectBody(ITestContext ctx) throws Exception { final DefaultHttpJsonRequest request = new DefaultHttpJsonRequest(getUrl(ctx) + "/application-json"); final Link link = LinksHelper.createLink("GET", "localhost:8080/application-json", "rel"); final List links = request .usePostMethod() .setBody(Collections.singletonList(link)) .request() .asList(Link.class); assertEquals(links, Collections.singletonList(link)); } @Test public void shouldSendQueryParameters(ITestContext ctx) throws Exception { final DefaultHttpJsonRequest request = new DefaultHttpJsonRequest(getUrl(ctx) + "/query-parameters"); final Map map = request .usePutMethod() .addQueryParam("param1", "value1") .addQueryParam("param2", "value2") .request() .asProperties(); assertEquals(map, ImmutableMap.of("param1", "value1", "param2", "value2")); } @Test public void shouldSendMultipleQueryParameters(ITestContext ctx) throws Exception { final DefaultHttpJsonRequest request = new DefaultHttpJsonRequest(getUrl(ctx) + "/multi-query-parameters"); @SuppressWarnings("unchecked") final Map> map = request .usePutMethod() .addQueryParam("param1", "value1") .addQueryParam("param1", "value2") .request() .as(Map.class, new TypeToken>>() {}.getType()); assertEquals(map.get("param1"), asList("value1", "value2")); } @Test public void shouldUseTokenFromCurrentContextForAuthorization(ITestContext ctx) throws Exception { final EnvironmentContext context = new EnvironmentContext(); context.setSubject(TEST_SUBJECT); EnvironmentContext.setCurrent(context); new DefaultHttpJsonRequest(getUrl(ctx) + "/token").usePostMethod().request(); } @Filter public static class EnvironmentFilter implements RequestFilter { public void doFilter(GenericContainerRequest request) { EnvironmentContext.getCurrent().setSubject(TEST_SUBJECT); } } private String getUrl(ITestContext ctx) { return "http://localhost:" + ctx.getAttribute(EverrestJetty.JETTY_PORT) + "/rest/test"; } private void prepareResponse(String response) throws Exception { lenient() .doReturn(new DefaultHttpJsonResponse(response, 200)) .when(request) .doRequest( anyInt(), anyString(), anyString(), nullable(Object.class), nullable(List.class), nullable(String.class), nullable(List.class)); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/DefaultHttpJsonResponseTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.util.LinksHelper.createLink; import static org.testng.Assert.assertEquals; import com.google.common.reflect.TypeToken; import java.io.IOException; import java.util.Set; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.dto.server.JsonArrayImpl; import org.eclipse.che.dto.server.JsonStringMapImpl; import org.testng.annotations.Test; /** * Tests of {@link DefaultHttpJsonResponse}. * * @author Yevhenii Voevodin */ public class DefaultHttpJsonResponseTest { @Test public void shouldReturnStringAsItIsIfStringIsRequested() throws Exception { final DefaultHttpJsonResponse response = new DefaultHttpJsonResponse("string response", 200); assertEquals(response.asString(), "string response"); } @Test public void shouldReturnJsonSerializableInstanceIfItWasRequested() throws Exception { final Link testLink = createLink("POST", "http://localhost:8080", "rel"); final DefaultHttpJsonResponse response = new DefaultHttpJsonResponse(DtoFactory.getInstance().toJson(testLink), 200); assertEquals(response.asDto(Link.class), testLink); } @Test public void shouldDeserializeResponseToGivenType() throws Exception { final String responseBody = DtoFactory.getInstance().toJson(new JsonArrayImpl<>(singletonList("element"))); final DefaultHttpJsonResponse response = new DefaultHttpJsonResponse(responseBody, 200); assertEquals( response.as(Set.class, new TypeToken>() {}.getType()), singleton("element")); } @Test public void shouldBeAbleToRequestProperties() throws Exception { final String responseBody = DtoFactory.getInstance().toJson(new JsonStringMapImpl<>(singletonMap("key", "value"))); final DefaultHttpJsonResponse response = new DefaultHttpJsonResponse(responseBody, 200); assertEquals(response.asProperties(), singletonMap("key", "value")); } @Test public void shouldBeAbleToRequestListOfJsonSerializableElements() throws Exception { final Link testLink = createLink("POST", "http://localhost:8080", "rel"); final String responseBody = DtoFactory.getInstance().toJson(new JsonArrayImpl<>(singletonList(testLink))); final DefaultHttpJsonResponse response = new DefaultHttpJsonResponse(responseBody, 200); assertEquals(response.asList(Link.class), singletonList(testLink)); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenClazzIsNull() throws Exception { new DefaultHttpJsonResponse("{}", 200).as(null, null); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenDtoInterfaceIsNull() throws Exception { new DefaultHttpJsonResponse("{}", 200).asDto(null); } @Test(expectedExceptions = IOException.class) public void shouldThrowServerExceptionWhenParsingNotValidJsonContent() throws Exception { new DefaultHttpJsonResponse("not valid json", 200).as(Set.class, null); } @Test public void shouldReturnCorrectResponseCode() { final DefaultHttpJsonResponse response = new DefaultHttpJsonResponse("not valid json", 201); assertEquals(response.getResponseCode(), 201); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/LinkHeaderGenerationTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static com.google.common.collect.Sets.symmetricDifference; import static io.restassured.RestAssured.given; import static java.util.Arrays.asList; import static org.eclipse.che.commons.lang.UrlUtils.getQueryParameters; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; import static org.everrest.assured.JettyHttpServer.SECURE_PATH; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import io.restassured.response.Response; import java.net.URI; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.che.api.core.util.PagingUtil; import org.everrest.assured.EverrestJetty; import org.testng.ITestContext; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests of {@link Service#createLinkHeader} methods. * * @author Yevhenii Voevodin */ @Listeners(EverrestJetty.class) public class LinkHeaderGenerationTest { @SuppressWarnings("unused") // used by EverrestJetty private static final TestService TEST_SERVICE = new TestService(); @Test public void linksHeaderShouldBeCorrectlyGenerated(ITestContext ctx) throws Exception { final Response response = given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .when() .get(SECURE_PATH + "/test/paging/test-path-param?query-param=test-query-param"); assertEquals(response.getStatusCode(), 200); final String headerValue = response.getHeader("Link"); assertNotNull(headerValue, "Link header is missing in the response"); final Map relToLinkMap = PagingUtil.parseLinkHeader(headerValue); final Set expectedRels = new HashSet<>(asList("first", "last", "prev", "next")); assertEquals( relToLinkMap.keySet(), expectedRels, "Rels are different " + symmetricDifference(expectedRels, relToLinkMap.keySet())); final String expectedUri = "http://localhost:" + ctx.getAttribute(EverrestJetty.JETTY_PORT) + "/rest/private/test/paging/test-path-param"; for (String link : relToLinkMap.values()) { final URI uri = URI.create(link); final Map> params = getQueryParameters(uri.toURL()); assertEquals(params.size(), 3); assertNotNull(params.get("skipCount")); assertNotNull(params.get("maxItems")); assertEquals(params.get("query-param").get(0), "test-query-param"); assertEquals(link, expectedUri + '?' + uri.getQuery()); } } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/RemoteServiceDescriptorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import java.util.List; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.rest.annotations.Description; import org.eclipse.che.api.core.rest.annotations.GenerateLink; import org.eclipse.che.api.core.rest.annotations.Required; import org.eclipse.che.api.core.rest.annotations.Valid; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.ServiceDescriptor; import org.eclipse.che.commons.test.mockito.answer.SelfReturningAnswer; import org.everrest.assured.EverrestJetty; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.testng.MockitoTestNGListener; import org.testng.ITestContext; import org.testng.annotations.BeforeClass; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ @Listeners({MockitoTestNGListener.class, EverrestJetty.class}) public class RemoteServiceDescriptorTest { @SuppressWarnings("unused") // used by EverrestJetty private static final EchoService service = new EchoService(); private static final String SERVICE_PATH = "/test"; private static final String SERVICE_DESCRIPTION = "test service"; @Spy private DefaultHttpJsonRequestFactory requestFactory; private HttpJsonRequest request; @Mock private HttpJsonResponse response; private String serverUrl; private RemoteServiceDescriptor remoteServiceDescriptor; @BeforeClass public void setUp(ITestContext ctx) throws Exception { serverUrl = getServerUrl(ctx); request = mock(HttpJsonRequest.class, new SelfReturningAnswer()); lenient().when(request.request()).thenReturn(response); } @Test public void shouldBeAbleToReturnServiceDescriptors() throws Exception { remoteServiceDescriptor = new RemoteServiceDescriptor(serverUrl + SERVICE_PATH, requestFactory); final ServiceDescriptor serviceDescriptor = remoteServiceDescriptor.getServiceDescriptor(); assertNotNull(serviceDescriptor); assertEquals(serviceDescriptor.getHref(), serverUrl + SERVICE_PATH); assertEquals(serviceDescriptor.getDescription(), SERVICE_DESCRIPTION); assertEquals(serviceDescriptor.getLinks().size(), 1); } @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "test error") public void shouldThrowServerExceptionIfServiceNotFound() throws Exception { doReturn(request).when(requestFactory).fromUrl(anyString()); when(request.request()).thenThrow(new NotFoundException("test error")); remoteServiceDescriptor = new RemoteServiceDescriptor(serverUrl + "/non/existing/path", requestFactory); remoteServiceDescriptor.getServiceDescriptor(); } @Test public void shouldBeAbleToConfirmAvailability() throws Exception { remoteServiceDescriptor = new RemoteServiceDescriptor(serverUrl + SERVICE_PATH, requestFactory); assertTrue(remoteServiceDescriptor.isAvailable()); } @Test public void shouldReturnFalseOnNonAvailableServiceAvailabilityCheck() { remoteServiceDescriptor = new RemoteServiceDescriptor(serverUrl + "/non/existing/path", requestFactory); assertFalse(remoteServiceDescriptor.isAvailable()); } @Test public void shouldReturnNullIfLinkWithRequiredRelDoesNotExist() throws Exception { remoteServiceDescriptor = new RemoteServiceDescriptor(serverUrl + SERVICE_PATH, requestFactory); assertNull(remoteServiceDescriptor.getLink("echo2")); } @Test public void shouldBeAbleToReturnLinks() throws Exception { remoteServiceDescriptor = new RemoteServiceDescriptor(serverUrl + SERVICE_PATH, requestFactory); final List links = remoteServiceDescriptor.getLinks(); assertEquals(links.size(), 1); assertEquals(links.get(0).getMethod(), HttpMethod.GET); assertEquals(links.get(0).getHref(), serverUrl + SERVICE_PATH + "/my_method"); assertEquals(links.get(0).getProduces(), MediaType.TEXT_PLAIN); } @Description(SERVICE_DESCRIPTION) @Path(SERVICE_PATH) public static class EchoService extends Service { @GET @Path("my_method") @GenerateLink(rel = "echo") @Produces(MediaType.TEXT_PLAIN) public String echo( @Description("some text") @Required @Valid({"a", "b"}) @DefaultValue("a") @QueryParam("text") String test) { return test; } } private String getServerUrl(ITestContext ctx) { return "http://localhost:" + ctx.getAttribute(EverrestJetty.JETTY_PORT) + "/rest"; } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/RuntimeExceptionMapperTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static io.restassured.RestAssured.expect; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.core.Response; import org.everrest.assured.EverrestJetty; import org.hamcrest.Matchers; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(value = {EverrestJetty.class}) public class RuntimeExceptionMapperTest { @Path("/runtime-exception") public static class RuntimeExceptionService { @GET @Path("/re-empty-msg") public String reWithEmptyMessage() { throw new NullPointerException(); } } RuntimeExceptionService service; RuntimeExceptionMapper mapper; @Test public void shouldHandleRuntimeException() { final String expectedErrorMessage = "{\"message\":\"Internal Server Error occurred, error time:"; expect() .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) .body(Matchers.startsWith(expectedErrorMessage)) .when() .get("/runtime-exception/re-empty-msg"); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/ServiceDescriptorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static org.everrest.core.ApplicationContext.anApplicationContext; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.MediaType; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.che.api.core.rest.annotations.Description; import org.eclipse.che.api.core.rest.annotations.GenerateLink; import org.eclipse.che.api.core.rest.annotations.Required; import org.eclipse.che.api.core.rest.annotations.Valid; import org.eclipse.che.api.core.rest.shared.ParameterType; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.LinkParameter; import org.eclipse.che.api.core.rest.shared.dto.ServiceDescriptor; import org.everrest.core.ApplicationContext; import org.everrest.core.ResourceBinder; import org.everrest.core.impl.ApplicationProviderBinder; import org.everrest.core.impl.ContainerResponse; import org.everrest.core.impl.EverrestConfiguration; import org.everrest.core.impl.EverrestProcessor; import org.everrest.core.impl.ProviderBinder; import org.everrest.core.impl.RequestDispatcher; import org.everrest.core.impl.RequestHandlerImpl; import org.everrest.core.impl.ResourceBinderImpl; import org.everrest.core.tools.DependencySupplierImpl; import org.everrest.core.tools.ResourceLauncher; import org.testng.Assert; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; /** * @author Andrey Parfonov */ public class ServiceDescriptorTest { final String BASE_URI = "http://localhost/service"; final String SERVICE_URI = BASE_URI + "/test"; @Description("test service") @Path("test") public static class EchoService extends Service { @GET @Path("my_method") @GenerateLink(rel = "echo") @Produces(MediaType.TEXT_PLAIN) public String echo( @Description("some text") @Required @Valid({"a", "b"}) @DefaultValue("a") @QueryParam("text") String test) { return test; } } public static class Deployer extends Application { private final Set singletons; private final Set> classes; public Deployer() { classes = new HashSet<>(1); classes.add(EchoService.class); singletons = Collections.emptySet(); } @Override public Set> getClasses() { return classes; } @Override public Set getSingletons() { return singletons; } } ResourceLauncher launcher; @BeforeTest public void setUp() throws Exception { DependencySupplierImpl dependencies = new DependencySupplierImpl(); ResourceBinder resources = new ResourceBinderImpl(); ProviderBinder providers = new ApplicationProviderBinder(); EverrestProcessor processor = new EverrestProcessor( new EverrestConfiguration(), dependencies, new RequestHandlerImpl(new RequestDispatcher(resources), providers), null); launcher = new ResourceLauncher(processor); processor.addApplication(new Deployer()); ApplicationContext.setCurrent(anApplicationContext().withProviders(providers).build()); System.out.println("initialized"); } @Test public void testDescription() throws Exception { Assert.assertEquals(getDescriptor().getDescription(), "test service"); } @Test public void testServiceLocation() throws Exception { Assert.assertEquals(getDescriptor().getHref(), SERVICE_URI); } @Test public void testLinkAvailable() throws Exception { Assert.assertEquals(getDescriptor().getLinks().size(), 1); } @Test public void testLinkInfo() throws Exception { Link link = getLink("echo"); Assert.assertEquals(link.getMethod(), HttpMethod.GET); Assert.assertEquals(link.getHref(), SERVICE_URI + "/my_method"); Assert.assertEquals(link.getProduces(), MediaType.TEXT_PLAIN); } @Test public void testLinkParameters() throws Exception { Link link = getLink("echo"); List parameters = link.getParameters(); Assert.assertEquals(parameters.size(), 1); LinkParameter linkParameter = parameters.get(0); Assert.assertEquals(linkParameter.getDefaultValue(), "a"); Assert.assertEquals(linkParameter.getDescription(), "some text"); Assert.assertEquals(linkParameter.getName(), "text"); Assert.assertEquals(linkParameter.getType(), ParameterType.String); Assert.assertTrue(linkParameter.isRequired()); List valid = linkParameter.getValid(); Assert.assertEquals(valid.size(), 2); Assert.assertTrue(valid.contains("a")); Assert.assertTrue(valid.contains("b")); } private Link getLink(String rel) throws Exception { List links = getDescriptor().getLinks(); for (Link link : links) { if (link.getRel().equals(rel)) { return link; } } return null; } private ServiceDescriptor getDescriptor() throws Exception { String path = SERVICE_URI; ContainerResponse response = launcher.service(HttpMethod.OPTIONS, path, BASE_URI, null, null, null, null); Assert.assertEquals(response.getStatus(), 200); return (ServiceDescriptor) response.getEntity(); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/TestService.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.POST; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; import java.net.URLDecoder; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.dto.server.JsonArrayImpl; /** * Test service class, used in {@link DefaultHttpJsonRequestTest}. * * @author Yevhenii Voevodin */ @Path("/test") public class TestService extends Service { public static final String JSON_OBJECT = new JsonArrayImpl<>(singletonList("element")).toJson(); @GET @Path("/{response-code}/response-code-test") public Response getRequestedResponseCode(@PathParam("response-code") int responseCode) { return Response.status(responseCode) .entity(DtoFactory.newDto(ServiceError.class).withMessage("response code test method")) .build(); } @GET @Path("/text-plain") @Produces(TEXT_PLAIN) public String getTextPlain() { return "this is text/plain message"; } @GET @Path("/application-json") @Produces(APPLICATION_JSON) public String getJsonObject() { return JSON_OBJECT; } @POST @Path("/application-json") @Produces(APPLICATION_JSON) public List receiveJsonObject(List elements) { return elements; } @PUT @Path("/query-parameters") @Produces(APPLICATION_JSON) public Map queryParamsTest( @QueryParam("param1") String qp1, @QueryParam("param2") String qp2) { final Map map = new HashMap<>(); map.put("param1", qp1); map.put("param2", qp2); return map; } @PUT @Path("/multi-query-parameters") @Produces(APPLICATION_JSON) public Map> queryParamsTest(@QueryParam("param1") List values) { final Map> map = new HashMap<>(); map.put("param1", values); return map; } @POST @Path("/token") public void checkAuthorization(@HeaderParam(HttpHeaders.AUTHORIZATION) String token) throws UnauthorizedException { if (!EnvironmentContext.getCurrent().getSubject().getToken().equals(token)) { throw new UnauthorizedException( "Token '" + token + "' it is different from token in EnvironmentContext"); } } @GET @Path("/decode") @Produces(APPLICATION_JSON) public String getUriInfo(@QueryParam("query") String query, @Context UriInfo uriInfo) { return URLDecoder.decode(uriInfo.getRequestUri().toString()); } @GET @Path("/paging/{value}") @Produces(APPLICATION_JSON) public Response getStringList( @PathParam("value") String value, @QueryParam("query-param") String param) { final Page page = new Page<>(asList("item3", "item4", "item5"), 3, 3, 7); return Response.ok() .entity(page.getItems()) .header( "Link", createLinkHeader(page, "getStringList", singletonMap("query-param", param), value)) .build(); } @DELETE @Path("no-content") public Response noContent() { return Response.noContent().build(); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/rest/it/ApiInfoProviderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.rest.it; import static org.testng.Assert.*; import org.eclipse.che.api.core.rest.ApiInfoProvider; import org.eclipse.che.api.core.rest.shared.dto.ApiInfo; import org.testng.annotations.Test; public class ApiInfoProviderTest { @Test public void testGet() { // given ApiInfoProvider provider = new ApiInfoProvider("my custom build"); // when ApiInfo apiInfo = provider.get(); // then assertEquals(apiInfo.getBuildInfo(), "my custom build"); assertNotNull(apiInfo.getScmRevision()); assertNotNull(apiInfo.getSpecificationTitle()); assertNotNull(apiInfo.getSpecificationVersion()); assertNotNull(apiInfo.getImplementationVendor()); assertNotNull(apiInfo.getImplementationVersion()); assertEquals(apiInfo.getScmRevision().length(), 40); assertEquals(apiInfo.getSpecificationTitle(), "Che REST API"); assertEquals(apiInfo.getSpecificationVersion(), "1.0-beta2"); assertEquals(apiInfo.getImplementationVendor(), "Red Hat, Inc."); assertTrue(apiInfo.getImplementationVersion().length() > 4); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/util/CompositeLineConsumerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertTrue; import java.nio.channels.ClosedByInterruptException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.che.api.core.util.lineconsumer.ConsumerAlreadyClosedException; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Mykola Morhun */ @Listeners(value = {MockitoTestNGListener.class}) public class CompositeLineConsumerTest { @Mock private LineConsumer lineConsumer1; @Mock private LineConsumer lineConsumer2; @Mock private LineConsumer lineConsumer3; private CompositeLineConsumer compositeLineConsumer; private LineConsumer subConsumers[]; @BeforeMethod public void beforeMethod() throws Exception { subConsumers = new LineConsumer[] {lineConsumer1, lineConsumer2, lineConsumer3}; compositeLineConsumer = new CompositeLineConsumer(subConsumers); } @Test public void shouldWriteMessageIntoEachConsumer() throws Exception { // given final String message = "Test line"; // when compositeLineConsumer.writeLine(message); // then for (LineConsumer subConsumer : subConsumers) { verify(subConsumer).writeLine(eq(message)); } } @Test public void shouldNotWriteIntoSubConsumersAfterClosingCompositeConsumer() throws Exception { // given final String message = "Test line"; // when compositeLineConsumer.close(); compositeLineConsumer.writeLine(message); // then for (LineConsumer subConsumer : subConsumers) { verify(subConsumer, never()).writeLine(anyString()); } } @DataProvider(name = "subConsumersExceptions") public Object[][] subConsumersExceptions() { return new Throwable[][] { {new ConsumerAlreadyClosedException("Error")}, }; } @Test(dataProvider = "subConsumersExceptions") public void shouldCloseSubConsumerOnException(Throwable exception) throws Exception { // given final String message = "Test line"; final String message2 = "Test line2"; LineConsumer closedConsumer = mock(LineConsumer.class); doThrow(exception).when(closedConsumer).writeLine(anyString()); compositeLineConsumer = new CompositeLineConsumer(appendTo(subConsumers, closedConsumer)); // when compositeLineConsumer.writeLine(message); compositeLineConsumer.writeLine(message2); // then verify(closedConsumer, never()).writeLine(eq(message2)); for (LineConsumer consumer : subConsumers) { verify(consumer).writeLine(eq(message2)); } } @Test public void shouldDoNothingOnWriteLineIfAllSubConsumersAreClosed() throws Exception { // given final String message = "Test line"; LineConsumer[] closedConsumers = subConsumers; for (LineConsumer consumer : closedConsumers) { doThrow(ConsumerAlreadyClosedException.class).when(consumer).writeLine(anyString()); } compositeLineConsumer = new CompositeLineConsumer(closedConsumers); // when compositeLineConsumer.writeLine("Error"); compositeLineConsumer.writeLine(message); // then for (LineConsumer subConsumer : closedConsumers) { verify(subConsumer, never()).writeLine(eq(message)); } } @Test public void stopsWritingOnceInterrupted() throws Exception { doThrow(new ClosedByInterruptException()).when(lineConsumer2).writeLine("test"); compositeLineConsumer.writeLine("test"); assertTrue(Thread.interrupted()); verify(lineConsumer1).writeLine("test"); verify(lineConsumer2).writeLine("test"); verify(lineConsumer3, never()).writeLine("test"); } private LineConsumer[] appendTo(LineConsumer[] base, LineConsumer... toAppend) { List allElements = new ArrayList<>(); allElements.addAll(Arrays.asList(base)); allElements.addAll(Arrays.asList(toAppend)); return allElements.toArray(new LineConsumer[allElements.size()]); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/util/ErrorFilteredConsumerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import org.testng.annotations.Test; /** * @author Anatolii Bazko */ public class ErrorFilteredConsumerTest { @Test public void testRedirect() throws Exception { LineConsumer lineConsumer = mock(LineConsumer.class); ErrorFilteredConsumer errorFilteredConsumer = new ErrorFilteredConsumer(lineConsumer); errorFilteredConsumer.writeLine("Line 1"); errorFilteredConsumer.writeLine("Line 2"); errorFilteredConsumer.writeLine("[STDERR] Line 3"); errorFilteredConsumer.writeLine("[STDERR] Line 4"); errorFilteredConsumer.writeLine("Line 5"); verify(lineConsumer).writeLine("[STDERR] Line 3"); verify(lineConsumer).writeLine("[STDERR] Line 4"); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/util/FileLineConsumerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.slf4j.LoggerFactory.getLogger; import java.io.File; import java.io.Writer; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.slf4j.Logger; import org.testng.annotations.*; /** * @author Mykola Morhun */ @Ignore @Listeners(value = {MockitoTestNGListener.class}) public class FileLineConsumerTest { private static final Logger LOG = getLogger(FileLineConsumerTest.class); @Mock private Writer writer; private FileLineConsumer fileLineConsumer; private File file; @BeforeMethod public void beforeMethod() throws Exception { file = File.createTempFile("file", ".tmp"); fileLineConsumer = new FileLineConsumer(file); injectWriterMock(fileLineConsumer, writer); } @AfterClass public void afterClass() { if (!file.delete()) { LOG.warn("Failed to remove temporary file: '{}'.", file); } } @Test public void shouldBeAbleToWriteIntoFile() throws Exception { // given final String message = "Test line"; // when fileLineConsumer.writeLine(message); // then verify(writer).write(eq(message)); } @Test public void shouldNotWriteIntoFileAfterConsumerClosing() throws Exception { // given final String message = "Test line"; // when fileLineConsumer.close(); fileLineConsumer.writeLine(message); // then verify(writer, never()).write(anyString()); } /** * Inject Writer mock into FileLineConsumer class. This allow to test the FileLineConsumer * operations. * * @param fileLineConsumer instance in which mock will be injected * @param writerMock mock to inject * @throws Exception */ private void injectWriterMock(FileLineConsumer fileLineConsumer, Writer writerMock) throws Exception { Field writerField = fileLineConsumer.getClass().getDeclaredField("writer"); writerField.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(writerField, writerField.getModifiers() & ~Modifier.FINAL); writerField.set(fileLineConsumer, writerMock); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/util/PagingUtilTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import static java.util.Arrays.asList; import static org.eclipse.che.api.core.util.PagingUtil.createLinkHeader; import static org.eclipse.che.api.core.util.PagingUtil.parseLinkHeader; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEqualsNoOrder; import java.net.URI; import java.util.Map; import org.eclipse.che.api.core.Page; import org.testng.annotations.Test; /** * Tests for {@link PagingUtil}. * * @author Yevhenii Voevodin */ public class PagingUtilTest { @Test public void testCreatingLinksHeader() throws Exception { final Page page = new Page<>(asList("item3", "item4", "item5"), 3, 3, 7); final URI srcUri = URI.create("http://localhost:8080/path?qp=test"); final String linkHeader = createLinkHeader(page, srcUri); final String[] expLinks = ("; rel=\"first\", " + "; rel=\"last\", " + "; rel=\"prev\", " + "; rel=\"next\"") .split(", "); assertEqualsNoOrder(linkHeader.split(", "), expLinks); } @Test public void testParsingLinksHeader() throws Exception { final Map relToLinks = parseLinkHeader( "; rel=\"first\", " + "; rel=\"last\", " + "; rel=\"prev\", " + "; rel=\"next\""); assertEquals(relToLinks.size(), 4); assertEquals( relToLinks.get("first"), "http://localhost:8080/path?qp=test&skipCount=0&maxItems=3"); assertEquals( relToLinks.get("last"), "http://localhost:8080/path?qp=test&skipCount=4&maxItems=3"); assertEquals( relToLinks.get("next"), "http://localhost:8080/path?qp=test&skipCount=5&maxItems=3"); assertEquals( relToLinks.get("prev"), "http://localhost:8080/path?qp=test&skipCount=0&maxItems=3"); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/util/RateExceedDetectorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import org.testng.Assert; import org.testng.annotations.Test; /** * @author andrew00x */ public class RateExceedDetectorTest { @Test public void testExceedRate() throws Exception { RateExceedDetector rd = new RateExceedDetector(1); // 1 per second Assert.assertFalse(rd.updateAndCheckRate()); Thread.sleep(500); Assert.assertTrue(rd.updateAndCheckRate()); } @Test public void testStayUnderLimit() throws Exception { RateExceedDetector rd = new RateExceedDetector(3); // 3 per second Assert.assertFalse(rd.updateAndCheckRate()); Thread.sleep(400); Assert.assertFalse(rd.updateAndCheckRate()); } @Test public void testComplex() throws Exception { RateExceedDetector rd = new RateExceedDetector(3); // 3 per second Assert.assertFalse(rd.updateAndCheckRate()); Thread.sleep(200); Assert.assertTrue(rd.updateAndCheckRate()); Thread.sleep(500); Assert.assertFalse(rd.updateAndCheckRate()); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/util/StandardLinuxShellTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import org.testng.Assert; import org.testng.annotations.Test; /** * @author Andrey Parfonov */ public class StandardLinuxShellTest { @Test public void testEscapeSpaces() throws Exception { CommandLine cmd = new CommandLine().add("ls", "-l", "/home/andrew/some dir"); final String[] line = new ShellFactory.StandardLinuxShell().createShellCommand(cmd); final String[] expected = {"/bin/bash", "-cl", "ls -l /home/andrew/some\\ dir"}; Assert.assertEquals(line, expected); } @Test public void testEscapeControls() { CommandLine cmd = new CommandLine().add("ls", "-l", "/home/andrew/c|r>a$z\"y'dir&"); final String[] line = new ShellFactory.StandardLinuxShell().createShellCommand(cmd); final String[] expected = { "/bin/bash", "-cl", "ls -l /home/andrew/c\\|r\\>a\\$z\\\"y\\'dir\\&" }; Assert.assertEquals(line, expected); } @Test public void testEscapeSpecCharacters() { CommandLine cmd = new CommandLine().add("ls", "-l", "/home/andrew/some\n\r\t\b\fdir"); final String[] line = new ShellFactory.StandardLinuxShell().createShellCommand(cmd); final String[] expected = {"/bin/bash", "-cl", "ls -l /home/andrew/some\\n\\r\\t\\b\\fdir"}; Assert.assertEquals(line, expected); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/util/WatchdogTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.testng.Assert; import org.testng.annotations.Test; /** * @author Andrey Parfonov */ public class WatchdogTest { @Test public void testWatchDog() throws Exception { final CountDownLatch latch = new CountDownLatch(1); final boolean[] cancel = new boolean[] {false}; final Cancellable myCancellable = new Cancellable() { @Override public void cancel() throws Exception { cancel[0] = true; latch.countDown(); } }; final Watchdog watchdog = new Watchdog(1, TimeUnit.SECONDS); // wait 1 sec then cancel myCancellable watchdog.start(myCancellable); latch.await(2, TimeUnit.SECONDS); // wait 2 sec Assert.assertTrue(cancel[0], "cancellation failed"); // should be cancelled } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/util/lineconsumer/ConcurrentCompositeLineConsumerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util.lineconsumer; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertFalse; import static org.testng.Assert.fail; import java.nio.channels.ClosedByInterruptException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.commons.test.mockito.answer.WaitingAnswer; import org.mockito.Mock; import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.invocation.InvocationMatcher; import org.mockito.internal.verification.VerificationModeFactory; import org.mockito.internal.verification.api.VerificationData; import org.mockito.invocation.Invocation; import org.mockito.testng.MockitoTestNGListener; import org.mockito.verification.VerificationMode; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Mykola Morhun */ @Listeners(value = {MockitoTestNGListener.class}) public class ConcurrentCompositeLineConsumerTest { @Mock private LineConsumer lineConsumer1; @Mock private LineConsumer lineConsumer2; @Mock private LineConsumer lineConsumer3; private ConcurrentCompositeLineConsumer concurrentCompositeLineConsumer; private LineConsumer subConsumers[]; private ExecutorService executor; @BeforeMethod public void beforeMethod() throws Exception { subConsumers = new LineConsumer[] {lineConsumer1, lineConsumer2, lineConsumer3}; concurrentCompositeLineConsumer = new ConcurrentCompositeLineConsumer(subConsumers); executor = Executors.newFixedThreadPool(3); } @AfterMethod public void afterMethod() { executor.shutdownNow(); } @Test public void shouldWriteMessageIntoEachConsumer() throws Exception { // given final String message = "Test line"; // when concurrentCompositeLineConsumer.writeLine(message); // then for (LineConsumer subConsumer : subConsumers) { verify(subConsumer).writeLine(eq(message)); } } @Test public void shouldNotWriteIntoSubConsumersAfterClosingCompositeConsumer() throws Exception { // given final String message = "Test line"; // when concurrentCompositeLineConsumer.close(); concurrentCompositeLineConsumer.writeLine(message); // then for (LineConsumer subConsumer : subConsumers) { verify(subConsumer, never()).writeLine(anyString()); } } @DataProvider(name = "subConsumersExceptions") public Object[][] subConsumersExceptions() { return new Throwable[][] { {new ConsumerAlreadyClosedException("Error")}, {new ClosedByInterruptException()} }; } @Test(dataProvider = "subConsumersExceptions") public void shouldCloseSubConsumerOnException(Throwable exception) throws Exception { // given final String message = "Test line"; final String message2 = "Test line2"; LineConsumer closedConsumer = mock(LineConsumer.class); doThrow(exception).when(closedConsumer).writeLine(anyString()); concurrentCompositeLineConsumer = new ConcurrentCompositeLineConsumer(appendTo(subConsumers, closedConsumer)); // when concurrentCompositeLineConsumer.writeLine(message); concurrentCompositeLineConsumer.writeLine(message2); // then verify(closedConsumer, never()).writeLine(eq(message2)); for (LineConsumer consumer : subConsumers) { verify(consumer).writeLine(eq(message2)); } } @Test public void shouldDoNothingOnWriteLineIfAllSubConsumersAreClosed() throws Exception { // given final String message = "Test line"; LineConsumer[] closedConsumers = subConsumers; for (LineConsumer consumer : closedConsumers) { doThrow(ConsumerAlreadyClosedException.class).when(consumer).writeLine(anyString()); } concurrentCompositeLineConsumer = new ConcurrentCompositeLineConsumer(closedConsumers); // when concurrentCompositeLineConsumer.writeLine("Error"); concurrentCompositeLineConsumer.writeLine(message); // then for (LineConsumer subConsumer : closedConsumers) { verify(subConsumer, never()).writeLine(eq(message)); } } @Test public void shouldBeAbleToWriteIntoSubConsumersSimultaneously() throws Exception { // given final String message1 = "Message 1"; final String message2 = "Message 2"; WaitingAnswer waitingAnswer = new WaitingAnswer<>(); doAnswer(waitingAnswer).when(lineConsumer2).writeLine(eq(message1)); executor.execute(() -> concurrentCompositeLineConsumer.writeLine(message1)); waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS); // when concurrentCompositeLineConsumer.writeLine(message2); waitingAnswer.completeAnswer(); // then awaitFinalization(); for (LineConsumer consumer : subConsumers) { verify(consumer).writeLine(eq(message1)); verify(consumer).writeLine(eq(message2)); } } @Test public void closeOperationShouldWaitUntilAllCurrentOperationsWillBeFinished() throws Exception { // given final String message1 = "Message 1"; final String message2 = "Message 2"; WaitingAnswer waitingAnswer1 = waitOnWrite(lineConsumer2, message1); WaitingAnswer waitingAnswer2 = waitOnWrite(lineConsumer2, message2); // when executor.execute(concurrentCompositeLineConsumer::close); waitingAnswer1.completeAnswer(); waitingAnswer2.completeAnswer(); // then awaitFinalization(); assertFalse(concurrentCompositeLineConsumer.isOpen()); for (LineConsumer consumer : subConsumers) { verify(consumer).writeLine(eq(message1)); verify(consumer).writeLine(eq(message2)); verify(consumer, last()).close(); } } @Test public void shouldIgnoreWriteToSubConsumersAfterCloseWasCalled() throws Exception { // given WaitingAnswer waitingAnswer = new WaitingAnswer<>(); doAnswer(waitingAnswer).when(lineConsumer2).close(); executor.execute(() -> concurrentCompositeLineConsumer.close()); waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS); // when concurrentCompositeLineConsumer.writeLine("Test line"); waitingAnswer.completeAnswer(); // then awaitFinalization(); for (LineConsumer consumer : subConsumers) { verify(consumer, never()).writeLine(anyString()); } } /** * Executes write line into file in separate thread and waits on writing to file operation until * this thread released. * * @param consumer subconsumer on which thread should be freezed * @param message message to write * @return waiting answer to release this thread later using {@link * WaitingAnswer#completeAnswer()} * @throws Exception */ private WaitingAnswer waitOnWrite(LineConsumer consumer, String message) throws Exception { WaitingAnswer waitingAnswer = new WaitingAnswer<>(); doAnswer(waitingAnswer).when(consumer).writeLine(eq(message)); executor.execute(() -> concurrentCompositeLineConsumer.writeLine(message)); waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS); return waitingAnswer; } private void awaitFinalization() throws Exception { executor.shutdown(); if (!executor.awaitTermination(1_000, TimeUnit.MILLISECONDS)) { fail("Operation is hanged up. Terminated."); } } private LineConsumer[] appendTo(LineConsumer[] base, LineConsumer... toAppend) { List allElements = new ArrayList<>(); allElements.addAll(Arrays.asList(base)); allElements.addAll(Arrays.asList(toAppend)); return allElements.toArray(new LineConsumer[allElements.size()]); } /** * Checks whether interaction with given mock is the last one so far. Typical using: * *

   * verify(someMock, last()).someMethod();
   * 
*/ public static class Last implements VerificationMode { public Last() {} public void verify(VerificationData verificationData) { List invocations = verificationData.getAllInvocations(); InvocationMatcher invocationMatcher = verificationData.getWanted(); if (invocations == null || invocations.isEmpty()) { throw new MockitoException( "\nNo interactions with " + invocationMatcher.getInvocation().getMock() + " mock so far"); } Invocation invocation = invocations.get(invocations.size() - 1); if (!invocationMatcher.matches(invocation)) { throw new MockitoException("\nWanted but not invoked:\n" + invocationMatcher); } } public VerificationMode description(String description) { return VerificationModeFactory.description(this, description); } } public static VerificationMode last() { return new Last(); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/util/lineconsumer/ConcurrentFileLineConsumerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.util.lineconsumer; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.slf4j.LoggerFactory.getLogger; import static org.testng.Assert.assertFalse; import static org.testng.Assert.fail; import java.io.File; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.eclipse.che.commons.test.mockito.answer.WaitingAnswer; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.slf4j.Logger; import org.testng.annotations.*; /** * @author Mykola Morhun */ @Ignore @Listeners(value = {MockitoTestNGListener.class}) public class ConcurrentFileLineConsumerTest { private static final Logger LOG = getLogger(ConcurrentFileLineConsumer.class); @Mock private Writer writer; private ConcurrentFileLineConsumer concurrentFileLineConsumer; private ExecutorService executor; private File file; @BeforeMethod public void beforeMethod() throws Exception { file = File.createTempFile("file", ".tmp"); concurrentFileLineConsumer = new ConcurrentFileLineConsumer(file); injectWriterMock(concurrentFileLineConsumer, writer); executor = Executors.newFixedThreadPool(3); } @AfterMethod public void afterMethod() { executor.shutdownNow(); } @AfterClass public void afterClass() { if (!file.delete()) { LOG.warn("Failed to remove temporary file: '{}'.", file); } } @Test public void shouldBeAbleToWriteIntoFile() throws Exception { // given final String message = "Test line"; // when concurrentFileLineConsumer.writeLine(message); // then verify(writer).write(eq(message)); } @Test public void shouldNotWriteIntoFileAfterConsumerClosing() throws Exception { // given final String message = "Test line"; // when concurrentFileLineConsumer.close(); concurrentFileLineConsumer.writeLine(message); // then verify(writer, never()).write(anyString()); } @Test public void shouldBeAbleToWriteIntoFileSimultaneously() throws Exception { // given final String message1 = "Message 1"; final String message2 = "Message 2"; WaitingAnswer waitingAnswer = new WaitingAnswer<>(); doAnswer(waitingAnswer).when(writer).write(eq(message1)); executor.execute(() -> writeIntoConsumer(message1)); waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS); // when writeIntoConsumer(message2); waitingAnswer.completeAnswer(); // then awaitFinalization(); verify(writer).write(eq(message1)); verify(writer).write(eq(message2)); } @Test public void closeOperationShouldWaitUntilAllCurrentWriteOperationsWillBeFinished() throws Exception { // given final String message1 = "Message 1"; final String message2 = "Message 2"; WaitingAnswer waitingAnswer1 = waitOnWrite(message1); WaitingAnswer waitingAnswer2 = waitOnWrite(message2); // when executor.execute(this::closeConsumer); waitingAnswer1.completeAnswer(); waitingAnswer2.completeAnswer(); // then awaitFinalization(); assertFalse(concurrentFileLineConsumer.isOpen()); verify(writer).write(eq(message1)); verify(writer).write(eq(message2)); } @Test public void shouldNotWriteIntoSubConsumersWhenLockForCloseIsLocked() throws Exception { // given WaitingAnswer waitingAnswer = new WaitingAnswer<>(); doAnswer(waitingAnswer).when(writer).close(); executor.execute(this::closeConsumer); waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS); // when writeIntoConsumer("Test line"); waitingAnswer.completeAnswer(); // then awaitFinalization(); verify(writer, never()).write(anyString()); } /** * Inject Writer mock into FileLineConsumer class. This allow to test the FileLineConsumer * operations. * * @param concurrentFileLineConsumer instance in which mock will be injected * @param writerMock mock to inject * @throws Exception */ private void injectWriterMock( ConcurrentFileLineConsumer concurrentFileLineConsumer, Writer writerMock) throws Exception { Field writerField = concurrentFileLineConsumer.getClass().getDeclaredField("writer"); writerField.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(writerField, writerField.getModifiers() & ~Modifier.FINAL); writerField.set(concurrentFileLineConsumer, writerMock); } private void writeIntoConsumer(String message) { try { concurrentFileLineConsumer.writeLine(message); } catch (IOException ignore) { } } private void closeConsumer() { try { concurrentFileLineConsumer.close(); } catch (IOException ignore) { } } /** * Executes write line into file in separate thread and waits on writing to file operation until * this thread released. * * @param message message to write * @return waiting answer to release this thread later using {@link * WaitingAnswer#completeAnswer()} * @throws Exception */ private WaitingAnswer waitOnWrite(String message) throws Exception { WaitingAnswer waitingAnswer = new WaitingAnswer<>(); doAnswer(waitingAnswer).when(writer).write(eq(message)); executor.execute(() -> writeIntoConsumer(message)); waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS); return waitingAnswer; } private void awaitFinalization() throws Exception { executor.shutdown(); if (!executor.awaitTermination(1_000, TimeUnit.MILLISECONDS)) { fail("Operation is hanged up. Terminated."); } } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/websocket/impl/BasicWebSocketMessageTransmitterTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.impl; import static java.util.Collections.emptySet; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import jakarta.websocket.RemoteEndpoint; import jakarta.websocket.Session; import java.io.IOException; import java.util.Optional; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Test for {@link BasicWebSocketMessageTransmitter} * * @author Dmitry Kuleshov */ @Listeners(MockitoTestNGListener.class) public class BasicWebSocketMessageTransmitterTest { private static final String MESSAGE = "message"; private static final String ENDPOINT_ID = "id"; @Mock private WebSocketSessionRegistry registry; @Mock private MessagesReSender reSender; @InjectMocks private BasicWebSocketMessageTransmitter transmitter; @Mock private Session session; @Mock private RemoteEndpoint.Basic remote; @BeforeMethod public void setUp() throws Exception { lenient().when(session.getBasicRemote()).thenReturn(remote); when(session.isOpen()).thenReturn(true); when(registry.get(ENDPOINT_ID)).thenReturn(Optional.of(session)); lenient().when(registry.getSessions()).thenReturn(emptySet()); } @Test public void shouldSendDirectMessageIfSessionIsOpenAndEndpointIsSet() throws IOException { transmitter.transmit(ENDPOINT_ID, MESSAGE); verify(session).getBasicRemote(); verify(remote).sendText(MESSAGE); verify(reSender, never()).add(eq(ENDPOINT_ID), anyString()); } @Test public void shouldAddMessageToPendingIfSessionIsNotOpenedAndEndpointIsSet() throws IOException { when(session.isOpen()).thenReturn(false); transmitter.transmit(ENDPOINT_ID, MESSAGE); verify(session, never()).getBasicRemote(); verify(remote, never()).sendText(MESSAGE); verify(reSender).add(ENDPOINT_ID, MESSAGE); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/websocket/impl/MessagesReSenderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.impl; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import jakarta.websocket.RemoteEndpoint; import jakarta.websocket.Session; import java.util.Optional; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests for {@link MessagesReSender} * * @author Dmitry Kuleshov */ @Listeners(MockitoTestNGListener.class) public class MessagesReSenderTest { private static final String MESSAGE = "message"; private static final String ENDPOINT_ID = "id"; @Mock private WebSocketSessionRegistry sessionRegistry; @InjectMocks private MessagesReSender reSender; @Mock private Session session; @Mock private RemoteEndpoint.Async endpoint; @BeforeMethod public void beforeMethod() { when(sessionRegistry.get(anyString())).thenReturn(Optional.of(session)); lenient().when(session.getAsyncRemote()).thenReturn(endpoint); lenient().when(session.isOpen()).thenReturn(true); } @BeforeMethod public void before() { reSender = new MessagesReSender(sessionRegistry); } @Test public void shouldStopIfSessionIsNotRegistered() { when(sessionRegistry.get(anyString())).thenReturn(Optional.empty()); reSender.add(ENDPOINT_ID, MESSAGE); reSender.resend(ENDPOINT_ID); verify(sessionRegistry).get(ENDPOINT_ID); verify(session, never()).getAsyncRemote(); verify(endpoint, never()).sendText(MESSAGE); } @Test public void shouldKeepMessagesIfSessionIsClosed() { reSender.add(ENDPOINT_ID, MESSAGE); when(session.isOpen()).thenReturn(false); reSender.resend(ENDPOINT_ID); verify(session, never()).getAsyncRemote(); verify(endpoint, never()).sendText(MESSAGE); when(session.isOpen()).thenReturn(true); reSender.resend(ENDPOINT_ID); verify(session).getAsyncRemote(); verify(endpoint).sendText(MESSAGE); } @Test public void shouldProperlyAddForSingleEndpoint() { reSender.add(ENDPOINT_ID, MESSAGE); reSender.resend(ENDPOINT_ID); verify(sessionRegistry).get(ENDPOINT_ID); verify(session).getAsyncRemote(); verify(endpoint).sendText(MESSAGE); } @Test public void shouldProperlyAddForSeveralEndpoints() { reSender.add(ENDPOINT_ID, MESSAGE); reSender.add("1", MESSAGE); reSender.resend(ENDPOINT_ID); reSender.resend("1"); verify(sessionRegistry).get(ENDPOINT_ID); verify(sessionRegistry).get("1"); verify(session, times(2)).getAsyncRemote(); verify(endpoint, times(2)).sendText(MESSAGE); } @Test public void shouldClearOnExtractionForSingleEndpoint() { reSender.add(ENDPOINT_ID, MESSAGE); reSender.resend(ENDPOINT_ID); verify(sessionRegistry).get(ENDPOINT_ID); verify(session).getAsyncRemote(); verify(endpoint).sendText(MESSAGE); reSender.resend(ENDPOINT_ID); verify(sessionRegistry).get(ENDPOINT_ID); verify(session).getAsyncRemote(); verify(endpoint).sendText(MESSAGE); } @Test public void shouldClearOnExtractionForSeveralEndpoint() { reSender.add(ENDPOINT_ID, MESSAGE); reSender.add("1", MESSAGE); reSender.resend(ENDPOINT_ID); reSender.resend("1"); verify(sessionRegistry).get(ENDPOINT_ID); verify(sessionRegistry).get("1"); verify(session, times(2)).getAsyncRemote(); verify(endpoint, times(2)).sendText(MESSAGE); reSender.resend(ENDPOINT_ID); reSender.resend("1"); verify(sessionRegistry).get(ENDPOINT_ID); verify(sessionRegistry).get("1"); verify(session, times(2)).getAsyncRemote(); verify(endpoint, times(2)).sendText(MESSAGE); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/api/core/websocket/impl/WebSocketSessionRegistryTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.websocket.impl; import static org.mockito.Mockito.mock; import static org.testng.Assert.*; import jakarta.websocket.Session; import java.util.Optional; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests for {@link WebSocketSessionRegistry} * * @author Dmitry Kuleshov */ @Listeners(MockitoTestNGListener.class) public class WebSocketSessionRegistryTest { private WebSocketSessionRegistry registry; @Mock private Session session; @BeforeMethod public void setUp() throws Exception { registry = new WebSocketSessionRegistry(); } @Test public void shouldAddSession() { assertTrue(registry.getSessions().isEmpty()); registry.add("0", session); assertFalse(registry.getSessions().isEmpty()); } @Test public void shouldAddCorrectSession() { registry.add("0", this.session); final Optional sessionOptional = registry.get("0"); assertTrue(sessionOptional.isPresent()); final Session session = sessionOptional.get(); assertEquals(this.session, session); } @Test public void shouldRemoveSession() { registry.add("0", session); assertFalse(registry.getSessions().isEmpty()); assertEquals(1, registry.getSessions().size()); registry.remove("0"); assertTrue(registry.getSessions().isEmpty()); } @Test public void shouldGetAllSessions() { registry.add("0", session); assertFalse(registry.getSessions().isEmpty()); assertEquals(1, registry.getSessions().size()); registry.add("1", mock(Session.class)); assertFalse(registry.getSessions().isEmpty()); assertEquals(2, registry.getSessions().size()); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/commons/env/EnvironmentContextTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.env; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import java.util.Collections; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.testng.annotations.Test; public class EnvironmentContextTest { @Test public void shouldBeAbleToSetEnvContextInSameThread() { // given EnvironmentContext expected = EnvironmentContext.getCurrent(); expected.setSubject(new SubjectImpl("user", Collections.emptyList(), "id", "token", false)); EnvironmentContext actual = EnvironmentContext.getCurrent(); Subject actualSubject = actual.getSubject(); assertEquals(actualSubject.getUserName(), "user"); assertEquals(actualSubject.getUserId(), "id"); assertEquals(actualSubject.getToken(), "token"); assertFalse(actualSubject.isTemporary()); } @Test public void shouldReturnAnonymousSubjectWhenThereIsNoSubject() { // given EnvironmentContext expected = EnvironmentContext.getCurrent(); expected.setSubject(null); // when Subject actualSubject = EnvironmentContext.getCurrent().getSubject(); // then assertEquals(actualSubject.getUserName(), Subject.ANONYMOUS.getUserName()); assertEquals(actualSubject.getUserId(), Subject.ANONYMOUS.getUserId()); assertEquals(actualSubject.getToken(), Subject.ANONYMOUS.getToken()); assertEquals(actualSubject.isTemporary(), Subject.ANONYMOUS.isTemporary()); assertEquals(actualSubject.isAnonymous(), Subject.ANONYMOUS.isAnonymous()); } @Test(enabled = false) public void shouldNotBeAbleToSeeContextInOtherThread() { // given final EnvironmentContext expected = EnvironmentContext.getCurrent(); expected.setSubject(new SubjectImpl("user", Collections.emptyList(), "id", "token", false)); Thread otherThread = new Thread() { @Override public void run() { EnvironmentContext.getCurrent(); } }; } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/commons/proxy/ProxyAuthenticatorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.proxy; import static org.testng.Assert.assertEquals; import java.net.Authenticator; import java.net.PasswordAuthentication; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; /** * @author Dmytro Nochevnov */ public class ProxyAuthenticatorTest { public static final String HTTP_URL = "http://some.address"; public static final String HTTPS_URL = "https://some.address"; @BeforeClass public void setup() { System.setProperty("http.proxyUser", "user1"); System.setProperty("http.proxyPassword", "paswd1"); System.setProperty("https.proxyUser", "user2"); System.setProperty("https.proxyPassword", "paswd2"); } @Test public void shouldInitHttpsProxyAuthenticator() throws Exception { // when ProxyAuthenticator.initAuthenticator(HTTPS_URL); PasswordAuthentication testAuthentication = Authenticator.requestPasswordAuthentication(null, 0, null, null, null); // then assertEquals(testAuthentication.getUserName(), "user2"); assertEquals(String.valueOf(testAuthentication.getPassword()), "paswd2"); // when ProxyAuthenticator.resetAuthenticator(); // then testAuthentication = Authenticator.requestPasswordAuthentication(null, 0, null, null, null); assertEquals(testAuthentication, null); } @Test public void shouldInitHttpProxyAuthenticator() throws Exception { // when ProxyAuthenticator.initAuthenticator(HTTP_URL); // then PasswordAuthentication testAuthentication = Authenticator.requestPasswordAuthentication(null, 0, null, null, null); assertEquals(testAuthentication.getUserName(), "user1"); assertEquals(String.valueOf(testAuthentication.getPassword()), "paswd1"); // when ProxyAuthenticator.resetAuthenticator(); // then testAuthentication = Authenticator.requestPasswordAuthentication(null, 0, null, null, null); assertEquals(testAuthentication, null); } @AfterClass public void tearDown() { System.clearProperty("http.proxyUser"); System.clearProperty("http.proxyPassword"); System.clearProperty("https.proxyUser"); System.clearProperty("https.proxyPassword"); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/everrest/DownloadFileResponseFilterTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.everrest; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static jakarta.ws.rs.core.Response.Status.OK; import static org.everrest.core.ApplicationContext.anApplicationContext; import static org.testng.Assert.assertEquals; import jakarta.ws.rs.GET; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; import java.net.URI; import java.util.Arrays; import java.util.List; import org.eclipse.che.api.core.rest.ApiExceptionMapper; import org.eclipse.che.api.core.rest.DownloadFileResponseFilter; import org.everrest.core.ApplicationContext; import org.everrest.core.impl.ApplicationProviderBinder; import org.everrest.core.impl.ContainerRequest; import org.everrest.core.impl.ContainerResponse; import org.everrest.core.impl.EverrestConfiguration; import org.everrest.core.impl.EverrestProcessor; import org.everrest.core.impl.RequestDispatcher; import org.everrest.core.impl.RequestHandlerImpl; import org.everrest.core.impl.ResourceBinderImpl; import org.everrest.core.tools.DependencySupplierImpl; import org.everrest.core.tools.ResourceLauncher; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * Test the DownloadFileResponseFilter filter * * @author Florent Benoit */ public class DownloadFileResponseFilterTest { /** Base URI */ private static final String BASE_URI = "http://localhost/service"; /** Base Service */ private static final String SERVICE_PATH = BASE_URI + "/myservice"; /** Dummy JAX-RS POJO */ @Path("/myservice") public static class MyJaxRSService { @GET @Path("/list") @Produces(APPLICATION_JSON) public List getMembers() { return Arrays.asList("a", "b", "c"); } @GET @Path("/single") @Produces(APPLICATION_JSON) public String getMember() { return "hello"; } @POST @Path("/modify") @Produces(APPLICATION_JSON) public Response myPostMethod() { return Response.ok("helloContent").build(); } } /** Resource Launcher */ private ResourceLauncher resourceLauncher; /** * Setup env for launching requests * * @throws Exception */ @BeforeMethod public void before() throws Exception { // set up launcher final ResourceBinderImpl resources = new ResourceBinderImpl(); resources.addResource(MyJaxRSService.class, null); final DependencySupplierImpl dependencies = new DependencySupplierImpl(); final ApplicationProviderBinder providers = new ApplicationProviderBinder(); providers.addExceptionMapper(ApiExceptionMapper.class); providers.addResponseFilter(EverrestDownloadFileResponseFilter.class); final URI uri = new URI(BASE_URI); final ContainerRequest req = new ContainerRequest(null, uri, uri, null, null, null); final ApplicationContext context = anApplicationContext() .withRequest(req) .withProviders(providers) .withDependencySupplier(dependencies) .build(); ApplicationContext.setCurrent(context); final EverrestProcessor processor = new EverrestProcessor( new EverrestConfiguration(), dependencies, new RequestHandlerImpl(new RequestDispatcher(resources), providers), null); resourceLauncher = new ResourceLauncher(processor); } /** Check if header is absent when parameter is empty */ @Test public void checkNoParameterTest() throws Exception { final ContainerResponse response = resourceLauncher.service( HttpMethod.GET, SERVICE_PATH + "/list", BASE_URI, null, null, null); assertEquals(response.getStatus(), OK.getStatusCode()); // check entity Assert.assertEquals(response.getEntity(), Arrays.asList("a", "b", "c")); // Check headers MultivaluedMap headerTags = response.getHttpHeaders(); Assert.assertNotNull(headerTags); Assert.assertEquals(headerTags.size(), 1); } /** Check if header for downloading is added in response if we're also using a custom header */ @Test public void checkDownloadFileWithParameter() throws Exception { final ContainerResponse response = resourceLauncher.service( HttpMethod.GET, SERVICE_PATH + "/list?" + DownloadFileResponseFilter.QUERY_DOWNLOAD_PARAMETER + "=hello.json", BASE_URI, null, null, null); assertEquals(response.getStatus(), OK.getStatusCode()); // check entity Assert.assertEquals(response.getEntity(), Arrays.asList("a", "b", "c")); // headers = 2 Assert.assertEquals(response.getHttpHeaders().size(), 2); // Check custom header List headers = response.getHttpHeaders().get(HttpHeaders.CONTENT_DISPOSITION); Assert.assertNotNull(headers); Assert.assertEquals(headers.size(), 1); Assert.assertEquals(headers.get(0), "attachment; filename=hello.json"); } /** Check that parameter is only handled if there is a GET method, not POST */ @Test public void checkOnlyOnGetMethodTest() throws Exception { final ContainerResponse response = resourceLauncher.service( HttpMethod.POST, SERVICE_PATH + "/modify?" + DownloadFileResponseFilter.QUERY_DOWNLOAD_PARAMETER + "=hello.json", BASE_URI, null, null, null); assertEquals(response.getStatus(), OK.getStatusCode()); // check entity Assert.assertEquals(response.getEntity(), "helloContent"); // headers = 2 Assert.assertEquals(response.getHttpHeaders().size(), 1); // Check custom header List headers = response.getHttpHeaders().get(HttpHeaders.CONTENT_DISPOSITION); Assert.assertNull(headers); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/everrest/ETagResponseFilterTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.everrest; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static jakarta.ws.rs.core.Response.Status.NOT_MODIFIED; import static jakarta.ws.rs.core.Response.Status.OK; import static org.everrest.core.ApplicationContext.anApplicationContext; import static org.testng.Assert.assertEquals; import jakarta.ws.rs.GET; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.EntityTag; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Response; import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.rest.ApiExceptionMapper; import org.everrest.core.ApplicationContext; import org.everrest.core.impl.ApplicationProviderBinder; import org.everrest.core.impl.ContainerRequest; import org.everrest.core.impl.ContainerResponse; import org.everrest.core.impl.EverrestConfiguration; import org.everrest.core.impl.EverrestProcessor; import org.everrest.core.impl.RequestDispatcher; import org.everrest.core.impl.RequestHandlerImpl; import org.everrest.core.impl.ResourceBinderImpl; import org.everrest.core.tools.DependencySupplierImpl; import org.everrest.core.tools.ResourceLauncher; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * Test the ETag filter * * @author Florent Benoit */ public class ETagResponseFilterTest { /** Base URI */ private static final String BASE_URI = "http://localhost/service"; /** Base Service */ private static final String SERVICE_PATH = BASE_URI + "/myservice"; /** Dummy JAX-RS POJO */ @Path("/myservice") public static class MyJaxRSService { @GET @Path("/list") @Produces(APPLICATION_JSON) public List getMembers() { return Arrays.asList("a", "b", "c"); } @GET @Path("/single") @Produces(APPLICATION_JSON) public String getMember() { return "hello"; } @GET @Path("/modify") @Produces(APPLICATION_JSON) public Response modifyHeader() { return Response.ok("helloContent") .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=my.json") .build(); } } /** Resource Launcher */ private ResourceLauncher resourceLauncher; /** * Setup env for launching requests * * @throws Exception */ @BeforeMethod public void before() throws Exception { // set up launcher final ResourceBinderImpl resources = new ResourceBinderImpl(); resources.addResource(MyJaxRSService.class, null); final DependencySupplierImpl dependencies = new DependencySupplierImpl(); final ApplicationProviderBinder providers = new ApplicationProviderBinder(); providers.addExceptionMapper(ApiExceptionMapper.class); providers.addResponseFilter(ETagResponseFilter.class); final URI uri = new URI(BASE_URI); final ContainerRequest req = new ContainerRequest(null, uri, uri, null, null, null); final ApplicationContext contextImpl = anApplicationContext().withRequest(req).withProviders(providers).build(); contextImpl.setDependencySupplier(dependencies); ApplicationContext.setCurrent(contextImpl); final EverrestProcessor processor = new EverrestProcessor( new EverrestConfiguration(), dependencies, new RequestHandlerImpl(new RequestDispatcher(resources), providers), null); resourceLauncher = new ResourceLauncher(processor); } /** Check if ETag is generated for a list of JSON */ @Test public void filterListEntityTest() throws Exception { final ContainerResponse response = resourceLauncher.service( HttpMethod.GET, SERVICE_PATH + "/list", BASE_URI, null, null, null); assertEquals(response.getStatus(), OK.getStatusCode()); // check entity Assert.assertEquals(response.getEntity(), Arrays.asList("a", "b", "c")); // Check etag List headerTags = response.getHttpHeaders().get("ETag"); Assert.assertNotNull(headerTags); Assert.assertEquals(headerTags.size(), 1); Assert.assertEquals(headerTags.get(0), new EntityTag("900150983cd24fb0d6963f7d28e17f72")); } /** Check if ETag is added in response if we're also using a custom header */ @Test public void useExistingHeaders() throws Exception { final ContainerResponse response = resourceLauncher.service( HttpMethod.GET, SERVICE_PATH + "/modify", BASE_URI, null, null, null); assertEquals(response.getStatus(), OK.getStatusCode()); // check entity Assert.assertEquals(response.getEntity(), "helloContent"); // headers = 2 Assert.assertEquals(response.getHttpHeaders().keySet().size(), 3); // Check custom header List customTags = response.getHttpHeaders().get(HttpHeaders.CONTENT_DISPOSITION); Assert.assertNotNull(customTags); Assert.assertEquals(customTags.size(), 1); Assert.assertEquals(customTags.get(0), "attachment; filename=my.json"); // Check etag List headerTags = response.getHttpHeaders().get("ETag"); Assert.assertNotNull(headerTags); Assert.assertEquals(headerTags.get(0), new EntityTag("77e671575d94cfd400ed26c5ef08e0fd")); } /** Check if ETag is generated for a simple entity of JSON */ @Test public void filterSingleEntityTest() throws Exception { final ContainerResponse response = resourceLauncher.service( HttpMethod.GET, SERVICE_PATH + "/single", BASE_URI, null, null, null); assertEquals(response.getStatus(), OK.getStatusCode()); // check entity Assert.assertEquals(response.getEntity(), "hello"); // Check etag List headerTags = response.getHttpHeaders().get("ETag"); Assert.assertNotNull(headerTags); Assert.assertEquals(headerTags.size(), 1); Assert.assertEquals(headerTags.get(0), new EntityTag("5d41402abc4b2a76b9719d911017c592")); } /** Check if ETag sent with header is redirecting to NOT_MODIFIED */ @Test public void filterListEntityTestWithEtag() throws Exception { Map> headers = new HashMap<>(); headers.put( "If-None-Match", Collections.singletonList(new EntityTag("900150983cd24fb0d6963f7d28e17f72").toString())); final ContainerResponse response = resourceLauncher.service( HttpMethod.GET, SERVICE_PATH + "/list", BASE_URI, headers, null, null); assertEquals(response.getStatus(), NOT_MODIFIED.getStatusCode()); // check null body Assert.assertNull(response.getEntity()); } /** Check if ETag sent with header is redirecting to NOT_MODIFIED */ @Test public void filterSingleEntityTestWithEtag() throws Exception { Map> headers = new HashMap<>(); headers.put( "If-None-Match", Collections.singletonList(new EntityTag("5d41402abc4b2a76b9719d911017c592").toString())); final ContainerResponse response = resourceLauncher.service( HttpMethod.GET, SERVICE_PATH + "/single", BASE_URI, headers, null, null); assertEquals(response.getStatus(), NOT_MODIFIED.getStatusCode()); // check null body Assert.assertNull(response.getEntity()); } } ================================================ FILE: core/che-core-api-core/src/test/java/org/eclipse/che/security/PasswordEncryptorsTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * @author Yevhenii Voevodin */ public class PasswordEncryptorsTest { @Test(dataProvider = "encryptorsProvider") public void testEncryption(PasswordEncryptor encryptor) throws Exception { final String password = "password"; final String hash = encryptor.encrypt(password); assertNotNull(hash, "encrypted password's hash"); assertTrue(encryptor.test(password, hash), "password test"); } @DataProvider(name = "encryptorsProvider") public Object[][] encryptorsProvider() { return new Object[][] {{new SHA512PasswordEncryptor()}, {new PBKDF2PasswordEncryptor()}}; } } ================================================ FILE: core/che-core-api-core/src/test/resources/logback-test.xml ================================================ %-41(%date[%.25thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex ================================================ FILE: core/che-core-api-dto/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-dto jar Che Core :: Commons :: API :: DTO false ${project.build.directory}/generated-test-sources/gen com.google.code.gson gson com.google.guava guava jakarta.validation jakarta.validation-api org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang org.reflections reflections org.javassist javassist runtime org.testng testng test org.apache.maven.plugins maven-compiler-plugin pre-compile-test-sources generate-test-sources testCompile org.codehaus.mojo build-helper-maven-plugin add-test-resources process-test-sources add-test-resource ${generated.test.sources.directory}/META-INF META-INF add-test-sources process-test-sources add-test-source ${generated.test.sources.directory} org.codehaus.mojo exec-maven-plugin generate-test-dto process-test-sources java org.eclipse.che.dto.generator.DtoGenerator --dto_packages=org.eclipse.che.dto --gen_file_name=${generated.test.sources.directory}/org/eclipse/che/dto/DtoServerImpls.java --impl=server --package_base=${generated.test.sources.directory}/ test com.mycila license-maven-plugin **/generator/DtoGenerator.java **/generator/DtoImplClientTemplate.java **/generator/DtoImplServerTemplate.java **/generator/DtoTemplate.java **/generator/DtoImpl.java **/server/JsonSerializable.java **/shared/CompactJsonDto.java **/shared/SerializationIndex.java ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoGenerator.java ================================================ // Copyright 2012 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.eclipse.che.dto.generator; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.che.dto.server.DtoFactoryVisitor; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.dto.shared.DTOImpl; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; import org.reflections.scanners.TypeAnnotationsScanner; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; /** * Simple source generator that takes in the packages list with interface definitions and generates * client and server DTO impls. */ public class DtoGenerator { /** Flag: location of the packages that contains dto interfaces. */ private String[] dtoPackages = null; /** Flag: Name of the generated java class file that contains the DTOs. */ private String genFileName = "DataObjects.java"; /** Flag: The type of impls to be generated, either client or server. */ private String impl = "client"; /** * Flag: A pattern we can use to search an absolute path and find the start of the package * definition.") */ private String packageBase = "java."; public static void main(String[] args) { DtoGenerator generator = new DtoGenerator(); for (String arg : args) { if (arg.startsWith("--dto_packages=")) { generator.setDtoPackages(arg.substring("--dto_packages=".length())); } else if (arg.startsWith("--gen_file_name=")) { generator.setGenFileName(arg.substring("--gen_file_name=".length())); } else if (arg.startsWith("--impl=")) { generator.setImpl(arg.substring("--impl=".length())); } else if (arg.startsWith("--package_base=")) { generator.setPackageBase(arg.substring("--package_base=".length())); } else { throw new RuntimeException("Unknown flag: " + arg); // System.exit(1); } } generator.generate(); } private static Set getClasspathForPackages(String[] packages) { Set urls = new HashSet<>(); for (String pack : packages) { urls.addAll(ClasspathHelper.forPackage(pack)); } return urls; } public String[] getDtoPackages() { return dtoPackages; } private void setDtoPackages(String packagesParam) { setDtoPackages(packagesParam.split(",")); } public void setDtoPackages(String[] dtoPackages) { this.dtoPackages = new String[dtoPackages.length]; System.arraycopy(dtoPackages, 0, this.dtoPackages, 0, this.dtoPackages.length); } public String getGenFileName() { return genFileName; } public void setGenFileName(String genFileName) { this.genFileName = genFileName; } public String getImpl() { return impl; } public void setImpl(String impl) { this.impl = impl; } public String getPackageBase() { return packageBase; } public void setPackageBase(String packageBase) { this.packageBase = packageBase; } public void generate() { Set urls = getClasspathForPackages(dtoPackages); genFileName = genFileName.replace('/', File.separatorChar); String outputFilePath = genFileName; // Extract the name of the output file that will contain all the DTOs and its package. String myPackageBase = packageBase; if (!myPackageBase.endsWith("/")) { myPackageBase += "/"; } myPackageBase = myPackageBase.replace('/', File.separatorChar); int packageStart = outputFilePath.lastIndexOf(myPackageBase) + myPackageBase.length(); int packageEnd = outputFilePath.lastIndexOf(File.separatorChar); String fileName = outputFilePath.substring(packageEnd + 1); String className = fileName.substring(0, fileName.indexOf(".java")); String packageName = outputFilePath.substring(packageStart, packageEnd).replace(File.separatorChar, '.'); File outFile = new File(outputFilePath); try { DtoTemplate dtoTemplate = new DtoTemplate(packageName, className, impl); Reflections reflection = new Reflections( new ConfigurationBuilder() .setUrls(urls) .setScanners(new SubTypesScanner(), new TypeAnnotationsScanner())); List> dtos = new ArrayList<>(reflection.getTypesAnnotatedWith(DTO.class)); // We sort alphabetically to ensure deterministic order of routing types. dtos.sort(new ClassesComparator()); for (Class clazz : dtos) { // DTO are interface if (clazz.isInterface()) { dtoTemplate.addInterface(clazz); } } reflection = new Reflections( new ConfigurationBuilder() .setUrls(ClasspathHelper.forClassLoader()) .setScanners(new SubTypesScanner(), new TypeAnnotationsScanner())); List> dtosDependencies = new ArrayList<>(reflection.getTypesAnnotatedWith(DTO.class)); dtosDependencies.removeAll(dtos); // We sort alphabetically to ensure deterministic order. dtosDependencies.sort(new ClassesComparator()); reflection = new Reflections( new ConfigurationBuilder() .setUrls(ClasspathHelper.forClassLoader()) .setScanners(new SubTypesScanner())); for (Class clazz : dtosDependencies) { List> impls = new ArrayList<>(reflection.getSubTypesOf(clazz)); // We sort alphabetically to ensure deterministic order. impls.sort(new ClassesComparator()); for (Class impl : impls) { if (!(impl.isInterface() || urls.contains(impl.getProtectionDomain().getCodeSource().getLocation()))) { if ("client".equals(dtoTemplate.getImplType())) { if (isClientImpl(impl)) { dtoTemplate.addImplementation(clazz, impl); } } else if ("server".equals(dtoTemplate.getImplType())) { if (isServerImpl(impl)) { dtoTemplate.addImplementation(clazz, impl); } } } } } // Emit the generated file. Files.createDirectories(outFile.toPath().getParent()); try (BufferedWriter writer = new BufferedWriter(new FileWriter(outFile))) { writer.write(dtoTemplate.toString()); } if ("server".equals(impl)) { // Create file in META-INF/services/ File outServiceFile = new File( myPackageBase + "META-INF/services/" + DtoFactoryVisitor.class.getCanonicalName()); Files.createDirectories(outServiceFile.toPath().getParent()); try (BufferedWriter serviceFileWriter = new BufferedWriter(new FileWriter(outServiceFile))) { serviceFileWriter.write(packageName + "." + className); } } } catch (IOException e) { e.printStackTrace(); } } private boolean isClientImpl(Class impl) { DTOImpl a = impl.getAnnotation(DTOImpl.class); return a != null && "client".equals(a.value()); } private boolean isServerImpl(Class impl) { DTOImpl a = impl.getAnnotation(DTOImpl.class); return a != null && "server".equals(a.value()); } private static class ClassesComparator implements Comparator { @Override public int compare(Class o1, Class o2) { return o1.getName().compareTo(o2.getName()); } } } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.generator; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.io.StringWriter; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.eclipse.che.dto.shared.CompactJsonDto; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.dto.shared.DelegateTo; import org.eclipse.che.dto.shared.JsonFieldName; import org.eclipse.che.dto.shared.SerializationIndex; /** Abstract base class for the source generating template for a single DTO. */ abstract class DtoImpl { protected static final String COPY_JSONS_PARAM = "copyJsons"; private final Class dtoInterface; private final DtoTemplate enclosingTemplate; private final boolean compactJson; private final String implClassName; private final List dtoMethods; DtoImpl(DtoTemplate enclosingTemplate, Class dtoInterface) { this.enclosingTemplate = enclosingTemplate; this.dtoInterface = dtoInterface; this.implClassName = dtoInterface.getSimpleName() + "Impl"; this.compactJson = DtoTemplate.implementsInterface(dtoInterface, CompactJsonDto.class); this.dtoMethods = ImmutableList.copyOf(calcDtoMethods()); } protected boolean isCompactJson() { return compactJson; } public Class getDtoInterface() { return dtoInterface; } public DtoTemplate getEnclosingTemplate() { return enclosingTemplate; } protected String getJavaFieldName(String getterName) { String fieldName; if (getterName.startsWith("get")) { fieldName = getterName.substring(3); } else { // starts with "is", see method '#ignoreMethod(Method)' fieldName = getterName.substring(2); } return normalizeIdentifier(Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1)); } private String normalizeIdentifier(String fieldName) { // use $ prefix according to http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.8 switch (fieldName) { case "default": fieldName = "$" + fieldName; break; // add other keywords here } return fieldName; } private String getCamelCaseName(String fieldName) { // see normalizeIdentifier method if (fieldName.charAt(0) == '$') { fieldName = fieldName.substring(1); } return Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); } protected String getImplClassName() { return implClassName; } protected String getSetterName(String fieldName) { return "set" + getCamelCaseName(fieldName); } protected String getWithName(String fieldName) { return "with" + getCamelCaseName(fieldName); } protected String getListAdderName(String fieldName) { return "add" + getCamelCaseName(fieldName); } protected String getMapPutterName(String fieldName) { return "put" + getCamelCaseName(fieldName); } protected String getClearName(String fieldName) { return "clear" + getCamelCaseName(fieldName); } protected String getEnsureName(String fieldName) { return "ensure" + getCamelCaseName(fieldName); } /** Get the canonical name of the field by deriving it from a getter method's name. */ protected String getFieldNameFromGetterName(String getterName) { String fieldName; if (getterName.startsWith("get")) { fieldName = getterName.substring(3); } else { // starts with "is", see method '#ignoreMethod(Method)' fieldName = getterName.substring(2); } return Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1); } /** * Get the name of the JSON field that corresponds to the given getter method in a DTO-annotated * type. */ protected String getJsonFieldName(Method getterMethod) { // First, check if a custom field name is defined for the getter JsonFieldName fieldNameAnn = getterMethod.getAnnotation(JsonFieldName.class); if (fieldNameAnn != null) { String customFieldName = fieldNameAnn.value(); if (customFieldName != null && !customFieldName.isEmpty()) { return customFieldName; } } // If no custom name is given for the field, deduce it from the camel notation return getFieldNameFromGetterName(getterMethod.getName()); } /** * Our super interface may implement some other interface (or not). We need to know because if it * does then we need to directly extend said super interfaces impl class. */ protected Class getSuperDtoInterface(Class dto) { Class[] superInterfaces = dto.getInterfaces(); if (superInterfaces.length > 0) { for (Class superInterface : superInterfaces) { if (superInterface.isAnnotationPresent(DTO.class)) { return superInterface; } } } return null; } protected List getDtoGetters(Class dto) { final Map getters = new HashMap<>(); if (enclosingTemplate.isDtoInterface(dto)) { addDtoGetters(dto, getters); addSuperGetters(dto, getters); } List dtoGetters = new ArrayList<>(getters.values()); dtoGetters.sort(new MethodComparator()); return dtoGetters; } /** Get the names of all the getters in the super DTO interface and upper ancestors. */ protected Set getSuperGetterNames(Class dto) { final Map getters = new HashMap<>(); Class superDto = getSuperDtoInterface(dto); if (superDto != null) { addDtoGetters(superDto, getters); addSuperGetters(superDto, getters); } return getters.keySet(); } /** * Adds all getters from parent NOT DTO interfaces for given {@code dto} interface. Does * not add method when it is already present in getters map. */ private void addSuperGetters(Class dto, Map getters) { for (Class superInterface : dto.getInterfaces()) { if (!superInterface.isAnnotationPresent(DTO.class)) { for (Method method : superInterface.getDeclaredMethods()) { // when method is already present in map then child interface // overrides it, which means that it should not be put into getters if (isDtoGetter(method) && !getters.containsKey(method.getName())) { getters.put(method.getName(), method); } } addSuperGetters(superInterface, getters); } } } protected List getInheritedDtoGetters(Class dto) { List getters = new ArrayList<>(); if (enclosingTemplate.isDtoInterface(dto)) { Class superInterface = getSuperDtoInterface(dto); while (superInterface != null) { addDtoGetters(superInterface, getters); superInterface = getSuperDtoInterface(superInterface); } addDtoGetters(dto, getters); } return getters; } private void addDtoGetters(Class dto, Map getters) { for (Method method : dto.getDeclaredMethods()) { if (!method.isDefault() && isDtoGetter(method)) { getters.put(method.getName(), method); } } } private void addDtoGetters(Class dto, List getters) { for (Method method : dto.getDeclaredMethods()) { if (!method.isDefault() && isDtoGetter(method)) { getters.add(method); } } } /** Check is specified method is DTO getter. */ protected boolean isDtoGetter(Method method) { if (method.isAnnotationPresent(DelegateTo.class)) { return false; } String methodName = method.getName(); if ((methodName.startsWith("get") || methodName.startsWith("is")) && method.getParameterTypes().length == 0) { if (methodName.length() > 2 && methodName.startsWith("is")) { return method.getReturnType() == Boolean.class || method.getReturnType() == boolean.class; } return methodName.length() > 3; } return false; } /** Tests whether or not a given generic type is allowed to be used as a generic. */ protected static boolean isWhitelisted(Class genericType) { return DtoTemplate.jreWhitelist.contains(genericType); } /** Tests whether or not a given return type is a number primitive or its wrapper type. */ protected static boolean isNumber(Class returnType) { if (null != returnType && (Number.class.isAssignableFrom(returnType) || int.class.equals(returnType) || long.class.equals(returnType) || short.class.equals(returnType) || float.class.equals(returnType) || double.class.equals(returnType) || byte.class.equals(returnType))) { return true; } return false; } /** Tests whether or not a given return type is a boolean primitive or its wrapper type. */ protected static boolean isBoolean(Class returnType) { return returnType.equals(Boolean.class) || returnType.equals(boolean.class); } protected static String getPrimitiveName(Class returnType) { if (returnType.equals(Integer.class) || returnType.equals(int.class)) { return "int"; } else if (returnType.equals(Long.class) || returnType.equals(long.class)) { return "long"; } else if (returnType.equals(Short.class) || returnType.equals(short.class)) { return "short"; } else if (returnType.equals(Float.class) || returnType.equals(float.class)) { return "float"; } else if (returnType.equals(Double.class) || returnType.equals(double.class)) { return "double"; } else if (returnType.equals(Byte.class) || returnType.equals(byte.class)) { return "byte"; } else if (returnType.equals(Boolean.class) || returnType.equals(boolean.class)) { return "boolean"; } else if (returnType.equals(Character.class) || returnType.equals(char.class)) { return "char"; } throw new IllegalArgumentException("Unknown wrapper class type."); } /** Tests whether or not a given return type is a java.util.List. */ public static boolean isList(Class returnType) { return returnType.equals(List.class); } /** Tests whether or not a given return type is a java.util.Map. */ public static boolean isMap(Class returnType) { return returnType.equals(Map.class); } public static boolean isAny(Class returnType) { return returnType.equals(Object.class); } /** * Expands the type and its first generic parameter (which can also have a first generic parameter * (...)). * *

For example, JsonArray<JsonStringMap<JsonArray<SomeDto>>> would produce * [JsonArray, JsonStringMap, JsonArray, SomeDto]. */ public static List expandType(Type curType) { List types = new LinkedList<>(); do { types.add(curType); if (curType instanceof ParameterizedType) { Type[] genericParamTypes = ((ParameterizedType) curType).getActualTypeArguments(); Type rawType = ((ParameterizedType) curType).getRawType(); boolean map = rawType instanceof Class && rawType == Map.class; if (!map && genericParamTypes.length != 1) { throw new IllegalStateException( "Multiple type parameters are not supported (neither are zero type parameters)"); } Type genericParamType = map ? genericParamTypes[1] : genericParamTypes[0]; if (genericParamType instanceof Class) { Class genericParamTypeClass = (Class) genericParamType; if (isWhitelisted(genericParamTypeClass)) { assert genericParamTypeClass.equals(String.class) : "For JSON serialization there can be only strings or DTO types. "; } } curType = genericParamType; } else { if (curType instanceof Class) { Class clazz = (Class) curType; if (isList(clazz) || isMap(clazz)) { throw new DtoTemplate.MalformedDtoInterfaceException( "JsonArray and JsonStringMap must have a generic type specified."); } } curType = null; } } while (curType != null); return types; } public static Class getRawClass(Type type) { return (Class) ((type instanceof ParameterizedType) ? ((ParameterizedType) type).getRawType() : type); } /** * Returns public methods specified in DTO interface. * *

* *

For compact DTO (see {@link org.eclipse.che.dto.shared.CompactJsonDto}) methods are ordered * corresponding to {@link org.eclipse.che.dto.shared.SerializationIndex} annotation. * *

* *

Gaps in index sequence are filled with {@code null}s. */ protected List getDtoMethods() { return dtoMethods; } private List calcDtoMethods() { if (!compactJson) { List result = new ArrayList<>(Arrays.asList(dtoInterface.getMethods())); result.sort(new MethodComparator()); return result; } Map methodsMap = new HashMap<>(); int maxIndex = 0; for (Method method : dtoInterface.getMethods()) { SerializationIndex serializationIndex = method.getAnnotation(SerializationIndex.class); Preconditions.checkNotNull( serializationIndex, "Serialization index is not specified for %s in %s", method.getName(), dtoInterface.getSimpleName()); // "53" is the number of bits in JS integer. // This restriction will allow to add simple bit-field // "serialization-skipping-list" in the future. int index = serializationIndex.value(); Preconditions.checkState( index > 0 && index <= 53, "Serialization index out of range [1..53] for %s in %s", method.getName(), dtoInterface.getSimpleName()); Preconditions.checkState( !methodsMap.containsKey(index), "Duplicate serialization index for %s in %s", method.getName(), dtoInterface.getSimpleName()); maxIndex = Math.max(index, maxIndex); methodsMap.put(index, method); } Method[] methods = new Method[maxIndex]; for (int index = 0; index < maxIndex; index++) { methods[index] = methodsMap.get(index + 1); } List result = new ArrayList<>(Arrays.asList(methods)); result.sort(new MethodComparator()); return result; } protected boolean isLastMethod(Method method) { Preconditions.checkNotNull(method); return method == dtoMethods.get(dtoMethods.size() - 1); } /** Create a textual representation of a string literal that evaluates to the given value. */ protected String quoteStringLiteral(String value) { StringWriter sw = new StringWriter(); try (JsonWriter writer = new JsonWriter(sw)) { writer.setLenient(true); writer.value(value); writer.flush(); } catch (IOException ex) { throw new RuntimeException("Unexpected I/O failure: " + ex.getLocalizedMessage(), ex); } return sw.toString(); } /** * @return String representing the source definition for the DTO impl as an inner class. */ abstract String serialize(); private static class MethodComparator implements Comparator { @Override public int compare(Method o1, Method o2) { return getMethodSignature(o1).compareTo(getMethodSignature(o2)); } private String getMethodSignature(Method method) { return method.getReturnType().getName() + " " + method.getName() + "(" + Arrays.stream(method.getParameterTypes()) .map(Class::getName) .collect(Collectors.joining(", ")) + ")"; } } } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoImplClientTemplate.java ================================================ /* * Copyright (c) 2012-2016 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.generator; import com.google.common.base.Preconditions; import com.google.common.primitives.Primitives; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.che.dto.server.JsonSerializable; import org.eclipse.che.dto.shared.DTOImpl; import org.eclipse.che.dto.shared.DelegateRule; import org.eclipse.che.dto.shared.DelegateTo; import org.eclipse.che.dto.shared.JsonArray; import org.eclipse.che.dto.shared.JsonStringMap; import org.eclipse.che.dto.shared.SerializationIndex; /** Generates the source code for a generated Client DTO impl. */ public class DtoImplClientTemplate extends DtoImpl { private static final String CLIENT_DTO_MARKER = " @" + DTOImpl.class.getCanonicalName() + "(\"client\")\n"; DtoImplClientTemplate(DtoTemplate template, Class superInterface) { super(template, superInterface); } @Override String serialize() { StringBuilder builder = new StringBuilder(); final Class dtoInterface = getDtoInterface(); final String dtoInterfaceName = dtoInterface.getCanonicalName(); emitPreamble(dtoInterface, builder); List getters = getDtoGetters(dtoInterface); // Enumerate the getters and emit field names and getters + setters. emitFields(getters, builder); emitGettersAndSetters(getters, builder); List inheritedGetters = getInheritedDtoGetters(dtoInterface); List methods = new ArrayList<>(); methods.addAll(getters); Set getterNames = new HashSet<>(); for (Method getter : getters) { getterNames.add(getter.getName()); } for (Method getter : inheritedGetters) { if (getterNames.add(getter.getName())) { methods.add(getter); } } // equals, hashCode, serialization and copy constructor emitEqualsAndHashCode(methods, builder); emitSerializer(methods, builder); emitDeserializer(methods, builder); emitDeserializerShortcut(builder); emitCopyConstructor(methods, builder); // Delegation DTO methods. emitDelegateMethods(builder); // "builder" method, it is method that set field and return "this" instance emitWithMethods(getters, dtoInterfaceName, builder); // Implement withXXX methods that are declared directly in this DTO even if there are no any // getter for the fields. // Need that to override methods from super DTO and return correct type for with method. // @DTO // public interface A { // String getProperty(); // void setProperty(String property); // A withProperty(); // } // // @DTO // public interface B extends A { // // New methods // ... // // Override method to return type B instead of A. // B withProperty(); // } getterNames.clear(); for (Method getter : getters) { getterNames.add(getter.getName()); } for (Method method : dtoInterface.getDeclaredMethods()) { if (!method.isDefault() && method.getName().startsWith("with")) { String noPrefixName = method.getName().substring(4); // Check do we already generate withXXX method or not. // If there is getter in DTO interface we already have withXXX method if (!getterNames.contains("get" + noPrefixName) && !getterNames.contains("is" + noPrefixName)) { String fieldName = Character.toLowerCase(noPrefixName.charAt(0)) + noPrefixName.substring(1); String parameterFqn = getFqParameterizedName(method.getGenericParameterTypes()[0]); emitWithMethod(method.getName(), fieldName, parameterFqn, dtoInterfaceName, builder); } } } emitPostamble(builder); return builder.toString(); } private void emitEqualsAndHashCode(List getters, StringBuilder builder) { builder.append(" @Override\n"); builder.append(" public boolean equals(Object o) {\n"); builder.append(" if (!(o instanceof ").append(getImplClassName()).append(")) {\n"); builder.append(" return false;\n"); builder.append(" }\n"); builder .append(" ") .append(getImplClassName()) .append(" other = (") .append(getImplClassName()) .append(") o;\n"); for (Method getter : getters) { String fieldName = getJavaFieldName(getter.getName()); Class returnType = getter.getReturnType(); if (returnType.isPrimitive()) { builder .append(" if (this.") .append(fieldName) .append(" != other.") .append(fieldName) .append(") {\n"); builder.append(" return false;\n"); builder.append(" }\n"); } else { builder.append(" if (this.").append(fieldName).append(" != null) {\n"); builder .append(" if (!this.") .append(fieldName) .append(".equals(other.") .append(fieldName) .append(")) {\n"); builder.append(" return false;\n"); builder.append(" }\n"); builder.append(" } else {\n"); builder.append(" if (other.").append(fieldName).append(" != null) {\n"); builder.append(" return false;\n"); builder.append(" }\n"); builder.append(" }\n"); } } builder.append(" return true;\n"); builder.append(" }\n\n"); // this isn't the greatest hash function in the world, but it meets the requirement that for any // two objects A and B, A.equals(B) only if A.hashCode() == B.hashCode() builder.append(" @Override\n"); builder.append(" public int hashCode() {\n"); builder.append(" int hash = 7;\n"); for (Method method : getters) { Class type = method.getReturnType(); String fieldName = getJavaFieldName(method.getName()); if (type.isPrimitive()) { Class wrappedType = Primitives.wrap(type); builder .append(" hash = hash * 31 + ") .append(wrappedType.getName()) .append(".valueOf(") .append(fieldName) .append(").hashCode();\n"); } else { builder .append(" hash = hash * 31 + (") .append(fieldName) .append(" != null ? ") .append(fieldName) .append(".hashCode() : 0);\n"); } } builder.append(" return hash;\n"); builder.append(" }\n\n"); } private void emitFactoryMethod(StringBuilder builder) { builder.append(" public static "); builder.append(getImplClassName()); builder.append(" make() {"); builder.append("\n return new "); builder.append(getImplClassName()); builder.append("();\n }\n\n"); } private void emitFields(List getters, StringBuilder builder) { for (Method getter : getters) { String fieldName = getJavaFieldName(getter.getName()); builder.append(" "); builder.append(getFieldTypeAndAssignment(getter, fieldName)); } builder.append("\n"); } /** Emits a method to get a field. Getting a collection ensures that the collection is created. */ private void emitGetter( Method method, String methodName, String fieldName, String returnType, StringBuilder builder) { builder.append(" @Override\n public "); builder.append(returnType); builder.append(" "); builder.append(methodName); builder.append("() {\n"); // Initialize the collection. Class returnTypeClass = method.getReturnType(); if (isList(returnTypeClass) || isMap(returnTypeClass)) { builder.append(" "); builder.append(getEnsureName(fieldName)); builder.append("();\n"); } builder.append(" return "); builder.append(fieldName); builder.append(";\n }\n\n"); } private void emitGettersAndSetters(List getters, StringBuilder builder) { for (Method getter : getters) { String fieldName = getJavaFieldName(getter.getName()); if (fieldName == null) { continue; } Class returnTypeClass = getter.getReturnType(); String returnType = getFqParameterizedName(getter.getGenericReturnType()); // Getter. emitGetter(getter, getter.getName(), fieldName, returnType, builder); // Setter. emitSetter(fieldName, returnType, builder); // List/Map-specific methods. if (isList(returnTypeClass)) { emitListAdd(getter, fieldName, builder); emitClear(fieldName, builder); emitEnsureCollection(getter, fieldName, builder); } else if (isMap(returnTypeClass)) { emitMapPut(getter, fieldName, builder); emitClear(fieldName, builder); emitEnsureCollection(getter, fieldName, builder); } } } private void emitDelegateMethod( String returnType, Method method, Class delegateToType, String delegateToMethod, StringBuilder builder) { builder .append(" public ") .append(returnType) .append(" ") .append(method.getName()) .append("("); Type[] parameterTypes = method.getGenericParameterTypes(); String[] parameters = new String[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { if (i > 0) { builder.append(", "); } builder.append(getFqParameterizedName(parameterTypes[i])); String parameter = "$p" + i; builder.append(" ").append(parameter); parameters[i] = parameter; } builder.append(") {\n"); builder.append(" "); if (!"void".equals(returnType)) { builder.append("return "); } builder .append(delegateToType.getCanonicalName()) .append(".") .append(delegateToMethod) .append("("); builder.append("this"); for (String parameter : parameters) { builder.append(", "); builder.append(parameter); } builder.append(");\n"); builder.append(" }\n\n"); } private void emitDelegateMethods(StringBuilder builder) { for (Method method : getDtoMethods()) { DelegateTo delegateTo = method.getAnnotation(DelegateTo.class); if (delegateTo != null) { DelegateRule serverRule = delegateTo.server(); String returnType = getFqParameterizedName(method.getGenericReturnType()); emitDelegateMethod(returnType, method, serverRule.type(), serverRule.method(), builder); } } } private void emitSerializer(List getters, StringBuilder builder) { builder.append(" public JSONObject toJsonObject() {\n"); // The default toJsonElement() returns JSONs for unsafe use thus 'any' properties should be // copied builder.append(" return toJsonObjectInt(true);\n"); builder.append(" }\n"); builder .append(" public JSONObject toJsonObjectInt(boolean ") .append(COPY_JSONS_PARAM) .append(") {\n"); if (isCompactJson()) { builder.append(" JSONArray result = new JSONArray();\n"); for (Method method : getters) { emitSerializeFieldForMethodCompact(method, builder); } } else { builder.append(" JSONObject result = new JSONObject();\n"); for (Method getter : getters) { emitSerializeFieldForMethod(getter, builder); } } builder.append(" return result;\n"); builder.append(" }\n"); builder.append("\n"); builder.append(" @Override\n"); builder.append(" public String toJson() {\n"); builder.append(" return toJsonObjectInt(false).toString();\n"); builder.append(" }\n"); builder.append("\n"); builder.append(" @Override\n"); builder.append(" public String toString() {\n"); builder.append(" return toJson();\n"); builder.append(" }\n\n"); } private void emitSerializeFieldForMethod(Method getter, final StringBuilder builder) { final String fieldName = getFieldNameFromGetterName(getter.getName()); final String fieldNameOut = fieldName + "Out"; final String baseIndentation = " "; final String jsonFieldName = getJsonFieldName(getter); final String jsonFieldNameLiteral = quoteStringLiteral(jsonFieldName); builder.append("\n"); List expandedTypes = expandType(getter.getGenericReturnType()); emitSerializerImpl( expandedTypes, 0, builder, getJavaFieldName(getter.getName()), fieldNameOut, baseIndentation); builder.append(" result.put("); builder.append(jsonFieldNameLiteral); builder.append(", "); builder.append(fieldNameOut); builder.append(");\n"); } private void emitSerializeFieldForMethodCompact(Method getter, StringBuilder builder) { if (getter == null) { builder.append(" result.set(0, JSONNull.getInstance());\n"); return; } final String jsonFieldName = getFieldNameFromGetterName(getter.getName()); final String fieldNameOut = jsonFieldName + "Out"; final String baseIndentation = " "; builder.append("\n"); List expandedTypes = expandType(getter.getGenericReturnType()); emitSerializerImpl( expandedTypes, 0, builder, getJavaFieldName(getter.getName()), fieldNameOut, baseIndentation); if (isLastMethod(getter)) { if (isList(getRawClass(expandedTypes.get(0)))) { builder.append(" if (").append(fieldNameOut).append(".size() != 0) {\n"); builder.append(" result.set(result.size(), ").append(fieldNameOut).append(");\n"); builder.append(" }\n"); return; } } builder.append(" result.add(").append(fieldNameOut).append(");\n"); } /** * Produces code to serialize the type with the given variable names. * * @param expandedTypes the type and its generic (and its generic (..)) expanded into a list, @see * {@link #expandType(java.lang.reflect.Type)} * @param depth the depth (in the generics) for this recursive call. This can be used to index * into {@code expandedTypes} * @param builder StringBuilder to add generated code for serialization * @param inVar the java type that will be the input for serialization * @param outVar the JsonElement subtype that will be the output for serialization * @param i indentation string */ private void emitSerializerImpl( List expandedTypes, int depth, StringBuilder builder, String inVar, String outVar, String i) { Type type = expandedTypes.get(depth); String childInVar = inVar + "_"; String childOutVar = outVar + "_"; String entryVar = "entry" + depth; Class rawClass = getRawClass(type); if (isList(rawClass)) { String childInTypeName = getImplName(expandedTypes.get(depth + 1), false); builder.append(i).append("JSONArray ").append(outVar).append(" = new JSONArray();\n"); if (depth == 0) { builder.append(i).append("this.").append(getEnsureName(inVar)).append("();\n"); } builder .append(i) .append("for (") .append(childInTypeName) .append(" ") .append(childInVar) .append(" : ") .append(depth == 0 ? "this." + inVar : inVar) .append(") {\n"); } else if (isMap(rawClass)) { String childInTypeName = getImplName(expandedTypes.get(depth + 1), false); builder.append(i).append("JSONObject ").append(outVar).append(" = new JSONObject();\n"); if (depth == 0) { builder.append(i).append("this.").append(getEnsureName(inVar)).append("();\n"); } builder .append(i) .append("for (java.util.Map.Entry ") .append(entryVar) .append(" : ") .append(depth == 0 ? "this." + inVar : inVar) .append(".entrySet()) {\n"); builder .append(i) .append(" ") .append(childInTypeName) .append(" ") .append(childInVar) .append(" = ") .append(entryVar) .append(".getValue();\n"); } else if (rawClass.isEnum()) { builder .append(i) .append("JSONValue ") .append(outVar) .append(" = (") .append(depth == 0 ? "this." + inVar : inVar) .append(" == null) ? JSONNull.getInstance() : new JSONString(") .append(depth == 0 ? "this." + inVar : inVar) .append(".name());\n"); } else if (getEnclosingTemplate().isDtoInterface(rawClass)) { builder .append(i) .append("JSONValue ") .append(outVar) .append(" = ") .append(depth == 0 ? "this." + inVar : inVar) .append(" == null ? JSONNull.getInstance() : ((") .append(getImplNameForDto((Class) expandedTypes.get(depth))) .append(")") .append(inVar) .append(").toJsonObject();\n"); } else if (rawClass.equals(String.class)) { builder .append(i) .append("JSONValue ") .append(outVar) .append(" = (") .append(depth == 0 ? "this." + inVar : inVar) .append(" == null) ? JSONNull.getInstance() : new JSONString(") .append(depth == 0 ? "this." + inVar : inVar) .append(");\n"); } else if (isNumber(rawClass)) { if (rawClass.isPrimitive()) { builder .append(i) .append("JSONValue ") .append(outVar) .append(" = new JSONNumber(") .append(depth == 0 ? "this." + inVar : inVar) .append(");\n"); } else { builder .append(i) .append("JSONValue ") .append(outVar) .append(" = ") .append(depth == 0 ? " this." + inVar : inVar) .append(" == null ? JSONNull.getInstance() : new JSONNumber(") .append(depth == 0 ? "this." + inVar : inVar) .append(");\n"); } } else if (isBoolean(rawClass)) { if (rawClass.isPrimitive()) { builder .append(i) .append("JSONValue ") .append(outVar) .append(" = JSONBoolean.getInstance(") .append(depth == 0 ? "this." + inVar : inVar) .append(");\n"); } else { builder .append(i) .append("JSONValue ") .append(outVar) .append(" = ") .append(depth == 0 ? " this." + inVar : inVar) .append(" == null ? JSONNull.getInstance() : JSONBoolean.getInstance(") .append(depth == 0 ? "this." + inVar : inVar) .append(");\n"); } } else if (isAny(rawClass)) { // TODO a better method to clone instances of // outVar = inVar == null ? JsonNull.INSTNACE : (copyJsons ? new JsonParser().parse(inVar) : // inVar); builder .append(i) .append("JSONValue ") .append(outVar) .append(" = ") .append(depth == 0 ? " this." + inVar : inVar) .append(" == null ? JSONNull.getInstance() : ("); appendCopyJsonExpression(inVar, builder).append(");\n"); } else { final Class dtoImplementation = getEnclosingTemplate().getDtoImplementation(rawClass); if (dtoImplementation != null) { builder .append(i) .append("JSONValue ") .append(outVar) .append(" = ") .append(depth == 0 ? "this." + inVar : inVar) .append(" == null ? JSONNull.getInstance() : ((") .append(dtoImplementation.getCanonicalName()) .append(")") .append(depth == 0 ? "this." + inVar : inVar) .append(").toJsonObject();\n"); } else { throw new IllegalArgumentException( "Unable to generate client implementation for DTO interface " + getDtoInterface().getCanonicalName() + ". Type " + rawClass + " is not allowed to use in DTO interface."); } } if (depth + 1 < expandedTypes.size()) { emitSerializerImpl(expandedTypes, depth + 1, builder, childInVar, childOutVar, i + " "); } if (isList(rawClass)) { builder .append(i) .append(" ") .append(outVar) .append(".set(") .append(outVar) .append(".size(), ") .append(childOutVar) .append(");\n"); builder.append(i).append("}\n"); } else if (isMap(rawClass)) { builder .append(i) .append(" ") .append(outVar) .append(".put(") .append(entryVar) .append(".getKey(), ") .append(childOutVar) .append(");\n"); builder.append(i).append("}\n"); } } /** * Append the expression that clones the given JsonElement variable into a new value. If the * copyJons run-time parameter is set to false, then the expression won't perform a clone but * instead will reuse the variable by reference. */ private static StringBuilder appendCopyJsonExpression(String inVar, StringBuilder builder) { builder.append(COPY_JSONS_PARAM).append(" ? "); appendNaiveCopyJsonExpression(inVar, builder) .append(" : (JSONValue)(") .append(inVar) .append(")"); return builder; } private static StringBuilder appendNaiveCopyJsonExpression( String inValue, StringBuilder builder) { return builder.append("JSONParser.parseStrict((").append(inValue).append(").toString())"); } /** Generates a static factory method that creates a new instance based on a JsonElement. */ private void emitDeserializer(List getters, StringBuilder builder) { // The default fromJsonObject(json) works in unsafe mode and clones the JSON's for 'any' // properties builder .append(" public static ") .append(getImplClassName()) .append(" fromJsonObject(JSONValue jsonValue) {\n"); builder.append(" return fromJsonObjectInt(jsonValue, true);\n"); builder.append(" }\n"); builder .append(" public static ") .append(getImplClassName()) .append(" fromJsonObjectInt(JSONValue jsonValue, boolean ") .append(COPY_JSONS_PARAM) .append(") {\n"); builder.append(" if (jsonValue == null || jsonValue.isNull() != null) {\n"); builder.append(" return null;\n"); builder.append(" }\n\n"); builder .append(" ") .append(getImplClassName()) .append(" dto = new ") .append(getImplClassName()) .append("();\n"); if (isCompactJson()) { for (Method method : getters) { emitDeserializeFieldForMethodCompact(method, builder); } } else { builder.append(" JSONObject json = jsonValue.isObject();\n"); for (Method getter : getters) { emitDeserializeFieldForMethod(getter, builder); } } builder.append("\n return dto;\n"); builder.append(" }\n\n"); } private void emitDeserializerShortcut(StringBuilder builder) { builder.append(" public static "); builder.append(getImplClassName()); builder.append(" fromJsonString(String jsonString) {\n"); builder.append(" if (jsonString == null) {\n"); builder.append(" return null;\n"); builder.append(" }\n\n"); builder.append(" return fromJsonObjectInt(JSONParser.parseStrict(jsonString), false);\n"); builder.append(" }\n\n"); } private void emitDeserializeFieldForMethod(Method getter, StringBuilder builder) { final String fieldName = getFieldNameFromGetterName(getter.getName()); final String fieldNameIn = fieldName + "In"; final String fieldNameOut = fieldName + "Out"; final String baseIndentation = " "; final String jsonFieldName = getJsonFieldName(getter); final String jsonFieldNameLiteral = quoteStringLiteral(jsonFieldName); builder.append("\n"); builder.append(" if (json.containsKey(").append(jsonFieldNameLiteral).append(")) {\n"); List expandedTypes = expandType(getter.getGenericReturnType()); builder .append(" JSONValue ") .append(fieldNameIn) .append(" = json.get(") .append(jsonFieldNameLiteral) .append(");\n"); emitDeserializerImpl(expandedTypes, 0, builder, fieldNameIn, fieldNameOut, baseIndentation); builder .append(" dto.") .append(getSetterName(fieldName)) .append("(") .append(fieldNameOut) .append(");\n"); builder.append(" }\n"); Type type = expandedTypes.get(0); Class aClass = getRawClass(type); if (Boolean.class.equals(aClass)) { builder.append(" else {\n"); builder .append(" dto.") .append(getSetterName(fieldName)) .append("(") .append("false") .append(");\n"); builder.append(" }\n"); } } private void emitDeserializeFieldForMethodCompact(Method method, final StringBuilder builder) { final String fieldName = getFieldNameFromGetterName(method.getName()); final String fieldNameIn = fieldName + "In"; final String fieldNameOut = fieldName + "Out"; final String baseIndentation = " "; SerializationIndex serializationIndex = Preconditions.checkNotNull(method.getAnnotation(SerializationIndex.class)); int index = serializationIndex.value() - 1; builder.append("\n"); builder.append(" if (").append(index).append(" < json.size()) {\n"); List expandedTypes = expandType(method.getGenericReturnType()); builder .append(" JSONValue ") .append(fieldNameIn) .append(" = json.get(") .append(index) .append(");\n"); emitDeserializerImpl(expandedTypes, 0, builder, fieldNameIn, fieldNameOut, baseIndentation); builder .append(" dto.") .append(getSetterName(fieldName)) .append("(") .append(fieldNameOut) .append(");\n"); builder.append(" }\n"); } /** * Produces code to deserialize the type with the given variable names. * * @param expandedTypes the type and its generic (and its generic (..)) expanded into a list, @see * {@link #expandType(java.lang.reflect.Type)} * @param depth the depth (in the generics) for this recursive call. This can be used to index * into {@code expandedTypes} * @param builder StringBuilder to add generated code for deserialization * @param inVar the java type that will be the input for deserialization * @param outVar the JsonElement subtype that will be the output for serialization * @param i indentation string */ private void emitDeserializerImpl( List expandedTypes, int depth, StringBuilder builder, String inVar, String outVar, String i) { Type type = expandedTypes.get(depth); String childInVar = inVar + "_"; String childInVarIterator = childInVar + "_iterator"; String childOutVar = outVar + "_"; Class rawClass = getRawClass(type); if (isList(rawClass)) { builder .append(i) .append(getImplName(type, false)) .append(" ") .append(outVar) .append(" = null;\n"); builder .append(i) .append("if (") .append(inVar) .append(" != null && ") .append(inVar) .append(".isNull() == null) {\n"); builder .append(i) .append(" ") .append(outVar) .append(" = new ") .append(getImplName(type, true)) .append("();\n"); builder .append(i) .append(" for (int ") .append(childInVarIterator) .append(" = 0; ") .append(childInVarIterator) .append(" < ") .append(inVar) .append(".isArray().size(); ") .append(childInVarIterator) .append("++) {\n"); builder .append(i) .append(" JSONValue ") .append(childInVar) .append(" = ") .append(inVar) .append(".isArray().get(") .append(childInVarIterator) .append(");\n"); emitDeserializerImpl(expandedTypes, depth + 1, builder, childInVar, childOutVar, i + " "); builder .append(i) .append(" ") .append(outVar) .append(".add(") .append(childOutVar) .append(");\n"); builder.append(i).append(" }\n"); builder.append(i).append("}\n"); } else if (isMap(rawClass)) { // TODO: Handle type String entryVar = "key" + depth; String entriesVar = "keySet" + depth; builder .append(i) .append(getImplName(type, false)) .append(" ") .append(outVar) .append(" = null;\n"); builder .append(i) .append("if (") .append(inVar) .append(" != null && ") .append(inVar) .append(".isNull() == null) {\n"); builder .append(i) .append(" ") .append(outVar) .append(" = new ") .append(getImplName(type, true)) .append("();\n"); builder .append(i) .append(" java.util.Set ") .append(entriesVar) .append(" = ") .append(inVar) .append(".isObject().keySet();\n"); builder .append(i) .append(" for (String ") .append(entryVar) .append(" : ") .append(entriesVar) .append(") {\n"); builder .append(i) .append(" JSONValue ") .append(childInVar) .append(" = ") .append(inVar) .append(".isObject().get(") .append(entryVar) .append(");\n"); emitDeserializerImpl(expandedTypes, depth + 1, builder, childInVar, childOutVar, i + " "); builder .append(i) .append(" ") .append(outVar) .append(".put(") .append(entryVar) .append(", ") .append(childOutVar) .append(");\n"); builder.append(i).append(" }\n"); builder.append(i).append("}\n"); } else if (rawClass.isEnum()) { String primitiveName = rawClass.getCanonicalName(); builder .append(i) .append(primitiveName) .append(" ") .append(outVar) .append(" = ") .append(primitiveName) .append(".valueOf(") .append(inVar) .append(".isString().stringValue());\n"); } else if (getEnclosingTemplate().isDtoInterface(rawClass)) { String className = getImplName(rawClass, false); builder .append(i) .append(className) .append(" ") .append(outVar) .append(" = ") .append(getImplNameForDto(rawClass)) .append(".fromJsonObject(") .append(inVar) .append(");\n"); } else if (rawClass.equals(String.class)) { String primitiveName = rawClass.getSimpleName(); builder .append(i) .append(primitiveName) .append(" ") .append(outVar) .append(" = ") .append(inVar) .append(".isString() != null ? ") .append(inVar) .append(".isString().stringValue() : null;\n"); } else if (isNumber(rawClass)) { String primitiveName = rawClass.getSimpleName(); String typeCast = rawClass.equals(double.class) || rawClass.equals(Double.class) ? "" : "(" + getPrimitiveName(rawClass) + ")"; if (rawClass.isPrimitive()) { builder .append(i) .append(primitiveName) .append(" ") .append(outVar) .append(" = ") .append(typeCast) .append(inVar) .append(".isNumber().doubleValue();\n"); } else { if (isInteger(rawClass)) { builder .append(i) .append(primitiveName) .append(" ") .append(outVar) .append(" = ") .append(inVar) .append(".isNumber() != null ? ((Double)") .append(inVar) .append(".isNumber().doubleValue()).intValue() : null;\n"); } else if (isFloat(rawClass)) { builder .append(i) .append(primitiveName) .append(" ") .append(outVar) .append(" = ") .append(inVar) .append(".isNumber() != null ? ((Double)") .append(inVar) .append(".isNumber().doubleValue()).floatValue() : null;\n"); } else if (isLong(rawClass)) { builder .append(i) .append(primitiveName) .append(" ") .append(outVar) .append(" = ") .append(inVar) .append(".isNumber() != null ? ((Double)") .append(inVar) .append(".isNumber().doubleValue()).longValue() : null;\n"); } else if (isDouble(rawClass)) { builder .append(i) .append(primitiveName) .append(" ") .append(outVar) .append(" = ") .append(inVar) .append(".isNumber() != null ? ((Double)") .append(inVar) .append(".isNumber().doubleValue()).doubleValue() : null;\n"); } } } else if (isBoolean(rawClass)) { String primitiveName = rawClass.getSimpleName(); if (rawClass.isPrimitive()) { builder .append(i) .append(primitiveName) .append(" ") .append(outVar) .append(" = ") .append(inVar) .append(".isBoolean().booleanValue();\n"); } else { builder .append(i) .append(primitiveName) .append(" ") .append(outVar) .append(" = ") .append(inVar) .append(".isBoolean() != null ? ") .append(inVar) .append(".isBoolean().booleanValue() : null;\n"); } } else if (isAny(rawClass)) { // TODO JsonElement.deepCopy() is package-protected, JSONs are serialized to strings then // parsed for copying them // outVar = copyJsons ? new JsonParser().parse(inVar) : inVar; builder.append(i).append("JSONValue ").append(outVar).append(" = "); appendCopyJsonExpression(inVar, builder).append(";\n"); } else { final Class dtoImplementation = getEnclosingTemplate().getDtoImplementation(rawClass); if (dtoImplementation != null) { String className = getImplName(rawClass, false); builder .append(i) .append(className) .append(" ") .append(outVar) .append(" = ") .append(dtoImplementation.getCanonicalName()) .append(".fromJsonObject(") .append(inVar) .append(");\n"); } else { throw new IllegalArgumentException( "Unable to generate client implementation for DTO interface " + getDtoInterface().getCanonicalName() + ". Type " + rawClass + " is not allowed to use in DTO interface."); } } } private boolean isDouble(Class returnType) { return returnType.equals(Double.class); } private boolean isLong(Class returnType) { return returnType.equals(Long.class); } private boolean isFloat(Class returnType) { return returnType.equals(Float.class); } private boolean isInteger(Class returnType) { return returnType.equals(Integer.class); } private void emitPreamble(Class dtoInterface, StringBuilder builder) { builder.append(CLIENT_DTO_MARKER); builder.append(" public static class "); builder.append(getImplClassName()); Class superType = getSuperDtoInterface(getDtoInterface()); if (superType != null && superType != JsonSerializable.class) { // We need to extend something. builder.append(" extends "); final Class superTypeImpl = getEnclosingTemplate().getDtoImplementation(superType); if (superTypeImpl == null) { builder.append(superType.getSimpleName()).append("Impl"); } else { builder.append(superTypeImpl.getCanonicalName()); } } builder.append(" implements "); builder.append(dtoInterface.getCanonicalName()); builder.append(", JsonSerializable "); builder.append(" {\n\n"); emitFactoryMethod(builder); emitDefaultConstructor(builder); } private void emitPostamble(StringBuilder builder) { builder.append(" }\n\n"); } private void emitDefaultConstructor(StringBuilder builder) { builder.append(" protected "); builder.append(getImplClassName()); builder.append("() {\n"); builder.append(" }\n\n"); } private void emitSetter(String fieldName, String returnType, StringBuilder builder) { builder.append(" public "); builder.append("void"); builder.append(" "); builder.append(getSetterName(fieldName)); builder.append("("); builder.append(returnType); builder.append(" v) {\n"); builder.append(" this."); builder.append(fieldName); builder.append(" = "); builder.append("v;\n }\n\n"); } private void emitWithMethods( List getters, String dtoInterfaceName, StringBuilder builder) { for (Method getter : getters) { String fieldName = getJavaFieldName(getter.getName()); emitWithMethod( getWithName(fieldName), fieldName, getFqParameterizedName(getter.getGenericReturnType()), dtoInterfaceName, builder); } } private void emitWithMethod( String methodName, String fieldName, String paramType, String dtoInterfaceName, StringBuilder builder) { builder.append(" public "); builder.append(dtoInterfaceName); builder.append(" "); builder.append(methodName); builder.append("("); builder.append(paramType); builder.append(" v) {\n"); builder.append(" this."); builder.append(fieldName); builder.append(" = "); builder.append("v;\n return this;\n }\n\n"); } /** * Emits an add method to add to a list. If the list is null, it is created. * * @param method a method with a list return type */ private void emitListAdd(Method method, String fieldName, StringBuilder builder) { builder.append(" public void "); builder.append(getListAdderName(fieldName)); builder.append("("); builder.append(getTypeArgumentImplName((ParameterizedType) method.getGenericReturnType(), 0)); builder.append(" v) {\n "); builder.append(getEnsureName(fieldName)); builder.append("();\n "); builder.append(fieldName); builder.append(".add(v);\n"); builder.append(" }\n\n"); } /** * Emits a put method to put a value into a map. If the map is null, it is created. * * @param method a method with a map return value */ private void emitMapPut(Method method, String fieldName, StringBuilder builder) { builder.append(" public void "); builder.append(getMapPutterName(fieldName)); builder.append("(String k, "); builder.append(getTypeArgumentImplName((ParameterizedType) method.getGenericReturnType(), 1)); builder.append(" v) {\n "); builder.append(getEnsureName(fieldName)); builder.append("();\n "); builder.append(fieldName); builder.append(".put(k, v);\n"); builder.append(" }\n\n"); } /** * Emits a method to clear a list or map. Clearing the collections ensures that the collection is * created. */ private void emitClear(String fieldName, StringBuilder builder) { builder.append(" public void "); builder.append(getClearName(fieldName)); builder.append("() {\n "); builder.append(getEnsureName(fieldName)); builder.append("();\n "); builder.append(fieldName); builder.append(".clear();\n"); builder.append(" }\n\n"); } private void emitCopyConstructor(List getters, StringBuilder builder) { String dtoInterface = getDtoInterface().getCanonicalName(); String implClassName = getImplClassName(); builder .append(" public ") .append(implClassName) .append("(") .append(dtoInterface) .append(" origin) {\n"); for (Method method : getters) { emitDeepCopyForGetters( expandType(method.getGenericReturnType()), 0, builder, "origin", method, " "); } builder.append(" }\n\n"); } private void emitDeepCopyForGetters( List expandedTypes, int depth, StringBuilder builder, String origin, Method getter, String i) { String getterName = getter.getName(); String fieldName = getJavaFieldName(getterName); String fieldNameIn = fieldName + "In"; String fieldNameOut = fieldName + "Out"; Type type = expandedTypes.get(depth); Class rawClass = getRawClass(type); String rawTypeName = getImplName(type, false); if (isList(rawClass) || isMap(rawClass)) { builder .append(i) .append(rawTypeName) .append(" ") .append(fieldNameIn) .append(" = ") .append(origin) .append(".") .append(getterName) .append("();\n"); builder.append(i).append("if (").append(fieldNameIn).append(" != null) {\n"); builder .append(i) .append(" ") .append(rawTypeName) .append(" ") .append(fieldNameOut) .append(" = new ") .append(getImplName(type, true)) .append("();\n"); emitDeepCopyCollections(expandedTypes, depth, builder, fieldNameIn, fieldNameOut, i); builder .append(i) .append(" ") .append("this.") .append(fieldName) .append(" = ") .append(fieldNameOut) .append(";\n"); builder.append(i).append("}\n"); } else if (getEnclosingTemplate().isDtoInterface(rawClass)) { builder .append(i) .append(rawTypeName) .append(" ") .append(fieldNameIn) .append(" = ") .append(origin) .append(".") .append(getterName) .append("();\n"); builder.append(i).append("this.").append(fieldName).append(" = "); emitCheckNullAndCopyDto(rawClass, fieldNameIn, builder); builder.append(";\n"); } else if (isAny(rawClass)) { builder.append(i).append("this.").append(fieldName).append(" = "); appendNaiveCopyJsonExpression(origin + "." + getterName + "()", builder).append(";\n"); } else { builder .append(i) .append("this.") .append(fieldName) .append(" = ") .append(origin) .append(".") .append(getterName) .append("();\n"); } } private void emitDeepCopyCollections( List expandedTypes, int depth, StringBuilder builder, String varIn, String varOut, String i) { Type type = expandedTypes.get(depth); String childVarIn = varIn + "_"; String childVarOut = varOut + "_"; String entryVar = "entry" + depth; Class rawClass = getRawClass(type); Class childRawType = getRawClass(expandedTypes.get(depth + 1)); final String childTypeName = getImplName(expandedTypes.get(depth + 1), false); if (isList(rawClass)) { builder .append(i) .append(" for (") .append(childTypeName) .append(" ") .append(childVarIn) .append(" : ") .append(varIn) .append(") {\n"); } else if (isMap(rawClass)) { builder .append(i) .append(" for (java.util.Map.Entry ") .append(entryVar) .append(" : ") .append(varIn) .append(".entrySet()) {\n"); builder .append(i) .append(" ") .append(childTypeName) .append(" ") .append(childVarIn) .append(" = ") .append(entryVar) .append(".getValue();\n"); } if (isList(childRawType) || isMap(childRawType)) { builder.append(i).append(" if (").append(childVarIn).append(" != null) {\n"); builder .append(i) .append(" ") .append(childTypeName) .append(" ") .append(childVarOut) .append(" = new ") .append(getImplName(expandedTypes.get(depth + 1), true)) .append("();\n"); emitDeepCopyCollections( expandedTypes, depth + 1, builder, childVarIn, childVarOut, i + " "); builder.append(i).append(" ").append(varOut); if (isList(rawClass)) { builder.append(".add("); } else { builder.append(".put(").append(entryVar).append(".getKey(), "); } builder.append(childVarOut); builder.append(");\n"); builder.append(i).append(" ").append("}\n"); } else { builder.append(i).append(" ").append(varOut); if (isList(rawClass)) { builder.append(".add("); } else { builder.append(".put(").append(entryVar).append(".getKey(), "); } if (getEnclosingTemplate().isDtoInterface(childRawType)) { emitCheckNullAndCopyDto(childRawType, childVarIn, builder); } else { builder.append(childVarIn); } builder.append(");\n"); } builder.append(i).append(" }\n"); } private void emitCheckNullAndCopyDto(Class dto, String fieldName, StringBuilder builder) { String implName = dto.getSimpleName() + "Impl"; builder .append(fieldName) .append(" == null ? null : ") .append("new ") .append(implName) .append("(") .append(fieldName) .append(")"); } /** Emit a method that ensures a collection is initialized. */ private void emitEnsureCollection(Method method, String fieldName, StringBuilder builder) { builder.append(" protected void "); builder.append(getEnsureName(fieldName)); builder.append("() {\n"); builder.append(" if ("); builder.append(fieldName); builder.append(" == null) {\n "); builder.append(fieldName); builder.append(" = new "); builder.append(getImplName(method.getGenericReturnType(), true)); builder.append("();\n"); builder.append(" }\n"); builder.append(" }\n"); } /** * Appends a suitable type for the given type. For example, at minimum, this will replace DTO * interfaces with their implementation classes and JSON collections with corresponding Java * types. If a suitable type cannot be determined, this will throw an exception. * * @param genericType the type as returned by e.g. method.getGenericReturnType() */ private void appendType(Type genericType, final StringBuilder builder) { builder.append(getImplName(genericType, false)); } /** * In most cases we simply echo the return type and field name, except for JsonArray, which is * special in the server impl case, since it must be represented by a List for Gson to * correctly serialize/deserialize it. * * @param method The getter method. * @return String representation of what the field type should be, as well as the assignment * (initial value) to said field type, if any. */ private String getFieldTypeAndAssignment(Method method, String fieldName) { StringBuilder builder = new StringBuilder(); builder.append("protected "); appendType(method.getGenericReturnType(), builder); builder.append(" "); builder.append(fieldName); builder.append(";\n"); return builder.toString(); } /** * Returns the fully-qualified type name using Java concrete implementation classes. * *

For example, for JsonArray<JsonStringMap<Dto>>, this would return * "ArrayList<Map<String, DtoImpl>>". */ private String getImplName(Type type, boolean allowJreCollectionInterface) { Class rawClass = getRawClass(type); String fqName = getFqParameterizedName(type); fqName = fqName.replaceAll(JsonArray.class.getCanonicalName(), ArrayList.class.getCanonicalName()); fqName = fqName.replaceAll( JsonStringMap.class.getCanonicalName() + "<", HashMap.class.getCanonicalName() + ") { return ((Class) type).getCanonicalName(); } else if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) type; StringBuilder sb = new StringBuilder(getRawClass(pType).getCanonicalName()); sb.append('<'); final Type[] actualTypeArguments = pType.getActualTypeArguments(); for (int i = 0; i < actualTypeArguments.length; i++) { if (i > 0) { sb.append(", "); } sb.append(getFqParameterizedName(actualTypeArguments[i])); } sb.append('>'); return sb.toString(); } else { throw new IllegalArgumentException( "Cannot handle type '" + type == null ? null : type.getTypeName() + "'."); } } /** * Returns the fully-qualified type name using Java concrete implementation classes of the first * type argument for a parameterized type. If one is not specified, returns "Object". * * @param type the parameterized type * @return the first type argument */ private String getTypeArgumentImplName(ParameterizedType type, int index) { Type[] typeArgs = type.getActualTypeArguments(); if (typeArgs.length == 0) { return "Object"; } return getImplName(typeArgs[index], false); } private String getImplNameForDto(Class dtoInterface) { if (getEnclosingTemplate().isDtoInterface(dtoInterface)) { // This will eventually get a generated impl type. return dtoInterface.getSimpleName() + "Impl"; } return dtoInterface.getCanonicalName(); } } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoImplServerTemplate.java ================================================ /* * Copyright (c) 2012-2016 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.generator; import com.google.common.primitives.Primitives; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.che.dto.server.JsonArrayImpl; import org.eclipse.che.dto.server.JsonSerializable; import org.eclipse.che.dto.server.JsonStringMapImpl; import org.eclipse.che.dto.shared.DTOImpl; import org.eclipse.che.dto.shared.DelegateRule; import org.eclipse.che.dto.shared.DelegateTo; import org.eclipse.che.dto.shared.JsonArray; import org.eclipse.che.dto.shared.JsonStringMap; /** Generates the source code for a generated Server DTO impl. */ public class DtoImplServerTemplate extends DtoImpl { private static final String JSON_ARRAY_IMPL = JsonArrayImpl.class.getCanonicalName(); private static final String JSON_MAP_IMPL = JsonStringMapImpl.class.getCanonicalName(); private static final String SERVER_DTO_MARKER = " @" + DTOImpl.class.getCanonicalName() + "(\"server\")\n"; DtoImplServerTemplate(DtoTemplate template, Class superInterface) { super(template, superInterface); } @Override String serialize() { StringBuilder builder = new StringBuilder(); final Class dtoInterface = getDtoInterface(); final String dtoInterfaceName = dtoInterface.getCanonicalName(); emitPreamble(dtoInterface, builder); List getters = getDtoGetters(dtoInterface); // A set of the super getters Set superGetterNames = getSuperGetterNames(dtoInterface); // Enumerate the getters and emit field names and getters + setters. emitFields(getters, builder, superGetterNames); emitGettersAndSetters(getters, builder); List inheritedGetters = getInheritedDtoGetters(dtoInterface); List methods = new ArrayList<>(); methods.addAll(getters); Set getterNames = new HashSet<>(); for (Method getter : getters) { getterNames.add(getter.getName()); } for (Method getter : inheritedGetters) { if (getterNames.add(getter.getName())) { methods.add(getter); } } // equals, hashCode, serialization and copy constructor emitEqualsAndHashCode(methods, builder); emitSerializer(methods, builder); emitDeserializer(methods, builder); emitDeserializerShortcut(builder); emitCopyConstructor(methods, builder); // Delegation DTO methods. emitDelegateMethods(builder); // "builder" method, it is method that set field and return "this" instance emitWithMethods(getters, dtoInterfaceName, builder); // Implement withXXX methods that are declared directly in this DTO even if there are no any // getter for the fields. // Need that to override methods from super DTO and return correct type for with method. // @DTO // public interface A { // String getProperty(); // void setProperty(String property); // A withProperty(); // } // // @DTO // public interface B extends A { // // New methods // ... // // Override method to return type B instead of A. // B withProperty(); // } getterNames.clear(); for (Method getter : getters) { getterNames.add(getter.getName()); } for (Method method : dtoInterface.getDeclaredMethods()) { if (!method.isDefault() && method.getName().startsWith("with")) { String noPrefixName = method.getName().substring(4); // Check do we already generate withXXX method or not. // If there is getter in DTO interface we already have withXXX method if (!getterNames.contains("get" + noPrefixName) && !getterNames.contains("is" + noPrefixName)) { String fieldName = Character.toLowerCase(noPrefixName.charAt(0)) + noPrefixName.substring(1); String parameterFqn = getFqParameterizedName(method.getGenericParameterTypes()[0]); emitWithMethod(method.getName(), fieldName, parameterFqn, dtoInterfaceName, builder); } } } emitPostamble(builder); return builder.toString(); } private void emitEqualsAndHashCode(List getters, StringBuilder builder) { builder.append(" @Override\n"); builder.append(" public boolean equals(Object o) {\n"); builder.append(" if (!(o instanceof ").append(getImplClassName()).append(")) {\n"); builder.append(" return false;\n"); builder.append(" }\n"); builder .append(" ") .append(getImplClassName()) .append(" other = (") .append(getImplClassName()) .append(") o;\n"); for (Method getter : getters) { String fieldName = getJavaFieldName(getter.getName()); Class returnType = getter.getReturnType(); if (returnType.isPrimitive()) { builder .append(" if (this.") .append(fieldName) .append(" != other.") .append(fieldName) .append(") {\n"); builder.append(" return false;\n"); builder.append(" }\n"); } else { if (isList(returnType) || isMap(returnType)) { builder.append(" ").append("this.").append(getEnsureName(fieldName)).append("();\n"); builder .append(" ") .append("other.") .append(getEnsureName(fieldName)) .append("();\n"); builder.append(" \n"); } builder.append(" if (this.").append(fieldName).append(" != null) {\n"); builder .append(" if (!this.") .append(fieldName) .append(".equals(other.") .append(fieldName) .append(")) {\n"); builder.append(" return false;\n"); builder.append(" }\n"); builder.append(" } else {\n"); builder.append(" if (other.").append(fieldName).append(" != null) {\n"); builder.append(" return false;\n"); builder.append(" }\n"); builder.append(" }\n"); } } builder.append(" return true;\n"); builder.append(" }\n\n"); // this isn't the greatest hash function in the world, but it meets the requirement that for any // two objects A and B, A.equals(B) only if A.hashCode() == B.hashCode() builder.append(" @Override\n"); builder.append(" public int hashCode() {\n"); builder.append(" int hash = 7;\n"); for (Method method : getters) { Class type = method.getReturnType(); String fieldName = getJavaFieldName(method.getName()); if (type.isPrimitive()) { Class wrappedType = Primitives.wrap(type); builder .append(" hash = hash * 31 + ") .append(wrappedType.getName()) .append(".valueOf(") .append(fieldName) .append(").hashCode();\n"); } else { if (isList(type) || isMap(type)) { builder.append(" ").append(getEnsureName(fieldName)).append("();\n"); } builder .append(" hash = hash * 31 + (") .append(fieldName) .append(" != null ? ") .append(fieldName) .append(".hashCode() : 0);\n"); } } builder.append(" return hash;\n"); builder.append(" }\n\n"); } private void emitFactoryMethod(StringBuilder builder) { builder.append(" public static "); builder.append(getImplClassName()); builder.append(" make() {"); builder.append("\n return new "); builder.append(getImplClassName()); builder.append("();\n }\n\n"); } private void emitFields( List getters, StringBuilder builder, Set superGetterNames) { for (Method getter : getters) { if (superGetterNames.contains(getter.getName())) { continue; } String fieldName = getJavaFieldName(getter.getName()); String jsonFieldName = getJsonFieldName(getter); if (!fieldName.equals(jsonFieldName)) { builder .append(" @com.google.gson.annotations.SerializedName(\"") .append(jsonFieldName) .append("\")\n"); } builder.append(" "); builder.append(getFieldTypeAndAssignment(getter, fieldName)); } builder.append("\n"); } /** Emits a method to get a field. Getting a collection ensures that the collection is created. */ private void emitGetter( Method getter, String fieldName, String returnType, StringBuilder builder) { if (getter.isAnnotationPresent(jakarta.validation.constraints.NotNull.class)) { builder.append(" @jakarta.validation.constraints.NotNull\n"); } else if (getter.isAnnotationPresent(org.eclipse.che.commons.annotation.Nullable.class)) { builder.append(" @org.eclipse.che.commons.annotation.Nullable\n"); } builder.append(" @Override\n public "); builder.append(returnType); builder.append(" "); builder.append(getter.getName()); builder.append("() {\n"); // Initialize the collection. Class returnTypeClass = getter.getReturnType(); if (isList(returnTypeClass) || isMap(returnTypeClass)) { builder.append(" "); builder.append(getEnsureName(fieldName)); builder.append("();\n"); } builder.append(" return (").append(returnType).append(")("); emitReturn(getter, fieldName, builder); builder.append(");\n }\n\n"); } private void emitGettersAndSetters(List getters, StringBuilder builder) { for (Method getter : getters) { String fieldName = getJavaFieldName(getter.getName()); if (fieldName == null) { continue; } Class returnTypeClass = getter.getReturnType(); String returnType = getFqParameterizedName(getter.getGenericReturnType()); // Getter. emitGetter(getter, fieldName, returnType, builder); // Setter. emitSetter(fieldName, returnType, builder); // List/Map-specific methods. if (isList(returnTypeClass)) { emitListAdd(getter, fieldName, builder); emitClear(fieldName, builder); emitEnsureCollection(getter, fieldName, builder); } else if (isMap(returnTypeClass)) { emitMapPut(getter, fieldName, builder); emitClear(fieldName, builder); emitEnsureCollection(getter, fieldName, builder); } } } private void emitDelegateMethod( String returnType, Method method, Class delegateToType, String delegateToMethod, StringBuilder builder) { builder .append(" public ") .append(returnType) .append(" ") .append(method.getName()) .append("("); Type[] parameterTypes = method.getGenericParameterTypes(); String[] parameters = new String[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { if (i > 0) { builder.append(", "); } builder.append(getFqParameterizedName(parameterTypes[i])); String parameter = "$p" + i; builder.append(" ").append(parameter); parameters[i] = parameter; } builder.append(") {\n"); builder.append(" "); if (!"void".equals(returnType)) { builder.append("return "); } builder .append(delegateToType.getCanonicalName()) .append(".") .append(delegateToMethod) .append("("); builder.append("this"); for (String parameter : parameters) { builder.append(", "); builder.append(parameter); } builder.append(");\n"); builder.append(" }\n\n"); } private void emitDelegateMethods(StringBuilder builder) { for (Method method : getDtoInterface().getDeclaredMethods()) { DelegateTo delegateTo = method.getAnnotation(DelegateTo.class); if (delegateTo != null) { DelegateRule serverRule = delegateTo.server(); String returnType = getFqParameterizedName(method.getGenericReturnType()); emitDelegateMethod(returnType, method, serverRule.type(), serverRule.method(), builder); } } } private void emitSerializer(List getters, StringBuilder builder) { builder.append(" @Override\n"); builder.append(" public JsonElement toJsonElement() {\n"); builder.append(" return gson.toJsonTree(this);\n"); builder.append(" }\n"); builder.append(" @Override\n"); builder.append(" public void toJson(java.io.Writer w) {\n"); builder.append(" gson.toJson(this, w);\n"); builder.append(" }\n"); builder.append(" @Override\n"); builder.append(" public String toJson() {\n"); builder.append(" return gson.toJson(this);\n"); builder.append(" }\n"); builder.append("\n"); builder.append(" @Override\n"); builder.append(" public String toString() {\n"); builder.append(" return toJson();\n"); builder.append(" }\n\n"); } /** Generates a static factory method that creates a new instance based on a JsonElement. */ private void emitDeserializer(List getters, StringBuilder builder) { // The default fromJsonElement(json) works in unsafe mode and clones the JSON's for 'any' // properties builder .append(" public static ") .append(getImplClassName()) .append(" fromJsonElement(JsonElement jsonElem) {\n"); builder .append(" return gson.fromJson(jsonElem, ") .append(getImplClassName()) .append(".class);\n"); builder.append(" }\n"); } private void emitDeserializerShortcut(StringBuilder builder) { builder.append(" public static "); builder.append(getImplClassName()); builder.append(" fromJsonString(String jsonString) {\n"); builder .append(" return gson.fromJson(jsonString, ") .append(getImplClassName()) .append(".class);\n"); builder.append(" }\n\n"); } private static StringBuilder appendNaiveCopyJsonExpression( String inValue, StringBuilder builder) { builder.append("(("); builder.append(inValue); builder.append(") != null ? new JsonParser().parse(("); builder.append(inValue); builder.append(").toString()) : null)"); return builder; } private void emitPreamble(Class dtoInterface, StringBuilder builder) { builder.append(SERVER_DTO_MARKER); builder.append(" public static class "); builder.append(getImplClassName()); Class superType = getSuperDtoInterface(getDtoInterface()); if (superType != null && superType != JsonSerializable.class) { // We need to extend something. builder.append(" extends "); final Class superTypeImpl = getEnclosingTemplate().getDtoImplementation(superType); if (superTypeImpl == null) { builder.append(superType.getSimpleName()).append("Impl"); } else { builder.append(superTypeImpl.getCanonicalName()); } } builder.append(" implements "); builder.append(dtoInterface.getCanonicalName()); builder.append(", JsonSerializable "); builder.append(" {\n\n"); emitFactoryMethod(builder); emitDefaultConstructor(builder); } private void emitPostamble(StringBuilder builder) { builder.append(" }\n\n"); } private void emitDefaultConstructor(StringBuilder builder) { builder.append(" public "); builder.append(getImplClassName()); builder.append("() {\n"); builder.append(" }\n\n"); } private void emitReturn(Method method, String fieldName, StringBuilder builder) { if (isList(method.getReturnType())) { // Wrap the returned List in the server adapter. builder.append("new "); builder.append(JSON_ARRAY_IMPL); builder.append("("); builder.append(fieldName); builder.append(")"); } else if (isMap(method.getReturnType())) { // Wrap the Map. builder.append("new "); builder.append(JSON_MAP_IMPL); builder.append("("); builder.append(fieldName); builder.append(")"); } else { builder.append(fieldName); } } private void emitSetter(String fieldName, String paramType, StringBuilder builder) { builder.append(" public "); builder.append("void"); builder.append(" "); builder.append(getSetterName(fieldName)); builder.append("("); builder.append(paramType); builder.append(" v) {\n"); builder.append(" this."); builder.append(fieldName); builder.append(" = "); builder.append("v;\n }\n\n"); } private void emitWithMethods( List getters, String dtoInterfaceName, StringBuilder builder) { for (Method getter : getters) { String fieldName = getJavaFieldName(getter.getName()); emitWithMethod( getWithName(fieldName), fieldName, getFqParameterizedName(getter.getGenericReturnType()), dtoInterfaceName, builder); } } private void emitWithMethod( String methodName, String fieldName, String paramType, String dtoInterfaceName, StringBuilder builder) { builder.append(" public "); builder.append(dtoInterfaceName); builder.append(" "); builder.append(methodName); builder.append("("); builder.append(paramType); builder.append(" v) {\n"); builder.append(" this."); builder.append(fieldName); builder.append(" = "); builder.append("v;\n return this;\n }\n\n"); } /** * Emits an add method to add to a list. If the list is null, it is created. * * @param method a method with a list return type */ private void emitListAdd(Method method, String fieldName, StringBuilder builder) { builder.append(" public void "); builder.append(getListAdderName(fieldName)); builder.append("("); builder.append(getTypeArgumentImplName((ParameterizedType) method.getGenericReturnType(), 0)); builder.append(" v) {\n "); builder.append(getEnsureName(fieldName)); builder.append("();\n "); builder.append(fieldName); builder.append(".add(v);\n"); builder.append(" }\n\n"); } /** * Emits a put method to put a value into a map. If the map is null, it is created. * * @param method a method with a map return value */ private void emitMapPut(Method method, String fieldName, StringBuilder builder) { builder.append(" public void "); builder.append(getMapPutterName(fieldName)); builder.append("(String k, "); builder.append(getTypeArgumentImplName((ParameterizedType) method.getGenericReturnType(), 1)); builder.append(" v) {\n "); builder.append(getEnsureName(fieldName)); builder.append("();\n "); builder.append(fieldName); builder.append(".put(k, v);\n"); builder.append(" }\n\n"); } /** * Emits a method to clear a list or map. Clearing the collections ensures that the collection is * created. */ private void emitClear(String fieldName, StringBuilder builder) { builder.append(" public void "); builder.append(getClearName(fieldName)); builder.append("() {\n "); builder.append(getEnsureName(fieldName)); builder.append("();\n "); builder.append(fieldName); builder.append(".clear();\n"); builder.append(" }\n\n"); } private void emitCopyConstructor(List getters, StringBuilder builder) { String dtoInterface = getDtoInterface().getCanonicalName(); String implClassName = getImplClassName(); builder .append(" public ") .append(implClassName) .append("(") .append(dtoInterface) .append(" origin) {\n"); for (Method method : getters) { emitDeepCopyForGetters( expandType(method.getGenericReturnType()), 0, builder, "origin", method, " "); } builder.append(" }\n\n"); } private void emitDeepCopyForGetters( List expandedTypes, int depth, StringBuilder builder, String origin, Method getter, String i) { String getterName = getter.getName(); String fieldName = getJavaFieldName(getterName); String fieldNameIn = fieldName + "In"; String fieldNameOut = fieldName + "Out"; Type type = expandedTypes.get(depth); Class rawClass = getRawClass(type); String rawTypeName = getImplName(type, false); if (isList(rawClass) || isMap(rawClass)) { builder .append(i) .append(rawTypeName) .append(" ") .append(fieldNameIn) .append(" = ") .append(origin) .append(".") .append(getterName) .append("();\n"); builder.append(i).append("if (").append(fieldNameIn).append(" != null) {\n"); builder .append(i) .append(" ") .append(rawTypeName) .append(" ") .append(fieldNameOut) .append(" = new ") .append(getImplName(type, true)) .append("();\n"); emitDeepCopyCollections(expandedTypes, depth, builder, fieldNameIn, fieldNameOut, i); builder .append(i) .append(" ") .append("this.") .append(fieldName) .append(" = ") .append(fieldNameOut) .append(";\n"); builder.append(i).append("}\n"); } else if (isAny(rawClass)) { builder.append(i).append("this.").append(fieldName).append(" = "); appendNaiveCopyJsonExpression(origin + "." + getterName + "()", builder).append(";\n"); } else if (getEnclosingTemplate().isDtoInterface(rawClass)) { builder .append(i) .append(rawTypeName) .append(" ") .append(fieldNameIn) .append(" = ") .append(origin) .append(".") .append(getterName) .append("();\n"); builder.append(i).append("this.").append(fieldName).append(" = "); emitCheckNullAndCopyDto(rawClass, fieldNameIn, builder); builder.append(";\n"); } else { builder .append(i) .append("this.") .append(fieldName) .append(" = ") .append(origin) .append(".") .append(getterName) .append("();\n"); } } private void emitDeepCopyCollections( List expandedTypes, int depth, StringBuilder builder, String varIn, String varOut, String i) { Type type = expandedTypes.get(depth); String childVarIn = varIn + "_"; String childVarOut = varOut + "_"; String entryVar = "entry" + depth; Class rawClass = getRawClass(type); Class childRawType = getRawClass(expandedTypes.get(depth + 1)); final String childTypeName = getImplName(expandedTypes.get(depth + 1), false); if (isList(rawClass)) { builder .append(i) .append(" for (") .append(childTypeName) .append(" ") .append(childVarIn) .append(" : ") .append(varIn) .append(") {\n"); } else if (isMap(rawClass)) { builder .append(i) .append(" for (java.util.Map.Entry ") .append(entryVar) .append(" : ") .append(varIn) .append(".entrySet()) {\n"); builder .append(i) .append(" ") .append(childTypeName) .append(" ") .append(childVarIn) .append(" = ") .append(entryVar) .append(".getValue();\n"); } if (isList(childRawType) || isMap(childRawType)) { builder.append(i).append(" if (").append(childVarIn).append(" != null) {\n"); builder .append(i) .append(" ") .append(childTypeName) .append(" ") .append(childVarOut) .append(" = new ") .append(getImplName(expandedTypes.get(depth + 1), true)) .append("();\n"); emitDeepCopyCollections( expandedTypes, depth + 1, builder, childVarIn, childVarOut, i + " "); builder.append(i).append(" ").append(varOut); if (isList(rawClass)) { builder.append(".add("); } else { builder.append(".put(").append(entryVar).append(".getKey(), "); } builder.append(childVarOut); builder.append(");\n"); builder.append(i).append(" ").append("}\n"); } else { builder.append(i).append(" ").append(varOut); if (isList(rawClass)) { builder.append(".add("); } else { builder.append(".put(").append(entryVar).append(".getKey(), "); } if (getEnclosingTemplate().isDtoInterface(childRawType)) { emitCheckNullAndCopyDto(childRawType, childVarIn, builder); } else { builder.append(childVarIn); } builder.append(");\n"); } builder.append(i).append(" }\n"); } private void emitCheckNullAndCopyDto(Class dto, String fieldName, StringBuilder builder) { String implName = dto.getSimpleName() + "Impl"; builder .append(fieldName) .append(" == null ? null : ") .append("new ") .append(implName) .append("(") .append(fieldName) .append(")"); } /** Emit a method that ensures a collection is initialized. */ private void emitEnsureCollection(Method method, String fieldName, StringBuilder builder) { builder.append(" protected void "); builder.append(getEnsureName(fieldName)); builder.append("() {\n"); builder.append(" if ("); builder.append(fieldName); builder.append(" == null) {\n "); builder.append(fieldName); builder.append(" = new "); builder.append(getImplName(method.getGenericReturnType(), true)); builder.append("();\n"); builder.append(" }\n"); builder.append(" }\n"); } /** * Appends a suitable type for the given type. For example, at minimum, this will replace DTO * interfaces with their implementation classes and JSON collections with corresponding Java * types. If a suitable type cannot be determined, this will throw an exception. * * @param genericType the type as returned by e.g. method.getGenericReturnType() */ private void appendType(Type genericType, final StringBuilder builder) { builder.append(getImplName(genericType, false)); } /** * In most cases we simply echo the return type and field name, except for JsonArray, which is * special in the server impl case, since it must be represented by a List for Gson to * correctly serialize/deserialize it. * * @param method The getter method. * @return String representation of what the field type should be, as well as the assignment * (initial value) to said field type, if any. */ private String getFieldTypeAndAssignment(Method method, String fieldName) { StringBuilder builder = new StringBuilder(); builder.append("protected "); appendType(method.getGenericReturnType(), builder); builder.append(" "); builder.append(fieldName); builder.append(";\n"); return builder.toString(); } /** * Returns the fully-qualified type name using Java concrete implementation classes. * *

For example, for JsonArray<JsonStringMap<Dto>>, this would return * "ArrayList<Map<String, DtoImpl>>". */ private String getImplName(Type type, boolean allowJreCollectionInterface) { Class rawClass = getRawClass(type); String fqName = getFqParameterizedName(type); fqName = fqName.replaceAll(JsonArray.class.getCanonicalName(), ArrayList.class.getCanonicalName()); fqName = fqName.replaceAll( JsonStringMap.class.getCanonicalName() + "<", HashMap.class.getCanonicalName() + ") { return ((Class) type).getCanonicalName(); // return getImplNameForDto((Class)type); } else if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) type; StringBuilder sb = new StringBuilder(getRawClass(pType).getCanonicalName()); sb.append('<'); for (int i = 0; i < pType.getActualTypeArguments().length; i++) { if (i > 0) { sb.append(", "); } sb.append(getFqParameterizedName(pType.getActualTypeArguments()[i])); } sb.append('>'); return sb.toString(); } else { throw new IllegalArgumentException( "Can't build implementation of " + getDtoInterface().getSimpleName() + ". DtoGenerator does not handle this type " + type.toString()); } } /** * Returns the fully-qualified type name using Java concrete implementation classes of the first * type argument for a parameterized type. If one is not specified, returns "Object". * * @param type the parameterized type * @return the first type argument */ private String getTypeArgumentImplName(ParameterizedType type, int index) { Type[] typeArgs = type.getActualTypeArguments(); if (typeArgs.length == 0) { return "Object"; } return getImplName(typeArgs[index], false); } private String getImplNameForDto(Class dtoInterface) { if (getEnclosingTemplate().isDtoInterface(dtoInterface)) { // This will eventually get a generated impl type. return dtoInterface.getSimpleName() + "Impl"; } return dtoInterface.getCanonicalName(); } } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoTemplate.java ================================================ // Copyright 2012 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.eclipse.che.dto.generator; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.che.dto.server.DtoFactoryVisitor; /** * Base template for the generated output file that contains all the DTOs. * *

Note that we generate client and server DTOs in separate runs of the generator. * *

The directionality of the DTOs only affects whether or not we expose methods to construct an * instance of the DTO. We need both client and server versions of all DTOs (irrespective of * direction). */ public class DtoTemplate { public static class MalformedDtoInterfaceException extends RuntimeException { public MalformedDtoInterfaceException(String msg) { super(msg); } } // We keep a whitelist of allowed non-DTO generic types. static final Set> jreWhitelist = new HashSet<>( Arrays.asList( new Class[] { String.class, Integer.class, Double.class, Float.class, Boolean.class })); private final List dtoInterfaces = new ArrayList<>(); // contains mapping for already implemented DTO interfaces private final Map, Set>> implementedDtoInterfaces = new HashMap<>(); private final String packageName; private final String className; private final String implType; /** * Walks the super interface hierarchy to determine if a Class implements some target interface * transitively. */ static boolean implementsInterface(Class i, Class target) { if (i.equals(target)) { return true; } boolean rtn = false; Class[] superInterfaces = i.getInterfaces(); for (Class superInterface : superInterfaces) { rtn = rtn || implementsInterface(superInterface, target); } return rtn; } /** * Constructor. * * @param packageName The name of the package for the outer DTO class. * @param className The name of the outer DTO class. * @param implType DTO impls type, "client" or "server". */ DtoTemplate(String packageName, String className, String implType) { this.packageName = packageName; this.className = className; this.implType = implType; } public String getImplType() { return implType; } public void addImplementation(Class dtoInterface, Class impl) { Set> classes = implementedDtoInterfaces.get(dtoInterface); if (classes == null) { implementedDtoInterfaces.put(dtoInterface, classes = new LinkedHashSet<>()); } classes.add(impl); } /** * Some DTO interfaces may be already implemented in dependencies of current project. Try to reuse * them. If this method returns null it means interface is not implemented yet. */ public Class getDtoImplementation(Class dtoInterface) { Set> classes = implementedDtoInterfaces.get(dtoInterface); if (classes == null || classes.isEmpty()) { return null; } for (Class impl : classes) { if (impl.getSimpleName().equals(dtoInterface.getSimpleName() + "Impl")) { return impl; } } return null; } /** * Adds an interface to the DtoTemplate for code generation. * * @param i interface to add */ public void addInterface(Class i) { getDtoInterfaces().add(createDtoImplTemplate(i)); } /** * @return the dtoInterfaces */ public List getDtoInterfaces() { return dtoInterfaces; } /** * Returns the source code for a class that contains all the DTO impls for any interfaces that * were added via the {@link #addInterface(Class)} method. */ @Override public String toString() { dtoInterfaces.sort(new DtoImplsComparator()); StringBuilder builder = new StringBuilder(); emitPreamble(builder); emitDtos(builder); emitPostamble(builder); return builder.toString(); } /** * Tests whether or not a given class is a part of our dto jar, and thus will eventually have a * generated Impl that is serializable (thus allowing it to be a generic type). */ boolean isDtoInterface(Class potentialDto) { for (DtoImpl dto : getDtoInterfaces()) { if (dto.getDtoInterface().equals(potentialDto)) { return true; } } return false; } private DtoImpl createDtoImplTemplate(Class i) { if ("server".equals(implType)) { return new DtoImplServerTemplate(this, i); } else if ("client".equals(implType)) { return new DtoImplClientTemplate(this, i); } throw new IllegalStateException( "Unsupported DTO implementation type, must be 'client' or 'server'"); } private void emitDtos(StringBuilder builder) { for (DtoImpl dto : getDtoInterfaces()) { builder.append(dto.serialize()); } } private void emitPostamble(StringBuilder builder) { builder.append("\n}"); } private void emitPreamble(StringBuilder builder) { builder.append( "/*******************************************************************************\n"); builder.append(" * Copyright (c) 2012-2016 Red Hat, Inc.\n"); builder.append(" * All rights reserved. This program and the accompanying materials\n"); builder.append(" * are made available under the terms of the Eclipse Public License v1.0\n"); builder.append(" * which accompanies this distribution, and is available at\n"); builder.append(" * http://www.eclipse.org/legal/epl-v10.html\n"); builder.append(" *\n"); builder.append(" * Contributors:\n"); builder.append(" * Red Hat, Inc. - initial API and implementation\n"); builder.append( " *******************************************************************************/\n\n\n"); builder.append("// GENERATED SOURCE. DO NOT EDIT.\npackage "); builder.append(packageName); builder.append(";\n\n"); if ("server".equals(implType)) { builder.append("import org.eclipse.che.dto.server.JsonSerializable;\n"); builder.append("\n"); builder.append("import com.google.gson.Gson;\n"); builder.append("import com.google.gson.GsonBuilder;\n"); builder.append("import com.google.gson.JsonArray;\n"); builder.append("import com.google.gson.JsonElement;\n"); builder.append("import com.google.gson.JsonNull;\n"); builder.append("import com.google.gson.JsonObject;\n"); builder.append("import com.google.gson.JsonParser;\n"); builder.append("import com.google.gson.JsonPrimitive;\n"); builder.append("\n"); builder.append("import java.util.List;\n"); builder.append("import java.util.Map;\n"); } if ("client".equals(implType)) { builder.append("import org.eclipse.che.ide.dto.ClientDtoFactoryVisitor;\n"); builder.append("import org.eclipse.che.ide.dto.DtoFactoryVisitor;\n"); builder.append("import org.eclipse.che.ide.dto.JsonSerializable;\n"); builder.append("import com.google.gwt.json.client.*;\n"); builder.append("import com.google.inject.Singleton;\n"); } builder.append("\n\n@SuppressWarnings({\"unchecked\", \"cast\", \"MissingOverride\"})\n"); if ("client".equals(implType)) { builder.append("@Singleton\n"); builder.append("@ClientDtoFactoryVisitor\n"); } // Note that we always use fully qualified path names when referencing Types // so we need not add any import statements for anything. builder.append("public class "); builder.append(className); if ("server".equals(implType)) { builder.append(" implements ").append(DtoFactoryVisitor.class.getCanonicalName()); } if ("client".equals(implType)) { builder.append(" implements ").append("DtoFactoryVisitor"); } builder.append(" {\n\n"); if ("server".equals(implType)) { builder.append( " private static final Gson gson = org.eclipse.che.dto.server.DtoFactory.getInstance().getGson();\n\n"); builder.append( " @Override\n" + " public void accept(org.eclipse.che.dto.server.DtoFactory dtoFactory) {\n"); for (DtoImpl dto : getDtoInterfaces()) { String dtoInterface = dto.getDtoInterface().getCanonicalName(); builder .append(" dtoFactory.registerProvider(") .append(dtoInterface) .append(".class") .append(", ") .append("new org.eclipse.che.dto.server.DtoProvider<") .append(dtoInterface) .append(">() {\n"); builder .append(" public Class getImplClass() {\n") .append(" return ") .append(dto.getImplClassName()) .append(".class;\n"); builder.append(" }\n\n"); builder .append(" public ") .append(dtoInterface) .append(" newInstance() {\n") .append(" return ") .append(dto.getImplClassName()) .append(".make();\n"); builder.append(" }\n\n"); builder .append(" public ") .append(dtoInterface) .append(" fromJson(String json) {\n") .append(" return ") .append(dto.getImplClassName()) .append(".fromJsonString(json);\n"); builder.append(" }\n\n"); builder .append(" public ") .append(dtoInterface) .append(" fromJson(com.google.gson.JsonElement json) {\n") .append(" return ") .append(dto.getImplClassName()) .append(".fromJsonElement(json);\n"); builder.append(" }\n\n"); builder .append(" public ") .append(dtoInterface) .append(" clone(") .append(dtoInterface) .append(" origin) {\n") .append(" return new ") .append(dto.getImplClassName()) .append("(origin);\n"); builder.append(" }\n"); builder.append(" });\n"); } builder.append(" }\n\n"); } if ("client".equals(implType)) { builder .append(" @Override\n") .append(" public void accept(org.eclipse.che.ide.dto.DtoFactory dtoFactory) {\n"); for (DtoImpl dto : getDtoInterfaces()) { String dtoInterface = dto.getDtoInterface().getCanonicalName(); builder .append(" dtoFactory.registerProvider(") .append(dtoInterface) .append(".class") .append(", ") .append("new org.eclipse.che.ide.dto.DtoProvider<") .append(dtoInterface) .append(">() {\n"); builder .append(" public Class getImplClass() {\n") .append(" return ") .append(dto.getImplClassName()) .append(".class;\n"); builder.append(" }\n\n"); builder .append(" public ") .append(dtoInterface) .append(" newInstance() {\n") .append(" return ") .append(dto.getImplClassName()) .append(".make();\n"); builder.append(" }\n\n"); builder .append(" public ") .append(dtoInterface) .append(" fromJson(String json) {\n") .append(" return ") .append(dto.getImplClassName()) .append(".fromJsonString(json);\n"); builder.append(" }\n"); builder.append(" });\n"); } builder.append(" }\n\n"); } } private static class DtoImplsComparator implements Comparator { @Override public int compare(DtoImpl o1, DtoImpl o2) { return o1.getImplClassName().compareTo(o2.getImplClassName()); } } } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/server/DtoFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.server; import static java.lang.String.format; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonSyntaxException; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.che.commons.lang.reflect.ParameterizedTypeImpl; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.dto.shared.JsonArray; import org.eclipse.che.dto.shared.JsonStringMap; /** * Provides implementations of DTO interfaces. * * @author andrew00x */ public final class DtoFactory { private static final LoadingCache listTypeCache = CacheBuilder.newBuilder() .concurrencyLevel(16) .build( new CacheLoader() { @Override public ParameterizedType load(Type type) { return new ParameterizedTypeImpl(List.class, type); } }); private static final LoadingCache mapTypeCache = CacheBuilder.newBuilder() .concurrencyLevel(16) .build( new CacheLoader() { @Override public ParameterizedType load(Type type) { return new ParameterizedTypeImpl(Map.class, String.class, type); } }); private static final DtoFactory INSTANCE = new DtoFactory(); public static DtoFactory getInstance() { return INSTANCE; } /** * Ge a {@link Gson} serializer that is configured to serializes/deserializes DTOs correctly. * * @return A Gson. */ public Gson getGson() { return dtoGson; } /** * Creates new instance of class which implements specified DTO interface. * * @param dtoInterface DTO interface * @throws IllegalArgumentException if can't provide any implementation for specified interface */ public static T newDto(Class dtoInterface) { return getInstance().createDto(dtoInterface); } private final Map, DtoProvider> dtoInterface2Providers = new ConcurrentHashMap<>(); // Additional mapping for implementation of DTO interfaces. // It helps avoid reflection when need create copy of exited DTO instance. private final Map, DtoProvider> dtoImpl2Providers = new ConcurrentHashMap<>(); private final Gson dtoGson = buildDtoParser( ServiceLoader.load(TypeAdapterFactory.class).iterator(), new DtoInterfaceTAF()); /** * Created deep copy of DTO object. * * @param origin origin DTO object * @return copy * @throws IllegalArgumentException if specified object doesn't implement DTO interface annotated * with {@link org.eclipse.che.dto.shared.DTO @DTO} or if specified instance implements * more than one interface annotated with {@link org.eclipse.che.dto.shared.DTO @DTO} */ @SuppressWarnings("unchecked") public T clone(T origin) { final Class implClass = origin.getClass(); DtoProvider provider = dtoImpl2Providers.get(implClass); if (provider == null) { Class dtoInterface = null; Class[] interfaces = implClass.getInterfaces(); if (interfaces.length == 0) { return null; } for (Class i : interfaces) { if (i.isAnnotationPresent(DTO.class)) { if (dtoInterface != null) { throw new IllegalArgumentException( "Unable determine DTO interface. Type " + implClass.getName() + " implements or extends more than one interface annotated with @DTO annotation."); } dtoInterface = i; } } if (dtoInterface != null) { provider = getDtoProvider(dtoInterface); } } if (provider == null) { throw new IllegalArgumentException("Unknown DTO type " + implClass); } return (T) provider.clone(origin); } /** * Shortcut for {@code DtoFactory.getInstance().clone(T dtoObject)} * * @see #clone(Object) */ public static T cloneDto(T origin) { return getInstance().clone(origin); } public String toJson(T dto) { if (dto instanceof JsonSerializable) { return ((JsonSerializable) dto).toJson(); } throw new IllegalArgumentException("JsonSerializable instance required. "); } public JsonElement toJsonElement(T dto) { if (dto instanceof JsonSerializable) { return ((JsonSerializable) dto).toJsonElement(); } throw new IllegalArgumentException("JsonSerializable instance required. "); } /** * Creates new instance of class which implements specified DTO interface. * * @param dtoInterface DTO interface * @throws IllegalArgumentException if can't provide any implementation for specified interface */ public T createDto(Class dtoInterface) { return getDtoProvider(dtoInterface).newInstance(); } // /** * Creates new instance of class which implements specified DTO interface, parses specified JSON * string and uses parsed data for initializing fields of DTO object. * * @param json JSON data * @param dtoInterface DTO interface * @throws IllegalArgumentException if can't provide any implementation for specified interface */ public T createDtoFromJson(String json, Class dtoInterface) { try { return createDtoFromJson(new StringReader(json), dtoInterface); } catch (IOException e) { throw new RuntimeException(e); // won't happen } } /** * Creates new instance of class which implements specified DTO interface, uses the specific JSON * data for initializing fields of DTO object. * * @param json JSON data * @param dtoInterface DTO interface * @throws IllegalArgumentException if can't provide any implementation for specified interface */ public T createDtoFromJson(JsonElement json, Class dtoInterface) { return getDtoProvider(dtoInterface).fromJson(json); } /** * Creates new instance of class which implements specified DTO interface, parses specified JSON * data and uses parsed data for initializing fields of DTO object. * * @param json JSON data * @param dtoInterface DTO interface * @throws IllegalArgumentException if can't provide any implementation for specified interface * @throws IOException if an i/o error occurs */ public T createDtoFromJson(Reader json, Class dtoInterface) throws IOException { getDtoProvider(dtoInterface); return dtoGson.fromJson(json, dtoInterface); } /** * Creates new instance of class which implements specified DTO interface, parses specified JSON * data and uses parsed data for initializing fields of DTO object. * * @param json JSON data * @param dtoInterface DTO interface * @throws IllegalArgumentException if can't provide any implementation for specified interface * @throws IOException if an i/o error occurs */ public T createDtoFromJson(InputStream json, Class dtoInterface) throws IOException { return createDtoFromJson(new InputStreamReader(json), dtoInterface); } // /** * Parses the JSON data from the specified sting into list of objects of the specified type. * * @param json JSON data * @param dtoInterface DTO interface * @return list of DTO * @throws IllegalArgumentException if can't provide any implementation for specified interface */ public JsonArray createListDtoFromJson(String json, Class dtoInterface) { try { return createListDtoFromJson(new StringReader(json), dtoInterface); } catch (IOException e) { throw new RuntimeException(e); // won't happen } } /** * Parses the JSON data from the specified reader into list of objects of the specified type. * * @param json JSON data * @param dtoInterface DTO interface * @return list of DTO * @throws IllegalArgumentException if can't provide any implementation for specified interface */ public JsonArray createListDtoFromJson(Reader json, Class dtoInterface) throws IOException { getDtoProvider(dtoInterface); final List list = parseDto(json, listTypeCache.getUnchecked(dtoInterface)); return new JsonArrayImpl<>(list); } /** * Parses the JSON data from the specified stream into list of objects of the specified type. * * @param json JSON data * @param dtoInterface DTO interface * @return list of DTO * @throws IllegalArgumentException if can't provide any implementation for specified interface * @throws IOException if an i/o error occurs */ public JsonArray createListDtoFromJson(InputStream json, Class dtoInterface) throws IOException { return createListDtoFromJson(new InputStreamReader(json), dtoInterface); } // /** * Parses the JSON data from the specified sting into map of objects of the specified type. * * @param json JSON data * @param dtoInterface DTO interface * @return map of DTO * @throws IllegalArgumentException if can't provide any implementation for specified interface */ public JsonStringMap createMapDtoFromJson(String json, Class dtoInterface) { try { return createMapDtoFromJson(new StringReader(json), dtoInterface); } catch (IOException e) { throw new RuntimeException(e); // won't happen } } /** * Parses the JSON data from the specified reader into map of objects of the specified type. * * @param json JSON data * @param dtoInterface DTO interface * @return map of DTO * @throws IllegalArgumentException if can't provide any implementation for specified interface * @throws IOException if an i/o error occurs */ public JsonStringMap createMapDtoFromJson(Reader json, Class dtoInterface) throws IOException { getDtoProvider(dtoInterface); final Map map = parseDto(json, mapTypeCache.getUnchecked(dtoInterface)); return new JsonStringMapImpl<>(map); } /** * Parse a JSON string that contains DTOs, propagating JSON exceptions correctly if they are * caused by failures in the given Reader. Real JSON syntax exceptions are propagated as-is. */ private T parseDto(Reader json, Type type) throws IOException { try { return dtoGson.fromJson(json, type); } catch (JsonSyntaxException e) { final Throwable cause = e.getCause(); if (cause instanceof IOException) { throw (IOException) cause; } throw e; } } /** * Parses the JSON data from the specified stream into map of objects of the specified type. * * @param json JSON data * @param dtoInterface DTO interface * @return map of DTO * @throws IllegalArgumentException if can't provide any implementation for specified interface * @throws IOException if an i/o error occurs */ public JsonStringMap createMapDtoFromJson(InputStream json, Class dtoInterface) throws IOException { return createMapDtoFromJson(new InputStreamReader(json), dtoInterface); } // @SuppressWarnings("unchecked") private DtoProvider getDtoProvider(Class dtoInterface) { DtoProvider dtoProvider = dtoInterface2Providers.get(dtoInterface); if (dtoProvider == null) { if (!dtoInterface.isInterface()) { throw new IllegalArgumentException( format("Only interfaces can be DTO, but %s is not an interface.", dtoInterface)); } if (hasDtoAnnotation(dtoInterface)) { throw new IllegalArgumentException( format("Provider of implementation for DTO type %s is not found", dtoInterface)); } else { throw new IllegalArgumentException(dtoInterface + " is not a DTO type"); } } return (DtoProvider) dtoProvider; } /** * Checks if dtoInterface or its parent have DTO annotation. * * @param dtoInterface DTO interface * @return true if only dtoInterface or one of its parent have DTO annotation. */ private boolean hasDtoAnnotation(Class dtoInterface) { if (dtoInterface.isAnnotationPresent(DTO.class)) { return true; } for (Class parent : dtoInterface.getInterfaces()) { if (hasDtoAnnotation(parent)) { return true; } } return false; } /** * Registers DtoProvider for DTO interface. * * @param dtoInterface DTO interface * @param provider provider for DTO interface * @see DtoProvider */ public void registerProvider(Class dtoInterface, DtoProvider provider) { dtoInterface2Providers.put(dtoInterface, provider); dtoImpl2Providers.put(provider.getImplClass(), provider); } /** * Unregisters DtoProvider. * * @see #registerProvider(Class, DtoProvider) */ public DtoProvider unregisterProvider(Class dtoInterface) { final DtoProvider dtoProvider = dtoInterface2Providers.remove(dtoInterface); if (dtoProvider != null) { dtoImpl2Providers.remove(dtoProvider.getImplClass()); } return dtoProvider; } /** * Test weather or not this DtoFactory has any DtoProvider which can provide implementation of DTO * interface. */ public boolean hasProvider(Class dtoInterface) { return dtoInterface2Providers.get(dtoInterface) != null; } /** * A specialization of Gson's {@link ReflectiveTypeAdapterFactory} delegates operation on DTO * interfaces to the corresponding implementation classes. The implementation classes generated * correctly by the DTO Gson. * * @author tareq.sha@gmail.com */ private class DtoInterfaceTAF implements TypeAdapterFactory { @Override @SuppressWarnings("unchecked") public TypeAdapter create(Gson gson, TypeToken type) { DtoProvider prov = dtoInterface2Providers.get(type.getRawType()); if (prov != null) { return (TypeAdapter) gson.getAdapter(prov.getImplClass()); } return null; } } /** * Wraps Gson's default List/Map adapter factories serialize null List/Map fields as empty * instead. * * @author tareq.sha@gmail.com */ private static class NullAsEmptyTAF implements TypeAdapterFactory { final Class matchedClass; final U defaultValue; NullAsEmptyTAF(Class matchedClass, U defaultValue) { this.matchedClass = matchedClass; this.defaultValue = defaultValue; } @Override @SuppressWarnings("unchecked") public TypeAdapter create(Gson gson, TypeToken type) { if (!matchedClass.isAssignableFrom(type.getRawType())) { return null; } TypeAdapter delegate = gson.getDelegateAdapter(this, type); return new TypeAdapter() { @Override public void write(JsonWriter out, T value) throws IOException { delegate.write(out, value != null ? value : (T) defaultValue); } @Override public T read(JsonReader in) throws IOException { return delegate.read(in); } }; } } static { for (DtoFactoryVisitor visitor : ServiceLoader.load(DtoFactoryVisitor.class)) { visitor.accept(INSTANCE); } } private DtoFactory() {} private static Gson buildDtoParser( Iterator factoryIterator, TypeAdapterFactory... factories) { GsonBuilder builder = new GsonBuilder(); for (Iterator it = factoryIterator; it.hasNext(); ) { TypeAdapterFactory factory = it.next(); builder.registerTypeAdapterFactory(factory); } for (TypeAdapterFactory factory : factories) { builder.registerTypeAdapterFactory(factory); } if (Boolean.valueOf(System.getenv("CHE_LEGACY__DTO__JSON__SERIALIZATION"))) { builder.registerTypeAdapterFactory( new NullAsEmptyTAF<>(Collection.class, Collections.emptyList())); builder.registerTypeAdapterFactory(new NullAsEmptyTAF<>(Map.class, Collections.emptyMap())); } else { builder.registerTypeHierarchyAdapter(Collection.class, new NullOrEmptyCollectionAdapter()); builder.registerTypeHierarchyAdapter(Map.class, new NullOrEmptyMapAdapter()); } builder.registerTypeAdapterFactory(new SerializableInterfaceAdapterFactory()); return builder.create(); } } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/server/DtoFactoryVisitor.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.server; /** * Visitor pattern. Generally needed to register DtoProviders by generated code in DtoFactory. Class * which contains generated code for server side implements this interface. When DtoFactory class is * loaded it looks up for all implementation of this interface and calls method {@link * #accept(DtoFactory)}. * * @author andrew00x */ public interface DtoFactoryVisitor { void accept(DtoFactory dtoFactory); } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/server/DtoProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.server; import com.google.gson.JsonElement; /** * Provides implementation of DTO interface. * * @author andrew00x */ public interface DtoProvider { Class getImplClass(); DTO fromJson(String json); DTO fromJson(JsonElement json); DTO newInstance(); DTO clone(DTO origin); } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/server/JsonArrayImpl.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.server; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import java.io.Writer; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import org.eclipse.che.dto.shared.JsonArray; public class JsonArrayImpl implements JsonArray { private static final Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); private final List delegate; public JsonArrayImpl(List delegate) { this.delegate = delegate; } @Override public int size() { return delegate.size(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } @Override public boolean contains(Object o) { return delegate.contains(o); } @Override public Iterator iterator() { return delegate.iterator(); } @Override public Object[] toArray() { return delegate.toArray(); } @Override public T[] toArray(T[] a) { return delegate.toArray(a); } public boolean add(T t) { return delegate.add(t); } @Override public boolean remove(Object o) { return delegate.remove(o); } @Override public boolean containsAll(Collection c) { return delegate.containsAll(c); } public boolean addAll(Collection c) { return delegate.addAll(c); } public boolean addAll(int index, Collection c) { return delegate.addAll(index, c); } @Override public boolean removeAll(Collection c) { return delegate.removeAll(c); } @Override public boolean retainAll(Collection c) { return delegate.retainAll(c); } @Override public void clear() { delegate.clear(); } @Override public boolean equals(Object o) { return delegate.equals(o); } @Override public int hashCode() { return delegate.hashCode(); } @Override public T get(int index) { return delegate.get(index); } public T set(int index, T element) { return delegate.set(index, element); } public void add(int index, T element) { delegate.add(index, element); } @Override public T remove(int index) { return delegate.remove(index); } @Override public int indexOf(Object o) { return delegate.indexOf(o); } @Override public int lastIndexOf(Object o) { return delegate.lastIndexOf(o); } @Override public ListIterator listIterator() { return delegate.listIterator(); } @Override public ListIterator listIterator(int index) { return delegate.listIterator(index); } @Override public List subList(int fromIndex, int toIndex) { return delegate.subList(fromIndex, toIndex); } @Override public String toJson() { return gson.toJson(this); } @Override public void toJson(Writer w) { gson.toJson(this, w); } @Override public JsonElement toJsonElement() { return gson.toJsonTree(this); } @Override public String toString() { return delegate.toString(); } } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/server/JsonSerializable.java ================================================ // Copyright 2012 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.eclipse.che.dto.server; import com.google.gson.JsonElement; import java.io.Serializable; import java.io.Writer; /** An entity that may serialize itself to JSON. */ public interface JsonSerializable extends Serializable { /** Serializes DTO to JSON format. */ String toJson(); /** Serialize the DTO to JSON text through the given Writer. */ void toJson(Writer w); /** Serializes DTO to JSON object. */ JsonElement toJsonElement(); } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/server/JsonStringMapImpl.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.server; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import java.io.Writer; import java.util.Collection; import java.util.Map; import java.util.Set; import org.eclipse.che.dto.shared.JsonStringMap; public class JsonStringMapImpl implements JsonStringMap { private static final Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create(); private final Map delegate; public JsonStringMapImpl(Map delegate) { this.delegate = delegate; } @Override public int size() { return delegate.size(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } @Override public boolean containsKey(Object key) { return delegate.containsKey(key); } @Override public boolean containsValue(Object value) { return delegate.containsValue(value); } @Override public T get(Object key) { return delegate.get(key); } public T put(String key, T value) { return delegate.put(key, value); } @Override public T remove(Object key) { return delegate.remove(key); } public void putAll(Map m) { delegate.putAll(m); } @Override public void clear() { delegate.clear(); } @Override public Set keySet() { return delegate.keySet(); } @Override public Collection values() { return delegate.values(); } @Override public Set> entrySet() { return delegate.entrySet(); } @Override public boolean equals(Object o) { return delegate.equals(o); } @Override public int hashCode() { return delegate.hashCode(); } @Override public String toJson() { return gson.toJson(this); } @Override public void toJson(Writer w) { gson.toJson(this, w); } @Override public JsonElement toJsonElement() { return gson.toJsonTree(this); } @Override public String toString() { return delegate.toString(); } } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/server/NullOrEmptyCollectionAdapter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.server; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import java.lang.reflect.Type; import java.util.List; /** * This class will prevent serialization of null or empty fields of DTOs with Collection types * * @author Mykhailo Kuznietsov */ public class NullOrEmptyCollectionAdapter implements JsonSerializer> { @Override public JsonElement serialize(List src, Type typeOfSrc, JsonSerializationContext context) { if (src == null || src.isEmpty()) { return null; } JsonArray array = new JsonArray(); for (Object child : src) { JsonElement element = context.serialize(child); array.add(element); } return array; } } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/server/NullOrEmptyMapAdapter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.server; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import java.lang.reflect.Type; import java.util.Map; /** * This class will prevent serialization of null or empty fields of DTOs with Map types * * @author Mykhailo Kuznietsov */ public class NullOrEmptyMapAdapter implements JsonSerializer> { @Override public JsonElement serialize(Map src, Type typeOfSrc, JsonSerializationContext context) { if (src == null || src.isEmpty()) { return null; } JsonObject object = new JsonObject(); for (Map.Entry entry : src.entrySet()) { object.add(entry.getKey().toString(), context.serialize(entry.getValue())); } return object; } } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/server/SerializableInterfaceAdapterFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.server; import com.google.gson.Gson; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.io.Serializable; /** * This adapter is required for fields of {@link java.io.Serializable} type to be treated as {@link * Object} */ public class SerializableInterfaceAdapterFactory implements TypeAdapterFactory { @Override public TypeAdapter create(Gson gson, TypeToken type) { if (Serializable.class.equals(type.getRawType())) { return (TypeAdapter) new SerializableAdapter(gson.getAdapter(Object.class)); } return null; } private static class SerializableAdapter extends TypeAdapter { TypeAdapter objectAdapter; public SerializableAdapter(TypeAdapter objectAdapter) { this.objectAdapter = objectAdapter; } @Override public void write(JsonWriter out, Object value) throws IOException { objectAdapter.write(out, value); } @Override public Object read(JsonReader in) throws IOException { JsonToken token = in.peek(); if (token.equals(JsonToken.NUMBER)) { try { return in.nextLong(); } catch (NumberFormatException e) { return in.nextDouble(); } } return objectAdapter.read(in); } } } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/shared/CompactJsonDto.java ================================================ // Copyright 2012 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.eclipse.che.dto.shared; /** * Tag interface for DTOs that are serialized to compact (non human readable) JSON. * *

* *

Compact JSON has array as a root element. As a consequence GSON library throws an exception * when you try do deserialize JSON containing compact object as a root. Deserialize using {@link * com.google.gson.JsonElement} in such cases. * *

* *

Note: try to eliminate enums and boolean fields in DTO to get better serialized form density. */ public interface CompactJsonDto {} ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/shared/DTO.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.shared; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * DTO interface definition annotation. Used to mark interface for generating DTO. * * @author Artem Zatsarynnyi */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DTO {} ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/shared/DTOImpl.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.shared; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Internal annotation for marking generated implementations for DTO interfaces. * *

Not expected to be used in any by developers directly. * * @author andrew00x */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DTOImpl { /** Type of DTO implementation,expected values are "client" or "server". */ String value(); } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/shared/DelegateRule.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.shared; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Single DTO method delegate rule. * * @author andrew00x * @see DelegateTo */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DelegateRule { Class type(); String method(); } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/shared/DelegateTo.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.shared; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Annotation that contains "rules" for delegation invocation of DTO method to some third party * classes. In some case we may need more then just getters and setters in DTO, but there is no * common mechanism to generate such implementation for DTO interface. In this case this annotation * may help. Here is example of usage: * *

* *

    *
  1. DTO interface. It contains getters, setters and with methods and we do nothing special for * them. Implementation generated automatically as well for client and server side. But if we * want to have more complex method generator doesn't help, for example add some method for * getting full name of user. *
     * @DTO
     * public interface User {
     *     String getFirstName();
     *
     *     void setFirstName(String firstName);
     *
     *     User withFirstName(String firstName);
     *
     *     String getLastName();
     *
     *     void setLastName(String lastName);
     *
     *     User withLastName(String lastName);
     *
     *     @DelegateTo(client = @DelegateRule(type = Util.class, method = "fullName"),
     *                 server = @DelegateRule(type = Util.class, method = "fullName"))
     *     String getFullName();
     * }
     * 
    * For method {@code getFullName} add annotation {@code @DelegateTo}. Annotations may * contains different delegate rules for client and server side. *

    DelegateTo
    * * * * * * * *
    ParameterDescription
    clientRules for client
    serverRules for server
    *
    DelegateRule
    * * * * * * * *
    ParameterDescription
    typeClass that contains method to delegate method call
    methodName of method
    *
  2. Util class *
     * public class Util {
     *     public static String fullName(User user) {
     *         return user.getFirstName() + " " + user.getLastName();
     *     }
     * }
     * 
    *
  3. Requirements for methods to delegate DTO methods calls: Method must be public and static. * Method must accept DTO interface as first parameter, if DTO method contains other * parameters then the delegate method must accept the whole set of DTO method parameters * starting from the second position. For example: *

    DTO method: *

     * @DelegateTo(client = @DelegateRule(type = Util.class, method = "fullName"),
     *             server = @DelegateRule(type = Util.class, method = "fullName"))
     * String getFullNameWithPrefix(String prefix);
     * 
    *

    Delegate method: *

     * public static String fullName(User user, String prefix) {
     *         return prefix + " " + user.getFirstName() + " " + user.getLastName();
     * }
     * 
    * * *
*/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DelegateTo { DelegateRule client(); DelegateRule server(); } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/shared/JsonArray.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.shared; import java.util.List; import org.eclipse.che.dto.server.JsonSerializable; /** Abstraction for list of JSON values. */ public interface JsonArray extends List, JsonSerializable {} ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/shared/JsonFieldName.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.shared; import com.google.common.annotations.Beta; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Use a custom name for the JSON element that corresponds to a class field * * @author Tareq Sharafy (tareq.sharafy@sap.com) */ @Beta @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface JsonFieldName { String value(); } ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/shared/JsonStringMap.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.shared; import java.util.Map; import org.eclipse.che.dto.server.JsonSerializable; /** Abstraction for map of JSON values. */ public interface JsonStringMap extends Map, JsonSerializable {} ================================================ FILE: core/che-core-api-dto/src/main/java/org/eclipse/che/dto/shared/SerializationIndex.java ================================================ // Copyright 2012 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.eclipse.che.dto.shared; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Annotation for specifying the index of field in serialized form. * *

* *

Index must be a positive integer. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SerializationIndex { int value(); } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/ServerDtoTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto; import static java.util.Arrays.asList; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.dto.definitions.ComplicatedDto; import org.eclipse.che.dto.definitions.DTOHierarchy; import org.eclipse.che.dto.definitions.DTOHierarchy.GrandchildDto; import org.eclipse.che.dto.definitions.DtoWithAny; import org.eclipse.che.dto.definitions.DtoWithDelegate; import org.eclipse.che.dto.definitions.DtoWithFieldNames; import org.eclipse.che.dto.definitions.DtoWithSerializable; import org.eclipse.che.dto.definitions.SimpleDto; import org.eclipse.che.dto.definitions.model.Model; import org.eclipse.che.dto.definitions.model.ModelComponentDto; import org.eclipse.che.dto.definitions.model.ModelDto; import org.eclipse.che.dto.server.DtoFactory; import org.testng.Assert; import org.testng.annotations.Test; /** * Tests that the interfaces specified in org.eclipse.che.dto.definitions have corresponding * generated server implementations. * * @author Artem Zatsarynnyi */ public class ServerDtoTest { protected static final DtoFactory dtoFactory = DtoFactory.getInstance(); @Test public void testCreateSimpleDto() throws Exception { final String fooString = "Something"; final int fooId = 1; final String _default = "test_default_keyword"; SimpleDto dto = dtoFactory .createDto(SimpleDto.class) .withName(fooString) .withId(fooId) .withDefault(_default); // Check to make sure things are in a sane state. checkSimpleDto(dto, fooString, fooId, _default); } @Test public void testSimpleDtoSerializer() throws Exception { final String fooString = "Something"; final int fooId = 1; final String _default = "test_default_keyword"; SimpleDto dto = dtoFactory .createDto(SimpleDto.class) .withName(fooString) .withId(fooId) .withDefault(_default); final String json = dtoFactory.toJson(dto); JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject(); assertEquals(jsonObject.get("name").getAsString(), fooString); assertEquals(jsonObject.get("id").getAsInt(), fooId); assertEquals(jsonObject.get("default").getAsString(), _default); } @Test public void testSimpleDtoDeserializer() throws Exception { final String fooString = "Something"; final int fooId = 1; final String _default = "test_default_keyword"; JsonObject json = new JsonObject(); json.add("name", new JsonPrimitive(fooString)); json.add("id", new JsonPrimitive(fooId)); json.add("default", new JsonPrimitive(_default)); SimpleDto dto = dtoFactory.createDtoFromJson(json.toString(), SimpleDto.class); // Check to make sure things are in a sane state. checkSimpleDto(dto, fooString, fooId, _default); } @Test public void testSerializerWithFieldNames() throws Exception { final String fooString = "Something"; final String _default = "test_default_keyword"; DtoWithFieldNames dto = dtoFactory .createDto(DtoWithFieldNames.class) .withTheName(fooString) .withTheDefault(_default); final String json = dtoFactory.toJson(dto); JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject(); assertEquals(jsonObject.get(DtoWithFieldNames.THENAME_FIELD).getAsString(), fooString); assertEquals(jsonObject.get(DtoWithFieldNames.THEDEFAULT_FIELD).getAsString(), _default); } @Test public void testDeserializerWithFieldNames() throws Exception { final String fooString = "Something"; final String _default = "test_default_keyword"; JsonObject json = new JsonObject(); json.add(DtoWithFieldNames.THENAME_FIELD, new JsonPrimitive(fooString)); json.add(DtoWithFieldNames.THEDEFAULT_FIELD, new JsonPrimitive(_default)); DtoWithFieldNames dto = dtoFactory.createDtoFromJson(json.toString(), DtoWithFieldNames.class); assertEquals(dto.getTheName(), fooString); assertEquals(dto.getTheDefault(), _default); } @Test public void testSerializerWithAny() throws Exception { final List objects = createListTestValueForAny(); DtoWithAny dto = dtoFactory .createDto(DtoWithAny.class) .withStuff(createTestValueForAny()) .withObjects(objects); final String json = dtoFactory.toJson(dto); JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject(); Assert.assertEquals(jsonObject.get("stuff"), createTestValueForAny()); Assert.assertTrue(jsonObject.has("objects")); JsonArray jsonArray = jsonObject.get("objects").getAsJsonArray(); assertEquals(jsonArray.size(), objects.size()); for (int i = 0; i < jsonArray.size(); i++) { assertEquals(jsonArray.get(i), objects.get(i)); } } @Test public void testDeserializerWithAny() throws Exception { JsonObject json = new JsonObject(); json.add("stuff", createTestValueForAny()); JsonArray jsonArray = createElementListTestValueForAny(); json.add("objects", jsonArray); DtoWithAny dto = dtoFactory.createDtoFromJson(json.toString(), DtoWithAny.class); Gson gson = new Gson(); Object stuffValue = gson.fromJson(createTestValueForAny(), Object.class); Assert.assertEquals(dto.getStuff(), stuffValue); Object objectsValue = gson.fromJson(createElementListTestValueForAny(), Object.class); Assert.assertEquals(dto.getObjects(), objectsValue); } @Test public void testShadowedFields() throws Exception { GrandchildDto dto1 = dtoFactory.createDto(GrandchildDto.class); dtoFactory.toJson(dto1); } /** Intentionally call several times to ensure non-reference equality */ private static JsonElement createTestValueForAny() { return new JsonParser().parse("{a:100,b:{c:'blah'}}"); } /** Intentionally call several times to ensure non-reference equality */ private static List createListTestValueForAny() { final ArrayList objects = new ArrayList<>(); objects.add(new JsonParser().parse("{x:1}")); objects.add(new JsonParser().parse("{b:120}")); return objects; } private JsonArray createElementListTestValueForAny() { JsonArray jsonArray = new JsonArray(); for (Object object : createListTestValueForAny()) { jsonArray.add((JsonElement) object); } return jsonArray; } @Test public void testListSimpleDtoDeserializer() throws Exception { final String fooString_1 = "Something 1"; final int fooId_1 = 1; final String _default_1 = "test_default_keyword_1"; final String fooString_2 = "Something 2"; final int fooId_2 = 2; final String _default_2 = "test_default_keyword_2"; JsonObject json1 = new JsonObject(); json1.add("name", new JsonPrimitive(fooString_1)); json1.add("id", new JsonPrimitive(fooId_1)); json1.add("default", new JsonPrimitive(_default_1)); JsonObject json2 = new JsonObject(); json2.add("name", new JsonPrimitive(fooString_2)); json2.add("id", new JsonPrimitive(fooId_2)); json2.add("default", new JsonPrimitive(_default_2)); JsonArray jsonArray = new JsonArray(); jsonArray.add(json1); jsonArray.add(json2); org.eclipse.che.dto.shared.JsonArray listDtoFromJson = dtoFactory.createListDtoFromJson(jsonArray.toString(), SimpleDto.class); assertEquals(listDtoFromJson.get(0).getName(), fooString_1); assertEquals(listDtoFromJson.get(0).getId(), fooId_1); assertEquals(listDtoFromJson.get(0).getDefault(), _default_1); assertEquals(listDtoFromJson.get(1).getName(), fooString_2); assertEquals(listDtoFromJson.get(1).getId(), fooId_2); assertEquals(listDtoFromJson.get(1).getDefault(), _default_2); } @Test public void testComplicatedDtoSerializer() throws Exception { final String fooString = "Something"; final int fooId = 1; final String _default = "test_default_keyword"; List listStrings = new ArrayList<>(2); listStrings.add("Something 1"); listStrings.add("Something 2"); ComplicatedDto.SimpleEnum simpleEnum = ComplicatedDto.SimpleEnum.ONE; // Assume that SimpleDto works. Use it to test nested objects SimpleDto simpleDto = dtoFactory .createDto(SimpleDto.class) .withName(fooString) .withId(fooId) .withDefault(_default); Map mapDtos = new HashMap<>(1); mapDtos.put(fooString, simpleDto); List listDtos = new ArrayList<>(1); listDtos.add(simpleDto); List> listOfListOfEnum = new ArrayList<>(1); List listOfEnum = new ArrayList<>(3); listOfEnum.add(ComplicatedDto.SimpleEnum.ONE); listOfEnum.add(ComplicatedDto.SimpleEnum.TWO); listOfEnum.add(ComplicatedDto.SimpleEnum.THREE); listOfListOfEnum.add(listOfEnum); ComplicatedDto dto = dtoFactory .createDto(ComplicatedDto.class) .withStrings(listStrings) .withSimpleEnum(simpleEnum) .withMap(mapDtos) .withSimpleDtos(listDtos) .withArrayOfArrayOfEnum(listOfListOfEnum); final String json = dtoFactory.toJson(dto); JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject(); Assert.assertTrue(jsonObject.has("strings")); JsonArray jsonArray = jsonObject.get("strings").getAsJsonArray(); assertEquals(jsonArray.get(0).getAsString(), listStrings.get(0)); assertEquals(jsonArray.get(1).getAsString(), listStrings.get(1)); Assert.assertTrue(jsonObject.has("simpleEnum")); assertEquals(jsonObject.get("simpleEnum").getAsString(), simpleEnum.name()); Assert.assertTrue(jsonObject.has("map")); JsonObject jsonMap = jsonObject.get("map").getAsJsonObject(); JsonObject value = jsonMap.get(fooString).getAsJsonObject(); assertEquals(value.get("name").getAsString(), fooString); assertEquals(value.get("id").getAsInt(), fooId); assertEquals(value.get("default").getAsString(), _default); Assert.assertTrue(jsonObject.has("simpleDtos")); JsonArray simpleDtos = jsonObject.get("simpleDtos").getAsJsonArray(); JsonObject simpleDtoJsonObject = simpleDtos.get(0).getAsJsonObject(); assertEquals(simpleDtoJsonObject.get("name").getAsString(), fooString); assertEquals(simpleDtoJsonObject.get("id").getAsInt(), fooId); assertEquals(simpleDtoJsonObject.get("default").getAsString(), _default); Assert.assertTrue(jsonObject.has("arrayOfArrayOfEnum")); JsonArray arrayOfArrayOfEnum = jsonObject.get("arrayOfArrayOfEnum").getAsJsonArray().get(0).getAsJsonArray(); assertEquals(arrayOfArrayOfEnum.get(0).getAsString(), ComplicatedDto.SimpleEnum.ONE.name()); assertEquals(arrayOfArrayOfEnum.get(1).getAsString(), ComplicatedDto.SimpleEnum.TWO.name()); assertEquals(arrayOfArrayOfEnum.get(2).getAsString(), ComplicatedDto.SimpleEnum.THREE.name()); } @Test public void testComplicatedDtoDeserializer() throws Exception { final String fooString = "Something"; final int fooId = 1; final String _default = "test_default_keyword"; JsonArray jsonArray = new JsonArray(); jsonArray.add(new JsonPrimitive(fooString)); JsonObject simpleDtoJsonObject = new JsonObject(); simpleDtoJsonObject.add("name", new JsonPrimitive(fooString)); simpleDtoJsonObject.add("id", new JsonPrimitive(fooId)); simpleDtoJsonObject.add("default", new JsonPrimitive(_default)); JsonObject jsonMap = new JsonObject(); jsonMap.add(fooString, simpleDtoJsonObject); JsonArray simpleDtosArray = new JsonArray(); simpleDtosArray.add(simpleDtoJsonObject); JsonArray arrayOfEnum = new JsonArray(); arrayOfEnum.add(new JsonPrimitive(ComplicatedDto.SimpleEnum.ONE.name())); arrayOfEnum.add(new JsonPrimitive(ComplicatedDto.SimpleEnum.TWO.name())); arrayOfEnum.add(new JsonPrimitive(ComplicatedDto.SimpleEnum.THREE.name())); JsonArray arrayOfArrayEnum = new JsonArray(); arrayOfArrayEnum.add(arrayOfEnum); JsonObject complicatedDtoJsonObject = new JsonObject(); complicatedDtoJsonObject.add("strings", jsonArray); complicatedDtoJsonObject.add( "simpleEnum", new JsonPrimitive(ComplicatedDto.SimpleEnum.ONE.name())); complicatedDtoJsonObject.add("map", jsonMap); complicatedDtoJsonObject.add("simpleDtos", simpleDtosArray); complicatedDtoJsonObject.add("arrayOfArrayOfEnum", arrayOfArrayEnum); ComplicatedDto complicatedDto = dtoFactory.createDtoFromJson(complicatedDtoJsonObject.toString(), ComplicatedDto.class); assertEquals(complicatedDto.getStrings().get(0), fooString); assertEquals(complicatedDto.getSimpleEnum(), ComplicatedDto.SimpleEnum.ONE); checkSimpleDto(complicatedDto.getMap().get(fooString), fooString, fooId, _default); checkSimpleDto(complicatedDto.getSimpleDtos().get(0), fooString, fooId, _default); assertEquals( complicatedDto.getArrayOfArrayOfEnum().get(0).get(0), ComplicatedDto.SimpleEnum.ONE); assertEquals( complicatedDto.getArrayOfArrayOfEnum().get(0).get(1), ComplicatedDto.SimpleEnum.TWO); assertEquals( complicatedDto.getArrayOfArrayOfEnum().get(0).get(2), ComplicatedDto.SimpleEnum.THREE); } private void checkSimpleDto( SimpleDto dto, String expectedName, int expectedId, String expectedDefault) { assertEquals(dto.getName(), expectedName); assertEquals(dto.getId(), expectedId); assertEquals(dto.getDefault(), expectedDefault); } @Test public void testDelegate() { assertEquals( DtoFactory.getInstance() .createDto(DtoWithDelegate.class) .withFirstName("TEST") .nameWithPrefix("### "), "### TEST"); } @Test public void shouldBeAbleToExtendModelSkeletonWithDTOs() { final DtoFactory factory = DtoFactory.getInstance(); final Model model = factory .createDto(ModelDto.class) .withPrimary(factory.createDto(ModelComponentDto.class).withName("primary name")) .withComponents( asList( factory.createDto(ModelComponentDto.class).withName("name"), factory.createDto(ModelComponentDto.class).withName("name2"), factory.createDto(ModelComponentDto.class).withName("name3"))); assertEquals( model.getPrimary(), factory.createDto(ModelComponentDto.class).withName("primary name")); assertEquals(model.getComponents().size(), 3); assertTrue( model .getComponents() .contains(factory.createDto(ModelComponentDto.class).withName("name"))); assertTrue( model .getComponents() .contains(factory.createDto(ModelComponentDto.class).withName("name2"))); assertTrue( model .getComponents() .contains(factory.createDto(ModelComponentDto.class).withName("name3"))); } @Test public void shouldBeAbleToExtendsNotDTOInterfacesHierarchyWithDTOInterface() { final DTOHierarchy.ChildDto childDto = DtoFactory.getInstance() .createDto(DTOHierarchy.ChildDto.class) .withDtoField("dto-field") .withChildField("child-field") .withParentField("parent-field"); assertEquals(childDto.getDtoField(), "dto-field"); assertEquals(childDto.getChildField(), "child-field"); assertEquals(childDto.getParentField(), "parent-field"); } @Test( expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Only interfaces can be DTO, but class java.lang.String is not an interface.") public void shouldThrowExceptionWhenThereIsClassType() { DtoFactory.newDto(String.class); } @Test( expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "interface org.eclipse.che.dto.definitions.DTOHierarchy\\$GrandchildWithoutDto is not a DTO type") public void shouldThrowExceptionWhenInterfaceIsNotAnnotatedAsDto() { DtoFactory.newDto(DTOHierarchy.GrandchildWithoutDto.class); } @Test public void checkDtoDeserializationWithSerializableFields() { final int fooId = 1; final String fooString = "some string"; final long fooLong = 1234514362645634611L; final double fooDouble = 1.2345; final double fooRoundingDouble = 6.00; JsonObject jsonMap = new JsonObject(); jsonMap.add("fooLong", new JsonPrimitive(fooLong)); jsonMap.add("fooBoolean", new JsonPrimitive(true)); jsonMap.add("fooDouble", new JsonPrimitive(fooDouble)); jsonMap.add("fooRoundingDouble", new JsonPrimitive(fooRoundingDouble)); jsonMap.add("fooString", new JsonPrimitive(fooString)); JsonObject json = new JsonObject(); json.add("id", new JsonPrimitive(fooId)); json.add("object", new JsonPrimitive(fooString)); json.add("objectMap", jsonMap); DtoWithSerializable dto = dtoFactory.createDtoFromJson(json.toString(), DtoWithSerializable.class); assertEquals(dto.getId(), fooId); assertEquals(dto.getObject(), fooString); assertEquals(dto.getObjectMap().get("fooLong"), fooLong); assertEquals(dto.getObjectMap().get("fooBoolean"), true); assertEquals(dto.getObjectMap().get("fooDouble"), fooDouble); assertEquals(dto.getObjectMap().get("fooString"), fooString); assertEquals(dto.getObjectMap().get("fooRoundingDouble"), Math.round(fooRoundingDouble)); } } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/ComplicatedDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions; import java.util.List; import java.util.Map; import org.eclipse.che.dto.shared.DTO; /** * DTO for testing that the {@link org.eclipse.che.dto.generator.DtoGenerator} correctly generates * server implementations for object graphs (nested lists, and maps). * * @author Artem Zatsarynnyi */ @DTO public interface ComplicatedDto { public enum SimpleEnum { ONE, TWO, THREE } List getStrings(); ComplicatedDto withStrings(List strings); SimpleEnum getSimpleEnum(); ComplicatedDto withSimpleEnum(SimpleEnum simpleEnum); Map getMap(); ComplicatedDto withMap(Map map); List getSimpleDtos(); ComplicatedDto withSimpleDtos(List listDtos); List> getArrayOfArrayOfEnum(); ComplicatedDto withArrayOfArrayOfEnum(List> arrayOfArrayOfEnum); } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/DTOHierarchy.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions; import org.eclipse.che.dto.shared.DTO; /** * Keeps DTO interfaces for hierarchy test * * @author Eugene Voevodin */ public final class DTOHierarchy { public interface Parent { String getParentField(); } public interface Child extends Parent { String getChildField(); } @DTO public interface ChildDto extends Child { String getDtoField(); void setDtoField(String dtoField); ChildDto withDtoField(String dtoField); void setChildField(String childField); ChildDto withChildField(String childField); void setParentField(String parentField); ChildDto withParentField(String parentField); ChildDto getShadowedField(); void setShadowedField(ChildDto v); } @DTO public interface GrandchildDto extends ChildDto { GrandchildDto getShadowedField(); void setShadowedField(GrandchildDto v); } public interface Child2 extends Parent { String getChild2Field(); } public interface GrandchildWithoutDto extends Child, Child2 {} } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/DtoWithAny.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions; import java.util.List; import org.eclipse.che.dto.shared.DTO; /** * Makes use of the 'any' (JsonElement) property type feature. * * @author Tareq Sharafy (tareq.sharafy@sap.com) */ @DTO public interface DtoWithAny { int getId(); Object getStuff(); void setStuff(Object stuff); DtoWithAny withStuff(Object stuff); List getObjects(); void setObjects(List objects); DtoWithAny withObjects(List objects); } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/DtoWithDelegate.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.dto.shared.DelegateRule; import org.eclipse.che.dto.shared.DelegateTo; /** * @author andrew00x * @author Alexander Garagatyi */ @DTO public interface DtoWithDelegate extends TestInterface { String getFirstName(); void setFirstName(String firstName); DtoWithDelegate withFirstName(String firstName); String getLastName(); void setLastName(String lastName); DtoWithDelegate withLastName(String lastName); @DelegateTo( client = @DelegateRule(type = Util.class, method = "addPrefix"), server = @DelegateRule(type = Util.class, method = "addPrefix")) String nameWithPrefix(String prefix); // @Override // @DelegateTo(client = @DelegateRule(type = Util.class, method = "getFullName"), // server = @DelegateRule(type = Util.class, method = "getFullName")) // String getFullName(); } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/DtoWithFieldNames.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.dto.shared.JsonFieldName; /** * Makes use of the JsonFieldName annotation * * @author Tareq Sharafy (tareq.sharafy@sap.com) */ @DTO public interface DtoWithFieldNames { public String THENAME_FIELD = "the name"; public String THEDEFAULT_FIELD = "default"; @JsonFieldName(THENAME_FIELD) String getTheName(); void setTheName(String v); DtoWithFieldNames withTheName(String v); @JsonFieldName(THEDEFAULT_FIELD) String getTheDefault(); void setTheDefault(String v); DtoWithFieldNames withTheDefault(String v); } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/DtoWithSerializable.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions; import java.io.Serializable; import java.util.Map; import org.eclipse.che.dto.shared.DTO; /** DTO for testing serialization of fields with {@link java.io.Serializable} type */ @DTO public interface DtoWithSerializable { int getId(); DtoWithSerializable withId(int id); Serializable getObject(); void setObject(Serializable object); DtoWithSerializable withObject(Serializable object); Map getObjectMap(); void setObjectMap(Map map); DtoWithSerializable withObjectMap(Map map); } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/SimpleDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions; import org.eclipse.che.dto.shared.DTO; /** * DTO for testing that the {@link org.eclipse.che.dto.generator.DtoGenerator} correctly generates * server implementations for simple DTO interface. * * @author Artem Zatsarynnyi */ @DTO public interface SimpleDto { int getId(); SimpleDto withId(int id); String getName(); SimpleDto withName(String name); String getDefault(); void setDefault(String s); SimpleDto withDefault(String s); } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/TestInterface.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions; /** * @author Alexander Garagatyi */ public interface TestInterface { String getFullName(); } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/Util.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions; /** * @author andrew00x * @author Alexander Garagatyi */ public class Util { public static String addPrefix(DtoWithDelegate dto, String prefix) { return prefix + dto.getFirstName(); } public static String getFullName(DtoWithDelegate dto) { return dto.getFirstName() + dto.getLastName(); } } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/model/Model.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions.model; import java.util.List; /** * Test interface serves as base model, should be extended with dto interface * * @author Eugene Voevodin */ public interface Model { List getComponents(); ModelComponent getPrimary(); } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/model/ModelComponent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions.model; /** * Test interface, serves as part of model {@link Model}, should be extended with dto interface * * @author Eugene Voevodin */ public interface ModelComponent { String getName(); } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/model/ModelComponentDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions.model; import org.eclipse.che.dto.shared.DTO; /** * Test dto extension for model component {@link ModelComponent} * * @author Eugene Voevodin */ @DTO public interface ModelComponentDto extends ModelComponent { void setName(String name); ModelComponentDto withName(String name); } ================================================ FILE: core/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/model/ModelDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.definitions.model; import java.util.List; import org.eclipse.che.dto.shared.DTO; /** * Test DTO extension for {@link Model} * * @author Eugene Voevodin */ @DTO public interface ModelDto extends Model { @Override List getComponents(); void setComponents(List components); ModelDto withComponents(List components); @Override ModelComponentDto getPrimary(); void setPrimary(ModelComponentDto primary); ModelDto withPrimary(ModelComponentDto primary); } ================================================ FILE: core/che-core-api-dto-maven-plugin/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-dto-maven-plugin maven-plugin Che Core :: Commons :: API :: DTO Maven Plugin true org.eclipse.che.core che-core-api-dto ${project.version} org.apache.maven maven-plugin-api provided org.apache.maven.plugin-tools maven-plugin-annotations provided org.apache.maven.plugins maven-plugin-plugin mojo-descriptor descriptor help-goal helpmojo true ================================================ FILE: core/che-core-api-dto-maven-plugin/src/main/java/org/eclipse/che/dto/generator/maven/plugin/DtoGeneratorMojo.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.dto.generator.maven.plugin; import java.io.File; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.eclipse.che.dto.generator.DtoGenerator; /** Mojo to run {@link org.eclipse.che.dto.generator.DtoGenerator}. */ @Mojo(name = "generate", requiresDependencyResolution = ResolutionScope.COMPILE) public class DtoGeneratorMojo extends AbstractMojo { @Parameter(property = "outputDir", required = true) private String outputDirectory; @Parameter(property = "dtoPackages", required = true) private String[] dtoPackages; @Parameter(property = "genClassName", required = true) private String genClassName; @Parameter(property = "impl", required = true) private String impl; /** A flag to disable generation of the DTOs. */ @Parameter(property = "che.dto.skip", defaultValue = "false") private boolean skip; @Override public void execute() throws MojoExecutionException { if (skip) { getLog().info("Skipping the execution"); return; } DtoGenerator dtoGenerator = new DtoGenerator(); dtoGenerator.setPackageBase(outputDirectory); String genFileName = genClassName.replace('.', File.separatorChar) + ".java"; dtoGenerator.setGenFileName( outputDirectory.endsWith("/") ? (outputDirectory + genFileName) : (outputDirectory + File.separatorChar + genFileName)); dtoGenerator.setImpl(impl); dtoGenerator.setDtoPackages(dtoPackages); dtoGenerator.generate(); } } ================================================ FILE: core/che-core-api-model/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-model jar Che Core :: Commons :: API :: Model com.google.guava guava org.eclipse.che.core che-core-commons-annotations ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Action.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.factory; import java.util.Map; /** * Defines the contract for the factory action instance. * * @author Anton Korneta */ public interface Action { /** Returns the IDE specific identifier of action e.g. ('openFile', 'editFile') */ String getId(); /** Returns properties of this action instance */ Map getProperties(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Author.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.factory; /** * Defines the contract for the factory creator instance. * * @author Anton Korneta */ public interface Author { /** Identifier of the user who created factory, it is mandatory */ String getUserId(); /** Creation time of factory, set by the server (in milliseconds, from Unix epoch, no timezone) */ Long getCreated(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Factory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.factory; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; /** * Defines the contract for the factory instance. * * @author Anton Korneta */ public interface Factory { /** Returns the identifier of this factory instance, it is mandatory and unique. */ String getId(); /** Returns the version of this factory instance, it is mandatory. */ String getV(); /** Returns a name of this factory instance, the name is unique for creator. */ String getName(); /** Returns creator of this factory instance. */ Author getCreator(); /** Returns a workspace configuration of this factory instance. */ WorkspaceConfig getWorkspace(); /** Returns restrictions of this factory instance. */ Policies getPolicies(); /** Returns IDE for this factory instance. */ Ide getIde(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Ide.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.factory; /** * Defines the contract for the factory IDE instance. * * @author Anton Korneta */ public interface Ide { /** Returns configuration of IDE on application loaded event */ OnAppLoaded getOnAppLoaded(); /** Returns configuration of IDE on application closed event */ OnAppClosed getOnAppClosed(); /** Returns configuration of IDE on projects loaded event */ OnProjectsLoaded getOnProjectsLoaded(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnAppClosed.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.factory; import java.util.List; /** * Defines IDE look and feel on application closed event. * * @author Anton Korneta */ public interface OnAppClosed { /** Returns actions for current event. */ List getActions(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnAppLoaded.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.factory; import java.util.List; /** * Defines IDE look and feel on application loaded event. * * @author Anton Korneta */ public interface OnAppLoaded { /** Returns actions for current event. */ List getActions(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnProjectsLoaded.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.factory; import java.util.List; /** * Defines IDE look and feel on project opened event. * * @author Anton Korneta */ public interface OnProjectsLoaded { /** Returns actions for current event. */ List getActions(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Policies.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.factory; /** * Defines the contract for the factory restrictions. * * @author Anton Korneta */ public interface Policies { /** Restrict access if referer header doesn't match this field */ String getReferer(); /** Restrict access for factories used earlier then author supposes */ Long getSince(); /** Restrict access for factories used later then author supposes */ Long getUntil(); /** Workspace creation strategy */ String getCreate(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/ScmInfo.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.factory; /** Defines the contract for the SCM information. */ public interface ScmInfo { String getScmProviderName(); String getRepositoryUrl(); String getBranch(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/ProjectProblem.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.project; /** * @author Vitalii Parfonov */ public interface ProjectProblem { int getCode(); String getMessage(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/type/Attribute.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.project.type; /** * Model interface for Project type's attribute * * @author gazarenkov */ public interface Attribute { /** * @return attribute unique Id */ String getId(); /** * @return attribute name */ String getName(); /** * @return project type this attribute belongs to */ String getProjectType(); /** * @return value for this attribute */ Value getValue(); /** * @return some test description of this attribute */ String getDescription(); /** * @return true if the attribute is mandatory */ boolean isRequired(); /* * @return true if attribute value can be changed */ boolean isVariable(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/type/ProjectType.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.project.type; import java.util.List; /** * Model interface for Project Type * * @author gazarenkov */ public interface ProjectType { /** * @return unique ID */ String getId(); /** * @return project type display name */ String getDisplayName(); /** * @return true if this project type can be mixed in */ boolean isMixable(); /** * @return true if this project type can be used as primary */ boolean isPrimaryable(); /** * @return true if this project type explicitly stored as is in the project description otherwise * it is considered as "runtime" and can be calculated runtime using defined mandatory * attributes thanks to Value Provider mechanism */ boolean isPersisted(); /** * @return attributes */ List getAttributes(); /** * @return parent project type IDs */ List getParents(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/project/type/Value.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.project.type; import java.util.List; /** * Attribute value * * @author gazarenkov */ public interface Value { /** * @return value as String. If attribute has multiple values it returns first one. */ String getString(); /** * @return value as list of strings */ List getList(); /** * @return whether the value is not initialized */ boolean isEmpty(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/user/Profile.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.user; import java.util.Map; /** * Defines the user's profile model. * *

User's profile describes an additional user information such as his job title or company name * which is not related to application business logic. If it is necessary to manage business logic * related attributes then user's preferences should be used instead. * * @author Yevhenii Voevodin * @see User */ public interface Profile { /** Returns the identifier of the user {@link User#getId()} whom this profile belongs to. */ String getUserId(); /** Returns the user profile attributes (e.g. job title). */ Map getAttributes(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/user/User.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.user; import java.util.List; import org.eclipse.che.commons.annotation.Nullable; /** * Defines the user model. * * @author Yevhenii Voevodin */ public interface User { /** * Returns the identifier of the user (e.g. "user0x124567890"). The identifier value is unique and * mandatory. */ String getId(); /** * Returns the user's email (e.g. user@codenvy.com). The email is unique, mandatory and updatable. */ String getEmail(); /** Returns the user's name (e.g. name_example). The name is unique, mandatory and updatable. */ String getName(); /** * Returns the list of the user's aliases, the aliases are the values which identify user in the * system with third party ids (e.g. if user is registered within google oauth the aliases list * may contain 'google:user_identifier' alias). * *

Note that user's {@link #getEmail() email} and {@link #getName() name} are not a part of the * result, and returned list never contains those values. Also note that returned aliases are * unique, so there are no two users who have the alias in common. */ List getAliases(); /** * Returns the user's password. The returned value may be the password placeholder such as 'none' * or even null, depends on the context. */ @Nullable String getPassword(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/Runtime.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.runtime.Machine; import org.eclipse.che.commons.annotation.Nullable; /** * Defines a contract for workspace runtime. * *

Workspace has runtime when workspace is running (its {@link Workspace#getStatus() * status} is one of the {@link WorkspaceStatus#STARTING}, {@link WorkspaceStatus#RUNNING}, {@link * WorkspaceStatus#STOPPING}). * *

Workspace runtime defines workspace attributes which exist only when workspace is running. All * those attributes are strongly related to the runtime environment. Workspace runtime always exists * in couple with {@link Workspace} instance. * * @author Yevhenii Voevodin */ public interface Runtime { /** * Returns an active environment name. The environment with such name must exist in {@link * WorkspaceConfig#getEnvironments()}. */ @Nullable String getActiveEnv(); /** * Returns all the machines which statuses are either {MachineStatus#RUNNING running} or * {MachineStatus#DESTROYING}. * *

Returned list always contains dev-machine. */ Map getMachines(); String getOwner(); /** * Returns the list of the warnings indicating that the runtime violates some non-critical * constraints or default configuration values are used to boot it. */ List getWarnings(); /** * Returns commands which are related to runtime, when runtime doesn't contain commands returns * empty list. It is optional, workspace may contain 0 or N commands. */ List getCommands(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/Warning.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace; /** * Describes a warning, a pair of a message and a code which may indicate some context specific * non-critical violation. * * @author Yevhenii Voevodin */ public interface Warning { /** Returns the code of the warning. */ int getCode(); /** Returns the message explaining the warning. */ String getMessage(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/Workspace.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace; import com.google.common.annotations.Beta; import java.util.Map; import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.commons.annotation.Nullable; /** * Defines a contract for workspace instance. * *

Workspace instance defines all the attributes related to the certain workspace plus * configuration used to create instance plus its runtime. * * @author Yevhenii Voevodin */ public interface Workspace { /** Returns the identifier of this workspace instance. It is mandatory and unique. */ String getId(); /** * Returns the namespace of the current workspace instance. Workspace name is unique for * workspaces in the same namespace. */ String getNamespace(); /** * Returns the status of the current workspace instance. * *

All the workspaces which are stopped have runtime are considered {@link * WorkspaceStatus#STOPPED}. */ WorkspaceStatus getStatus(); /** * Returns workspace instance attributes (e.g. last modification date). Workspace attributes must * not contain null keys or values. */ Map getAttributes(); /** * Returns true if this workspace is temporary, and false otherwise. Temporary workspace exists * only in runtime so {@link #getRuntime()} will never return null for temporary workspace as well * as {@link #getStatus()} will never return {@link WorkspaceStatus#STOPPED}. */ boolean isTemporary(); /** * Returns a configuration of this workspace instance. The only one format (workspace config or * devfile) may be used for workspace at the same time. */ @Nullable WorkspaceConfig getConfig(); /** * Returns a configuration of this workspace instance in Devfile format. The only one format * (workspace config or devfile) may be used for workspace at the same time. */ @Beta @Nullable Devfile getDevfile(); /** * Returns the runtime of this workspace instance. If status of this workspace instance is either * {@link WorkspaceStatus#RUNNING} or {@link WorkspaceStatus#STARTING}, or {@link * WorkspaceStatus#STOPPING} then returned value is not null, otherwise it is. */ @Nullable Runtime getRuntime(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/WorkspaceConfig.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.config.Environment; import org.eclipse.che.api.core.model.workspace.config.ProjectConfig; import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.commons.annotation.Nullable; /** * Defines workspace configuration. * * @author gazarenkov * @author Yevhenii Voevodin */ public interface WorkspaceConfig { /** Returns the name of the current workspace instance. Workspace name is unique per namespace. */ String getName(); /** Returns description of workspace. */ @Nullable String getDescription(); /** * Returns default environment name. It is mandatory, implementation should guarantee that * environment with returned name exists for current workspace config. */ @Nullable String getDefaultEnv(); /** * Returns commands which are related to workspace, when workspace doesn't contain commands * returns empty list. It is optional, workspace may contain 0 or N commands. */ List getCommands(); /** * Returns project configurations which are related to workspace, when workspace doesn't contain * projects returns empty list. It is optional, workspace may contain 0 or N project * configurations. */ List getProjects(); /** * Returns mapping of environment names to environment configurations. Workspace must contain at * least 1 default environment and may contain N environments. */ Map getEnvironments(); /** * Returns workspace config attributes. Workspace config attributes must not contain null keys or * values. */ Map getAttributes(); /** Returns devfile that was to generating workspace config, null otherwise. */ Devfile getDevfile(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/WorkspaceStatus.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace; /** * Defines the contract between workspace and its active environment. * *

Workspace is rather part of the {@link Workspace} than {@link Runtime} or {@link * WorkspaceConfig}, as it shows the state of certain user's workspace and exists * earlier than runtime workspace instance e.g. UsersWorkspace may be considered as * 'STARTING' before it becomes runtime('RUNNING'). * * @author Alexander Garagatyi * @author Yevhenii Voevodin */ public enum WorkspaceStatus { /** * Workspace considered as starting if and only if its active environment is booting. * *

Workspace becomes starting only if it was {@link #STOPPED}. The status map: * *

   *  STOPPED -> STARTING -> RUNNING  (normal behaviour)
   *  STOPPED -> STARTING -> STOPPED  (failed to start)
   *  STOPPED -> STARTING -> STOPPING (explicitly stopped)
   * 
*/ STARTING, /** * Workspace considered as running if and only if its environment is running. * *

Workspace becomes running after it was {@link #STARTING}. The status map: * *

   *  STARTING -> RUNNING -> STOPPING (normal behaviour)
   *  STARTING -> RUNNING -> STOPPED (environment start was interrupted)
   * 
*/ RUNNING, /** * Workspace considered as stopping if and only if its active environment is shutting down. * *

Workspace is in stopping status only if it was in {@link #RUNNING} or {@link #STARTING} * status before. The status map: * *

   *  RUNNING  -> STOPPING -> STOPPED (normal behaviour)/(error while stopping)
   *  STARTING -> STOPPING -> STOPPED (stopped while starting)
   * 
*/ STOPPING, /** * Workspace considered as stopped when: * *
    *
  • Environment was successfully stopped *
  • Error occurred while environment was stopping *
  • Dev-machine failed to start *
  • Running environment machine was stopped by internal problem(e.g. OOM of a machine) *
  • Workspace hasn't been started yet(e.g stopped is the status of the user's workspace * instance without its runtime) *
* *

The status map: * *

   *  STOPPING -> STOPPED (normal behaviour)/(error while stopping)
   *  STARTING -> STOPPED (failed to start)
   *  RUNNING  -> STOPPED (environment machine was interrupted)
   * 
*/ STOPPED } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/Command.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.config; import java.util.Map; import org.eclipse.che.api.core.model.workspace.devfile.PreviewUrl; /** * Command that can be used to create {@link Process} in a machine * * @author Eugene Voevodin * @author gazarenkov */ public interface Command { /** * {@link Command} attribute which indicates the working directory where the given command must be * run */ String WORKING_DIRECTORY_ATTRIBUTE = "workingDir"; /** * {@link Command} attribute which indicates in which machine command must be run. It is optional, * IDE should asks user to choose machine if null. */ String MACHINE_NAME_ATTRIBUTE = "machineName"; /** * Optional {@link Command} attribute to store full url of the view of the command. This url * should be opened on command run. */ String PREVIEW_URL_ATTRIBUTE = "previewUrl"; /** * {@link Command} attribute which indicates in which plugin command must be run. If specified * plugin has multiple containers then first containers should be used. Attribute value has the * following format: `{PLUGIN_PUBLISHER}/{PLUGIN_NAME}/{PLUGIN_VERSION}`. For example: * eclipse/sample-plugin/0.0.1 */ String PLUGIN_ATTRIBUTE = "plugin"; /** * An attribute of the command to store the original path to the file that contains the editor * specific configuration. */ String COMMAND_ACTION_REFERENCE_ATTRIBUTE = "actionReference"; /** The contents of editor-specific content. */ String COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE = "actionReferenceContent"; /** * Returns command name (i.e. 'start tomcat') The name should be unique per user in one workspace, * which means that user may create only one command with the same name in the same workspace */ String getName(); /** * Returns command line (i.e. 'mvn clean install') which is going to be executed * *

Serves as a base for {@link Process} creation. */ String getCommandLine(); /** Returns command type (i.e. 'maven') */ String getType(); /** * @return preview url of the command or null if no preview url specified */ PreviewUrl getPreviewUrl(); /** * Returns attributes related to this command. * * @return command attributes */ Map getAttributes(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/Environment.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.config; import java.util.Map; /** * Defines environment for machines network. * * @author gazarenkov * @author Alexander Garagatyi */ public interface Environment { /** * Returns the recipe (the main script) to define this environment (compose, kubernetes pod). Type * of this recipe defines engine for composing machines network runtime. */ Recipe getRecipe(); /** Returns mapping of machine name to additional configuration of machine. */ Map getMachines(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/MachineConfig.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.config; import java.util.Map; import org.eclipse.che.api.core.model.workspace.devfile.Component; /** * Machine configuration * * @author Alexander Garagatyi */ public interface MachineConfig { /** * Name of the attribute from {@link #getAttributes()} which if present defines memory limit of * the machine in bytes. If memory limit is set in environment specific recipe this attribute used * in {@code MachineConfig} should override value from recipe. */ String MEMORY_LIMIT_ATTRIBUTE = "memoryLimitBytes"; /** * Name of the attribute from {@link #getAttributes()} which if present defines requested memory * allocation of the machine in bytes. If memory request is set in environment specific recipe * this attribute used in {@code MachineConfig} should override value from recipe. If both request * and limit are defined, and memory request is greater than the memory limit, this value is * ignored and only limit is used */ String MEMORY_REQUEST_ATTRIBUTE = "memoryRequestBytes"; /** * Name of the attribute from {@link #getAttributes()} which if present defines CPU limit of the * machine in cores. */ String CPU_LIMIT_ATTRIBUTE = "cpuLimitCores"; /** * Name of the attribute from {@link #getAttributes()} which if present defines requested CPU * allocation of the machine in cores. */ String CPU_REQUEST_ATTRIBUTE = "cpuRequestCores"; /** * Name of the attribute from {@link #getAttributes()} which, if present, defines the entrypoint * command to be executed in the machine/container. * *

The format is a YAML list of strings, e.g. {@code ['/bin/sh', '-c']} */ String CONTAINER_COMMAND_ATTRIBUTE = "containerCommand"; /** * Name of the attribute from {@link #getAttributes()} which, if present, defines the command line * arguments of the entrypoint command specified using the {@link #CONTAINER_COMMAND_ATTRIBUTE}. * *

If {@link #CONTAINER_COMMAND_ATTRIBUTE} is not present, the default command defined in the * image is used and the arguments are provided to it. * *

The format is a YAML list of strings, e.g. {@code ['-f', '--yes']} */ String CONTAINER_ARGS_ATTRIBUTE = "containerArgs"; /** * Name of the attribute from {@link #getAttributes()} which, if present, defines the alias of the * {@link Component} of the devfile which was a source of the given machine. */ String DEVFILE_COMPONENT_ALIAS_ATTRIBUTE = "component"; /** Returns mapping of references to configurations of servers deployed into machine. */ Map getServers(); /** Returns environment variables of machine. */ Map getEnv(); /** Returns attributes of machine. */ Map getAttributes(); /** Returns volumes of machine */ Map getVolumes(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/ProjectConfig.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.config; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.project.ProjectProblem; /** * @author gazarenkov * @author Dmitry Shnurenko */ public interface ProjectConfig { String getName(); String getPath(); String getDescription(); String getType(); List getMixins(); Map> getAttributes(); SourceStorage getSource(); List getProblems(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/Recipe.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.config; /** * Describes recipe of workspace environment. * * @author Alexander Garagatyi */ public interface Recipe { /** Type of the environment. */ String getType(); /** Content type of the environment recipe, e.g. application/x-yaml. */ String getContentType(); /** Content of an environment recipe. Content and location fields are mutually exclusive. */ String getContent(); /** Location of an environment recipe. Content and location fields are mutually exclusive. */ String getLocation(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/ServerConfig.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.config; import static java.lang.String.join; import static java.util.Collections.emptyList; import java.util.Arrays; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.devfile.Endpoint; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.commons.annotation.Nullable; /** * Configuration of server that can be started inside of machine. * * @author Alexander Garagatyi */ public interface ServerConfig { /** * {@link ServerConfig} and {@link Server} attribute name which can identify server as internal or * external. Attribute value {@code true} makes a server internal, any other value or lack of the * attribute makes the server external. */ String INTERNAL_SERVER_ATTRIBUTE = "internal"; /** * {@link ServerConfig} and {@link Server} attribute name which can identify server as secure or * non-secure. Requests to secure servers will be authenticated and must contain machine token. * Attribute value {@code true} makes a server secure, any other value or lack of the attribute * makes the server non-secure. */ String SECURE_SERVER_ATTRIBUTE = "secure"; /** * {@link ServerConfig} and {@link Server} attribute name which can contain an comma-separated * list of URI-s which are considered as non-secure on the given server and can be accessible with * unauthenticated requests. */ String UNSECURED_PATHS_ATTRIBUTE = "unsecuredPaths"; /** * {@link ServerConfig} and {@link Server} attribute name which indicates whether authentication * with cookies is allowed or not. Attribute value {@code true} cookies authentication enabled, * any other value or lack of the attribute denies to use cookies authentication. */ String SECURE_SERVER_COOKIES_AUTH_ENABLED_ATTRIBUTE = "cookiesAuthEnabled"; /** * {@link ServerConfig} and {@link Server} attribute name which sets the server as unique, meaning * that, if exposed, it has its own endpoint even if it shares the same port with other servers. */ String UNIQUE_SERVER_ATTRIBUTE = "unique"; /** * {@link ServerConfig} and {@link Server} attribute name which can identify endpoint as * discoverable(i.e. it is accessible by its name from workspace's containers). Attribute value * {@code true} makes a endpoint discoverable, any other value or lack of the attribute makes the * server non-discoverable. */ String DISCOVERABLE_SERVER_ATTRIBUTE = "discoverable"; /** * This attribute is used to remember {@link Endpoint#getName()} inside {@link ServerConfig} for * internal use. */ String SERVER_NAME_ATTRIBUTE = "serverName"; /** * This attribute is used to remember name of the service for single-host gateway configuration. * It's used internally only, so the attribute is removed from {@link ServerConfig}'s attributes * before going outside. */ String SERVICE_NAME_ATTRIBUTE = "serviceName"; /** * This attribute is used to remember port of the service for single-host gateway configuration. * It's used internally only, so the attribute is removed from {@link ServerConfig}'s attributes * before going outside. */ String SERVICE_PORT_ATTRIBUTE = "servicePort"; /** * This attributes is marking that server should be exposed on subdomain if we're on single-host. * It has no effect on other server exposure strategies. */ String REQUIRE_SUBDOMAIN = "requireSubdomain"; /** Attribute that specifies the base location of the JWT authenticating callback. */ String ENDPOINT_ORIGIN = "endpointOrigin"; /** * Port used by server. * *

It may contain protocol(tcp or udp) after '/' symbol. If protocol is missing tcp will be * used by default. Example: * *

    *
  • 8080/tcp *
  • 8080/udp *
  • 8080 *
*/ String getPort(); /** * Protocol for configuring preview url of this server. * *

Example: * *

    *
  • http *
  • https *
  • tcp *
  • udp *
  • ws *
  • wss *
*/ String getProtocol(); /** Path used by server. */ @Nullable String getPath(); /** Attributes of the server */ Map getAttributes(); /** * Determines whether the attributes configure the server to be internal. * * @param attributes the attributes with additional server configuration * @see #INTERNAL_SERVER_ATTRIBUTE */ static boolean isInternal(Map attributes) { return AttributesEvaluator.booleanAttr(attributes, INTERNAL_SERVER_ATTRIBUTE, false); } /** * Sets the "internal" flag in the provided attributes to the provided value. * * @param attributes the attributes with the additional server configuration */ static void setInternal(Map attributes, boolean value) { attributes.put(INTERNAL_SERVER_ATTRIBUTE, Boolean.toString(value)); } /** * Determines whether the attributes configure the server to be secure. * * @param attributes the attributes with additional server configuration * @see #SECURE_SERVER_ATTRIBUTE */ static boolean isSecure(Map attributes) { return AttributesEvaluator.booleanAttr(attributes, SECURE_SERVER_ATTRIBUTE, false); } /** * Sets the "secure" flag in the provided attributes to the provided value. * * @param attributes the attributes with the additional server configuration */ static void setSecure(Map attributes, boolean value) { attributes.put(SECURE_SERVER_ATTRIBUTE, Boolean.toString(value)); } /** * Determines whether the attributes configure the server to be unique. * * @param attributes the attributes with additional server configuration * @see #UNIQUE_SERVER_ATTRIBUTE */ static boolean isUnique(Map attributes) { return AttributesEvaluator.booleanAttr(attributes, UNIQUE_SERVER_ATTRIBUTE, false); } /** * Sets the "unique" flag in the provided attributes to the provided value. * * @param attributes the attributes with the additional server configuration */ static void setUnique(Map attributes, boolean value) { attributes.put(UNIQUE_SERVER_ATTRIBUTE, Boolean.toString(value)); } /** * Determines whether the attributes configure the server to be discoverable. * * @param attributes the attributes with additional server configuration * @see #DISCOVERABLE_SERVER_ATTRIBUTE */ static boolean isDiscoverable(Map attributes) { return AttributesEvaluator.booleanAttr(attributes, DISCOVERABLE_SERVER_ATTRIBUTE, false); } /** * Determines whether the attributes configure the server to be authenticated using JWT cookies. A * null value means that the attributes don't require any particular authentication. * * @param attributes the attributes with additional server configuration * @see #SECURE_SERVER_COOKIES_AUTH_ENABLED_ATTRIBUTE */ static @Nullable Boolean isCookiesAuthEnabled(Map attributes) { String val = attributes.get(SECURE_SERVER_COOKIES_AUTH_ENABLED_ATTRIBUTE); return val == null ? null : Boolean.parseBoolean(val); } /** * Sets the "cookiesAuthEnabled" flag in the provided attributes to the provided value. A null * value means that the attributes don't require any particular authentication. * * @param attributes the attributes with the additional server configuration */ static void setCookiesAuthEnabled(Map attributes, @Nullable Boolean value) { if (value == null) { attributes.remove(SECURE_SERVER_COOKIES_AUTH_ENABLED_ATTRIBUTE); } else { attributes.put(SECURE_SERVER_COOKIES_AUTH_ENABLED_ATTRIBUTE, Boolean.toString(value)); } } /** * This is checking if the attributes configure the server to be exposed on a subdomain if we're * on single-host. It has no effect on other server exposure strategies. */ static boolean isRequireSubdomain(Map attributes) { return AttributesEvaluator.booleanAttr(attributes, REQUIRE_SUBDOMAIN, false); } /** * Modify the attributes to configure the server to be exposed on a subdomain if we're on * single-host. It has no effect on other server exposure strategies. */ static void setRequireSubdomain(Map attributes, boolean value) { if (value) { attributes.put(REQUIRE_SUBDOMAIN, Boolean.TRUE.toString()); } else { attributes.remove(REQUIRE_SUBDOMAIN); } } /** * Returns the base location of the JWT authenticating callback. * * @param attributes the server attributes */ static @Nullable String getEndpointOrigin(Map attributes) { return attributes.get(ENDPOINT_ORIGIN); } /** * Sets the base location of the JWT authenticating callback. * * @param attributes the server attributes * @param value the auth origin or null if none should be used */ static void setEndpointOrigin(Map attributes, @Nullable String value) { if (value == null) { attributes.remove(ENDPOINT_ORIGIN); } else { attributes.putIfAbsent(ENDPOINT_ORIGIN, value); } } /** * Finds the unsecured paths configuration in the provided attributes.s * * @param attributes the attributes with additional server configuration * @see #UNSECURED_PATHS_ATTRIBUTE */ static List getUnsecuredPaths(Map attributes) { if (attributes == null) { return emptyList(); } String paths = attributes.get(UNSECURED_PATHS_ATTRIBUTE); if (paths == null) { return emptyList(); } return Arrays.asList(paths.split("\\s*,\\s*")); } static void setUnsecuredPaths(Map attributes, List value) { attributes.put(UNSECURED_PATHS_ATTRIBUTE, join(",", value)); } /** * @see #isInternal(Map) */ default boolean isInternal() { return isInternal(getAttributes()); } /** * @see #isSecure(Map) */ default boolean isSecure() { return isSecure(getAttributes()); } /** * @see #isUnique(Map) */ default boolean isUnique() { return isUnique(getAttributes()); } /** * @see #isCookiesAuthEnabled(Map) */ default @Nullable Boolean isCookiesAuthEnabled() { return isCookiesAuthEnabled(getAttributes()); } /** * @see #getUnsecuredPaths(Map) */ default List getUnsecuredPaths() { return getUnsecuredPaths(getAttributes()); } /** * @see #isDiscoverable(Map) */ default boolean isDiscoverable() { return isDiscoverable(getAttributes()); } /** * @see #isRequireSubdomain(Map) */ default boolean isRequireSubdomain() { return isRequireSubdomain(getAttributes()); } /** * @see #getEndpointOrigin(Map) */ default String getEndpointOrigin() { return getEndpointOrigin(getAttributes()); } } // helper class for the default methods in the above interface class AttributesEvaluator { static boolean booleanAttr(Map attrs, String name, boolean defaultValue) { if (attrs == null) { return defaultValue; } String attr = attrs.get(name); if (attr == null) { return defaultValue; } return Boolean.parseBoolean(attr); } } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/SourceStorage.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.config; import java.util.Map; /** * @author gazarenkov */ public interface SourceStorage { /** * The key with this name in the parameters designates the branch that should be initially checked * out from the source location. */ String BRANCH_PARAMETER_NAME = "branch"; /** * The key with this name in the parameters designates the tag that the initially checked out * branch should be reset to. */ String TAG_PARAMETER_NAME = "tag"; /** * The key with this name in the parameters designates the commit id that the initially checked * out branch should be reset to. */ String COMMIT_ID_PARAMETER_NAME = "commitId"; /** * The key with this name in the parameters designates the revision (of any kind) that the * initially checked out branch should be reset to. */ String START_POINT_PARAMETER_NAME = "startPoint"; /** * The key with this name in the parameters designates the directory that should be used for * sparse checkout, i.e. the only directory of repository which should be created by git. */ String SPARSE_CHECKOUT_DIR_PARAMETER_NAME = "sparseCheckoutDir"; String getType(); String getLocation(); Map getParameters(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/Volume.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.config; /** * Machine volume configuration. * * @author Alexander Garagatyi */ public interface Volume { /** Mount path of the volume in the machine */ String getPath(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/Action.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; public interface Action { /** Returns action type. Is is mandatory. */ String getType(); /** Returns component to which given action relates. */ String getComponent(); /** Returns the actual action command-line string. */ String getCommand(); /** Returns the working directory where the command should be executed. It is optional. */ String getWorkdir(); /** Returns the name of the referenced IDE-specific configuration file. */ String getReference(); /** Returns the content of the referenced IDE-specific configuration file. */ String getReferenceContent(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/Command.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; import java.util.List; import java.util.Map; public interface Command { /** Returns the name of the command. It is mandatory and unique per commands set. */ String getName(); /** Returns preview url of the command. Optional parameter, can be null if not specified. */ PreviewUrl getPreviewUrl(); /** * Returns the command actions. Now the only one command must be specified in list but there are * plans to implement supporting multiple actions commands. It is mandatory. */ List getActions(); /** * Returns the command attributes. Empty map is returned when command does not have attributes. It * is optional. */ Map getAttributes(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/Component.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; import java.io.Serializable; import java.util.List; import java.util.Map; public interface Component { /** Returns the alias of the component. Is optional and must be unique per components set. */ String getAlias(); /** * Returns type of the component, e.g. whether it is an plugin or editor or other type. It is * mandatory. */ String getType(); /** Returns the plugin/editor FQN. Is mandatory only for cheEditor/chePlugin components types. */ String getId(); /** * Returns the preferences of the plugin. Example value of preference: {@code java.home: * /home/user/jdk11} */ Map getPreferences(); /** * For 'kubernetes' and 'openshift' components types, returns absolute or devfile-relative * location of Kubernetes list yaml file. * *

For 'cheEditor' and 'chePlugin' components types, returns absolute location of plugin * descriptor (typically, named meta.yaml). For those types this field is optional. */ String getReference(); /** * Returns address of custom plugin registry. It is optional and applicable only for 'cheEditor' * and 'chePlugin' components types. */ String getRegistryUrl(); /** * Returns inlined content of a file specified in field 'reference'. It is optional and applicable * only for 'kubernetes' and 'openshift' components types. */ String getReferenceContent(); /** * Returns selector that should be used for picking up objects from specified content, if all * objects should be picked up then empty map is returned. It is optional and applicable only for * 'kubernetes' and 'openshift' components types. */ Map getSelector(); /** * Returns entrypoints that should be overridden for specified objects. If components does not * have overridden entrypoints then empty map is returned. It is optional and applicable only for * 'kubernetes' and 'openshift' components types. */ List getEntrypoints(); /** * Returns the docker image that should be used for component. It is mandatory and applicable only * for 'dockerimage' component type. */ String getImage(); /** * Returns memory limit for the component. * *

You can express memory as a plain integer or as a fixed-point integer using one of these * suffixes: E, P, T, G, M, K. You can also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, * Ki */ String getMemoryLimit(); /** * Returns memory request for the component. * *

You can express memory as a plain integer or as a fixed-point integer using one of these * suffixes: E, P, T, G, M, K. You can also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, * Ki */ String getMemoryRequest(); /** * Returns CPU limit for the component. * *

You can express CPU request as a floating-point cores or as a fixed-point integer millicores * using 'm' suffix. Examples: 1.5, 1500m. */ String getCpuLimit(); /** * Returns CPU request for the component. * *

You can express CPU request as a floating-point cores or as a fixed-point integer millicores * using 'm' suffix. Examples: 1.5, 1500m. */ String getCpuRequest(); /** * Returns true if projects sources should be mount to the component or false otherwise. It is * optional and applicable only for 'dockerimage' component type. `CHE_PROJECTS_ROOT` environment * variable should contains a path where projects sources are mount. */ Boolean getMountSources(); /** * Returns the command to run in the dockerimage component instead of the default one provided in * the image. It is optional, if missing then empty list is returned and command which is defined * in the image will be used. Applicable only for 'dockerimage' component type. */ List getCommand(); /** * Returns the arguments to supply to the command running the dockerimage component. The arguments * are supplied either to the default command provided in the image or to the overridden command. * It is optional, if missing then empty list is returned and args which are defined in the image * will be used. Applicable only for 'dockerimage' component type. */ List getArgs(); /** * Returns volumes which should be mount to component. It is optional and applicable only for * 'dockerimage' component type. */ List getVolumes(); /** * Returns the environment variables list that should be set to docker container. It is optional * and applicable only for 'dockerimage' component type. */ List getEnv(); /** * Returns endpoints configuration. It is optional and applicable only for 'dockerimage' component * type. */ List getEndpoints(); /** Indicates whether namespace secrets should be mount into containers of this component. */ Boolean getAutomountWorkspaceSecrets(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/Devfile.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; import java.util.List; import java.util.Map; /** Defines Devfile. */ public interface Devfile { /** Returns the name of the devfile. Shortcut for {@code getMetadata().getName()}. */ default String getName() { return getMetadata() == null ? null : getMetadata().getName(); } /** Returns Devfile API Version. It is required. */ String getApiVersion(); /** * Returns projects configurations which are related to the devfile, when devfile doesn't contain * projects returns empty list. It is optional, devfile may contain 0 or N project configurations. */ List getProjects(); /** * Returns components configurations which are related to the devfile, when devfile doesn't * contain components returns empty list. It is optional, devfile may contain 0 or N components. */ List getComponents(); /** * Returns commands which are related to the devfile, when devfile doesn't contain commands * returns empty list. It is optional, devfile may contain 0 or N commands. */ List getCommands(); /** Returns devfile attributes. Devfile attributes must not contain null keys or values. */ Map getAttributes(); /** Returns the metadata of the devfile. */ Metadata getMetadata(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/Endpoint.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; import java.util.Map; public interface Endpoint { /** Returns the endpoint name. It is mandatory and unique per endpoints set. */ String getName(); /** Returns the container port that should be used for endpoint. It is mandatory. */ Integer getPort(); /** * Returns endpoints attributes. Emtpy map is returned is endpoint does not have attributes. It is * optional. */ Map getAttributes(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/Entrypoint.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; import java.util.List; import java.util.Map; public interface Entrypoint { /** * Returns the name of the container to apply the entrypoint to. If not specified, the entrypoint * is modified on all matching containers. */ String getContainerName(); /** * Returns the name of the top level object in the referenced object list in which to search for * containers. If not specified, the objects to search through can have any name. */ String getParentName(); /** * Returns the selector for matching top level objects. If not specified, the objects to search * through can have any labels. */ Map getParentSelector(); /** * The command to run in the component instead of the default one provided in the image of the * container. Defaults to to empty list, meaning use whatever is defined in the image. */ List getCommand(); /** * The arguments to supply to the command running the component. The arguments are supplied either * to the default command provided in the image of the container or to the overridden command. * Defaults to empty list, meaning use whatever is defined in the image. */ List getArgs(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/Env.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; public interface Env { /** Returns the environment variable name. It is mandatory. */ String getName(); /** Returns the environment variable value. It is mandatory. */ String getValue(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/Metadata.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; /** Defines the metadata of a devfile. */ public interface Metadata { /** * @return the name of the devfile */ String getName(); /** 'generateName' is used as a base string for generated name, when 'name' is not defined. */ String getGenerateName(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/PreviewUrl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; /** * Preview url is optional parameter of {@link Command}. It is used to construct proper * service+ingress/route and to compose valid path to the application. Typical use-case for * applications that doesn't have UI on root path. Preview url also partially replaces endpoint, * that is not needed to expose the application. */ public interface PreviewUrl { /** * {@code port} specifies where application, that is executed by command, listens. It is used to * create service+ingress/route pair to make application accessible. * * @return applications's listen port */ int getPort(); /** * Specifies path and/or query parameters that should be opened after command execution. * * @return path and/or query params to open or {@code null} when not defined */ String getPath(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/Project.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; public interface Project { /** Returns projects name. It is mandatory and unique per projects set. */ String getName(); /** Returns source where project should be cloned from. It is mandatory. */ Source getSource(); /** * Returns the path relative to the root of the projects to which this project should be cloned * into. This is a unix-style relative path (i.e. uses forward slashes). The path is invalid if it * is absolute or tries to escape the project root through the usage of '..'. If not specified, * defaults to the project name. */ String getClonePath(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/Source.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; public interface Source { /** Returns type of source. It is mandatory. */ String getType(); /** Returns project's source location address. It is mandatory. */ String getLocation(); /** * The name of the of the branch to check out after obtaining the source from the location. The * branch has to already exist in the source otherwise the default branch is used. In case of git, * this is also the name of the remote branch to push to. */ String getBranch(); /** The tag or commit id to reset the checked out branch to. */ String getStartPoint(); /** * The name of the tag to reset the checked out branch to. Note that this is equivalent to * 'startPoint' and provided for convenience. */ String getTag(); /** * The id of the commit to reset the checked out branch to. Note that this is equivalent to * 'startPoint' and provided for convenience. */ String getCommitId(); /** * The directory which is kept by sparse checkout. If this parameter is not null then the only * given directory will be present after clone. */ String getSparseCheckoutDir(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/UserDevfile.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; /** Devfile that is persisted in permanent storage. */ public interface UserDevfile { /** Returns the identifier of this persisted devfile instance. It is mandatory and unique. */ String getId(); /** Returns the name of devfile. It is mandatory. */ String getName(); /** * Returns the namespace also known as the account name. This name can be the name of the * organization or the name of the user to which this devfile belong to. Namespace and name * uniquely identify devfile. */ String getNamespace(); /** Returns description of devfile */ String getDescription(); /** Returns devfile content */ Devfile getDevfile(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/devfile/Volume.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.devfile; public interface Volume { /** * Returns the volume name. If several components mount the same volume then they will reuse the * volume and will be able to access to the same files. It is mandatory. */ String getName(); /** Returns the path where volume should be mount to container. It is mandatory. */ String getContainerPath(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/runtime/BootstrapperStatus.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.runtime; /** * Describes possible bootstrapper statuses. * * @author Max Shaposhnik (mshaposh@redhat.com) */ public enum BootstrapperStatus { /** Bootstrapping is in progress. */ STARTING, /** Bootstrapping done, everything is started ok. */ DONE, /** Bootstrapping failed (when any installer fails or any error occurs). */ FAILED } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/runtime/Machine.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.runtime; import java.util.Map; /** * Runtime information about machine. * * @author Alexander Garagatyi */ public interface Machine { /** Returns machine specific attributes. */ Map getAttributes(); /** * Returns mapping of exposed ports to {@link Server}. * *

Key is a symbolic server name
* Example: * *

   * {
   *     server1 : {
   *         "url" : "http://server-with-machines.com:8080"
   *     }
   * }
   * 
*/ Map getServers(); MachineStatus getStatus(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/runtime/MachineStatus.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.runtime; /** * Status of Machine * * @author gazarenkov */ public enum MachineStatus { /** Machine is starting */ STARTING, /** Machine is up and running */ RUNNING, /** Machine is not running */ STOPPED, /** Machine failed */ FAILED } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/runtime/RuntimeIdentity.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.runtime; import org.eclipse.che.commons.annotation.Nullable; /** * Holds meta information about where workspace is run, and to whom it belongs to (workspace and * user info). * * @author gazarenkov */ public interface RuntimeIdentity { /** The id of workspace to which the runtime belongs to. Must not be null. */ String getWorkspaceId(); /** * The workspace environment name that was used to start runtime. May be null if workspace does * not contain stored environment configuration (it may be generated on fly). */ @Nullable String getEnvName(); /** The id of user who initialized workspace start. Must not be null. */ String getOwnerId(); /** Returns infrastructure namespace where runtime is run. Must not be null. */ String getInfrastructureNamespace(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/runtime/Server.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.runtime; import java.util.Map; /** * Server Runtime exposed by URL * * @author gazarenkov */ public interface Server { /** * @return URL exposing the server */ String getUrl(); /** * @return the status */ ServerStatus getStatus(); /** * Returns attributes of the server with some metadata. You can use static methods on {@link * org.eclipse.che.api.core.model.workspace.config.ServerConfig} to evaluate attributes in this * map easily. */ Map getAttributes(); } ================================================ FILE: core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/runtime/ServerStatus.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.core.model.workspace.runtime; /** * Status of Server * * @author gazarenkov */ public enum ServerStatus { /** Server is up and running */ RUNNING, /** Server is not sunning */ STOPPED, /** unknown */ UNKNOWN } ================================================ FILE: core/che-core-logback/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-logback jar Che Core :: Commons :: Logback ch.qos.logback logback-classic ch.qos.logback logback-core jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-core org.slf4j slf4j-api jakarta.servlet jakarta.servlet-api provided ================================================ FILE: core/che-core-logback/src/main/java/org/eclipse/che/commons/logback/EnvironmentVariablesLogLevelPropagator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.logback; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.LoggerContextListener; import ch.qos.logback.core.spi.ContextAwareBase; import ch.qos.logback.core.spi.LifeCycle; import java.util.Arrays; import org.slf4j.LoggerFactory; /** * Class searches environment variable CHE_LOGGER_CONFIG Value of this variable is expected in such * format: * *

CHE_LOGGER_CONFIG=logger1_name=logger1_level,logger2_name=logger2_level * *

In case if some logger name or logger level are omitted this pair will be silently ignored. */ public class EnvironmentVariablesLogLevelPropagator extends ContextAwareBase implements LoggerContextListener, LifeCycle { private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(EnvironmentVariablesLogLevelPropagator.class); private boolean isStarted; @Override public void start() { String config = System.getenv("CHE_LOGGER_CONFIG"); if (config != null && !config.isEmpty()) { Arrays.stream(config.split(",")).map(String::trim).forEach(this::setLoggerLevel); LOG.info("The following Che Logger Config is applied: {}", config); } isStarted = true; } private void setLoggerLevel(String loggerConfig) { String[] parts = loggerConfig.split("=", 2); if (parts.length < 2) { return; } String loggerName = parts[0]; String levelStr = parts[1]; if (levelStr.isEmpty() || loggerName.isEmpty()) { return; } loggerName = loggerName.trim(); levelStr = levelStr.trim(); LoggerContext lc = (LoggerContext) context; Logger logger = lc.getLogger(loggerName); if ("null".equalsIgnoreCase(levelStr)) { logger.setLevel(null); } else { Level level = Level.toLevel(levelStr, null); if (level != null) { logger.setLevel(level); } } } @Override public void stop() { isStarted = false; } @Override public boolean isStarted() { return isStarted; } @Override public boolean isResetResistant() { return false; } @Override public void onStart(LoggerContext context) {} @Override public void onReset(LoggerContext context) {} @Override public void onStop(LoggerContext context) {} @Override public void onLevelChange(Logger logger, Level level) {} } ================================================ FILE: core/che-core-logback/src/main/java/org/eclipse/che/commons/logback/filter/IdentityIdLoggerFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.logback.filter; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import java.io.IOException; import javax.inject.Singleton; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.slf4j.MDC; /** * A servlet filter that retrieves the identity_id from the servlet context and put it in the MDC * context. Logback can be configured to display this value in each log message when available. MDC * property name is `identity_id`. */ @Singleton public class IdentityIdLoggerFilter implements Filter { private static final String IDENTITY_ID_MDC_KEY = "identity_id"; @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public final void doFilter( ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { Subject subject = EnvironmentContext.getCurrent().getSubject(); if (subject != null && subject.getUserId() != null) { MDC.put(IDENTITY_ID_MDC_KEY, subject.getUserId()); } try { filterChain.doFilter(request, response); } finally { MDC.remove(IDENTITY_ID_MDC_KEY); } } @Override public void destroy() {} } ================================================ FILE: core/che-core-logback/src/main/java/org/eclipse/che/commons/logback/filter/RequestIdLoggerFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.logback.filter; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import javax.inject.Singleton; import org.slf4j.MDC; /** * A servlet filter that retrieves the X-Request-Id header from the http request and put it in the * MDC context. Logback can be configured to display this value in each log message when available. * MDC property name is `req_id`. */ @Singleton public class RequestIdLoggerFilter implements Filter { private static final String REQUEST_ID_HEADER = "X-Request-Id"; private static final String REQUEST_ID_MDC_KEY = "req_id"; @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public final void doFilter( ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { final HttpServletRequest httpRequest = (HttpServletRequest) request; String requestId = httpRequest.getHeader(REQUEST_ID_HEADER); if (requestId != null) { MDC.put(REQUEST_ID_MDC_KEY, requestId); } try { filterChain.doFilter(request, response); } finally { MDC.remove(REQUEST_ID_MDC_KEY); } } @Override public void destroy() {} } ================================================ FILE: core/che-core-metrics-core/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-metrics-core Che Core :: Commons :: Metrics :: Core com.google.guava guava com.google.inject guice io.github.mweirauch micrometer-jvm-extras io.micrometer micrometer-core io.micrometer micrometer-registry-prometheus io.prometheus simpleclient io.prometheus simpleclient_httpserver jakarta.annotation jakarta.annotation-api jakarta.inject jakarta.inject-api org.everrest everrest-guice-servlet org.slf4j slf4j-api jakarta.servlet jakarta.servlet-api provided org.apache.tomcat tomcat-catalina provided ch.qos.logback logback-classic test commons-fileupload commons-fileupload test io.rest-assured rest-assured test jakarta.ws.rs jakarta.ws.rs-api test org.everrest everrest-assured test org.hamcrest hamcrest test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test org.apache.maven.plugins maven-dependency-plugin analyze org.apache.tomcat:tomcat-annotations-api ================================================ FILE: core/che-core-metrics-core/src/main/java/org/eclipse/che/core/metrics/ApiResponseCounter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Metric binding for Che API responses, that are grouped by http status codes. * * @author Mykhailo Kuznietsov */ @Singleton public class ApiResponseCounter implements MeterBinder { private static final Logger LOG = LoggerFactory.getLogger(ApiResponseCounter.class); // package private access for visibility in tests Counter informationalResponseCounter; Counter successResponseCounter; Counter redirectResponseCounter; Counter clientErrorResponseCounter; Counter serverErrorResponseCounter; @Override public void bindTo(MeterRegistry registry) { informationalResponseCounter = Counter.builder("che.server.api.response") .description("Che Server Tomcat informational responses (1xx responses)") .tag("code", "1xx") .tag("area", "http") .register(registry); successResponseCounter = Counter.builder("che.server.api.response") .description("Che Server Tomcat success responses (2xx responses)") .tag("code", "2xx") .tag("area", "http") .register(registry); redirectResponseCounter = Counter.builder("che.server.api.response") .description("Che Server Tomcat redirect responses (3xx responses)") .tag("code", "3xx") .tag("area", "http") .register(registry); clientErrorResponseCounter = Counter.builder("che.server.api.response") .description("Che Server Tomcat client errors (4xx responses)") .tag("code", "4xx") .tag("area", "http") .register(registry); serverErrorResponseCounter = Counter.builder("che.server.api.response") .description("Che Server Tomcat server errors (5xx responses)") .tag("code", "5xx") .tag("area", "http") .register(registry); } public void handleStatus(int status) { status = status / 100; switch (status) { case 1: informationalResponseCounter.increment(); break; case 2: successResponseCounter.increment(); break; case 3: redirectResponseCounter.increment(); break; case 4: clientErrorResponseCounter.increment(); break; case 5: serverErrorResponseCounter.increment(); break; default: // should not happen LOG.warn("Unhandled HTTP status ", status); } } } ================================================ FILE: core/che-core-metrics-core/src/main/java/org/eclipse/che/core/metrics/ApiResponseMetricFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import javax.inject.Inject; import javax.inject.Singleton; /** * Filter for tracking all HTTP requests through {@link ApiResponseCounter} * * @author Mykhailo Kuznietsov */ @Singleton public class ApiResponseMetricFilter implements Filter { private ApiResponseCounter apiResponseCounter; @Inject public void setApiResponseCounter(ApiResponseCounter counter) { this.apiResponseCounter = counter; } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { filterChain.doFilter(request, response); if (response instanceof HttpServletResponse) { apiResponseCounter.handleStatus(((HttpServletResponse) response).getStatus()); } } @Override public void destroy() {} } ================================================ FILE: core/che-core-metrics-core/src/main/java/org/eclipse/che/core/metrics/FileStoresMeterBinder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.binder.MeterBinder; import java.io.IOException; import java.nio.file.FileStore; import java.nio.file.FileSystems; import java.util.function.ToDoubleFunction; import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Bind disk usage metrics for for every {@link java.nio.file.FileStore}. */ @Singleton public class FileStoresMeterBinder implements MeterBinder { private static final Logger LOG = LoggerFactory.getLogger(FileStoresMeterBinder.class); @Override public void bindTo(MeterRegistry registry) { for (FileStore fileStore : FileSystems.getDefault().getFileStores()) { LOG.debug( "Add gauge metric for {}, isReadOnly {}, type {}", fileStore.name(), fileStore.isReadOnly(), fileStore.type()); Iterable tagsWithPath = Tags.concat(Tags.empty(), "path", fileStore.toString()); Gauge.builder("disk.free", fileStore, exceptionToNonWrapper(FileStore::getUnallocatedSpace)) .tags(tagsWithPath) .description("Unallocated space for file storage") .baseUnit("bytes") .strongReference(true) .register(registry); Gauge.builder("disk.total", fileStore, exceptionToNonWrapper(FileStore::getTotalSpace)) .tags(tagsWithPath) .description("Total space for file storage") .baseUnit("bytes") .strongReference(true) .register(registry); Gauge.builder("disk.usable", fileStore, exceptionToNonWrapper(FileStore::getUsableSpace)) .tags(tagsWithPath) .description("Usable space for file storage") .baseUnit("bytes") .strongReference(true) .register(registry); } } static ToDoubleFunction exceptionToNonWrapper( ThrowingToDoubleFunction throwingConsumer) { return i -> { try { return throwingConsumer.applyAsDouble(i); } catch (Exception ex) { return Double.NaN; } }; } @FunctionalInterface interface ThrowingToDoubleFunction { double applyAsDouble(T t) throws IOException; } } ================================================ FILE: core/che-core-metrics-core/src/main/java/org/eclipse/che/core/metrics/MetricsBinder.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.prometheus.PrometheusMeterRegistry; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; /** * Takes all {@link io.micrometer.core.instrument.binder.MeterBinder} from guice container, binded * with {@link com.google.inject.multibindings.Multibinder}, and bind them to {@link * io.micrometer.prometheus.PrometheusMeterRegistry} on PostConstruct. */ @Singleton public class MetricsBinder { @Inject public void bindToRegistry( PrometheusMeterRegistry meterRegistry, Set meterBinderList) { meterBinderList.forEach(e -> e.bindTo(meterRegistry)); } } ================================================ FILE: core/che-core-metrics-core/src/main/java/org/eclipse/che/core/metrics/MetricsModule.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import com.google.common.annotations.Beta; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import io.github.mweirauch.micrometer.jvm.extras.ProcessMemoryMetrics; import io.github.mweirauch.micrometer.jvm.extras.ProcessThreadMetrics; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics; import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics; import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics; import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics; import io.micrometer.core.instrument.binder.logging.LogbackMetrics; import io.micrometer.core.instrument.binder.system.FileDescriptorMetrics; import io.micrometer.core.instrument.binder.system.ProcessorMetrics; import io.micrometer.core.instrument.binder.system.UptimeMetrics; import io.micrometer.prometheus.PrometheusMeterRegistry; import io.prometheus.client.CollectorRegistry; @Beta public class MetricsModule extends AbstractModule { @Override protected void configure() { bind(MetricsServer.class).asEagerSingleton(); bind(MetricsBinder.class).asEagerSingleton(); bind(CollectorRegistry.class).toInstance(CollectorRegistry.defaultRegistry); bind(PrometheusMeterRegistry.class) .toProvider(PrometheusMeterRegistryProvider.class) .asEagerSingleton(); bind(MeterRegistry.class).to(PrometheusMeterRegistry.class); Multibinder meterMultibinder = Multibinder.newSetBinder(binder(), MeterBinder.class); meterMultibinder.addBinding().to(ClassLoaderMetrics.class); meterMultibinder.addBinding().to(JvmMemoryMetrics.class); meterMultibinder.addBinding().to(JvmGcMetrics.class); meterMultibinder.addBinding().to(JvmThreadMetrics.class); meterMultibinder.addBinding().to(LogbackMetrics.class); meterMultibinder.addBinding().to(FileDescriptorMetrics.class); meterMultibinder.addBinding().to(ProcessorMetrics.class); meterMultibinder.addBinding().to(UptimeMetrics.class); meterMultibinder.addBinding().to(FileStoresMeterBinder.class); meterMultibinder.addBinding().to(ApiResponseCounter.class); meterMultibinder.addBinding().to(ProcessMemoryMetrics.class); meterMultibinder.addBinding().to(ProcessThreadMetrics.class); } } ================================================ FILE: core/che-core-metrics-core/src/main/java/org/eclipse/che/core/metrics/MetricsServer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.exporter.HTTPServer; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import java.io.IOException; import java.net.InetSocketAddress; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Bind on 8087 port http endpoint with prometheus metrics. */ @Singleton public class MetricsServer { private static final Logger LOG = LoggerFactory.getLogger(MetricsServer.class); private HTTPServer server; private final CollectorRegistry collectorRegistry; private final Integer metricsPort; @Inject public MetricsServer( CollectorRegistry collectorRegistry, @Named("che.metrics.port") Integer metricsPort) { this.collectorRegistry = collectorRegistry; this.metricsPort = metricsPort; } @PostConstruct public void startServer() throws IOException { this.server = new HTTPServer(new InetSocketAddress(metricsPort), collectorRegistry, true); LOG.info("Metrics server started at port {} successfully ", metricsPort); } @PreDestroy public void stopServer() { if (server != null) { server.stop(); LOG.info("Metrics server suspended at port {} successfully ", metricsPort); } } } ================================================ FILE: core/che-core-metrics-core/src/main/java/org/eclipse/che/core/metrics/MetricsServletModule.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import com.google.inject.multibindings.Multibinder; import com.google.inject.servlet.ServletModule; import io.micrometer.core.instrument.binder.MeterBinder; import jakarta.servlet.ServletContext; import java.lang.reflect.Field; import org.apache.catalina.Manager; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.ApplicationContextFacade; import org.apache.catalina.core.StandardContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ServletModule is made to bind {@link org.eclipse.che.core.metrics.TomcatMetricsProvider} in guice * container. */ public class MetricsServletModule extends ServletModule { private static final Logger LOG = LoggerFactory.getLogger(TomcatMetricsProvider.class); @Override protected void configureServlets() { Multibinder meterMultibinder = Multibinder.newSetBinder(binder(), MeterBinder.class); meterMultibinder.addBinding().toProvider(TomcatMetricsProvider.class); bind(Manager.class).toInstance(getManager(getServletContext())); filter("/*").through(ApiResponseMetricFilter.class); } private Manager getManager(ServletContext servletContext) { try { ApplicationContextFacade acf = (ApplicationContextFacade) servletContext; Field applicationContextFacadeField = ApplicationContextFacade.class.getDeclaredField("context"); applicationContextFacadeField.setAccessible(true); ApplicationContext appContext = (ApplicationContext) applicationContextFacadeField.get(acf); Field applicationContextField = ApplicationContext.class.getDeclaredField("context"); applicationContextField.setAccessible(true); StandardContext stdContext = (StandardContext) applicationContextField.get(appContext); return stdContext.getManager(); } catch (Exception e) { // maybe not in Tomcat? LOG.error("Unable to get Catalina Manager. Cause: {}", e.getMessage(), e); throw new RuntimeException(e.getMessage(), e); } } } ================================================ FILE: core/che-core-metrics-core/src/main/java/org/eclipse/che/core/metrics/PrometheusMeterRegistryProvider.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Metrics; import io.micrometer.prometheus.PrometheusConfig; import io.micrometer.prometheus.PrometheusMeterRegistry; import io.prometheus.client.CollectorRegistry; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; /** * {@link javax.inject.Provider} of {@link io.micrometer.prometheus.PrometheusMeterRegistry} * instances. Used constructor with PrometheusConfig#DEFAULT and Clock.SYSTEM parameters. */ @Singleton public class PrometheusMeterRegistryProvider implements Provider { private final PrometheusMeterRegistry prometheusMeterRegistry; @Inject public PrometheusMeterRegistryProvider(CollectorRegistry registry) { prometheusMeterRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT, registry, Clock.SYSTEM); Metrics.addRegistry(prometheusMeterRegistry); } @Override public PrometheusMeterRegistry get() { return prometheusMeterRegistry; } } ================================================ FILE: core/che-core-metrics-core/src/main/java/org/eclipse/che/core/metrics/TomcatMetricsProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.binder.tomcat.TomcatMetrics; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import org.apache.catalina.Manager; /** * {@link javax.inject.Provider} of {@link * io.micrometer.core.instrument.binder.tomcat.TomcatMetrics} instance. Used constructor with empty * {@link io.micrometer.core.instrument.Tags} */ @Singleton public class TomcatMetricsProvider implements Provider { private final Manager manager; @Inject public TomcatMetricsProvider(Manager manager) { this.manager = manager; } @Override public TomcatMetrics get() { return new TomcatMetrics(manager, Tags.empty()); } } ================================================ FILE: core/che-core-metrics-core/src/test/java/org/eclipse/che/core/metrics/ApiResponseCounterTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import static org.testng.Assert.assertEquals; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * Test for {@link ApiResponseCounter} functionality * * @author Mykhailo Kuznietsov */ public class ApiResponseCounterTest { private ApiResponseCounter apiResponseCounter; private MeterRegistry registry; @BeforeMethod public void setup() { registry = new SimpleMeterRegistry(); apiResponseCounter = new ApiResponseCounter(); apiResponseCounter.bindTo(registry); } @Test(dataProvider = "information") public void shouldCount1xxResponses(int status) { apiResponseCounter.handleStatus(status); assertEquals(apiResponseCounter.informationalResponseCounter.count(), 1.0); assertEquals(apiResponseCounter.successResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.redirectResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.clientErrorResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.serverErrorResponseCounter.count(), 0.0); } @Test(dataProvider = "success") public void shouldCount2xxResponses(int status) { apiResponseCounter.handleStatus(status); assertEquals(apiResponseCounter.informationalResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.successResponseCounter.count(), 1.0); assertEquals(apiResponseCounter.redirectResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.clientErrorResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.serverErrorResponseCounter.count(), 0.0); } @Test(dataProvider = "redirect") public void shouldCount3xxResponses(int status) { apiResponseCounter.handleStatus(status); assertEquals(apiResponseCounter.informationalResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.successResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.redirectResponseCounter.count(), 1.0); assertEquals(apiResponseCounter.clientErrorResponseCounter.count(), .0); assertEquals(apiResponseCounter.serverErrorResponseCounter.count(), 0.0); } @Test(dataProvider = "clientError") public void shouldCount4xxResponses(int status) { apiResponseCounter.handleStatus(status); assertEquals(apiResponseCounter.informationalResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.successResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.redirectResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.clientErrorResponseCounter.count(), 1.0); assertEquals(apiResponseCounter.serverErrorResponseCounter.count(), 0.0); } @Test(dataProvider = "serverError") public void shouldCount5xxResponses(int status) { apiResponseCounter.handleStatus(status); assertEquals(apiResponseCounter.informationalResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.successResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.redirectResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.clientErrorResponseCounter.count(), 0.0); assertEquals(apiResponseCounter.serverErrorResponseCounter.count(), 1.0); } @DataProvider(name = "information") public Object[][] information() { return new Object[][] {{100}, {101}}; } @DataProvider(name = "success") public Object[][] success() { return new Object[][] {{200}, {201}, {202}, {203}, {204}}; } @DataProvider(name = "redirect") public Object[][] redirect() { return new Object[][] {{300}, {301}, {302}, {303}, {304}}; } @DataProvider(name = "clientError") public Object[][] clientError() { return new Object[][] {{400}, {401}, {402}, {403}, {404}, {405}}; } @DataProvider(name = "serverError") public Object[][] serverError() { return new Object[][] {{500}, {501}, {502}, {503}, {504}}; } } ================================================ FILE: core/che-core-metrics-core/src/test/java/org/eclipse/che/core/metrics/ApiResponseMetricFilterTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import static io.restassured.RestAssured.given; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; import static org.everrest.assured.JettyHttpServer.SECURE_PATH; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import org.everrest.assured.EverrestJetty; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Test for {@link ApiResponseMetricFilter} functionality * * @author Mykhailo Kuznietsov */ @Listeners({ MockitoTestNGListener.class, EverrestJetty.class, }) public class ApiResponseMetricFilterTest { @Mock private ApiResponseCounter apiResponseCounter; private ApiResponseMetricFilter filter; @BeforeMethod public void setUp() { filter = new ApiResponseMetricFilter(); filter.setApiResponseCounter(apiResponseCounter); } @Test public void shouldHandleStatusOnHttpRequest() { // requesting a non existing resource, so 404 is expected int status = 404; given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/service") .then() .statusCode(status); verify(apiResponseCounter).handleStatus(eq(status)); } } ================================================ FILE: core/che-core-metrics-core/src/test/java/org/eclipse/che/core/metrics/FileStoresMeterTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.metrics; import static org.testng.Assert.*; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.Collection; import org.testng.annotations.Test; public class FileStoresMeterTest { @Test public void shouldBindFileStores() { MeterRegistry registry = new SimpleMeterRegistry(); new FileStoresMeterBinder().bindTo(registry); Collection df = registry.get("disk.free").gauges(); assertNotNull(df); assertTrue(df.size() > 0); assertEquals(df.size(), registry.get("disk.total").gauges().size()); assertEquals(df.size(), registry.get("disk.usable").gauges().size()); } } ================================================ FILE: core/che-core-metrics-core/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: core/che-core-tracing-core/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-tracing-core Che Core :: Commons :: Tracing :: Core aopalliance aopalliance com.google.guava guava com.google.inject guice io.opentracing opentracing-api io.opentracing opentracing-noop io.opentracing opentracing-util io.opentracing.contrib opentracing-tracerresolver jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-commons-annotations org.slf4j slf4j-api ================================================ FILE: core/che-core-tracing-core/src/main/java/org/eclipse/che/core/tracing/NopTracingModule.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.tracing; import com.google.inject.AbstractModule; import io.opentracing.Tracer; import io.opentracing.noop.NoopTracerFactory; /** Guice module that is used if tracing is not enabled. */ public class NopTracingModule extends AbstractModule { @Override protected void configure() { bind(Tracer.class).toProvider(new TracerProvider(NoopTracerFactory.create())); } } ================================================ FILE: core/che-core-tracing-core/src/main/java/org/eclipse/che/core/tracing/TracerProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.tracing; import com.google.common.annotations.Beta; import io.opentracing.Tracer; import io.opentracing.contrib.tracerresolver.TracerResolver; import io.opentracing.util.GlobalTracer; import javax.inject.Provider; import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Guice @{@link javax.inject.Provider} of @{@link io.opentracing.Tracer} objects. Register Tracer * in @{@link io.opentracing.util.GlobalTracer} for future use by classes that has no access to * container like datasources, etc. */ @Beta @Singleton public class TracerProvider implements Provider { private static final Logger LOG = LoggerFactory.getLogger(TracerProvider.class); private final Tracer tracer; public TracerProvider() { this(TracerResolver.resolveTracer()); } public TracerProvider(Tracer tracer) { this.tracer = tracer; GlobalTracer.registerIfAbsent(tracer); } @Override public Tracer get() { LOG.debug("{} tracer is used ", tracer); return tracer; } } ================================================ FILE: core/che-core-tracing-core/src/main/java/org/eclipse/che/core/tracing/TracingInterceptor.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.tracing; import com.google.common.annotations.Beta; import com.google.inject.Inject; import io.opentracing.Scope; import io.opentracing.Span; import io.opentracing.Tracer; import io.opentracing.tag.Tags; import java.lang.reflect.Method; import java.util.Map; import java.util.WeakHashMap; import java.util.function.Supplier; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.eclipse.che.commons.annotation.Traced; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A Guice interceptor that interprets the {@link Traced @Traced} annotations on methods and creates * tracing spans for the annotated method calls. It also captures the {@link Traced.Tags} and adds * them to the created spans. */ @Beta public class TracingInterceptor implements MethodInterceptor { private static final Logger LOG = LoggerFactory.getLogger(TracingInterceptor.class); private Tracer tracer; private WeakHashMap, WeakHashMap> spanNames = new WeakHashMap<>(); @Inject public void init(Tracer tracer) { this.tracer = tracer; } @Override public Object invoke(MethodInvocation invocation) throws Throwable { String spanName = getSpanName(invocation); Span span = tracer .buildSpan(spanName) .asChildOf(tracer.activeSpan()) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) .start(); try (Scope scope = tracer.scopeManager().activate(span)) { Traced.TagsStack.push(); return invocation.proceed(); } catch (Exception e) { LOG.error(e.getMessage(), e); span.log(e.getMessage()); throw e; } finally { for (Map.Entry> e : Traced.TagsStack.pop().entrySet()) { Object val; try { val = e.getValue().get(); } catch (Exception ex) { if (LOG.isDebugEnabled()) { LOG.debug( "Could not get the value for a tag called {} when tracing {}.", e.getKey(), spanName, ex); } continue; } if (val instanceof String) { span.setTag(e.getKey(), (String) val); } else if (val instanceof Boolean) { span.setTag(e.getKey(), (Boolean) val); } else if (val instanceof Number) { span.setTag(e.getKey(), (Number) val); } } span.finish(); } } private String getSpanName(MethodInvocation invocation) { Class objectType = invocation.getThis().getClass(); Method method = invocation.getMethod(); // we assume that there won't be more than 4 traced methods on a type. If there are, we're // adding a little bit of runtime overhead of enlarging the hashmap's capacity, but in the usual // case we're saving 12 entries in the map (16 is the default capacity). String ret = spanNames.computeIfAbsent(objectType, __ -> new WeakHashMap<>(4)).get(method); if (ret != null) { return ret; } Traced annotation = method.getAnnotation(Traced.class); if (annotation == null) { throw new IllegalStateException( "Misconfigured Guice interception. Tracing interceptor called on method " + method + " that is not annotated with @Traced."); } String name = annotation.name(); if (name.isEmpty()) { name = cleanName(objectType) + "#" + method.getName(); } spanNames.get(objectType).put(method, name); return name; } private static String cleanName(Class type) { String simpleName = type.getSimpleName(); // if there is '$$' in the class name, it is most probably a marker of a Guice or Hibernate // generated class... Let's just not pollute the name with generated subclass name garbage int doubleDollarIdx = simpleName.indexOf("$$"); if (doubleDollarIdx > 0) { simpleName = simpleName.substring(0, doubleDollarIdx); } return simpleName; } } ================================================ FILE: core/che-core-tracing-core/src/main/java/org/eclipse/che/core/tracing/TracingModule.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.tracing; import com.google.common.annotations.Beta; import com.google.inject.AbstractModule; import com.google.inject.matcher.Matchers; import io.opentracing.Tracer; import org.eclipse.che.commons.annotation.Traced; @Beta public class TracingModule extends AbstractModule { @Override protected void configure() { bind(Tracer.class).toProvider(TracerProvider.class); TracingInterceptor traceInterceptor = new TracingInterceptor(); requestInjection(traceInterceptor); bindInterceptor(Matchers.any(), Matchers.annotatedWith(Traced.class), traceInterceptor); } } ================================================ FILE: core/che-core-tracing-metrics/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-tracing-metrics Che Core :: Commons :: Tracing :: Metrics com.google.inject guice io.jaegertracing jaeger-core io.jaegertracing jaeger-micrometer io.opentracing opentracing-api io.opentracing.contrib opentracing-metrics io.opentracing.contrib opentracing-metrics-micrometer jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-tracing-core ================================================ FILE: core/che-core-tracing-metrics/src/main/java/org/eclipse/che/core/tracing/metrics/MeteredTracerProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.tracing.metrics; import io.jaegertracing.Configuration; import io.jaegertracing.micrometer.MicrometerMetricsFactory; import io.opentracing.Tracer; import io.opentracing.contrib.metrics.Metrics; import io.opentracing.contrib.metrics.MetricsReporter; import java.util.Set; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import org.eclipse.che.core.tracing.TracerProvider; /** * Provider of {@link Tracer}, that is integrated with metrics. * *

Tracer instance is created with custom metrics factory, so it would expose internal metrics to * prometheus server * *

Tracer is also wrapped in custom metrics reporter, which would report data about traced spans * and expose them as prometheus metrics */ @Singleton public class MeteredTracerProvider implements Provider { private TracerProvider tracerProvider; @Inject public MeteredTracerProvider(Set metricsReporter) { MicrometerMetricsFactory internalMetricsFactory = new MicrometerMetricsFactory(); Configuration configuration = Configuration.fromEnv(); Tracer tracer = configuration.getTracerBuilder().withMetricsFactory(internalMetricsFactory).build(); this.tracerProvider = new TracerProvider(Metrics.decorate(tracer, metricsReporter)); } @Override public TracerProvider get() { return tracerProvider; } } ================================================ FILE: core/che-core-tracing-metrics/src/main/java/org/eclipse/che/core/tracing/metrics/MicrometerMetricsReporterProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.tracing.metrics; import io.opentracing.contrib.metrics.micrometer.MicrometerMetricsReporter; import io.opentracing.tag.Tags; import javax.inject.Provider; import javax.inject.Singleton; /** * Provider of {@link MicrometerMetricsReporter}, which is responsible for reporting metrics of * traced spans to prometheus server. Here is also specified configuration as for metrics name and * tags. * *

This reporter is configured to report all spans with "span.kind" server, as well as provide * additional label "http.status_code", if such tag is available in the span. * *

When defining tags , if "Default value" will be null, then the spans which don't have such * tag, then will not be reported. * *

Visit https://github.com/opentracing-contrib/java-metrics to find out about how to configure * reporter. */ @Singleton public class MicrometerMetricsReporterProvider implements Provider { private static final String TRACING_METRIC_NAME = "che_server_api_tracing_span"; private final MicrometerMetricsReporter micrometerMetricsReporter; public MicrometerMetricsReporterProvider() { micrometerMetricsReporter = MicrometerMetricsReporter.newMetricsReporter() .withName(TRACING_METRIC_NAME) .withTagLabel(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) .withTagLabel(Tags.HTTP_STATUS.getKey(), "undefined") .build(); } @Override public MicrometerMetricsReporter get() { return micrometerMetricsReporter; } } ================================================ FILE: core/che-core-tracing-metrics/src/main/java/org/eclipse/che/core/tracing/metrics/TracingMetricsModule.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.tracing.metrics; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import io.opentracing.contrib.metrics.MetricsReporter; import org.eclipse.che.core.tracing.TracerProvider; public class TracingMetricsModule extends AbstractModule { @Override protected void configure() { bind(TracerProvider.class).toProvider(MeteredTracerProvider.class); Multibinder metricsReporterBinder = Multibinder.newSetBinder(binder(), MetricsReporter.class); metricsReporterBinder.addBinding().toProvider(MicrometerMetricsReporterProvider.class); } } ================================================ FILE: core/che-core-tracing-web/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-tracing-web Che Core :: Commons :: Tracing :: Web com.google.guava guava com.google.inject guice io.opentracing opentracing-api io.opentracing opentracing-util jakarta.inject jakarta.inject-api org.everrest everrest-guice-servlet jakarta.servlet jakarta.servlet-api provided com.mycila license-maven-plugin **/opentracing/** ================================================ FILE: core/che-core-tracing-web/src/main/java/io/opentracing/contrib/web/servlet/filter/HttpServletRequestExtractAdapter.java ================================================ /* * Copyright 2016-2018 The OpenTracing Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package io.opentracing.contrib.web.servlet.filter; import io.opentracing.propagation.TextMap; import jakarta.servlet.http.HttpServletRequest; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * Tracer extract adapter for {@link HttpServletRequest}. * * @author Pavol Loffay */ public class HttpServletRequestExtractAdapter implements TextMap { private Map> headers; public HttpServletRequestExtractAdapter(HttpServletRequest httpServletRequest) { headers = servletHeadersToMultiMap(httpServletRequest); } @Override public Iterator> iterator() { return new MultivaluedMapFlatIterator<>(headers.entrySet()); } @Override public void put(String key, String value) { throw new UnsupportedOperationException("This class should be used only with Tracer.inject()!"); } protected Map> servletHeadersToMultiMap( HttpServletRequest httpServletRequest) { Map> headersResult = new HashMap<>(); Enumeration headerNamesIt = httpServletRequest.getHeaderNames(); while (headerNamesIt.hasMoreElements()) { String headerName = headerNamesIt.nextElement(); Enumeration valuesIt = httpServletRequest.getHeaders(headerName); List valuesList = new ArrayList<>(1); while (valuesIt.hasMoreElements()) { valuesList.add(valuesIt.nextElement()); } headersResult.put(headerName, valuesList); } return headersResult; } public static final class MultivaluedMapFlatIterator implements Iterator> { private final Iterator>> mapIterator; private Map.Entry> mapEntry; private Iterator listIterator; public MultivaluedMapFlatIterator(Set>> multiValuesEntrySet) { this.mapIterator = multiValuesEntrySet.iterator(); } @Override public boolean hasNext() { if (listIterator != null && listIterator.hasNext()) { return true; } return mapIterator.hasNext(); } @Override public Map.Entry next() { if (mapEntry == null || (!listIterator.hasNext() && mapIterator.hasNext())) { mapEntry = mapIterator.next(); listIterator = mapEntry.getValue().iterator(); } if (listIterator.hasNext()) { return new AbstractMap.SimpleImmutableEntry<>(mapEntry.getKey(), listIterator.next()); } else { return new AbstractMap.SimpleImmutableEntry<>(mapEntry.getKey(), null); } } @Override public void remove() { throw new UnsupportedOperationException(); } } } ================================================ FILE: core/che-core-tracing-web/src/main/java/io/opentracing/contrib/web/servlet/filter/ServletFilterSpanDecorator.java ================================================ /* * Copyright 2016-2018 The OpenTracing Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package io.opentracing.contrib.web.servlet.filter; import io.opentracing.Span; import io.opentracing.tag.Tags; import jakarta.servlet.AsyncEvent; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.PrintWriter; import java.io.StringWriter; import java.util.HashMap; import java.util.Map; /** * SpanDecorator to decorate span at different stages in filter processing (before * filterChain.doFilter(), after and if exception is thrown). * * @author Pavol Loffay */ public interface ServletFilterSpanDecorator { /** * Decorate span before {@link jakarta.servlet.Filter#doFilter(ServletRequest, ServletResponse, * FilterChain)} is called. This is called right after span in created. Span is already present in * request attributes with name {@link TracingFilter#SERVER_SPAN_CONTEXT}. * * @param httpServletRequest request * @param span span to decorate */ void onRequest(HttpServletRequest httpServletRequest, Span span); /** * Decorate span after {@link jakarta.servlet.Filter#doFilter(ServletRequest, ServletResponse, * FilterChain)}. When it is an async request this will be called in {@link * jakarta.servlet.AsyncListener#onComplete(AsyncEvent)}. * * @param httpServletRequest request * @param httpServletResponse response * @param span span to decorate */ void onResponse( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Span span); /** * Decorate span when an exception is thrown during processing in {@link * jakarta.servlet.Filter#doFilter(ServletRequest, ServletResponse, FilterChain)}. This is also * called in {@link jakarta.servlet.AsyncListener#onError(AsyncEvent)}. * * @param httpServletRequest request * @param exception exception * @param span span to decorate */ void onError( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Throwable exception, Span span); /** * Decorate span on asynchronous request timeout. It is called in {@link * jakarta.servlet.AsyncListener#onTimeout(AsyncEvent)}. * * @param httpServletRequest request * @param httpServletResponse response * @param timeout timeout * @param span span to decorate */ void onTimeout( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long timeout, Span span); /** * Adds standard tags to span. {@link Tags#HTTP_URL}, {@link Tags#HTTP_STATUS}, {@link * Tags#HTTP_METHOD} and {@link Tags#COMPONENT}. If an exception during {@link * jakarta.servlet.Filter#doFilter(ServletRequest, ServletResponse, FilterChain)} is thrown tag * {@link Tags#ERROR} is added and {@link Tags#HTTP_STATUS} not because at this point it is not * known. */ ServletFilterSpanDecorator STANDARD_TAGS = new ServletFilterSpanDecorator() { @Override public void onRequest(HttpServletRequest httpServletRequest, Span span) { Tags.COMPONENT.set(span, "java-web-servlet"); Tags.HTTP_METHOD.set(span, httpServletRequest.getMethod()); // without query params Tags.HTTP_URL.set(span, httpServletRequest.getRequestURL().toString()); } @Override public void onResponse( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Span span) { Tags.HTTP_STATUS.set(span, httpServletResponse.getStatus()); } @Override public void onError( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Throwable exception, Span span) { Tags.ERROR.set(span, Boolean.TRUE); span.log(logsForException(exception)); if (httpServletResponse.getStatus() == HttpServletResponse.SC_OK) { // exception is thrown in filter chain, but status code is incorrect Tags.HTTP_STATUS.set(span, 500); } } @Override public void onTimeout( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long timeout, Span span) { Map timeoutLogs = new HashMap<>(2); timeoutLogs.put("event", "timeout"); timeoutLogs.put("timeout", timeout); span.log(timeoutLogs); } private Map logsForException(Throwable throwable) { Map errorLog = new HashMap<>(3); errorLog.put("event", Tags.ERROR.getKey()); String message = throwable.getCause() != null ? throwable.getCause().getMessage() : throwable.getMessage(); if (message != null) { errorLog.put("message", message); } StringWriter sw = new StringWriter(); throwable.printStackTrace(new PrintWriter(sw)); errorLog.put("stack", sw.toString()); return errorLog; } }; } ================================================ FILE: core/che-core-tracing-web/src/main/java/io/opentracing/contrib/web/servlet/filter/TracingFilter.java ================================================ /* * Copyright 2016-2018 The OpenTracing Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package io.opentracing.contrib.web.servlet.filter; import io.opentracing.Scope; import io.opentracing.Span; import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.tag.Tags; import io.opentracing.util.GlobalTracer; import jakarta.servlet.AsyncEvent; import jakarta.servlet.AsyncListener; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Logger; import java.util.regex.Pattern; /** * Tracing servlet filter. * *

Filter can be programmatically added to {@link ServletContext} or initialized via web.xml. * *

Following code examples show possible initialization: * *

{@code
 * TracingFilter filter = new TracingFilter(tracer);
 *  servletContext.addFilter("tracingFilter", filter);
 * }
* * Or include filter in web.xml and: * *
{@code
 * GlobalTracer.register(tracer);
 * servletContext.setAttribute({@link TracingFilter#SPAN_DECORATORS}, listOfDecorators); // optional, if no present ServletFilterSpanDecorator.STANDARD_TAGS is applied
 * }
* * Current server span context is accessible via {@link HttpServletRequest#getAttribute(String)} * with name {@link TracingFilter#SERVER_SPAN_CONTEXT}. * * @author Pavol Loffay */ public class TracingFilter implements Filter { private static final Logger log = Logger.getLogger(TracingFilter.class.getName()); /** Use as a key of {@link ServletContext#setAttribute(String, Object)} to set span decorators */ public static final String SPAN_DECORATORS = TracingFilter.class.getName() + ".spanDecorators"; /** Use as a key of {@link ServletContext#setAttribute(String, Object)} to skip pattern */ public static final String SKIP_PATTERN = TracingFilter.class.getName() + ".skipPattern"; /** * Used as a key of {@link HttpServletRequest#setAttribute(String, Object)} to inject server span * context */ public static final String SERVER_SPAN_CONTEXT = TracingFilter.class.getName() + ".activeSpanContext"; private FilterConfig filterConfig; protected Tracer tracer; private List spanDecorators; private Pattern skipPattern; /** Tracer instance has to be registered with {@link GlobalTracer#register(Tracer)}. */ public TracingFilter() { this(GlobalTracer.get()); } /** * @param tracer */ public TracingFilter(Tracer tracer) { this(tracer, Collections.singletonList(ServletFilterSpanDecorator.STANDARD_TAGS), null); } /** * @param tracer tracer * @param spanDecorators decorators * @param skipPattern null or pattern to exclude certain paths from tracing e.g. "/health" */ public TracingFilter( Tracer tracer, List spanDecorators, Pattern skipPattern) { this.tracer = tracer; this.spanDecorators = new ArrayList<>(spanDecorators); this.spanDecorators.removeAll(Collections.singleton(null)); this.skipPattern = skipPattern; } @Override public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; ServletContext servletContext = filterConfig.getServletContext(); // Check whether the servlet context provides a tracer Object tracerObj = servletContext.getAttribute(Tracer.class.getName()); if (tracerObj instanceof Tracer) { tracer = (Tracer) tracerObj; } else { // Add current tracer to servlet context, so available to webapp servletContext.setAttribute(Tracer.class.getName(), tracer); } // use decorators from context attributes Object contextAttribute = servletContext.getAttribute(SPAN_DECORATORS); if (contextAttribute instanceof Collection) { List decorators = new ArrayList<>(); for (Object decorator : (Collection) contextAttribute) { if (decorator instanceof ServletFilterSpanDecorator) { decorators.add((ServletFilterSpanDecorator) decorator); } else { log.severe(decorator + " is not an instance of " + ServletFilterSpanDecorator.class); } } this.spanDecorators = decorators.size() > 0 ? decorators : this.spanDecorators; } contextAttribute = servletContext.getAttribute(SKIP_PATTERN); if (contextAttribute instanceof Pattern) { skipPattern = (Pattern) contextAttribute; } } @Override public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; if (!isTraced(httpRequest, httpResponse)) { chain.doFilter(httpRequest, httpResponse); return; } /** If request is traced then do not start new span. */ if (servletRequest.getAttribute(SERVER_SPAN_CONTEXT) != null) { chain.doFilter(servletRequest, servletResponse); } else { SpanContext extractedContext = tracer.extract( Format.Builtin.HTTP_HEADERS, new HttpServletRequestExtractAdapter(httpRequest)); final Span span = tracer .buildSpan(httpRequest.getMethod()) .asChildOf(extractedContext) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) .start(); httpRequest.setAttribute(SERVER_SPAN_CONTEXT, span.context()); for (ServletFilterSpanDecorator spanDecorator : spanDecorators) { spanDecorator.onRequest(httpRequest, span); } try (Scope scope = tracer.activateSpan(span)) { chain.doFilter(servletRequest, servletResponse); if (!httpRequest.isAsyncStarted()) { for (ServletFilterSpanDecorator spanDecorator : spanDecorators) { spanDecorator.onResponse(httpRequest, httpResponse, span); } } // catch all exceptions (e.g. RuntimeException, ServletException...) } catch (Throwable ex) { for (ServletFilterSpanDecorator spanDecorator : spanDecorators) { spanDecorator.onError(httpRequest, httpResponse, ex, span); } throw ex; } finally { if (httpRequest.isAsyncStarted()) { // what if async is already finished? This would not be called httpRequest .getAsyncContext() .addListener( new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { HttpServletRequest httpRequest = (HttpServletRequest) event.getSuppliedRequest(); HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse(); for (ServletFilterSpanDecorator spanDecorator : spanDecorators) { spanDecorator.onResponse(httpRequest, httpResponse, span); } span.finish(); } @Override public void onTimeout(AsyncEvent event) throws IOException { HttpServletRequest httpRequest = (HttpServletRequest) event.getSuppliedRequest(); HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse(); for (ServletFilterSpanDecorator spanDecorator : spanDecorators) { spanDecorator.onTimeout( httpRequest, httpResponse, event.getAsyncContext().getTimeout(), span); } } @Override public void onError(AsyncEvent event) throws IOException { HttpServletRequest httpRequest = (HttpServletRequest) event.getSuppliedRequest(); HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse(); for (ServletFilterSpanDecorator spanDecorator : spanDecorators) { spanDecorator.onError( httpRequest, httpResponse, event.getThrowable(), span); } } @Override public void onStartAsync(AsyncEvent event) throws IOException {} }); } else { // If not async, then need to explicitly finish the span associated with the scope. // This is necessary, as we don't know whether this request is being handled // asynchronously until after the scope has already been started. span.finish(); } } } } @Override public void destroy() { this.filterConfig = null; } /** * It checks whether a request should be traced or not. * * @param httpServletRequest request * @param httpServletResponse response * @return whether request should be traced or not */ protected boolean isTraced( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { // skip URLs matching skip pattern // e.g. pattern is defined as '/health|/status' then URL 'http://localhost:5000/context/health' // won't be traced if (skipPattern != null) { int contextLength = httpServletRequest.getContextPath() == null ? 0 : httpServletRequest.getContextPath().length(); String url = httpServletRequest.getRequestURI().substring(contextLength); return !skipPattern.matcher(url).matches(); } return true; } /** * Get context of server span. * * @param servletRequest request * @return server span context */ public static SpanContext serverSpanContext(ServletRequest servletRequest) { return (SpanContext) servletRequest.getAttribute(SERVER_SPAN_CONTEXT); } } ================================================ FILE: core/che-core-tracing-web/src/main/java/io/opentracing/contrib/web/servlet/filter/decorator/ServletFilterHeaderSpanDecorator.java ================================================ package io.opentracing.contrib.web.servlet.filter.decorator; import io.opentracing.Span; import io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator; import io.opentracing.tag.StringTag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.List; /** * ServletFilterHeaderSpanDecorator will decorate the span based on incoming HTTP headers. Incoming * are compared to the list of {@link #allowedHeaders}, if the header is part of the provided list, * they will be added as {@link StringTag}. The tag format will be a concatenation of {@link * #prefix} and {@link HeaderEntry#tag} */ public class ServletFilterHeaderSpanDecorator implements ServletFilterSpanDecorator { private final String prefix; private final List allowedHeaders; /** * Constructor of ServletFilterHeaderSpanDecorator with a default prefix of "http.header." * * @param allowedHeaders list of {@link HeaderEntry} to extract from the incoming request */ public ServletFilterHeaderSpanDecorator(List allowedHeaders) { this(allowedHeaders, "http.header."); } /** * Constructor of ServletFilterHeaderSpanDecorator * * @param allowedHeaders list of {@link HeaderEntry} to extract from the incoming request * @param prefix the prefix to prepend on each @{@link StringTag}. Can be null is not prefix is * desired */ public ServletFilterHeaderSpanDecorator(List allowedHeaders, String prefix) { this.allowedHeaders = new ArrayList<>(allowedHeaders); this.prefix = (prefix != null && !prefix.isEmpty()) ? prefix : null; } @Override public void onRequest(HttpServletRequest httpServletRequest, Span span) { for (HeaderEntry headerEntry : allowedHeaders) { String headerValue = httpServletRequest.getHeader(headerEntry.getHeader()); if (headerValue != null && !headerValue.isEmpty()) { buildTag(headerEntry.getTag()).set(span, headerValue); } } } @Override public void onResponse( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Span span) {} @Override public void onError( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Throwable exception, Span span) {} @Override public void onTimeout( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long timeout, Span span) {} private StringTag buildTag(String tag) { if (prefix == null) { return new StringTag(tag); } return new StringTag(prefix + tag); } public String getPrefix() { return this.prefix; } public List getAllowedHeaders() { return this.allowedHeaders; } /** * HeaderEntry is used to configure {@link ServletFilterHeaderSpanDecorator} {@link #header} is * used to check if the header exists using {@link HttpServletRequest#getHeader(String)} {@link * #tag} will be used as a {@link StringTag} if {@link #header} is found on the incoming request */ public static class HeaderEntry { private final String header; private final String tag; /** * @param header Header on the {@link HttpServletRequest} * @param tag Tag to be used if {@link #header} is found */ public HeaderEntry(String header, String tag) { this.header = header; this.tag = tag; } public String getHeader() { return this.header; } public String getTag() { return this.tag; } } } ================================================ FILE: core/che-core-tracing-web/src/main/java/org/eclipse/che/core/tracing/web/TracingFilterProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.tracing.web; import com.google.common.annotations.Beta; import io.opentracing.Tracer; import io.opentracing.contrib.web.servlet.filter.TracingFilter; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; /** * Guice @{@link javax.inject.Provider} of @{@link * io.opentracing.contrib.web.servlet.filter.TracingFilter} objects */ @Beta @Singleton public class TracingFilterProvider implements Provider { private final TracingFilter filter; @Inject public TracingFilterProvider(Tracer tracer) { filter = new TracingFilter(tracer); } @Override public TracingFilter get() { return filter; } } ================================================ FILE: core/che-core-tracing-web/src/main/java/org/eclipse/che/core/tracing/web/TracingWebModule.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.core.tracing.web; import com.google.common.annotations.Beta; import com.google.inject.servlet.ServletModule; import io.opentracing.contrib.web.servlet.filter.TracingFilter; import javax.inject.Singleton; @Beta public class TracingWebModule extends ServletModule { @Override protected void configureServlets() { // tracing bind(TracingFilter.class).toProvider(TracingFilterProvider.class).in(Singleton.class); filter("/*").through(TracingFilter.class); } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-typescript-dto-maven-plugin maven-plugin Che Core :: Commons :: API :: TypeScript DTO maven plugin true com.google.code.gson gson com.google.guava guava org.antlr ST4 org.apache.maven maven-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-commons-lang org.reflections reflections org.apache.maven maven-artifact provided org.apache.maven maven-model provided org.apache.maven maven-plugin-api provided org.apache.maven.plugin-tools maven-plugin-annotations provided org.eclipse.sisu org.eclipse.sisu.plexus provided jsr250-api javax.annotation org.javassist javassist runtime junit junit test org.apache.maven maven-compat test org.apache.maven.plugin-testing maven-plugin-testing-harness test org.codehaus.plexus plexus-utils test org.codehaus.plexus plexus-xml test org.eclipse.che.core che-core-api-core test org.mockito mockito-core test org.slf4j slf4j-api test org.testng testng test org.codehaus.mojo build-helper-maven-plugin add-it-test-source process-resources add-test-source src/it/java add-it-test-resources process-resources add-test-resource src/it/resources org.apache.maven.plugins maven-surefire-plugin **/*ITest.java org.apache.maven.plugins maven-plugin-plugin mojo-descriptor descriptor true integration-tests !skipIntegrationTests org.apache.maven.plugins maven-surefire-plugin integration-test integration-test test 0 ${project.build.directory} none **/*ITest.java **/*ITest.java ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/it/java/org/eclipse/che/plugin/typescript/dto/TypeScriptDTOGeneratorMojoTST.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; import java.util.concurrent.TimeUnit; import org.eclipse.che.api.core.util.SystemInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; /** * Integration test of TypeScriptDTOGeneratorMojo * It uses docker to launch TypeScript compiler and then launch JavaScript tests to ensure generator has worked correctly * @author Florent Benoit */ public class TypeScriptDTOGeneratorMojoTST { /** * Logger. */ private static final Logger LOG = LoggerFactory.getLogger(TypeScriptDTOGeneratorMojoTST.class); /** * DTO Generated file */ private static final String GENERATED_DTO_NAME = "my-typescript-test-module.ts"; private static final String GENERATED_DTO_DTS_NAME = "my-typescript-test-module.d.ts"; /** * DTO new name */ private static final String DTO_FILENAME = "dto.ts"; private static final String DTO_DTS_FILENAME = "dtoD.d.ts"; /** * DTO test name */ private static final String DTO_SPEC_FILENAME = "dto.spec.ts"; /** * Target folder of maven. */ private Path buildDirectory; /** * Path to the package.json file used to setup typescript compiler */ private Path dtoSpecJsonPath; /** * Path to the package.json file used to setup typescript compiler */ private Path packageJsonPath; /** * Root directory for our tests */ private Path rootPath; /** * Linux uid. */ private String linuxUID; /** * Linux gid. */ private String linuxGID; /** * Init folders */ @BeforeClass public void init() throws URISyntaxException, IOException, InterruptedException { // setup packages this.packageJsonPath = new File(TypeScriptDTOGeneratorMojoTST.class.getClassLoader().getResource("package.json").toURI()).toPath(); this.rootPath = this.packageJsonPath.getParent(); // target folder String buildDirectoryProperty = System.getProperty("buildDirectory"); if (buildDirectoryProperty != null) { buildDirectory = new File(buildDirectoryProperty).toPath(); } else { buildDirectory = packageJsonPath.getParent().getParent(); } LOG.info("Using building directory {0}", buildDirectory); } /** * Generates a docker exec command used to launch node commands. Uses podman if present on the * system. * * @return list of command parameters */ protected List getDockerExec() throws IOException, InterruptedException { // setup command line List command = new ArrayList<>(); if (hasPodman()) { command.add("podman"); } else { command.add("docker"); } command.add("run"); command.add("--rm"); command.add("-v"); command.add(rootPath.toString() + ":/usr/src/app"); command.add("-w"); command.add("/usr/src/app"); command.add("node:6"); command.add("/bin/sh"); command.add("-c"); return command; } /** * Get UID of current user (used on Linux) */ protected String getUid() throws IOException, InterruptedException { if (this.linuxUID == null) { // grab user id ProcessBuilder uidProcessBuilder = new ProcessBuilder("id", "-u"); Process processId = uidProcessBuilder.start(); int resultId = processId.waitFor(); String uid = ""; try (BufferedReader outReader = new BufferedReader(new InputStreamReader(processId.getInputStream()))) { uid = String.join(System.lineSeparator(), outReader.lines().collect(toList())); } catch (Exception error) { throw new IllegalStateException("Unable to get uid" + uid); } if (resultId != 0) { throw new IllegalStateException("Unable to get uid" + uid); } try { Integer.valueOf(uid); } catch (NumberFormatException e) { throw new IllegalStateException("The uid is not a number" + uid); } this.linuxUID = uid; } return this.linuxUID; } /** * Get GID of current user (used on Linux) */ protected String getGid() throws IOException, InterruptedException { if (this.linuxGID == null) { ProcessBuilder gidProcessBuilder = new ProcessBuilder("id", "-g"); Process processGid = gidProcessBuilder.start(); int resultGid = processGid.waitFor(); String gid = ""; try (BufferedReader outReader = new BufferedReader(new InputStreamReader(processGid.getInputStream()))) { gid = String.join(System.lineSeparator(), outReader.lines().collect(toList())); } catch (Exception error) { throw new IllegalStateException("Unable to get gid" + gid); } if (resultGid != 0) { throw new IllegalStateException("Unable to get gid" + gid); } try { Integer.valueOf(gid); } catch (NumberFormatException e) { throw new IllegalStateException("The uid is not a number" + gid); } this.linuxGID = gid; } return this.linuxGID; } /** * Setup typescript compiler by downloading the dependencies * @throws IOException if unable to start process * @throws InterruptedException if unable to wait the end of the process */ @Test(groups = {"tools"}) protected void installTypeScriptCompiler() throws IOException, InterruptedException { // setup command line List command = getDockerExec(); // avoid root permissions in generated files if (SystemInfo.isLinux()) { command.add(wrapLinuxCommand("npm install")); } else { command.add("npm install"); } // setup typescript compiler ProcessBuilder processBuilder = new ProcessBuilder().command(command).directory(rootPath.toFile()).redirectErrorStream(true).inheritIO(); Process process = processBuilder.start(); LOG.info("Installing TypeScript compiler in {0}", rootPath); int resultProcess = process.waitFor(); if (resultProcess != 0) { throw new IllegalStateException("Install of TypeScript has failed"); } LOG.info("TypeScript compiler installed."); } /** * Wrap the given command into a command with chown. Also add group/user that match host environment if not exists * @param command the command to wrap * @return an updated command with chown applied on it */ protected String wrapLinuxCommand(String command) throws IOException, InterruptedException { if (hasPodman()) { LOG.debug("using podman, don't need to wrap anything"); return command; } String setGroup = "export GROUP_NAME=`(getent group " + getGid() + " || (groupadd -g " + getGid() + " user && echo user:x:" + getGid() + ")) | cut -d: -f1`"; String setUser = "export USER_NAME=`(getent passwd " + getUid() + " || (useradd -u " + getUid() + " -g ${GROUP_NAME} user && echo user:x:" + getGid() + ")) | cut -d: -f1`"; String chownCommand = "chown --silent -R ${USER_NAME}.${GROUP_NAME} /usr/src/app || true"; return setGroup + " && " + setUser + " && " + chownCommand + " && " + command + " && " + chownCommand; } /** * Starts tests by compiling first generated DTO from maven plugin * @throws IOException if unable to start process * @throws InterruptedException if unable to wait the end of the process */ @Test(dependsOnGroups = "tools") public void compileDTOAndLaunchTests() throws IOException, InterruptedException { // search DTO Path p = this.buildDirectory; final int maxDepth = 10; Stream matches = java.nio.file.Files.find( p, maxDepth, (path, basicFileAttributes) -> path.getFileName().toString().equals(GENERATED_DTO_NAME)); // take first Optional optionalPath = matches.findFirst(); if (!optionalPath.isPresent()) { throw new IllegalStateException("Unable to find generated DTO file named '" + GENERATED_DTO_NAME + "'. Check it has been generated first"); } Path generatedDtoPath = optionalPath.get(); //copy it in test resources folder where package.json is java.nio.file.Files.copy(generatedDtoPath, this.rootPath.resolve(DTO_FILENAME), StandardCopyOption.REPLACE_EXISTING); matches = java.nio.file.Files.find( p, maxDepth, (path, basicFileAttributes) -> path.getFileName().toString().equals(GENERATED_DTO_DTS_NAME)); // take first optionalPath = matches.findFirst(); if (!optionalPath.isPresent()) { throw new IllegalStateException("Unable to find generated DTO file named '" + GENERATED_DTO_DTS_NAME + "'. Check it has been generated first"); } generatedDtoPath = optionalPath.get(); //copy it in test resources folder where package.json is java.nio.file.Files.copy(generatedDtoPath, this.rootPath.resolve(DTO_DTS_FILENAME), StandardCopyOption.REPLACE_EXISTING); // setup command line List command = getDockerExec(); // avoid root permissions in generated files if (SystemInfo.isLinux()) { command.add(wrapLinuxCommand("npm test")); } else { command.add("npm test"); } // setup typescript compiler ProcessBuilder processBuilder = new ProcessBuilder().command(command).directory(rootPath.toFile()).redirectErrorStream(true).inheritIO(); Process process = processBuilder.start(); LOG.info("Starting TypeScript tests..."); int resultProcess = process.waitFor(); if (resultProcess != 0) { throw new IllegalStateException("DTO has failed to compile"); } LOG.info("TypeScript tests OK"); } private boolean hasPodman() throws InterruptedException, IOException { if (SystemInfo.isLinux()) { ProcessBuilder podmanProcessBuilder = new ProcessBuilder("which", "podman"); Process podmanProcess = podmanProcessBuilder.start(); if (podmanProcess.waitFor(1, TimeUnit.SECONDS)) { return podmanProcess.exitValue() == 0; } else { return false; } } else { return false; } } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/it/resources/dto.spec.ts ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ import {org} from './dto'; import {che} from './dtoD' let expect = require('chai').expect; class DTOBuilder { static MY_CUSTOM_NAME : string = "myCustomName"; static MY_CUSTOM_STATUS : string = "myCustomStatus"; static MY_CUSTOM_MAP_ENTRY_NAME : string = "myEntry"; static MY_OTHER_NAME : string = "myOtherName"; static MY_SIMPLE_ID : number = 2503; static MY_SIMPLE_BOOLEAN : boolean = true; static MY_SIMPLE_DOUBLE : number = 19.79; static MY_SIMPLE_FLOAT : number = 3.14; static buildSimpleDto() : org.eclipse.che.plugin.typescript.dto.MySimpleDTO { let mySimpleDTO : org.eclipse.che.plugin.typescript.dto.MySimpleDTO = new org.eclipse.che.plugin.typescript.dto.MySimpleDTOImpl(); mySimpleDTO.withId(DTOBuilder.MY_SIMPLE_ID).withBoolean(DTOBuilder.MY_SIMPLE_BOOLEAN).withDouble(DTOBuilder.MY_SIMPLE_DOUBLE).withFloat(DTOBuilder.MY_SIMPLE_FLOAT); return mySimpleDTO; } static buildCustomDto() : org.eclipse.che.plugin.typescript.dto.MyCustomDTO { let myCustomDTO : org.eclipse.che.plugin.typescript.dto.MyCustomDTO = new org.eclipse.che.plugin.typescript.dto.MyCustomDTOImpl(); myCustomDTO.withName(DTOBuilder.MY_CUSTOM_NAME).withStatus(DTOBuilder.MY_CUSTOM_STATUS).withConfig(DTOBuilder.buildConfigDTO()); myCustomDTO.getCustomMap().set(DTOBuilder.MY_CUSTOM_MAP_ENTRY_NAME, DTOBuilder.buildConfigDTO()); return myCustomDTO; } static buildConfigDTO() : org.eclipse.che.plugin.typescript.dto.MyOtherDTO { let configDTO : org.eclipse.che.plugin.typescript.dto.MyOtherDTO = new org.eclipse.che.plugin.typescript.dto.MyOtherDTOImpl(); configDTO.withName(DTOBuilder.MY_OTHER_NAME); return configDTO; } } describe("DTO serialization tests", () => { it("check simple DTO implementation", () => { let myCustomDTO : org.eclipse.che.plugin.typescript.dto.MySimpleDTO = DTOBuilder.buildSimpleDto(); expect(myCustomDTO.getId()).to.eql(DTOBuilder.MY_SIMPLE_ID); expect(myCustomDTO.getBoolean()).to.eql(DTOBuilder.MY_SIMPLE_BOOLEAN); expect(myCustomDTO.getDouble()).to.eql(DTOBuilder.MY_SIMPLE_DOUBLE); expect(myCustomDTO.getFloat()).to.eql(DTOBuilder.MY_SIMPLE_FLOAT); }); it("check build DTO implementation", () => { let myCustomDTO : org.eclipse.che.plugin.typescript.dto.MyCustomDTO = DTOBuilder.buildCustomDto(); expect(myCustomDTO.getName()).to.eql(DTOBuilder.MY_CUSTOM_NAME); expect(myCustomDTO.getStatus()).to.eql(DTOBuilder.MY_CUSTOM_STATUS); expect(myCustomDTO.getConfig()).to.exist; expect(myCustomDTO.getConfig().getName()).to.eql(DTOBuilder.MY_OTHER_NAME); expect(myCustomDTO.getCustomMap().get(DTOBuilder.MY_CUSTOM_MAP_ENTRY_NAME)).to.exist; expect(myCustomDTO.getCustomMap().get(DTOBuilder.MY_CUSTOM_MAP_ENTRY_NAME).getName()).to.eql(DTOBuilder.MY_OTHER_NAME); }); it("check build DTO implementation", () => { let myCustomDTO : org.eclipse.che.plugin.typescript.dto.MyCustomDTO = DTOBuilder.buildCustomDto(); expect(myCustomDTO.getName()).to.eql(DTOBuilder.MY_CUSTOM_NAME); expect(myCustomDTO.getStatus()).to.eql(DTOBuilder.MY_CUSTOM_STATUS); expect(myCustomDTO.getConfig()).to.exist; expect(myCustomDTO.getConfig().getName()).to.eql(DTOBuilder.MY_OTHER_NAME); expect(myCustomDTO.getCustomMap().get(DTOBuilder.MY_CUSTOM_MAP_ENTRY_NAME)).to.exist; expect(myCustomDTO.getCustomMap().get(DTOBuilder.MY_CUSTOM_MAP_ENTRY_NAME).getName()).to.eql(DTOBuilder.MY_OTHER_NAME); }); it("check build DTO implementation from source", () => { let myCustomDTO : org.eclipse.che.plugin.typescript.dto.MyCustomDTO = DTOBuilder.buildCustomDto(); // build it from generated output let myCustomDTOFromSource : org.eclipse.che.plugin.typescript.dto.MyCustomDTO = new org.eclipse.che.plugin.typescript.dto.MyCustomDTOImpl(myCustomDTO.toJson()); expect(myCustomDTO.getName()).to.eql(myCustomDTOFromSource.getName()); expect(myCustomDTOFromSource.getConfig()).to.exist; expect(myCustomDTO.getConfig().getName()).to.eql(myCustomDTOFromSource.getConfig().getName()); expect(myCustomDTOFromSource.getCustomMap().get(DTOBuilder.MY_CUSTOM_MAP_ENTRY_NAME)).to.exist; expect(myCustomDTO.getCustomMap().get(DTOBuilder.MY_CUSTOM_MAP_ENTRY_NAME).getName()).to.eql(myCustomDTOFromSource.getCustomMap().get(DTOBuilder.MY_CUSTOM_MAP_ENTRY_NAME).getName()); expect(myCustomDTO.toJson()).to.eql(myCustomDTOFromSource.toJson()); }); it("check d.ts types", () => { const customDto: che.plugin.typescript.MyCustom = { internal: { internalValue: "foo" }, status: "SHUTDOWN", customMap: {"bar": { name: "foo"}}, arguments: [{}, {},] }; expect(customDto.internal).to.eql({ internalValue: "foo" } as che.plugin.typescript.internal.Internal); expect(customDto.customMap).to.have.property("bar"); expect(customDto.customMap["bar"].name).to.eql("foo"); }); it("check Serializable type", () => { const customDto: che.plugin.typescript.MyDtoWithSerializable = { }; }); }); ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/it/resources/package.json ================================================ { "name": "che-typescript-maven-generator-integration-test", "version": "1.0.0", "description": "TypeScript integration test", "author": "Florent Benoit", "license": "EPL-1.0", "scripts": { "pretest": "./node_modules/.bin/tsc --target ES6 --outDir lib --module commonjs *.ts", "test": "./node_modules/.bin/mocha lib/*.spec.js" }, "devDependencies": { "chai": "3.5.0", "mocha": "3.0.2", "typescript": "2.0.3" }, "dependencies": { "@types/mocha": "2.2.32", "@types/node": "6.0.41" } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/main/java/org/eclipse/che/plugin/typescript/dto/DTOHelper.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.dto.shared.DelegateTo; /** * Helper class * * @author Florent Benoit */ public class DTOHelper { /** Utility class. */ private DTOHelper() {} /** Check is specified method is DTO getter. */ public static boolean isDtoGetter(Method method) { if (method.isAnnotationPresent(DelegateTo.class)) { return false; } if (method.getParameterTypes().length > 0) { return false; } String methodName = method.getName(); return methodName.startsWith("get") || (methodName.startsWith("is") && ((method.getReturnType() == Boolean.class || method.getReturnType() == boolean.class))); } /** Check is specified method is DTO setter. */ public static boolean isDtoSetter(Method method) { if (method.isAnnotationPresent(DelegateTo.class)) { return false; } String methodName = method.getName(); return methodName.startsWith("set") && method.getParameterTypes().length == 1; } /** Check is specified method is DTO with. */ public static boolean isDtoWith(Method method) { if (method.isAnnotationPresent(DelegateTo.class)) { return false; } String methodName = method.getName(); return methodName.startsWith("with") && method.getParameterTypes().length == 1; } /** Compute field name from the stringified string type */ public static String getFieldName(String type) { char[] c = type.toCharArray(); c[0] = Character.toLowerCase(c[0]); return new String(c); } /** Compute argument name from the stringified string type */ public static String getArgumentName(String type) { String val = getFieldName(type); // replace reserved keyword if ("arguments".equals(val)) { val = "argumentsObj"; } return val; } /** Extract field name from the getter method */ public static Pair getGetterFieldName(Method method) { String methodName = method.getName(); if (methodName.startsWith("get")) { String name = methodName.substring(3); return Pair.of(getFieldName(name), getArgumentName(name)); } else if (methodName.startsWith("is")) { String name = methodName.substring(2); return Pair.of(getFieldName(name), getArgumentName(name)); } throw new IllegalArgumentException("Invalid getter method" + method.getName()); } /** Extract field name from the setter method */ public static Pair getSetterFieldName(Method method) { String methodName = method.getName(); if (methodName.startsWith("set")) { String name = methodName.substring(3); return Pair.of(getFieldName(name), getArgumentName(name)); } throw new IllegalArgumentException("Invalid setter method" + method.getName()); } /** Extract field name and argument name from the with method */ public static Pair getWithFieldName(Method method) { String methodName = method.getName(); if (methodName.startsWith("with")) { String name = methodName.substring(4); return Pair.of(getFieldName(name), getArgumentName(name)); } throw new IllegalArgumentException("Invalid with method" + method.getName()); } /** Convert Java type to TypeScript type */ public static String convertType(Type type) { if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; Type rawType = parameterizedType.getRawType(); return convertParametrizedType(type, parameterizedType, rawType); } else if (String.class.equals(type) || (type instanceof Class && ((Class) type).isEnum())) { // Maybe find a better enum type for typescript return "string"; } else if (Integer.class.equals(type) || Integer.TYPE.equals(type) || Long.class.equals(type) || Long.TYPE.equals(type) || Double.class.equals(type) || Double.TYPE.equals(type) || Float.TYPE.equals(type)) { return "number"; } else if (Boolean.class.equals(type) || Boolean.TYPE.equals(type)) { return "boolean"; } else if (Serializable.class.equals(type)) { return "string | number | boolean"; } return type.getTypeName(); } /** Handle convert of a parametrized Java type to TypeScript type */ public static String convertParametrizedType( Type type, ParameterizedType parameterizedType, Type rawType) { if (List.class.equals(rawType)) { return "Array<" + convertType(parameterizedType.getActualTypeArguments()[0]) + ">"; } else if (Map.class.equals(rawType)) { return "Map<" + convertType(parameterizedType.getActualTypeArguments()[0]) + "," + convertType(parameterizedType.getActualTypeArguments()[1]) + ">"; } else { throw new IllegalArgumentException("Invalid type" + type); } } /** * Same as {@link #convertParametrizedType(Type, ParameterizedType, Type)} but use [] instead if * 'Array[' for arrays */ public static String convertParametrizedTypeDTS( Type type, ParameterizedType parameterizedType, Type rawType, Type containerType) { if (List.class.equals(rawType)) { return convertTypeForDTS(containerType, parameterizedType.getActualTypeArguments()[0]) + "[]"; } else if (Map.class.equals(rawType)) { return "Map<" + convertTypeForDTS(containerType, parameterizedType.getActualTypeArguments()[0]) + "," + convertTypeForDTS(containerType, parameterizedType.getActualTypeArguments()[1]) + ">"; } else { throw new IllegalArgumentException("Invalid type" + type); } } /** * Convert Java type to TypeScript type for .d.ts Same as {@link #convertType(Type)} but in * addition check if dto and its container dto in same package than skip adding namespace */ public static String convertTypeForDTS(Type containerType, Type type) { if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; Type rawType = parameterizedType.getRawType(); return convertParametrizedTypeDTS(type, parameterizedType, rawType, containerType); } else if (String.class.equals(type)) { // Maybe find a better enum type for typescript return "string"; } else if ((type instanceof Class && ((Class) type).isEnum())) { Object[] constants = ((Class) type).getEnumConstants(); return Arrays.stream(constants) .map(o -> "\'" + o.toString() + "\'") .collect(Collectors.joining(" | ")); } else if (Integer.class.equals(type) || Integer.TYPE.equals(type) || Long.class.equals(type) || Long.TYPE.equals(type) || Double.class.equals(type) || Double.TYPE.equals(type) || Float.TYPE.equals(type)) { return "number"; } else if (Boolean.class.equals(type) || Boolean.TYPE.equals(type)) { return "boolean"; } else if (Serializable.class.equals(type)) { return "string | number | boolean"; } String declarationPackage = convertToDTSPackageName((Class) containerType); String typePackage = convertToDTSPackageName((Class) type); if (declarationPackage.equals(typePackage)) { return convertToDTSName((Class) type); } return typePackage + "." + convertToDTSName((Class) type); } /** Remove 'Dto' suffix from class name */ public static String convertToDTSName(Class type) { String name = type.getSimpleName(); if (name.toLowerCase().endsWith("dto")) { name = name.substring(0, name.length() - 3); } return name; } /** * Convert Java package to TypeScript namespace. This method deletes "org.eclipse.", ".api", * ".dto", ".shared" segments from dto package name * * @param dto * @return the TS namespace name */ public static String convertToDTSPackageName(Class dto) { return removeSubStrings(dto.getPackage().getName(), "org.eclipse.", ".api", ".dto", ".shared"); } /** Remove all substrings from original string */ private static String removeSubStrings(String str, String... subStrings) { StringBuilder builder = new StringBuilder(str); for (String subString : subStrings) { int index = builder.indexOf(subString); if (index != -1) { builder.delete(index, index + subString.length()); } } return builder.toString(); } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/main/java/org/eclipse/che/plugin/typescript/dto/TypeScriptDTOGeneratorMojo.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; import java.io.File; import java.io.IOException; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; /** * Mojo for generating TypeScript DTO interface + implementation for handling JSON data. * * @author Florent Benoit */ @Mojo( name = "build", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, requiresDependencyCollection = ResolutionScope.RUNTIME) public class TypeScriptDTOGeneratorMojo extends AbstractMojo { /** Project providing artifact id, version and dependencies. */ @Parameter(defaultValue = "${project}", readonly = true) private MavenProject project; /** build directory used to write the intermediate bom file. */ @Parameter(defaultValue = "${project.build.directory}") private File targetDirectory; /** should build new '.d.ts' DTO on old 'ts' DTO interfaces */ @Parameter(defaultValue = "false", property = "dts") private boolean dts; @Component private MavenProjectHelper projectHelper; /** Path to the generated typescript file */ private File typescriptFile; /** Use of classpath instead of classloader */ private boolean useClassPath; @Override public void execute() throws MojoExecutionException { if (dts) { getLog().info("Generating TypeScript DTO d.ts"); generateDTs(); } else { getLog().info("Generating TypeScript DTO"); generateTs(); } } private void generateDTs() throws MojoExecutionException { TypeScriptDtoDTSGenerator typeScriptDtoGenerator = new TypeScriptDtoDTSGenerator(); typeScriptDtoGenerator.setUseClassPath(useClassPath); // define output path for the file to write with typescript definition String output = typeScriptDtoGenerator.execute(); this.typescriptFile = new File(targetDirectory, project.getArtifactId() + ".d.ts"); File parentDir = this.typescriptFile.getParentFile(); if (!parentDir.exists() && !parentDir.mkdirs()) { throw new MojoExecutionException( "Unable to create a directory for writing DTO typescript file '" + parentDir + "'."); } try (Writer fileWriter = Files.newBufferedWriter(this.typescriptFile.toPath(), StandardCharsets.UTF_8)) { fileWriter.write(output); } catch (IOException e) { throw new MojoExecutionException("Cannot write DTO typescript file"); } // attach this typescript file as maven artifact projectHelper.attachArtifact(project, "ts", typescriptFile); } private void generateTs() throws MojoExecutionException { TypeScriptDtoGenerator typeScriptDtoGenerator = new TypeScriptDtoGenerator(); typeScriptDtoGenerator.setUseClassPath(useClassPath); // define output path for the file to write with typescript definition String output = typeScriptDtoGenerator.execute(); this.typescriptFile = new File(targetDirectory, project.getArtifactId() + ".ts"); File parentDir = this.typescriptFile.getParentFile(); if (!parentDir.exists() && !parentDir.mkdirs()) { throw new MojoExecutionException( "Unable to create a directory for writing DTO typescript file '" + parentDir + "'."); } try (Writer fileWriter = Files.newBufferedWriter(this.typescriptFile.toPath(), StandardCharsets.UTF_8)) { fileWriter.write(output); } catch (IOException e) { throw new MojoExecutionException("Cannot write DTO typescript file"); } // attach this typescript file as maven artifact projectHelper.attachArtifact(project, "ts", typescriptFile); } /** * Gets the TypeScript generated file * * @return the generated file TypeScript link */ public File getTypescriptFile() { return typescriptFile; } /** * Allow to configure generator to use classpath instead of classloader * * @param useClassPath true if want to use classpath loading */ public void setUseClassPath(boolean useClassPath) { this.useClassPath = useClassPath; } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/main/java/org/eclipse/che/plugin/typescript/dto/TypeScriptDtoDTSGenerator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.io.Resources; import java.io.IOException; import java.net.URL; import java.util.HashMap; import java.util.Map; import org.eclipse.che.plugin.typescript.dto.model.DtoModel; import org.eclipse.che.plugin.typescript.dto.model.DtoNamespace; import org.stringtemplate.v4.ST; /** Write all DTOs found in classpath into a specific '.d.ts' file */ public class TypeScriptDtoDTSGenerator extends TypeScriptDtoGenerator { /** Name of the template */ public static final String TEMPLATE_NAME = "/" .concat(TypeScriptDtoDTSGenerator.class.getPackage().getName().replace(".", "/")) .concat("/typescript.d.ts.template"); /** String template instance used */ private ST st; private Map dtoNamespaces = new HashMap<>(); public static void main(String[] args) { TypeScriptDtoGenerator.main(args); } @Override protected void analyze(Class dto) { // for each dto class, store some data about it DtoModel model = new DtoModel(dto); String namespace = model.getDTSPackageName(); if (!dtoNamespaces.containsKey(namespace)) { dtoNamespaces.put(namespace, new DtoNamespace(namespace)); } dtoNamespaces.get(namespace).addModel(model); } @Override public String execute() { init(); ST template = getTemplate(); template.add("dtoNamespaces", this.dtoNamespaces.values()); String output = template.render(); return output; } /** * Get the template for typescript * * @return the String Template */ protected ST getTemplate() { if (st == null) { URL url = Resources.getResource(TypeScriptDtoGenerator.class, TEMPLATE_NAME); try { st = new ST(Resources.toString(url, UTF_8)); } catch (IOException e) { throw new IllegalArgumentException("Unable to read template", e); } } return st; } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/main/java/org/eclipse/che/plugin/typescript/dto/TypeScriptDtoGenerator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; import static java.nio.charset.StandardCharsets.UTF_8; import static org.reflections.util.ClasspathHelper.forClassLoader; import static org.reflections.util.ClasspathHelper.forJavaClassPath; import com.google.common.io.Resources; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.plugin.typescript.dto.model.DtoModel; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; import org.reflections.scanners.TypeAnnotationsScanner; import org.reflections.util.ConfigurationBuilder; import org.stringtemplate.v4.ST; /** * Write all DTOs found in classpath into a specific file. It will contains both DTO interface and * DTO implementation. It will generate TypeScript code * * @author Florent Benoit */ public class TypeScriptDtoGenerator { /** Name of the template */ public static final String TEMPLATE_NAME = "/" .concat(TypeScriptDtoGenerator.class.getPackage().getName().replace(".", "/")) .concat("/typescript.template"); /** String template instance used */ private ST st; /** Model of DTOs that will be provided to the String Template */ private List dtoModels; /** Use of classpath */ private boolean useClassPath; /** Setup a new generator */ public TypeScriptDtoGenerator() { this.dtoModels = new ArrayList<>(); } public static void main(String[] args) { new TypeScriptDtoGenerator().execute(); } /** * Init stuff is responsible to grab all DTOs found in classpath (classloader) and setup model for * String Template */ protected void init() { ConfigurationBuilder configurationBuilder = new ConfigurationBuilder().setScanners(new SubTypesScanner(), new TypeAnnotationsScanner()); if (useClassPath) { configurationBuilder.setUrls(forJavaClassPath()); } else { configurationBuilder.setUrls(forClassLoader()); } // keep only DTO interfaces Reflections reflections = new Reflections(configurationBuilder); List> annotatedWithDtos = new ArrayList<>(reflections.getTypesAnnotatedWith(DTO.class)); List> interfacesDtos = annotatedWithDtos.stream() .filter(clazz -> clazz.isInterface()) .collect(Collectors.toList()); interfacesDtos.stream().forEach(this::analyze); } /** * Analyze a DTO interface by registering the associate model. * * @param dto the DTO to analyze */ protected void analyze(Class dto) { // for each dto class, store some data about it this.dtoModels.add(new DtoModel(dto)); } /** Execute this generator. */ public String execute() { init(); ST template = getTemplate(); template.add("dtos", this.dtoModels); String output = template.render(); return output; } /** * Get the template for typescript * * @return the String Template */ protected ST getTemplate() { if (st == null) { URL url = Resources.getResource(TypeScriptDtoGenerator.class, TEMPLATE_NAME); try { st = new ST(Resources.toString(url, UTF_8)); } catch (IOException e) { throw new IllegalArgumentException("Unable to read template", e); } } return st; } public void setUseClassPath(boolean useClassPath) { this.useClassPath = useClassPath; } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/main/java/org/eclipse/che/plugin/typescript/dto/model/DtoModel.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto.model; import static org.eclipse.che.plugin.typescript.dto.DTOHelper.*; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.dto.shared.DTO; /** * * Model of the DTO It includes attributes/fields and methods. * * @author Florent Benoit */ public class DtoModel { /** DTO instance (interface). */ private Class dto; /** Model of methods for this interface. */ private List methods; /** Map of all attributes found when scanning methods */ private Map fieldAttributes = new HashMap<>(); /** Model for the attributes of this interface (for generating implementation) */ private List fieldAttributeModels; private String interfaces; private boolean extendsDto; /** * Build a new model for the given DTO class by scanning it. * * @param dto the interface with {@link org.eclipse.che.dto.shared.DTO} annotation */ public DtoModel(Class dto) { this.dto = dto; this.methods = new ArrayList<>(); this.fieldAttributeModels = new ArrayList<>(); analyze(); } /** Scan all getter/setter/with methods that are not inherited */ protected void analyze() { Arrays.asList(this.dto.getMethods()).stream() .filter( method -> !method.isBridge() && (isDtoGetter(method) || isDtoSetter(method) || isDtoWith(method))) .forEach( method -> { MethodModel methodModel = new MethodModel(method); // check method with same name already exist if (!methods.contains(methodModel)) { methods.add(methodModel); if (isDtoGetter(method)) { analyzeDtoGetterMethod(method, methodModel); } else if (isDtoSetter(method)) { analyzeDtoSetterMethod(method, methodModel); } else if (isDtoWith(method)) { analyzeDtoWithMethod(method, methodModel); } } }); // now convert map into list fieldAttributes.entrySet().stream() .forEach( field -> fieldAttributeModels.add( new FieldAttributeModel(field.getKey(), field.getValue(), dto))); String interfaces = Arrays.stream(this.dto.getInterfaces()) .filter(i -> i.getAnnotation(DTO.class) != null) .map(i -> convertTypeForDTS(this.dto, i)) .collect(Collectors.joining(", ")); if (!interfaces.isEmpty()) { this.interfaces = interfaces; this.extendsDto = true; } } /** * Populate model from given reflect getter method * * @param method the method to analyze * @param methodModel the model to update */ protected void analyzeDtoGetterMethod(Method method, MethodModel methodModel) { methodModel.setGetter(true); Type fieldType = method.getGenericReturnType(); Pair names = getGetterFieldName(method); fieldAttributes.put(names.first, fieldType); methodModel.setFieldName(names.first); methodModel.setArgumentName(names.second); methodModel.setFieldType(convertType(fieldType)); } /** * Populate model from given reflect setter method * * @param method the method to analyze * @param methodModel the model to update */ protected void analyzeDtoSetterMethod(Method method, MethodModel methodModel) { methodModel.setSetter(true); // add the parameter Type fieldType = method.getGenericParameterTypes()[0]; Pair names = getSetterFieldName(method); fieldAttributes.put(names.first, fieldType); methodModel.setFieldName(names.first); methodModel.setArgumentName(names.second); methodModel.setFieldType(convertType(fieldType)); } /** * Populate model from given reflect with method * * @param method the method to analyze * @param methodModel the model to update */ protected void analyzeDtoWithMethod(Method method, MethodModel methodModel) { methodModel.setWith(true); // add the parameter Type fieldType = method.getGenericParameterTypes()[0]; Pair names = getWithFieldName(method); fieldAttributes.put(names.first, fieldType); methodModel.setFieldName(names.first); methodModel.setArgumentName(names.second); methodModel.setFieldType(convertType(fieldType)); } /** * @return model of attributes */ public List getFieldAttributeModels() { return fieldAttributeModels; } /** * Gets the package name of this interface * * @return the package name of this interface */ public String getPackageName() { return this.dto.getPackage().getName(); } /** * Gets the package name of this interface, without 'dto', 'shared', 'api' sections and * 'org.eclipse.' prefix * * @return the package name of this interface */ public String getDTSPackageName() { return convertToDTSPackageName(this.dto); } /** * Gets the short (simple) name of the interface. Like HelloWorld if FQN class is * foo.bar.HelloWorld * * @return the name of the interface */ public String getSimpleName() { return this.dto.getSimpleName(); } /** * Gets the FQN of this interface like foo.bar.HelloWorld * * @return the FQN name of this DTO interface */ public String getName() { return this.dto.getName(); } /** * Provides the model for every methods of the DTO that are getter/setter/with methods * * @return the list */ public List getMethods() { return this.methods; } /** * Gets the FQN of this interface like foo.bar.HelloWorld, but without 'Dto' suffix * * @return the name of the interface */ public String getDtsName() { return convertToDTSName(this.dto); } public String getInterfaces() { return interfaces; } public boolean isExtendsDto() { return extendsDto; } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/main/java/org/eclipse/che/plugin/typescript/dto/model/DtoNamespace.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto.model; import java.util.ArrayList; import java.util.List; /** Class for storing all Dto TS namespace interfaces */ public class DtoNamespace { private final String namespace; private List childs = new ArrayList<>(); public DtoNamespace(String namespace) { this.namespace = namespace; } /** * @return the namespace name */ public String getNamespace() { return namespace; } /** * @return all interfaces that placed in this namespace */ public List getDtoInterfaces() { return childs; } /** * Add Dto in this namespace * * @param model the dto */ public void addModel(DtoModel model) { childs.add(model); } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/main/java/org/eclipse/che/plugin/typescript/dto/model/FieldAttributeModel.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto.model; import static org.eclipse.che.plugin.typescript.dto.DTOHelper.convertType; import static org.eclipse.che.plugin.typescript.dto.DTOHelper.convertTypeForDTS; import com.google.gson.internal.Primitives; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import org.eclipse.che.dto.shared.DTO; /** * A field model will used for providing class generation of a DTO * * @author Florent Benoit */ public class FieldAttributeModel { /** Typescript value of the type of the field */ private final String typeName; /** For Map, List object, need to initialize field first. Like new Field<>() */ private boolean needInitialize; /** Name of the field */ private String fieldName; /** Java Type of the object (used internally) */ private Type type; /** This field type is a List of objects ? */ private boolean isList; /** This field type is a simple primitive */ private boolean isPrimitive; /** This field type is a map */ private boolean isMap; /** This list type is in fact a list of DTOs */ private boolean isListOfDto; /** This map type is a map of DTOs */ private boolean isMapOfDto; /** * The type is a DTO or a list of DTO and then this value is the name of the DTO implementation */ private String dtoImpl; /** type is a DTO object. */ private boolean isDto; /** type is a Enum object. */ private boolean isEnum; /** Map key type */ private String mapKeyType; /** Map value type */ private String mapValueType; /** Dto type for d.ts */ private String dtsType; /** Dto class where this field declared */ private Class declarationClass; /** * Build a new field model based on the name and Java type * * @param fieldName the name of the field * @param type the Java raw type that will allow further analyzes * @param declarationClass */ public FieldAttributeModel(String fieldName, Type type, Class declarationClass) { this.fieldName = fieldName; this.type = type; this.typeName = convertType(type); this.dtsType = convertTypeForDTS(declarationClass, type); this.declarationClass = declarationClass; if (typeName.startsWith("Array<") || typeName.startsWith("Map<")) { this.needInitialize = true; } if (this.type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) this.type; Type rawType = parameterizedType.getRawType(); analyzeParametrizedType(parameterizedType, rawType); } else if (Primitives.isPrimitive(this.type) || Primitives.isWrapperType(this.type) || String.class.equals(this.type)) { this.isPrimitive = true; } else if (this.type instanceof Class && ((Class) this.type).isAnnotationPresent(DTO.class)) { this.isDto = true; dtoImpl = this.type.getTypeName() + "Impl"; } else if (this.type instanceof Class && ((Class) this.type).isEnum()) { this.isEnum = true; } } /** * Analyze a complex parametrized type attribute (which can be a list or map for example) * * @param parameterizedType * @param rawType */ protected void analyzeParametrizedType(ParameterizedType parameterizedType, Type rawType) { if (List.class.equals(rawType)) { this.isList = true; if (parameterizedType.getActualTypeArguments()[0] instanceof Class && ((Class) parameterizedType.getActualTypeArguments()[0]) .isAnnotationPresent(DTO.class)) { isListOfDto = true; dtoImpl = convertType(parameterizedType.getActualTypeArguments()[0]) + "Impl"; } } else if (Map.class.equals(rawType)) { isMap = true; mapKeyType = convertTypeForDTS(declarationClass, parameterizedType.getActualTypeArguments()[0]); if (parameterizedType.getActualTypeArguments()[1] instanceof Class && ((Class) parameterizedType.getActualTypeArguments()[1]) .isAnnotationPresent(DTO.class)) { isMapOfDto = true; dtoImpl = convertType(parameterizedType.getActualTypeArguments()[1]) + "Impl"; } mapValueType = convertTypeForDTS(declarationClass, parameterizedType.getActualTypeArguments()[1]); } } public String getTypeName() { return typeName; } public String getFieldName() { return fieldName; } public Type getType() { return type; } public boolean isList() { return isList; } public boolean isPrimitive() { return isPrimitive; } public boolean isMap() { return isMap; } public boolean isListOfDto() { return isListOfDto; } public boolean isMapOfDto() { return isMapOfDto; } public String getDtoImpl() { return dtoImpl; } public boolean isDto() { return isDto; } public boolean isNeedInitialize() { return needInitialize; } public boolean isEnum() { return isEnum; } public String getName() { return this.fieldName; } public String getSimpleType() { return this.typeName; } public String getMapKeyType() { return mapKeyType; } public String getMapValueType() { return mapValueType; } public String getDtsType() { return dtsType; } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/main/java/org/eclipse/che/plugin/typescript/dto/model/MethodModel.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto.model; import static java.util.Objects.hash; import static org.eclipse.che.plugin.typescript.dto.DTOHelper.convertType; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.IntStream; /** * Defines the model of the method * * @author Florent Benoit */ public class MethodModel { /** Reflect object used internally. */ private Method method; /** Typescript return value of the method */ private String returnType; /** List of parameters for this method */ private List parameters; /** This method is a DTO getter method */ private boolean isGetter; /** This method is a DTO setter method */ private boolean isSetter; /** This method is a DTO with method */ private boolean isWith; /** * Name of the field associated to this method (field to return for a getter, field to store for * setter/with) */ private String fieldName; /** Type of the field associated to this method. */ private String fieldType; /** Name of setter or with* method argument */ private String argumentName; /** * Build a new model around the DTO method. * * @param method */ public MethodModel(Method method) { this.method = method; this.parameters = new ArrayList<>(); analyze(); } /** Loop on all parameters and initialize return value as well */ protected void analyze() { IntStream.range(0, method.getGenericParameterTypes().length) .forEach( i -> parameters.add( new ParameterMethodModel( "arg" + i, convertType(method.getGenericParameterTypes()[i])))); // add return type this.returnType = convertType(method.getGenericReturnType()); } public boolean isGetter() { return isGetter; } public void setGetter(boolean getter) { isGetter = getter; } public boolean isSetter() { return isSetter; } public void setSetter(boolean setter) { isSetter = setter; } public boolean isWith() { return isWith; } public void setWith(boolean with) { isWith = with; } public String getName() { return this.method.getName(); } public List getParameters() { return this.parameters; } public String getReturnType() { return this.returnType; } public void setFieldName(String fieldName) { this.fieldName = fieldName; } public String getFieldName() { return fieldName; } public void setFieldType(String fieldType) { this.fieldType = fieldType; } public String getFieldType() { return fieldType; } public int hashCode() { return hash(this.parameters.toString(), this.returnType); } public boolean equals(Object other) { if (other == null) { return false; } if (!(other instanceof MethodModel)) { return false; } MethodModel methodModelOther = (MethodModel) other; return this.getName().equals(methodModelOther.getName()) && this.returnType.equals(methodModelOther.returnType) && Arrays.equals(this.parameters.toArray(), methodModelOther.parameters.toArray()); } public String getArgumentName() { return argumentName; } public void setArgumentName(String argumentName) { this.argumentName = argumentName; } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/main/java/org/eclipse/che/plugin/typescript/dto/model/ParameterMethodModel.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto.model; import static java.util.Objects.hash; /** * Defines the model link to parameter of a method * * @author Florent Benoit */ public class ParameterMethodModel { /** Name of the parameter. */ private String parameterName; /** Type of the parameter. (Type is in TypeScript format) */ private String parameterType; /** * Create a new instance of parameter model with specified name and type * * @param parameterName the name of the parameter like foo * @param parameterType the type of the parameter (like foo.bar.MyDTO or primitive value like * string) */ public ParameterMethodModel(String parameterName, String parameterType) { this.parameterName = parameterName; this.parameterType = parameterType; } /** * Getter for the name * * @return the name of the parameter */ public String getName() { return this.parameterName; } /** * Getter for the type * * @return the type of the parameter */ public String getType() { return this.parameterType; } public int hashCode() { return hash(this.parameterName, this.parameterType); } public boolean equals(Object other) { if (other == null) { return false; } if (!(other instanceof ParameterMethodModel)) { return false; } ParameterMethodModel parameterMethodModelOther = (ParameterMethodModel) other; return this.parameterName.equals(parameterMethodModelOther.parameterName) && this.parameterType.equals(((ParameterMethodModel) other).parameterType); } public String toString() { return "ParameterMethodModel[" + this.parameterName + "/" + this.parameterType + "]"; } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/main/resources/org/eclipse/che/plugin/typescript/dto/typescript.d.ts.template ================================================ // File has been generated automatically by Eclipse Che TypeScript DTO generator export namespace { export interface extends { ?: { [key: ]: ; \}; }> \} }>\} }> ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/main/resources/org/eclipse/che/plugin/typescript/dto/typescript.template ================================================ // File has been generated automatically by Eclipse Che TypeScript DTO generator export module { export interface { (,}>): ; }> toJson() : any; \} \} export module { export class Impl implements { : ; }> __jsonObject : any; constructor(__jsonObject?: any) { this.__jsonObject = __jsonObject; this. = new (); if (__jsonObject) { if (__jsonObject.) { __jsonObject..forEach((item) => { this..push(new (item)); this..push(item); \}); let tmp : Array\ = Object.keys(__jsonObject.); tmp.forEach((key) => { this..set(key, new (__jsonObject.[key])); this..set(key, __jsonObject.[key]); \}); this. = __jsonObject.; this. = __jsonObject.; this. = new (__jsonObject.); \} \} }> \} () : { return this.; \} ( : ) : void { this. = ; \} ( : ) : { this. = ; return this; \} }> toJson() : any { let json : any = {\}; ) { let listArray = []; this..forEach((item) => { listArray.push((item as ).toJson()); listArray.push(item); json. = listArray; \}); let tmpMap : any = {\}; for (const [key, value] of this..entries()) { tmpMap[key] = (value as ).toJson(); tmpMap[key] = value; \} json. = tmpMap; json. = this.; json. = this.; json. = (this. as ).toJson(); \} }> return json; \} \} \} }> ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/java/org/eclipse/che/plugin/typescript/dto/MyCustomDTO.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; import java.util.List; import java.util.Map; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.plugin.typescript.dto.internal.InternalDto; /** * @author Florent Benoit */ @DTO public interface MyCustomDTO { String getName(); void setName(String name); MyCustomDTO withName(String name); MyOtherDTO getConfig(); MyCustomDTO withConfig(MyOtherDTO otherDTO); void setConfig(MyOtherDTO otherDTO); void setStatus(Status status); MyCustomDTO withStatus(Status status); Status getStatus(); Map getCustomMap(); void setCustomMap(Map map); MyCustomDTO withCustomMap(Map map); // arguments is a reserved keyword for TypeScript List getArguments(); void setArguments(List arguments); MyCustomDTO withArguments(List arguments); InternalDto getInternal(); void setInternal(InternalDto internal); } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/java/org/eclipse/che/plugin/typescript/dto/MyDtoWithSerializableDTO.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; import java.io.Serializable; import java.util.Map; import org.eclipse.che.dto.shared.DTO; @DTO public interface MyDtoWithSerializableDTO { Map getPreferences(); void setPreferences(Map preferences); } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/java/org/eclipse/che/plugin/typescript/dto/MyOtherDTO.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; import org.eclipse.che.dto.shared.DTO; /** * @author Florent Benoit */ @DTO public interface MyOtherDTO extends MySuperClassDTO, MySuperSuperClass { void setName(String name); MyOtherDTO withName(String name); } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/java/org/eclipse/che/plugin/typescript/dto/MySimpleDTO.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; import org.eclipse.che.dto.shared.DTO; /** * @author Florent Benoit */ @DTO public interface MySimpleDTO { int getId(); MySimpleDTO withId(int id); boolean getBoolean(); MySimpleDTO withBoolean(boolean bool); double getDouble(); MySimpleDTO withDouble(double d); float getFloat(); MySimpleDTO withFloat(float f); } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/java/org/eclipse/che/plugin/typescript/dto/MySuperClassDTO.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.dto.shared.DTO; /** * @author Florent Benoit */ @DTO public interface MySuperClassDTO extends MySuperSuperClass { @Override @FactoryParameter(obligation = OPTIONAL) String getName(); void setName(String name); MySuperClassDTO withName(String name); } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/java/org/eclipse/che/plugin/typescript/dto/MySuperSuperClass.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; /** * @author Florent Benoit */ public interface MySuperSuperClass { String getName(); } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/java/org/eclipse/che/plugin/typescript/dto/Status.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; /** * @author Florent Benoit */ public enum Status { SHUTDOWN, ALIVE } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/java/org/eclipse/che/plugin/typescript/dto/TypeScriptDTOGeneratorMojoTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.BufferedReader; import java.io.File; import java.nio.file.Files; import org.apache.maven.plugin.testing.MojoRule; import org.apache.maven.plugin.testing.resources.TestResources; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; /** * @author Florent Benoit */ public class TypeScriptDTOGeneratorMojoTest { /** Rule to manage the mojo (inject, get variables from mojo) */ @Rule public MojoRule rule = new MojoRule(); /** Resources of each test mapped on the name of the method */ @Rule public TestResources resources = new TestResources(); /** * Helper method used to inject data in mojo * * @param mojo the mojo * @param baseDir root dir on which we extract files * @throws IllegalAccessException if unable to set variables */ protected void configure(TypeScriptDTOGeneratorMojo mojo, File baseDir) throws Exception { this.rule.setVariableValueToObject(mojo, "targetDirectory", this.resources.getBasedir("")); this.rule.setVariableValueToObject(mojo, "useClassPath", true); } /** * Check that the TypeScript definition is generated and that WorkspaceDTO is generated * (dependency is part of the test) */ @Test public void testCheckTypeScriptGenerated() throws Exception { File projectCopy = this.resources.getBasedir("project"); File pom = new File(projectCopy, "pom.xml"); assertNotNull(pom); assertTrue(pom.exists()); TypeScriptDTOGeneratorMojo mojo = (TypeScriptDTOGeneratorMojo) this.rule.lookupMojo("build", pom); configure(mojo, projectCopy); mojo.execute(); File typeScriptFile = mojo.getTypescriptFile(); // Check file has been generated Assert.assertTrue(typeScriptFile.exists()); // Now check there is "org.eclipse.che.plugin.typescript.dto.MyCustomDTO" inside boolean foundMyCustomDTO = false; try (BufferedReader reader = Files.newBufferedReader(typeScriptFile.toPath(), UTF_8)) { String line = reader.readLine(); while (line != null && !foundMyCustomDTO) { if (line.contains("MyCustomDTO")) { foundMyCustomDTO = true; } line = reader.readLine(); } } Assert.assertTrue( "The MyCustomDTO has not been generated in the typescript definition file.", foundMyCustomDTO); } @Test public void checkDTSFileCreated() throws Exception { File projectCopy = this.resources.getBasedir("project-d-ts"); File pom = new File(projectCopy, "pom.xml"); assertNotNull(pom); assertTrue(pom.exists()); TypeScriptDTOGeneratorMojo mojo = (TypeScriptDTOGeneratorMojo) this.rule.lookupMojo("build", pom); configure(mojo, projectCopy); mojo.execute(); File typeScriptFile = mojo.getTypescriptFile(); Assert.assertNotNull(typeScriptFile); // Check file has been generated Assert.assertTrue(typeScriptFile.exists()); } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/java/org/eclipse/che/plugin/typescript/dto/internal/InternalDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto.internal; import org.eclipse.che.dto.shared.DTO; @DTO public interface InternalDto { String getInternalValue(); void setInternalValue(String value); } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/java/org/eclipse/che/plugin/typescript/dto/stub/TypeScriptDTOGeneratorMojoProjectStub.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.plugin.typescript.dto.stub; import static org.mockito.Mockito.when; import java.io.File; import java.util.ArrayList; import java.util.List; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.model.Build; import org.apache.maven.model.Model; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.apache.maven.plugin.testing.stubs.MavenProjectStub; import org.codehaus.plexus.util.ReaderFactory; import org.mockito.Mockito; /** * @author Florent Benoit */ public class TypeScriptDTOGeneratorMojoProjectStub extends MavenProjectStub { /** {@inheritDoc} */ @Override public File getBasedir() { return new File(super.getBasedir() + "/src/test/projects/project"); } /** Default constructor */ public TypeScriptDTOGeneratorMojoProjectStub() { MavenXpp3Reader pomReader = new MavenXpp3Reader(); Model model; try { model = pomReader.read(ReaderFactory.newXmlReader(new File(getBasedir(), "pom.xml"))); setModel(model); } catch (Exception e) { throw new RuntimeException(e); } setGroupId(model.getGroupId()); setArtifactId(model.getArtifactId()); setVersion(model.getVersion()); setName(model.getName()); setUrl(model.getUrl()); setPackaging(model.getPackaging()); Build build = new Build(); build.setFinalName(model.getArtifactId()); build.setDirectory(getBasedir() + "/target"); build.setSourceDirectory(getBasedir() + "/src/main/java"); build.setOutputDirectory(getBasedir() + "/target/classes"); build.setTestSourceDirectory(getBasedir() + "/src/test/java"); build.setTestOutputDirectory(getBasedir() + "/target/test-classes"); setBuild(build); List compileSourceRoots = new ArrayList(); compileSourceRoots.add(getBasedir() + "/src/main/java"); setCompileSourceRoots(compileSourceRoots); List testCompileSourceRoots = new ArrayList(); testCompileSourceRoots.add(getBasedir() + "/src/test/java"); setTestCompileSourceRoots(testCompileSourceRoots); } /** Use of mockito artifact */ @Override public Artifact getArtifact() { Artifact artifact = Mockito.mock(Artifact.class); when(artifact.getArtifactId()).thenReturn(getModel().getArtifactId()); when(artifact.getGroupId()).thenReturn(getModel().getGroupId()); when(artifact.getVersion()).thenReturn(getModel().getVersion()); when(artifact.getVersionRange()) .thenReturn(VersionRange.createFromVersion(getModel().getVersion())); return artifact; } } ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/projects/project/pom.xml ================================================ 4.0.0 org.eclipse.che.test my-typescript-test-module 1.0-SNAPSHOT pom Test of Eclipse Che TypeScript plugin org.eclipse.che.core che-core-typescript-dto-maven-plugin true ================================================ FILE: core/che-core-typescript-dto-maven-plugin/src/test/projects/project-d-ts/pom.xml ================================================ 4.0.0 org.eclipse.che.test my-typescript-test-module 1.0-SNAPSHOT pom Test of Eclipse Che TypeScript plugin org.eclipse.che.core che-core-typescript-dto-maven-plugin true true ================================================ FILE: core/commons/che-core-commons-annotations/pom.xml ================================================ 4.0.0 che-core-commons-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-commons-annotations jar Che Core :: Commons :: Annotations com.google.guava guava provided ================================================ FILE: core/commons/che-core-commons-annotations/src/main/java/org/eclipse/che/commons/annotation/Nullable.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * @author Mihail Kuznyetsov */ @Retention(RetentionPolicy.RUNTIME) public @interface Nullable {} ================================================ FILE: core/commons/che-core-commons-annotations/src/main/java/org/eclipse/che/commons/annotation/Traced.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.annotation; import com.google.common.annotations.Beta; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayDeque; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; /** * Annotates a method as traced. * *

If the method is declared in a Guice-managed class and the Guice environment is equipped with * an interceptor for handling this annotation (which should be the case at least in the workspace * server), each call of such method will create a new span declared as a child of some current * span, if any, with the provided name (or the default name). * *

The method can declare additional tags in its body that will be applied to the span, see * {@link Tags}. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Beta public @interface Traced { /** * The name of the span generated for the method. Defaults to the "classSimpleName#methodName". */ String name() default ""; /** * Use this class to statically set the tags on the span generated for a {@code @Traced} method. * Note that the tags are applied after the method is invoked no matter where in the method * they are set. */ final class Tags { /** Adds a new tag. If the tag already exists, it is NOT updated. */ public static void addString(String tagName, Supplier valueSupplier) { internalAdd(tagName, valueSupplier); } /** Adds a new tag. If the tag already exists, it is NOT updated. */ public static void addBoolean(String tagName, Supplier valueSupplier) { internalAdd(tagName, valueSupplier); } /** Adds a new tag. If the tag already exists, it is NOT updated. */ public static void addInteger(String tagName, Supplier valueSupplier) { internalAdd(tagName, valueSupplier); } private static void internalAdd(String tagName, Supplier value) { Map> tags = TagsStack.TAGS.get().peek(); if (tags != null) { tags.putIfAbsent(tagName, value); } } } /** * This class is not meant for use in methods annotated with {@code @Traced}, rather it is used by * the interceptor wrapping those methods setting up tags storage for each {@code @Traced} method. * *

This class supports {@link Tags} so that it can be correctly and easily used from the * annotated methods. */ final class TagsStack { private static final ThreadLocal>>> TAGS = ThreadLocal.withInitial(ArrayDeque::new); private TagsStack() { throw new AssertionError("I shall not be instantiated."); } public static Map> pop() { Deque>> tagsStack = TAGS.get(); if (tagsStack.isEmpty()) { return Collections.emptyMap(); } Map> tags = tagsStack.pop(); return Collections.unmodifiableMap(tags); } public static void push() { // we're assuming max 4 tags per span is gonna be the usual case. Saving 12 entries per method // invocation will make a GC difference. If there are more than 4 tags, we're adding runtime // overhead of enlarging the hash map but that's not gonna be a usual occurrence (currently, // we have at most 3 tags on a span). TAGS.get().push(new HashMap<>(4)); } } } ================================================ FILE: core/commons/che-core-commons-inject/pom.xml ================================================ 4.0.0 che-core-commons-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-commons-inject jar Che Core :: Commons :: Dependency Injecting com.google.guava guava com.google.inject guice jakarta.annotation jakarta.annotation-api jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-commons-lang org.everrest everrest-guice-servlet org.everrest everrest-integration-guice org.slf4j slf4j-api jakarta.servlet jakarta.servlet-api provided ch.qos.logback logback-core test org.eclipse.che.core che-core-commons-annotations test org.eclipse.che.core che-core-commons-test test org.mockito mockito-core test org.testng testng test org.apache.maven.plugins maven-surefire-plugin ${project.build.directory}/conf che_underscore_environment_variable_value che_underscore_environment_variable_value_with_underscores_in_name ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/CheBootstrap.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import static java.lang.String.format; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.eclipse.che.inject.lifecycle.DestroyErrorHandler.LOG_HANDLER; import com.google.common.base.Splitter; import com.google.common.io.Files; import com.google.inject.AbstractModule; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.name.Names; import com.google.inject.servlet.ServletModule; import com.google.inject.util.Modules; import com.google.inject.util.Providers; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContextEvent; import java.io.File; import java.io.IOException; import java.io.Reader; import java.net.URL; import java.nio.charset.Charset; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.eclipse.che.inject.lifecycle.DestroyModule; import org.eclipse.che.inject.lifecycle.Destroyer; import org.eclipse.che.inject.lifecycle.InitModule; import org.everrest.guice.servlet.EverrestGuiceContextListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * CheBootstrap is entry point of Che application implemented as ServletContextListener. * *

    *
  • Initializes Guice Injector *
  • Automatically binds all the subclasses of com.google.inject.Module annotated with * @DynaModule *
  • Loads configuration from .properties and .xml files located in /WEB-INF/classes/che * directory *
  • Overrides it with external configuration located in directory pointed by * CHE_LOCAL_CONF_DIR env variable (if any) *
  • Binds all environment variables (visible as prefixed with "env.") and system properties * (visible as prefixed with "sys.") *
  • Thanks to Everrest integration injects all the properly annotated (see Everrest docs) REST * Resources. Providers and ExceptionMappers and inject necessary dependencies *
* *

Configuration properties are bound as a {@code @Named}. For example: Following entry in * the .property file: {@code myProp=value} may be injected into constructor (other options are * valid too of course) as following: * *

 * @Inject
 * public MyClass(@Named("myProp") String my) {
 * }
 * 
* *

It's possible to use system properties or environment variables in .properties files. * *

 * my_app.input_dir=${root_data}/input/
 * my_app.output_dir=${root_data}/output/
 * 
* * NOTE: System property always takes preference on environment variable with the same name. * *

* * * * * * * *
ValueSystem propertyEnvironment variableResult
${root_data}/input//home/andrew/temp /home/andrew/temp/input/
${root_data}/input/ /usr/local/usr/local/input/
${root_data}/input//home/andrew/temp/usr/local/home/andrew/temp/input/
${root_data}/input/  ${root_data}/input/
* * During code evolution might be the case when someone will want to rename some property. This * brings a couple of problems like support of old property name in external plugins and support old * configuration values in code with the new property name. To cover these cases there is a file * che_aliases.properties that contains old names of all existed properties. It has such format * current_name =old_name, very_old_name. In this case will be such binding. * *

Always current_name = current_value if old_name property exist it will be binded to old_value * and current_name = old_value and very_old_name = old_value if very_old_name property exist it * will be binded to very_old_value, and current_name = very_old_value and old_name = very_old_value * *

NOTE: it's prohibited to use a different name for same property on the same level. From the * example above - you can use environment property CHE_CURRENT_NAME and CHE_OLD_NAME. But you can * use it on a different level, for instance, environment property and system property. * * @author gazarenkov * @author andrew00x * @author Florent Benoit * @author Sergii Kabashniuk */ public class CheBootstrap extends EverrestGuiceContextListener { private static final Logger LOG = LoggerFactory.getLogger(CheBootstrap.class); /** Environment variable that is used to override some Che settings properties. */ public static final String CHE_LOCAL_CONF_DIR = "CHE_LOCAL_CONF_DIR"; public static final String PROPERTIES_ALIASES_CONFIG_FILE = "che_aliases.properties"; /** Path to the internal folder that is expected in WEB-INF/classes */ private static final String WEB_INF_RESOURCES = "che"; /** Backward compliant path to the internal folder that is expected in WEB-INF/classes */ private static final String COMPLIANT_WEB_INF_RESOURCES = "codenvy"; private static final String NULL = "NULL"; private final List modules = new ArrayList<>(); static { Thread.setDefaultUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()); } @Override public void contextDestroyed(ServletContextEvent sce) { final ServletContext ctx = sce.getServletContext(); final Injector injector = getInjector(ctx); if (injector != null) { injector.getInstance(Destroyer.class).destroy(); } super.contextDestroyed(sce); } @Override protected List getModules() { // based on logic that getServletModule() is called BEFORE getModules() in the // EverrestGuiceContextListener modules.add(new InitModule(PostConstruct.class)); modules.add(new DestroyModule(PreDestroy.class, LOG_HANDLER)); modules.add(new URIConverter()); modules.add(new URLConverter()); modules.add(new FileConverter()); modules.add(new PathConverter()); modules.add(new StringArrayConverter()); modules.add(new PairConverter()); modules.add(new PairArrayConverter()); modules.addAll(ModuleScanner.findModules()); Map> aliases = readConfigurationAliases(); Module firstConfigurationPermutation = Modules.override(new WebInfConfiguration(aliases)).with(new ExtConfiguration(aliases)); Module secondConfigurationPermutation = Modules.override(firstConfigurationPermutation) .with(new CheSystemPropertiesConfigurationModule(aliases)); Module lastConfigurationPermutation = Modules.override(secondConfigurationPermutation) .with(new CheEnvironmentVariablesConfigurationModule(aliases)); modules.add(lastConfigurationPermutation); return modules; } private Map> readConfigurationAliases() { URL aliasesResource = getClass().getClassLoader().getResource(PROPERTIES_ALIASES_CONFIG_FILE); Map> aliases = new HashMap<>(); if (aliasesResource != null) { Properties properties = new Properties(); File aliasesFile = new File(aliasesResource.getFile()); try (Reader reader = Files.newReader(aliasesFile, Charset.forName("UTF-8"))) { properties.load(reader); } catch (IOException e) { throw new IllegalStateException( format("Unable to read configuration aliases from file %s", aliasesFile), e); } for (Map.Entry entry : properties.entrySet()) { String value = (String) entry.getValue(); aliases.put( (String) entry.getKey(), Splitter.on(',').splitToList(value).stream().map(String::trim).collect(toSet())); } } return aliases; } /** * see http://google-guice.googlecode.com/git/javadoc/com/google/inject/servlet/ServletModule.html */ @Override protected ServletModule getServletModule() { // Servlets and other web components may be configured with custom Modules. return null; } /** ConfigurationModule binding configuration located in /WEB-INF/classes/che directory */ static class WebInfConfiguration extends AbstractConfigurationModule { WebInfConfiguration(Map> aliases) { super(aliases); } protected void configure() { URL compliantWebInfConf = getClass().getClassLoader().getResource(COMPLIANT_WEB_INF_RESOURCES); if (compliantWebInfConf != null) { bindConf(new File(compliantWebInfConf.getFile())); } URL webInfConf = getClass().getClassLoader().getResource(WEB_INF_RESOURCES); if (webInfConf != null) { bindConf(new File(webInfConf.getFile())); } } } /** * ConfigurationModule binding environment variables, system properties and configuration in * directory pointed by CHE_LOCAL_CONF_DIR Env variable. */ static class ExtConfiguration extends AbstractConfigurationModule { ExtConfiguration(Map> aliases) { super(aliases); } @Override protected void configure() { bindProperties("env.", System.getenv()); bindProperties("sys.", System.getProperties()); String extConfig = System.getenv(CHE_LOCAL_CONF_DIR); if (extConfig != null) { bindConf(new File(extConfig)); } } } static class CheSystemPropertiesConfigurationModule extends AbstractConfigurationModule { CheSystemPropertiesConfigurationModule(Map> aliases) { super(aliases); } @Override protected void configure() { Iterable> cheProperties = System.getProperties().entrySet().stream() .filter(new PropertyNamePrefixPredicate<>("che.", "codenvy.")) .collect(toList()); bindProperties(null, cheProperties); } } static class CheEnvironmentVariablesConfigurationModule extends AbstractConfigurationModule { CheEnvironmentVariablesConfigurationModule(Map> aliases) { super(aliases); } @Override protected void configure() { Iterable> cheProperties = System.getenv().entrySet().stream() .filter(new PropertyNamePrefixPredicate<>("CHE_", "CODENVY_")) .map(new EnvironmentVariableToSystemPropertyFormatNameConverter()) .collect(toList()); bindProperties(null, cheProperties); } } static class PropertyNamePrefixPredicate implements Predicate> { final String[] prefixes; PropertyNamePrefixPredicate(String... prefix) { this.prefixes = prefix; } @Override public boolean test(Map.Entry entry) { for (String prefix : prefixes) { if (((String) entry.getKey()).startsWith(prefix)) { return true; } } return false; } } static class PropertyNamePrefixRemover implements Function, Map.Entry> { final int prefixLength; PropertyNamePrefixRemover(int prefixLength) { this.prefixLength = prefixLength; } @Override public Map.Entry apply(Map.Entry entry) { return new SimpleEntry<>(((String) entry.getKey()).substring(prefixLength), entry.getValue()); } } static class EnvironmentVariableToSystemPropertyFormatNameConverter implements Function, Map.Entry> { @Override public Map.Entry apply(Map.Entry entry) { String name = entry.getKey(); name = name.toLowerCase(); // replace single underscore with dot and double underscores with single underscore // at first replace double underscores with equal sign which is forbidden in env variable name // then replace single underscores // then recover underscore from equal sign name = name.replace("__", "="); name = name.replace('_', '.'); name = name.replace("=", "_"); return new SimpleEntry<>(name, entry.getValue()); } } private static final Pattern PROPERTIES_PLACE_HOLDER_PATTERN = Pattern.compile("\\$\\{[^\\}^\\$\\{]+\\}"); abstract static class AbstractConfigurationModule extends AbstractModule { final Map> bindMap; AbstractConfigurationModule(Map> aliases) { this.bindMap = new HashMap<>(aliases); for (Entry> entry : aliases.entrySet()) { for (String alias : entry.getValue()) { Set newAliases = new HashSet<>(entry.getValue()); newAliases.remove(alias); newAliases.add(entry.getKey()); bindMap.put(alias, newAliases); } } } protected void bindConf(File confDir) { final File[] files = confDir.listFiles(); if (files != null) { for (File file : files) { if (!file.isDirectory()) { if ("properties".equals(Files.getFileExtension(file.getName()))) { Properties properties = new Properties(); try (Reader reader = Files.newReader(file, Charset.forName("UTF-8"))) { properties.load(reader); } catch (IOException e) { throw new IllegalStateException( format("Unable to read configuration file %s", file), e); } bindProperties(properties); } } } } } protected void bindProperties(Properties properties) { bindProperties(null, properties.entrySet()); } protected void bindProperties(String prefix, Properties properties) { bindProperties(prefix, properties.entrySet()); } protected void bindProperties(String prefix, Map properties) { bindProperties(prefix, properties.entrySet(), true); } protected void bindProperties(String prefix, Iterable> properties) { bindProperties(prefix, properties, false); } protected void bindProperties( String prefix, Iterable> properties, boolean skipUnresolved) { StringBuilder buf = null; for (Map.Entry e : properties) { String name = (String) e.getKey(); String value = (String) e.getValue(); if (NULL.equals(value)) { bindProperty(prefix, name, null); } else { final Matcher matcher = PROPERTIES_PLACE_HOLDER_PATTERN.matcher(value); if (matcher.find()) { int start = 0; if (buf == null) { buf = new StringBuilder(); } else { buf.setLength(0); } do { buf.append(value.substring(start, matcher.start())); final String placeholder = value.substring(matcher.start(), matcher.end()); final String placeholderName = removePlaceholderFormatting(placeholder); String resolvedPlaceholder = resolvePlaceholder(placeholderName); if (resolvedPlaceholder != null) { buf.append(resolvedPlaceholder); } else if (skipUnresolved) { buf.append(placeholder); LOG.warn( "Placeholder {} cannot be resolved neither from environment variable nor from system property, " + "leaving as is.", placeholderName); } else { throw new ConfigurationException( format( "%s is not a system property or environment variable.", placeholderName)); } start = matcher.end(); } while (matcher.find()); buf.append(value.substring(start)); value = buf.toString(); } bindProperty(prefix, name, value); } } } private void bindProperty(String prefix, String name, String value) { String key = prefix == null ? name : (prefix + name); Set aliasesForName = bindMap.get(name); if (value == null) { LOG.debug("Binding `{}` to `null`", key); bind(String.class).annotatedWith(Names.named(key)).toProvider(Providers.of(null)); if (aliasesForName != null) { for (String alias : aliasesForName) { String bindKey = prefix == null ? alias : prefix + alias; LOG.debug("Binding `{}` to `null`", bindKey); bind(String.class) .annotatedWith(Names.named(bindKey)) .toProvider(Providers.of(null)); } } } else { LOG.debug("Binding `{}` to `{}`", key, value); bindConstant().annotatedWith(Names.named(key)).to(value); if (aliasesForName != null) { for (String alias : aliasesForName) { String bindKey = prefix == null ? alias : prefix + alias; LOG.debug("Binding `{}` to `{}`", bindKey, value); bindConstant().annotatedWith(Names.named(bindKey)).to(value); } } } } private String removePlaceholderFormatting(String placeholder) { return placeholder.substring(2, placeholder.length() - 1); } private String resolvePlaceholder(String placeholderName) { String resolved = System.getProperty(placeholderName); if (resolved == null) { resolved = System.getenv(placeholderName); } LOG.debug("Resolving `{}` to `{}`", placeholderName, resolved); return resolved; } } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/ConfigurationException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; /** * Exception happen during guice configuration phase. * * @author Sergii Kabashniuk */ public class ConfigurationException extends RuntimeException { public ConfigurationException(String message) { super(message); } public ConfigurationException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/ConfigurationProperties.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Key; import java.lang.annotation.Annotation; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; /** * Helper for getting set of configuration properties by name's pattern. * *

{@code
 * public class Service {
 *     private Map configuration;
 *     @Inject
 *     Service(ConfigurationProperties configurationProperties) {
 *         configuration = configurationProperties.getProperties("test.*");
 *     }
 *     ...
 * }
 * }
* * @author andrew00x */ @Singleton public class ConfigurationProperties { private final Provider injectorProvider; @Inject ConfigurationProperties(Provider injectorProvider) { this.injectorProvider = injectorProvider; } public Map getProperties(String namePattern) { final Pattern pattern = Pattern.compile(namePattern); final Map result = new HashMap<>(); for (Map.Entry, Binding> keyBindingEntry : injectorProvider.get().getAllBindings().entrySet()) { final Key key = keyBindingEntry.getKey(); final Annotation annotation = key.getAnnotation(); if (annotation instanceof com.google.inject.name.Named && key.getTypeLiteral().getRawType() == String.class) { final String name = ((com.google.inject.name.Named) annotation).value(); if (name != null && pattern.matcher(name).matches()) { final String value = (String) keyBindingEntry.getValue().getProvider().get(); result.put(name, value); } } } return result; } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/DynaModule.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Marker annotation for dynamically created modules. * *

{@link CheBootstrap} automatically finds and loads Guice modules (subclasses of {@link * com.google.inject.Module}) annotated with @DynaModule. * * @author gazarenkov */ @Retention(RetentionPolicy.RUNTIME) public @interface DynaModule {} ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/FileConverter.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import com.google.inject.AbstractModule; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.TypeConverter; import java.io.File; /** * @author andrew00x */ public class FileConverter extends AbstractModule implements TypeConverter { @Override public Object convert(String value, TypeLiteral toType) { return new File(value); } @Override protected void configure() { convertToTypes(Matchers.only(TypeLiteral.get(File.class)), this); } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/Matchers.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import static java.util.Objects.requireNonNull; import com.google.inject.matcher.AbstractMatcher; import com.google.inject.matcher.Matcher; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInterceptor; /** * Matcher implementations. Supports matching methods. It can be used for binding of {@link * MethodInterceptor}. * *

Example of usage: * bindInterceptor(com.google.inject.matcher.Matchers.subclassesOf(SomeClass.class), Matcher.names("getInstance"), new MethodInterceptor() {...});} * * * @author Sergii Leschenko */ public class Matchers { private Matchers() {} /** Returns a matcher which matches methods with matching name. */ public static Matcher names(String methodName) { return new Names(methodName); } private static class Names extends AbstractMatcher { private String methodName; private Names(String methodName) { requireNonNull(methodName, "methodName"); this.methodName = methodName; } @Override public boolean matches(Method m) { return m.getName().equals(methodName); } @Override public boolean equals(Object other) { return other == this || other instanceof Names && ((Names) other).methodName.equals(methodName); } @Override public int hashCode() { return 37 * methodName.hashCode(); } @Override public String toString() { return "names(" + methodName + ")"; } } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/ModuleFinder.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import com.google.inject.Module; import java.util.List; /** * Interface used for ServiceLoader mechanism. Implementations will have to implement this interface * to provide list of modules at runtime * * @author Florent Benoit */ public interface ModuleFinder { /** * Provides the list of additional modules * * @return the list of modules to add. */ List getModules(); } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/ModuleScanner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import com.google.common.annotations.VisibleForTesting; import com.google.inject.Module; import jakarta.servlet.ServletContainerInitializer; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.HandlesTypes; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Utility for finding Guice modules annotated with @DynaModule. */ @HandlesTypes({DynaModule.class}) public class ModuleScanner implements ServletContainerInitializer { private static final Logger LOG = LoggerFactory.getLogger(ModuleScanner.class); @VisibleForTesting static final List modules = new ArrayList<>(); public static List findModules() { // also search if classes are provided through service loader mechanism // It's useful when the scanning is disabled or ServletContainerInitializer is disabled. // onStartup may not be called at all so it's another way of plugging modules. ServiceLoader moduleFinderServiceLoader = ServiceLoader.load(ModuleFinder.class); moduleFinderServiceLoader.forEach(moduleFinder -> modules.addAll(moduleFinder.getModules())); return new ArrayList<>(modules); } @Override public void onStartup(Set> c, ServletContext ctx) throws ServletException { if (c != null) { for (Class clazz : c) { if (Module.class.isAssignableFrom(clazz)) { try { modules.add((Module) clazz.newInstance()); } catch (Exception e) { LOG.error("Problem with instantiating Module {} : {}", clazz, e.getMessage()); } } else { LOG.warn( "Ignored non {} class annotated with {}", Module.class.getName(), DynaModule.class.getName()); } } } } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/PairArrayConverter.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; import com.google.inject.AbstractModule; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.TypeConverter; import org.eclipse.che.commons.lang.Pair; /** * @author andrew00x */ public class PairArrayConverter extends AbstractModule implements TypeConverter { @Override public Object convert(String value, TypeLiteral toType) { final String[] pairs = Iterables.toArray(Splitter.on(",").split(value), String.class); @SuppressWarnings("unchecked") final Pair[] result = new Pair[pairs.length]; for (int i = 0; i < pairs.length; i++) { result[i] = PairConverter.fromString(pairs[i]); } return result; } @Override protected void configure() { convertToTypes(Matchers.only(new StringPairTypeLiteral()), this); } private static class StringPairTypeLiteral extends TypeLiteral[]> {} } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/PairConverter.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import com.google.inject.AbstractModule; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.TypeConverter; import org.eclipse.che.commons.lang.Pair; /** * @author andrew00x */ public class PairConverter extends AbstractModule implements TypeConverter { @Override public Object convert(String value, TypeLiteral toType) { return fromString(value); } static Pair fromString(String value) { final int p = value.indexOf('='); if (p < 0) { return Pair.of(value, null); } final int length = value.length(); if (p == length) { return Pair.of(value.substring(0, p), ""); } return Pair.of(value.substring(0, p), value.substring(p + 1, length)); } @Override protected void configure() { convertToTypes(Matchers.only(new StringPairTypeLiteral()), this); } private static class StringPairTypeLiteral extends TypeLiteral> {} } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/PathConverter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import com.google.inject.AbstractModule; import com.google.inject.Binder; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.TypeConverter; import java.nio.file.Path; import java.nio.file.Paths; /** * Converts {@link Binder#bindConstant() constant bindings} to {@link java.nio.Path} values. * * @author Tareq Sharafy */ public class PathConverter extends AbstractModule implements TypeConverter { @Override public Object convert(String value, TypeLiteral toType) { return Paths.get(value); } @Override protected void configure() { convertToTypes(Matchers.only(TypeLiteral.get(Path.class)), this); } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/StringArrayConverter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; import com.google.inject.AbstractModule; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.TypeConverter; import java.util.regex.Pattern; /** * Converts injected string value to an array of strings if such an array is requested by injection. * *

Entries of the array should be separated by a comma sign. Spaces around entries are trimmed. * Supports injection from property files, environment variables and Java system properties. * * @author andrew00x */ public class StringArrayConverter extends AbstractModule implements TypeConverter { private static final Pattern PATTERN = Pattern.compile(" *, *"); @Override public Object convert(String value, TypeLiteral toType) { return Iterables.toArray(Splitter.on(PATTERN).split(value), String.class); } @Override protected void configure() { convertToTypes(Matchers.only(TypeLiteral.get(String[].class)), this); } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/URIConverter.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import com.google.inject.AbstractModule; import com.google.inject.ProvisionException; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.TypeConverter; import java.net.URI; import java.net.URISyntaxException; /** * @author andrew00x */ public class URIConverter extends AbstractModule implements TypeConverter { @Override public Object convert(String value, TypeLiteral toType) { try { return new URI(value); } catch (URISyntaxException e) { throw new ProvisionException(String.format("Invalid URI '%s'", value), e); } } @Override protected void configure() { convertToTypes(Matchers.only(TypeLiteral.get(URI.class)), this); } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/URLConverter.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import com.google.inject.AbstractModule; import com.google.inject.ProvisionException; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.TypeConverter; import java.net.MalformedURLException; import java.net.URL; /** * @author andrew00x */ public class URLConverter extends AbstractModule implements TypeConverter { @Override public Object convert(String value, TypeLiteral toType) { try { return new URL(value); } catch (MalformedURLException e) { throw new ProvisionException(String.format("Invalid URL '%s'", value), e); } } @Override protected void configure() { convertToTypes(Matchers.only(TypeLiteral.get(URL.class)), this); } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/lifecycle/DestroyErrorHandler.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject.lifecycle; import static org.slf4j.LoggerFactory.getLogger; import java.lang.reflect.Method; /** * Helps to be more flexible when need handle errors of invocation destroy-methods. * * @author andrew00x */ public interface DestroyErrorHandler { void onError(Object instance, Method method, Throwable error); /** Implementation of DestroyErrorHandler that log errors. */ DestroyErrorHandler LOG_HANDLER = (instance, method, error) -> getLogger(instance.getClass()).error(error.getMessage(), error); } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/lifecycle/DestroyModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject.lifecycle; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.InjectionListener; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /** * @author andrew00x */ public final class DestroyModule extends LifecycleModule { private final Class annotationType; private final DestroyErrorHandler errorHandler; public DestroyModule( Class annotationType, DestroyErrorHandler errorHandler) { this.annotationType = annotationType; this.errorHandler = errorHandler; } @Override protected void configure() { final Destroyer destroyer = new Destroyer(errorHandler); bind(Destroyer.class).toInstance(destroyer); bindListener( Matchers.any(), new TypeListener() { @Override public void hear(TypeLiteral type, TypeEncounter encounter) { encounter.register( new InjectionListener() { @Override public void afterInjection(T injectee) { final Method[] methods = get(injectee.getClass(), annotationType); if (methods.length > 0) { // copy array when pass it outside final Method[] copy = new Method[methods.length]; System.arraycopy(methods, 0, copy, 0, methods.length); destroyer.add(injectee, copy); } } }); } }); } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/lifecycle/Destroyer.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject.lifecycle; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.WeakHashMap; /** * @author andrew00x */ public final class Destroyer { // Don't prevent instance from being discarded by the garbage collector. private final WeakHashMap map; private final DestroyErrorHandler errorHandler; public Destroyer(DestroyErrorHandler errorHandler) { this.errorHandler = errorHandler; map = new WeakHashMap<>(); } public void add(Object instance, Method[] m) { synchronized (map) { map.put(instance, m); } } public void destroy() { synchronized (map) { for (Map.Entry entry : map.entrySet()) { final Object instance = entry.getKey(); final Method[] methods = entry.getValue(); for (Method method : methods) { try { method.invoke(instance); } catch (IllegalArgumentException e) { // method MUST NOT have any parameters errorHandler.onError(instance, method, e); } catch (IllegalAccessException e) { errorHandler.onError(instance, method, e); } catch (InvocationTargetException e) { errorHandler.onError(instance, method, e.getTargetException()); } } } } } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/lifecycle/InitModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject.lifecycle; import com.google.inject.ProvisionException; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.InjectionListener; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @author andrew00x */ public final class InitModule extends LifecycleModule { private final Class annotationType; public InitModule(Class annotationType) { this.annotationType = annotationType; } @Override protected void configure() { bindListener( Matchers.any(), new TypeListener() { @Override public void hear(TypeLiteral type, TypeEncounter encounter) { encounter.register( new InjectionListener() { @Override public void afterInjection(T injectee) { final Method[] methods = get(injectee.getClass(), annotationType); if (methods.length > 0) { for (Method method : methods) { try { method.invoke(injectee); } catch (IllegalArgumentException e) { // method MUST NOT have any parameters throw new ProvisionException(e.getMessage(), e); } catch (IllegalAccessException e) { throw new ProvisionException( String.format("Failed access to %s on %s", method, injectee), e); } catch (InvocationTargetException e) { final Throwable cause = e.getTargetException(); throw new ProvisionException( String.format( "Invocation error of method %s on %s", method, injectee), cause); } } } } }); } }); } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/java/org/eclipse/che/inject/lifecycle/LifecycleModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject.lifecycle; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.inject.AbstractModule; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; /** * @author andrew00x */ abstract class LifecycleModule extends AbstractModule { private static class Key { final Class type; final Class annotationType; final int hashCode; static Key of(Class type, Class annotationType) { return new Key(type, annotationType); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Key)) { return false; } Key key = (Key) o; return annotationType.equals(key.annotationType) && type.equals(key.type); } @Override public int hashCode() { return hashCode; } private Key(Class type, Class annotationType) { this.type = type; this.annotationType = annotationType; int hash = annotationType.hashCode(); hash = 31 * hash + type.hashCode(); this.hashCode = hash; } } private final LoadingCache cache; @SuppressWarnings("unchecked") LifecycleModule() { cache = CacheBuilder.newBuilder() .maximumSize(1_000) .expireAfterWrite(1, TimeUnit.HOURS) .build( new CacheLoader() { @Override public Method[] load(Key key) throws Exception { return doGet(key.type, key.annotationType); } }); } Method[] get(Class type, Class annotationType) { final Key key = Key.of(type, annotationType); try { return cache.get(key); } catch (ExecutionException e) { // should never happen throw new RuntimeException(e.getLocalizedMessage(), e); } } private Method[] doGet(Class type, Class annotationType) { final List allMethods = getAllMethods(type); final LinkedList methods = new LinkedList<>(); final Set methodNames = new HashSet<>(); for (Method method : allMethods) { if (method.isAnnotationPresent(annotationType) && method.getParameterTypes().length == 0 && method.getReturnType() == void.class && methodNames.add(method.getName())) { method.setAccessible(true); methods.addFirst(method); } } return methods.toArray(new Method[methods.size()]); } private List getAllMethods(Class c) { final List list = new ArrayList<>(); while (c != null && c != Object.class) { Collections.addAll(list, c.getDeclaredMethods()); c = c.getSuperclass(); } return list; } } ================================================ FILE: core/commons/che-core-commons-inject/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer ================================================ org.eclipse.che.inject.ModuleScanner ================================================ FILE: core/commons/che-core-commons-inject/src/test/java/org/eclipse/che/inject/CheBootstrapTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import static java.io.File.pathSeparator; import static java.util.Collections.emptyEnumeration; import static org.eclipse.che.commons.test.SystemPropertiesHelper.overrideSystemProperties; import static org.eclipse.che.inject.CheBootstrap.CHE_LOCAL_CONF_DIR; import static org.eclipse.che.inject.CheBootstrap.PROPERTIES_ALIASES_CONFIG_FILE; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import com.google.common.io.Files; import com.google.inject.Inject; import com.google.inject.Injector; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContextEvent; import java.io.File; import java.io.IOException; import java.io.Writer; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.util.Map; import java.util.Properties; import javax.inject.Named; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.IoUtil; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.test.SystemPropertiesHelper; import org.mockito.ArgumentCaptor; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class CheBootstrapTest { private CheBootstrap cheBootstrap; private ServletContext servletContext; private File che; private File userCongDir; private SystemPropertiesHelper systemPropertiesHelper; @BeforeMethod public void setUp() throws Exception { systemPropertiesHelper = overrideSystemProperties(); cheBootstrap = new CheBootstrap(); servletContext = mock(ServletContext.class); URL classesDirUrl = Thread.currentThread().getContextClassLoader().getResource("."); File classesDir = new File(classesDirUrl.toURI()); che = new File(classesDir, "che"); che.mkdir(); userCongDir = new File(System.getenv(CHE_LOCAL_CONF_DIR)); userCongDir.mkdirs(); mockServletContext(); } @AfterMethod public void tearDown() throws Exception { try { cheBootstrap.contextDestroyed(new ServletContextEvent(servletContext)); } catch (Throwable ignored) { } systemPropertiesHelper.restoreFromBackup(); IoUtil.deleteRecursive(che); IoUtil.deleteRecursive(userCongDir); File aliases = new File(che.getParent(), PROPERTIES_ALIASES_CONFIG_FILE); if (aliases.exists()) { aliases.delete(); } ModuleScanner.modules.clear(); } private Properties createTestProperties() { final Properties properties = new Properties(); properties.put("test_int", "123"); properties.put("test_bool", "true"); properties.put("test_uri", "file:/a/b/c"); properties.put("test_url", "http://localhost"); properties.put("test_file", "/a/b/c"); properties.put("test_strings", "a, b, c"); properties.put("test_pair_of_strings", "a=b"); properties.put("test_pair_of_strings2", "a"); properties.put("test_pair_of_strings3", "a="); properties.put("test_pair_array", "a=b,c=d"); properties.put("some.dir.in_tmp_dir", "${java.io.tmpdir}/some_dir"); properties.put("suffixed.PATH", "${PATH}" + pathSeparator + "some_path"); properties.put("nullable", "NULL"); return properties; } @Test public void readsConfigurationPropertiesFromCheDirectory() throws Exception { writePropertiesFile(che, "che.properties", createTestProperties()); ModuleScanner.modules.add(binder -> binder.bind(TestChePropertiesComponent.class)); cheBootstrap.contextInitialized(new ServletContextEvent(servletContext)); Injector injector = retrieveComponentFromServletContext(Injector.class); TestChePropertiesComponent testComponent = injector.getInstance(TestChePropertiesComponent.class); assertEquals(testComponent.parameter_pair, Pair.of("a", "b")); assertEquals(testComponent.parameter_pair2, Pair.of("a", (String) null)); assertEquals(testComponent.parameter_pair3, Pair.of("a", "")); assertEquals( testComponent.parameter_pair_array, new Pair[] {Pair.of("a", "b"), Pair.of("c", "d")}); assertEquals(testComponent.nullable, null); assertEquals(testComponent.parameter_uri, new URI("file:/a/b/c")); assertEquals(testComponent.parameter_url, new URL("http://localhost")); assertEquals(testComponent.parameter_file, new File("/a/b/c")); assertEquals(testComponent.parameter_strings, new String[] {"a", "b", "c"}); assertEquals(testComponent.parameter_int, 123); assertEquals(testComponent.parameter_long, 123); assertEquals(testComponent.parameter_bool, true); assertEquals( testComponent.someDir, new File(System.getProperty("java.io.tmpdir"), "/some_dir")); assertEquals(testComponent.suffixedPath, System.getenv("PATH") + pathSeparator + "some_path"); } @Test public void readsConfigurationPropertiesFromSystemProperties() throws Exception { setSystemProperties(createTestProperties()); ModuleScanner.modules.add(binder -> binder.bind(TestSystemPropertiesComponent.class)); cheBootstrap.contextInitialized(new ServletContextEvent(servletContext)); Injector injector = retrieveComponentFromServletContext(Injector.class); TestSystemPropertiesComponent testComponent = injector.getInstance(TestSystemPropertiesComponent.class); assertEquals(testComponent.parameter_pair, Pair.of("a", "b")); assertEquals(testComponent.parameter_pair2, Pair.of("a", (String) null)); assertEquals(testComponent.parameter_pair3, Pair.of("a", "")); assertEquals( testComponent.parameter_pair_array, new Pair[] {Pair.of("a", "b"), Pair.of("c", "d")}); assertEquals(testComponent.nullable, null); assertEquals(testComponent.parameter_uri, new URI("file:/a/b/c")); assertEquals(testComponent.parameter_url, new URL("http://localhost")); assertEquals(testComponent.parameter_file, new File("/a/b/c")); assertEquals(testComponent.parameter_strings, new String[] {"a", "b", "c"}); assertEquals(testComponent.parameter_int, 123); assertEquals(testComponent.parameter_long, 123); assertEquals(testComponent.parameter_bool, true); assertEquals( testComponent.someDir, new File(System.getProperty("java.io.tmpdir"), "/some_dir")); assertEquals(testComponent.suffixedPath, System.getenv("PATH") + pathSeparator + "some_path"); } @Test public void readsConfigurationPropertiesFromEnvProperties() throws Exception { ModuleScanner.modules.add(binder -> binder.bind(TestEnvPropertiesComponent.class)); cheBootstrap.contextInitialized(new ServletContextEvent(servletContext)); Injector injector = retrieveComponentFromServletContext(Injector.class); TestEnvPropertiesComponent testComponent = injector.getInstance(TestEnvPropertiesComponent.class); assertEquals(testComponent.string, System.getenv("PATH")); } @Test public void propertiesFromUserSpecifiedLocationOverrideCheProperties() throws Exception { systemPropertiesHelper.property(CHE_LOCAL_CONF_DIR, userCongDir.getAbsolutePath()); Properties cheProperties = new Properties(); cheProperties.put("che.some.name", "che_value"); writePropertiesFile(che, "che.properties", cheProperties); Properties userProperties = new Properties(); userProperties.put("che.some.name", "user_value"); writePropertiesFile(userCongDir, "user.properties", userProperties); ModuleScanner.modules.add(binder -> binder.bind(TestConfOverrideComponent.class)); cheBootstrap.contextInitialized(new ServletContextEvent(servletContext)); Injector injector = retrieveComponentFromServletContext(Injector.class); TestConfOverrideComponent testComponent = injector.getInstance(TestConfOverrideComponent.class); assertEquals(testComponent.string, "user_value"); } @Test public void system_properties_prefixed_with_che_dot_override_user_specified_and_che_properties() throws Exception { Properties cheProperties = new Properties(); cheProperties.put("che.some.name", "che_value"); cheProperties.put("che.some.other.name", "NULL"); writePropertiesFile(che, "che.properties", cheProperties); Properties userProperties = new Properties(); userProperties.put("che.some.name", "user_value"); writePropertiesFile(userCongDir, "user.properties", userProperties); systemPropertiesHelper.property("che.some.name", "che_dot_system_property_value"); ModuleScanner.modules.add(binder -> binder.bind(TestConfOverrideComponent.class)); cheBootstrap.contextInitialized(new ServletContextEvent(servletContext)); Injector injector = retrieveComponentFromServletContext(Injector.class); TestConfOverrideComponent testComponent = injector.getInstance(TestConfOverrideComponent.class); assertEquals(testComponent.string, "che_dot_system_property_value"); } @Test public void environment_variables_prefixed_with_che_underscore_override_che_dot_prefixed_system_and_user_specified_and_che_properties() throws Exception { Properties cheProperties = new Properties(); cheProperties.put("che.some.other.name", "che_value"); cheProperties.put("che.some.name", "NULL"); writePropertiesFile(che, "che.properties", cheProperties); Properties userProperties = new Properties(); userProperties.put("che.some.other.name", "user_value"); writePropertiesFile(userCongDir, "user.properties", userProperties); systemPropertiesHelper.property("che.some.other.name", "che_dot_system_property_value"); ModuleScanner.modules.add(binder -> binder.bind(TestConfOverrideComponent.class)); cheBootstrap.contextInitialized(new ServletContextEvent(servletContext)); Injector injector = retrieveComponentFromServletContext(Injector.class); TestConfOverrideComponent testComponent = injector.getInstance(TestConfOverrideComponent.class); assertEquals(testComponent.otherString, System.getenv("CHE_SOME_OTHER_NAME")); } @Test public void environment_variables_prefixed_with_che_underscore_convert_double_underscores_into_one_underscore_in_variable_name() throws Exception { Properties cheProperties = new Properties(); cheProperties.put("che.some.other.name_with_underscores", "che_value"); cheProperties.put("che.some.name", "NULL"); writePropertiesFile(che, "che.properties", cheProperties); Properties userProperties = new Properties(); userProperties.put("che.some.other.name_with_underscores", "user_value"); writePropertiesFile(userCongDir, "user.properties", userProperties); systemPropertiesHelper.property( "che.some.other.name_with_underscores", "che_dot_system_property_value"); ModuleScanner.modules.add( binder -> binder.bind(TestConfOverrideWithUnderscoresComponent.class)); cheBootstrap.contextInitialized(new ServletContextEvent(servletContext)); Injector injector = retrieveComponentFromServletContext(Injector.class); TestConfOverrideWithUnderscoresComponent testComponent = injector.getInstance(TestConfOverrideWithUnderscoresComponent.class); assertEquals( testComponent.otherString, System.getenv("CHE_SOME_OTHER_NAME__WITH__UNDERSCORES")); } @Test public void processesPropertyAliases() throws Exception { Properties cheProperties = new Properties(); cheProperties.put("very.new.some.name", "some_value"); writePropertiesFile(che, "che.properties", cheProperties); Properties aliases = new Properties(); aliases.put("very.new.some.name", "new.some.name, che.some.name"); writePropertiesFile(che.getParentFile(), PROPERTIES_ALIASES_CONFIG_FILE, aliases); ModuleScanner.modules.add(binder -> binder.bind(TestConfAliasComponent.class)); cheBootstrap.contextInitialized(new ServletContextEvent(servletContext)); Injector injector = retrieveComponentFromServletContext(Injector.class); TestConfAliasComponent testComponent = injector.getInstance(TestConfAliasComponent.class); assertEquals(testComponent.string, "some_value"); assertEquals(testComponent.otherString, "some_value"); assertEquals(testComponent.otherOtherString, "some_value"); } @Test public void processesOld2NewPropertyAliases() throws Exception { Properties cheProperties = new Properties(); cheProperties.put("che.some.name", "some_value"); writePropertiesFile(che, "che.properties", cheProperties); Properties aliases = new Properties(); aliases.put("very.new.some.name", "new.some.name, che.some.name"); writePropertiesFile(che.getParentFile(), PROPERTIES_ALIASES_CONFIG_FILE, aliases); ModuleScanner.modules.add(binder -> binder.bind(TestConfAliasComponent.class)); cheBootstrap.contextInitialized(new ServletContextEvent(servletContext)); Injector injector = retrieveComponentFromServletContext(Injector.class); TestConfAliasComponent testComponent = injector.getInstance(TestConfAliasComponent.class); assertEquals(testComponent.string, "some_value"); assertEquals(testComponent.otherString, "some_value"); assertEquals(testComponent.otherOtherString, "some_value"); } static class TestChePropertiesComponent { @Named("test_int") @Inject int parameter_int; @Named("test_int") @Inject int parameter_long; @Named("test_bool") @Inject boolean parameter_bool; @Named("test_uri") @Inject URI parameter_uri; @Named("test_url") @Inject URL parameter_url; @Named("test_file") @Inject File parameter_file; @Named("test_strings") @Inject String[] parameter_strings; @Named("test_pair_of_strings") @Inject Pair parameter_pair; @Named("test_pair_of_strings2") @Inject Pair parameter_pair2; @Named("test_pair_of_strings3") @Inject Pair parameter_pair3; @Named("test_pair_array") @Inject Pair[] parameter_pair_array; @Named("some.dir.in_tmp_dir") @Inject File someDir; @Named("suffixed.PATH") @Inject String suffixedPath; @Named("nullable") @Inject @Nullable String nullable; } static class TestSystemPropertiesComponent { @Named("sys.test_int") @Inject int parameter_int; @Named("sys.test_int") @Inject int parameter_long; @Named("sys.test_bool") @Inject boolean parameter_bool; @Named("sys.test_uri") @Inject URI parameter_uri; @Named("sys.test_url") @Inject URL parameter_url; @Named("sys.test_file") @Inject File parameter_file; @Named("sys.test_strings") @Inject String[] parameter_strings; @Named("sys.test_pair_of_strings") @Inject Pair parameter_pair; @Named("sys.test_pair_of_strings2") @Inject Pair parameter_pair2; @Named("sys.test_pair_of_strings3") @Inject Pair parameter_pair3; @Named("sys.test_pair_array") @Inject Pair[] parameter_pair_array; @Named("sys.some.dir.in_tmp_dir") @Inject File someDir; @Named("sys.suffixed.PATH") @Inject String suffixedPath; @Named("sys.nullable") @Inject @Nullable String nullable; } static class TestEnvPropertiesComponent { @Named("env.PATH") @Inject String string; } static class TestConfOverrideComponent { @Named("che.some.name") @Inject @Nullable String string; @Named("che.some.other.name") @Inject @Nullable String otherString; } static class TestConfOverrideWithUnderscoresComponent { @Named("che.some.name") @Inject @Nullable String string; @Named("che.some.other.name_with_underscores") @Inject @Nullable String otherString; } static class TestConfAliasComponent { @Named("che.some.name") @Inject String string; @Named("new.some.name") @Inject String otherString; @Named("very.new.some.name") @Inject String otherOtherString; } private void writePropertiesFile(File parent, String name, Properties properties) throws IOException { File propertiesFile = new File(parent, name); try (Writer writer = Files.newWriter(propertiesFile, Charset.forName("UTF-8"))) { properties.store(writer, null); } } private void setSystemProperties(Properties properties) throws IOException { for (Map.Entry entry : properties.entrySet()) { systemPropertiesHelper.property((String) entry.getKey(), (String) entry.getValue()); } } private void mockServletContext() { servletContext = mock(ServletContext.class); when(servletContext.getInitParameterNames()).thenReturn(emptyEnumeration()); when(servletContext.getAttribute(Injector.class.getName())) .thenAnswer(invocation -> retrieveComponentFromServletContext(Injector.class)); } private T retrieveComponentFromServletContext(Class componentType) { ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(componentType); verify(servletContext, atLeastOnce()) .setAttribute(eq(componentType.getName()), argumentCaptor.capture()); return argumentCaptor.getValue(); } } ================================================ FILE: core/commons/che-core-commons-inject/src/test/java/org/eclipse/che/inject/LifecycleTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import static org.eclipse.che.inject.lifecycle.DestroyErrorHandler.LOG_HANDLER; import com.google.inject.Binder; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.inject.lifecycle.DestroyModule; import org.eclipse.che.inject.lifecycle.Destroyer; import org.eclipse.che.inject.lifecycle.InitModule; import org.testng.Assert; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; /** * @author andrew00x */ public class LifecycleTest { Injector injector; @BeforeTest public void init() { injector = Guice.createInjector( new InitModule(PostConstruct.class), new DestroyModule(PreDestroy.class, LOG_HANDLER), new MyModule()); } @Test public void testInit() { TestComponent component = injector.getInstance(TestComponent.class); Assert.assertEquals(component.init, 1, "'init' method must be called just once"); } @Test public void testDestroy() { TestComponent component = injector.getInstance(TestComponent.class); injector.getInstance(Destroyer.class).destroy(); Assert.assertEquals(component.destroy, 1, "'destroy' method must be called just once"); } public static class MyModule implements Module { @Override public void configure(Binder binder) { binder.bind(TestComponent.class); } } public abstract static class SuperClass { int init; int destroy; @PostConstruct public void init() { init++; } @PreDestroy public void destroy() { destroy++; } } @Singleton public static class TestComponent extends SuperClass { @Inject public TestComponent() {} @PostConstruct @Override public void init() { super.init(); } @PreDestroy @Override public void destroy() { super.destroy(); } } } ================================================ FILE: core/commons/che-core-commons-inject/src/test/java/org/eclipse/che/inject/MultiBindingTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import com.google.inject.Binder; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.multibindings.Multibinder; import java.util.HashSet; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; import org.testng.Assert; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; /** * @author andrew00x */ public class MultiBindingTest { Injector injector; @BeforeTest public void init() { injector = Guice.createInjector( new MyModule(), new ServiceModule1(), new ServiceModule2(), new ServiceModule3()); } @Test public void testMultiInject() { TestComponent instance = injector.getInstance(TestComponent.class); Set services = instance.services; Set names = new HashSet<>(services.size()); for (Service service : services) { names.add(service.toString()); } Assert.assertEquals(names.size(), 3); Assert.assertTrue(names.contains("Service1")); Assert.assertTrue(names.contains("Service2")); Assert.assertTrue(names.contains("Service3")); } public static class MyModule implements Module { @Override public void configure(Binder binder) { binder.bind(TestComponent.class); } } public static class ServiceModule1 implements Module { @Override public void configure(Binder binder) { Multibinder multiBinder = Multibinder.newSetBinder(binder, Service.class); multiBinder.addBinding().to(Service1.class); } } public static class ServiceModule2 implements Module { @Override public void configure(Binder binder) { Multibinder multiBinder = Multibinder.newSetBinder(binder, Service.class); multiBinder.addBinding().to(Service2.class); } } public static class ServiceModule3 implements Module { @Override public void configure(Binder binder) { Multibinder multiBinder = Multibinder.newSetBinder(binder, Service.class); multiBinder.addBinding().to(Service3.class); } } @Singleton public static class TestComponent { private final Set services; @Inject public TestComponent(Set services) { this.services = services; } } public interface Service {} @Singleton public static class Service1 implements Service { @Override public String toString() { return "Service1"; } } @Singleton public static class Service2 implements Service { @Override public String toString() { return "Service2"; } } @Singleton public static class Service3 implements Service { @Override public String toString() { return "Service3"; } } } ================================================ FILE: core/commons/che-core-commons-inject/src/test/java/org/eclipse/che/inject/PathConverterTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject; import static org.testng.Assert.assertEquals; import com.google.inject.Binder; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.name.Names; import java.nio.file.Path; import java.nio.file.Paths; import org.testng.annotations.Test; /** * @author Tareq Sharafy */ public class PathConverterTest { @Test public void testConvertPaths() { Injector injector = Guice.createInjector( new PathConverter(), new Module() { @Override public void configure(Binder binder) { binder.bindConstant().annotatedWith(Names.named("abc")).to("aa/bb"); } }); Path path = injector.getInstance(Key.get(Path.class, Names.named("abc"))); assertEquals(Paths.get("aa/bb"), path); } } ================================================ FILE: core/commons/che-core-commons-inject/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: core/commons/che-core-commons-j2ee/pom.xml ================================================ 4.0.0 che-core-commons-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-commons-j2ee jar Che Core :: Commons :: Commons J2ee jakarta.servlet jakarta.servlet-api provided org.apache.tomcat tomcat-catalina provided org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test com.mycila license-maven-plugin **/xemantic/**/*.java ================================================ FILE: core/commons/che-core-commons-j2ee/src/main/java/com/xemantic/tadedon/servlet/CacheDisablingFilter.java ================================================ /* * Copyright 2010 Xemantic * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.xemantic.tadedon.servlet; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Date; /** * Servlet applying "never cache" HTTP headers. * *

See: * *

* *

Created on Aug 6, 2010 * * @author hshsce */ public class CacheDisablingFilter extends SimpleFilter { private static final long ONE_DAY_IN_MILISECONDS = (1000L * 60L * 60L * 24L); /** {@inheritDoc} */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (response instanceof HttpServletResponse) { HttpServletResponse httpResponse = (HttpServletResponse) response; Date now = new Date(); httpResponse.setDateHeader("Date", now.getTime()); httpResponse.setDateHeader("Expires", now.getTime() + ONE_DAY_IN_MILISECONDS); httpResponse.setHeader("Pragma", "no-cache"); httpResponse.setHeader("Cache-control", "no-cache, no-store, must-revalidate"); } chain.doFilter(request, response); } } ================================================ FILE: core/commons/che-core-commons-j2ee/src/main/java/com/xemantic/tadedon/servlet/CacheForcingFilter.java ================================================ /* * Copyright 2010 Xemantic * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.xemantic.tadedon.servlet; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Date; /** * Servlet applying "cache forever" HTTP headers. * *

See: ArticleHttpCaching * *

Created on Aug 6, 2010 * * @author hshsce */ public class CacheForcingFilter extends SimpleFilter { private static final long ONE_MONTH_IN_SECONDS = 60L * 60L * 24L * 30L; private static final long ONE_MONTH_IN_MILISECONDS = 1000L * ONE_MONTH_IN_SECONDS; /** {@inheritDoc} */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (response instanceof HttpServletResponse) { HttpServletResponse httpResponse = (HttpServletResponse) response; Date now = new Date(); httpResponse.setDateHeader("Date", now.getTime()); httpResponse.setDateHeader("Expires", now.getTime() + ONE_MONTH_IN_MILISECONDS); httpResponse.setHeader("Pragma", "no-cache"); httpResponse.setHeader("Cache-control", "public, max-age=" + ONE_MONTH_IN_SECONDS); } chain.doFilter(request, response); } } ================================================ FILE: core/commons/che-core-commons-j2ee/src/main/java/com/xemantic/tadedon/servlet/SimpleFilter.java ================================================ /* * Copyright 2010 Xemantic * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.xemantic.tadedon.servlet; import jakarta.servlet.Filter; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; /** * Servlet {@link Filter} which does not require initialization and clean up. * *

Created on Aug 6, 2010 * * @author hshsce */ public abstract class SimpleFilter implements Filter { /** {@inheritDoc} */ @Override public void init(FilterConfig filterConfig) throws ServletException { /* nothing to do */ } /** {@inheritDoc} */ @Override public void destroy() { /* nothing to do */ } } ================================================ FILE: core/commons/che-core-commons-j2ee/src/main/java/org/eclipse/che/filter/CheCacheDisablingFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.filter; import com.xemantic.tadedon.servlet.CacheDisablingFilter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; /** * Disabling caching for the given URL resource patterns. * * @author Max Shaposhnik (mshaposhnik@codenvy.com) */ public class CheCacheDisablingFilter extends CacheDisablingFilter { private Set actionPatterns = new HashSet<>(); @Override public void init(FilterConfig filterConfig) { Enumeration names = filterConfig.getInitParameterNames(); while (names.hasMoreElements()) { String name = names.nextElement(); if (name.startsWith("pattern")) { actionPatterns.add(Pattern.compile(filterConfig.getInitParameter(name))); } } } /** {@inheritDoc} */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { for (Pattern pattern : actionPatterns) { if (pattern.matcher(((HttpServletRequest) request).getRequestURI()).matches()) { super.doFilter(request, response, chain); return; } } chain.doFilter(request, response); } } ================================================ FILE: core/commons/che-core-commons-j2ee/src/main/java/org/eclipse/che/filter/CheCacheForcingFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.filter; import com.xemantic.tadedon.servlet.CacheForcingFilter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; /** * Forcing caching for the given URL resource patterns. * * @author Max Shaposhnik */ public class CheCacheForcingFilter extends CacheForcingFilter { private Set actionPatterns = new HashSet<>(); @Override public void init(FilterConfig filterConfig) { Enumeration names = filterConfig.getInitParameterNames(); while (names.hasMoreElements()) { String name = names.nextElement(); if (name.startsWith("pattern")) { actionPatterns.add(Pattern.compile(filterConfig.getInitParameter(name))); } } } /** {@inheritDoc} */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { for (Pattern pattern : actionPatterns) { if (pattern.matcher(((HttpServletRequest) request).getRequestURI()).matches()) { super.doFilter(request, response, chain); return; } } chain.doFilter(request, response); } } ================================================ FILE: core/commons/che-core-commons-j2ee/src/test/java/org/eclipse/che/filter/CheCacheDisablingFilterTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.filter; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Max Shaposhnik (mshaposhnik@codenvy.com) */ @Listeners(value = {MockitoTestNGListener.class}) public class CheCacheDisablingFilterTest { @Mock private FilterChain chain; @Mock private HttpServletRequest request; @Mock private HttpServletResponse response; @InjectMocks private CheCacheDisablingFilter filter; @BeforeMethod public void init() throws Exception { filter.init(new MockFilterConfig()); } @Test(dataProvider = "nonCachedPathProvider") public void shouldSetDisablingCacheHeaders(String uri) throws Exception { when(request.getRequestURI()).thenReturn(uri); // when filter.doFilter(request, response, chain); verify(response).setDateHeader(eq("Date"), anyLong()); verify(response).setDateHeader(eq("Expires"), anyLong()); verify(response).setHeader(eq("Cache-control"), eq("no-cache, no-store, must-revalidate")); verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); } @Test(dataProvider = "cachedPathProvider") public void shouldBypassDisablingCacheHeaders(String uri) throws Exception { when(request.getRequestURI()).thenReturn(uri); // when filter.doFilter(request, response, chain); verify(response, never()).setHeader(eq("Cache-control"), anyString()); verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); } @DataProvider(name = "nonCachedPathProvider") public Object[][] nonCachedPathProvider() { return new Object[][] { {"/_app/browserNotSupported.js"}, {"/_app/_app.nocache.js"}, {"/other/_app/something.js"}, {"/other/something.nocache.js"} }; } @DataProvider(name = "cachedPathProvider") public Object[][] cachedPathProvider() { return new Object[][] { {"/other/.something.js"}, {"/app/something.js"}, {"/something/something"} }; } private class MockFilterConfig implements FilterConfig { private final Map filterParams = new HashMap<>(); MockFilterConfig() { this.filterParams.put("pattern1", "^.*\\.nocache\\..*$"); this.filterParams.put("pattern2", "^.*/_app/.*$"); } @Override public String getFilterName() { return this.getClass().getName(); } @Override public ServletContext getServletContext() { throw new UnsupportedOperationException( "The method does not supported in " + this.getClass()); } @Override public String getInitParameter(String key) { return this.filterParams.get(key); } @Override public Enumeration getInitParameterNames() { return Collections.enumeration(filterParams.keySet()); } } } ================================================ FILE: core/commons/che-core-commons-j2ee/src/test/java/org/eclipse/che/filter/CheCacheForcingFilterTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.filter; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Max Shaposhnik (mshaposhnik@codenvy.com) on 1/17/17. */ @Listeners(value = {MockitoTestNGListener.class}) public class CheCacheForcingFilterTest { @Mock private FilterChain chain; @Mock private HttpServletRequest request; @Mock private HttpServletResponse response; @InjectMocks private CheCacheForcingFilter filter; @BeforeMethod public void setUp() throws Exception { filter.init(new MockFilterConfig()); } @Test(dataProvider = "cachedPathProvider") public void shouldSetForceCacheHeaders(String uri) throws Exception { when(request.getRequestURI()).thenReturn(uri); // when filter.doFilter(request, response, chain); verify(response).setDateHeader(eq("Date"), anyLong()); verify(response).setDateHeader(eq("Expires"), anyLong()); verify(response).setHeader(eq("Cache-control"), matches("public, max-age=[0-9]+")); verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); } @Test(dataProvider = "nonCachedPathProvider") public void shouldBypassForceCacheHeaders(String uri) throws Exception { when(request.getRequestURI()).thenReturn(uri); // when filter.doFilter(request, response, chain); verify(response, never()).setHeader(eq("Cache-control"), anyString()); verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); } @DataProvider(name = "cachedPathProvider") public Object[][] cachedPathProvider() { return new Object[][] { {"/_app/browserNotSupported.js"}, {"/_app/_app.cache.js"}, {"/other/_app/cache.js"}, {"/other/something.cache.js"} }; } @DataProvider(name = "nonCachedPathProvider") public Object[][] nonCachedPathProvider() { return new Object[][] { {"/other/.something.js"}, {"/app/something.js"}, {"/something/something"} }; } private class MockFilterConfig implements FilterConfig { private final Map filterParams = new HashMap<>(); MockFilterConfig() { this.filterParams.put("pattern1", "^.*\\.cache\\..*$"); this.filterParams.put("pattern2", "^.*/_app/.*$"); } @Override public String getFilterName() { return this.getClass().getName(); } @Override public ServletContext getServletContext() { throw new UnsupportedOperationException( "The method does not supported in " + this.getClass()); } @Override public String getInitParameter(String key) { return this.filterParams.get(key); } @Override public Enumeration getInitParameterNames() { return Collections.enumeration(filterParams.keySet()); } } } ================================================ FILE: core/commons/che-core-commons-json/pom.xml ================================================ 4.0.0 che-core-commons-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-commons-json jar Che Core :: Commons :: Json helpers org.everrest everrest-core org.testng testng test ================================================ FILE: core/commons/che-core-commons-json/src/main/java/org/eclipse/che/commons/json/JsonHelper.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.json; import java.io.*; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.Collection; import java.util.Map; import org.everrest.core.impl.provider.json.*; /** Tool to serialize/deserialize Java objects to/from JSON representation. */ public class JsonHelper { @SuppressWarnings("unchecked") public static String toJson(O instance) { return toJson(instance, JsonNameConventions.DEFAULT); } @SuppressWarnings("unchecked") public static String toJson(O instance, JsonNameConvention nameConvention) { try { JsonValue json; if (instance.getClass().isArray()) { json = JsonGenerator.createJsonArray(instance); } else if (instance instanceof Collection) { json = JsonGenerator.createJsonArray((Collection) instance); } else if (instance instanceof Map) { json = JsonGenerator.createJsonObjectFromMap((Map) instance); } else { json = JsonGenerator.createJsonObject(instance); } Writer w = new StringWriter(); json.writeTo(new NameConventionJsonWriter(w, nameConvention)); return w.toString(); } catch (JsonException jsone) { // Must not happen since serialize well known object. throw new RuntimeException(jsone.getMessage(), jsone); } } public static O fromJson(String json, Class klass, Type type) throws JsonParseException { return fromJson(parseJson(json), klass, type); } public static O fromJson( String json, Class klass, Type type, JsonNameConvention nameConvention) throws JsonParseException { return fromJson(parseJson(json, nameConvention), klass, type); } public static O fromJson(InputStream json, Class klass, Type type) throws JsonParseException { return fromJson(parseJson(json), klass, type); } public static O fromJson( InputStream json, Class klass, Type type, JsonNameConvention nameConvention) throws JsonParseException { return fromJson(parseJson(json, nameConvention), klass, type); } public static O fromJson(Reader json, Class klass, Type type) throws JsonParseException { return fromJson(parseJson(json), klass, type); } public static O fromJson( Reader json, Class klass, Type type, JsonNameConvention nameConvention) throws JsonParseException { return fromJson(parseJson(json, nameConvention), klass, type); } public static O fromJson(JsonValue jsonValue, Class klass, Type type) throws JsonParseException { try { O instance; if (klass.isArray()) { instance = (O) ObjectBuilder.createArray(klass, jsonValue); } else if (Collection.class.isAssignableFrom(klass)) { Class k = klass; instance = (O) ObjectBuilder.createCollection(k, type, jsonValue); } else if (Map.class.isAssignableFrom(klass)) { Class k = klass; instance = (O) ObjectBuilder.createObject(k, type, jsonValue); } else { instance = ObjectBuilder.createObject(klass, jsonValue); } return instance; } catch (JsonException jsone) { throw new JsonParseException(jsone.getMessage(), jsone); } } public static JsonValue parseJson(String json) throws JsonParseException { return parseJson(new StringReader(json)); } public static JsonValue parseJson(String json, JsonNameConvention nameConvention) throws JsonParseException { return parseJson(new StringReader(json), nameConvention); } public static JsonValue parseJson(InputStream json) throws JsonParseException { return parseJson(new InputStreamReader(json, Charset.forName("UTF-8"))); } public static JsonValue parseJson(InputStream json, JsonNameConvention nameConvention) throws JsonParseException { return parseJson(new InputStreamReader(json, Charset.forName("UTF-8")), nameConvention); } public static JsonValue parseJson(Reader json) throws JsonParseException { return parseJson(json, JsonNameConventions.DEFAULT); } public static JsonValue parseJson(Reader json, JsonNameConvention nameConvention) throws JsonParseException { try { JsonParser parser = new NameConventionJsonParser(nameConvention); parser.parse(json); return parser.getJsonObject(); } catch (JsonException jsone) { throw new JsonParseException(jsone.getMessage(), jsone); } } } ================================================ FILE: core/commons/che-core-commons-json/src/main/java/org/eclipse/che/commons/json/JsonNameConvention.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.json; /** * Abstraction to provide name transformation between JSON and Java names. It helps correct * translate JSON names to correct name of Java fields or methods, e.g translate Java camel-case * name to lowercase JSON names with '-' or '_' separator. Pass implementation of this interface to * methods of {@link JsonHelper} to get required behaviour whe serialize or deserialize objects * to|from JSON. * * @see JsonNameConventions * @see NameConventionJsonParser * @see NameConventionJsonWriter */ public interface JsonNameConvention { /** * Translate Java field name to JSON name, e.g. 'userName' -> 'user_name' * * @param javaName Java field name * @return JSON name */ String toJsonName(String javaName); /** * Translate JSON name to Java field name, e.g. 'user_name' -> 'userName' * * @param jsonName JSON name * @return Java field name */ String toJavaName(String jsonName); } ================================================ FILE: core/commons/che-core-commons-json/src/main/java/org/eclipse/che/commons/json/JsonNameConventions.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.json; /** Prepared name conversion conventions for Java <-> JSON transformation. */ public enum JsonNameConventions implements JsonNameConvention { /** * Default implementation of JsonNameConvention. It does not convert name. * *

How to serialize with JsonHelper: * *

   *    Foo foo = new Foo();
   *    foo.setFooBar("foo");
   *    System.out.println(JsonHelper.toJson(foo, JsonNameConventions.DEFAULT));
   * 
* * Code above prints: * *
   *    {"fooBar":"foo"}
   * 
* *

How to deserialize with JsonHelper: * *

   *    class Foo {
   *       private String fooBar;
   *
   *       public String getFooBar() {
   *          return fooBar;
   *       }
   *
   *       public void setFooBar(String fooBar) {
   *          this.fooBar = fooBar;
   *       }
   *    }
   *
   *    ...
   *
   *    String json = "{\"fooBar\":\"foo\"}";
   *    Foo foo = fromJson(json, Foo.class, null, JsonNameConventions.DEFAULT);
   *    System.out.println(foo.getFooBar());
   * 
* * Code above prints: * *
   *    foo
   * 
*/ DEFAULT() { @Override public String toJsonName(String javaName) { return javaName; } @Override public String toJavaName(String jsonName) { return jsonName; } }, /** * Implementation of JsonNameConvention converts Java camel-case names to lower-case names with * underscore as separator, e.g 'userName' -> 'user_name'. * *

How to serialize with JsonHelper: * *

   *    Foo foo = new Foo();
   *    foo.setFooBar("foo");
   *    System.out.println(JsonHelper.toJson(foo, JsonNameConventions.CAMEL_UNDERSCORE));
   * 
* * Code above prints: * *
   *    {"foo_bar":"foo"}
   * 
* *

How to deserialize with JsonHelper: * *

   *    class Foo {
   *       private String fooBar;
   *
   *       public String getFooBar() {
   *          return fooBar;
   *       }
   *
   *       public void setFooBar(String fooBar) {
   *          this.fooBar = fooBar;
   *       }
   *    }
   *
   *    ...
   *
   *    String json = "{\"foo_bar\":\"foo\"}";
   *    Foo foo = fromJson(json, Foo.class, null, JsonNameConventions.CAMEL_UNDERSCORE);
   *    System.out.println(foo.getFooBar());
   * 
* * Code above prints: * *
   *    foo
   * 
*/ CAMEL_UNDERSCORE() { @Override public String toJsonName(String javaName) { return camelToUnderscored(javaName); } @Override public String toJavaName(String jsonName) { return separateWithToCamel(jsonName, '_'); } }, /** * Implementation of JsonNameConvention converts Java camel-case names to lower-case names with * dash as separator, e.g 'userName' -> 'user_name'. * *

How to serialize with JsonHelper: * *

   *    Foo foo = new Foo();
   *    foo.setFooBar("foo");
   *    System.out.println(JsonHelper.toJson(foo, JsonNameConventions.CAMEL_DASHES));
   * 
* * Code above prints: * *
   *    {"foo-bar":"foo"}
   * 
* *

How to deserialize with JsonHelper: * *

   *    class Foo {
   *       private String fooBar;
   *
   *       public String getFooBar() {
   *          return fooBar;
   *       }
   *
   *       public void setFooBar(String fooBar) {
   *          this.fooBar = fooBar;
   *       }
   *    }
   *
   *    ...
   *
   *    String json = "{\"foo-bar\":\"foo\"}";
   *    Foo foo = fromJson(json, Foo.class, null, JsonNameConventions.CAMEL_DASHES);
   *    System.out.println(foo.getFooBar());
   * 
* * Code above prints: * *
   *    foo
   * 
*/ CAMEL_DASH() { @Override public String toJsonName(String javaName) { return camelToDashed(javaName); } @Override public String toJavaName(String jsonName) { return separateWithToCamel(jsonName, '-'); } }; private static String camelToUnderscored(String src) { return camelToSeparateWith(src, '_'); } private static String camelToDashed(String src) { return camelToSeparateWith(src, '-'); } private static String camelToSeparateWith(String src, char separator) { final StringBuilder sb = new StringBuilder(); final char[] chars = src.toCharArray(); for (char character : chars) { if (Character.isUpperCase(character)) { if (sb.length() > 0) { sb.append(separator); } sb.append(Character.toLowerCase(character)); } else { sb.append(character); } } return sb.toString(); } private static String separateWithToCamel(String src, char separator) { StringBuilder sb = new StringBuilder(); final char[] chars = src.toCharArray(); for (int i = 0, length = chars.length; i < length; i++) { if (chars[i] == separator) { if (i == 0 || i == (length - 1)) { // add as is first or last character sb.append(chars[i]); } else { sb.append(Character.toUpperCase(chars[++i])); } } else { sb.append(chars[i]); } } return sb.toString(); } } ================================================ FILE: core/commons/che-core-commons-json/src/main/java/org/eclipse/che/commons/json/JsonParseException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.json; @SuppressWarnings("serial") public class JsonParseException extends Exception { public JsonParseException(String message, Throwable cause) { super(message, cause); } public JsonParseException(String message) { super(message); } public JsonParseException(Throwable cause) { super(cause); } } ================================================ FILE: core/commons/che-core-commons-json/src/main/java/org/eclipse/che/commons/json/NameConventionJsonParser.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.json; import org.everrest.core.impl.provider.json.JsonHandler; import org.everrest.core.impl.provider.json.JsonParser; /** * JSON parser that support transformation of names in JSON document. * * @see JsonNameConvention * @see JsonNameConventions */ public class NameConventionJsonParser extends JsonParser { public NameConventionJsonParser(JsonNameConvention nameConvention) { super(new NameConventionJsonHandler(nameConvention)); } private static class NameConventionJsonHandler extends JsonHandler { private final JsonNameConvention nameConvention; private NameConventionJsonHandler(JsonNameConvention nameConvention) { this.nameConvention = nameConvention; } @Override public void key(String key) { super.key(nameConvention.toJavaName(key)); } } } ================================================ FILE: core/commons/che-core-commons-json/src/main/java/org/eclipse/che/commons/json/NameConventionJsonWriter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.json; import java.io.Writer; import org.everrest.core.impl.provider.json.JsonException; import org.everrest.core.impl.provider.json.JsonWriter; public class NameConventionJsonWriter extends JsonWriter { private final JsonNameConvention nameConvention; public NameConventionJsonWriter(Writer writer, JsonNameConvention nameConvention) { super(writer); this.nameConvention = nameConvention; } @Override public void writeKey(String key) throws JsonException { super.writeKey(nameConvention.toJsonName(key)); } } ================================================ FILE: core/commons/che-core-commons-json/src/test/java/org/eclipse/che/commons/json/JsonTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.json; import static org.testng.Assert.assertEquals; import org.testng.annotations.Test; public class JsonTest { public static class Foo { private String fooBar; public String getFooBar() { return fooBar; } public void setFooBar(String fooBar) { this.fooBar = fooBar; } } @Test public void testSerializeDefault() throws Exception { String expectedJson = "{\"fooBar\":\"test\"}"; Foo foo = new Foo(); foo.setFooBar("test"); assertEquals(expectedJson, JsonHelper.toJson(foo)); } @Test public void testSerializeUnderscore() throws Exception { String expectedJson = "{\"foo_bar\":\"test\"}"; Foo foo = new Foo(); foo.setFooBar("test"); assertEquals(expectedJson, JsonHelper.toJson(foo, JsonNameConventions.CAMEL_UNDERSCORE)); } @Test public void testSerializeDash() throws Exception { String expectedJson = "{\"foo-bar\":\"test\"}"; Foo foo = new Foo(); foo.setFooBar("test"); assertEquals(expectedJson, JsonHelper.toJson(foo, JsonNameConventions.CAMEL_DASH)); } @Test public void testDeserializeDefault() throws Exception { String json = "{\"fooBar\":\"test\"}"; Foo foo = JsonHelper.fromJson(json, Foo.class, null); assertEquals("test", foo.getFooBar()); } @Test public void testDeserializeUnderscore() throws Exception { String json = "{\"foo_bar\":\"test\"}"; Foo foo = JsonHelper.fromJson(json, Foo.class, null, JsonNameConventions.CAMEL_UNDERSCORE); assertEquals("test", foo.getFooBar()); } @Test public void testDeserializeDash() throws Exception { String json = "{\"foo-bar\":\"test\"}"; Foo foo = JsonHelper.fromJson(json, Foo.class, null, JsonNameConventions.CAMEL_DASH); assertEquals("test", foo.getFooBar()); } } ================================================ FILE: core/commons/che-core-commons-lang/pom.xml ================================================ 4.0.0 che-core-commons-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-commons-lang jar Che Core :: Commons :: Java API extension classes ${project.build.testSourceDirectory}/../resources/findbugs-exclude.xml com.google.guava guava jakarta.ws.rs jakarta.ws.rs-api org.slf4j slf4j-api ch.qos.logback logback-classic test org.mockito mockito-core test org.testng testng test src/test/resources com.mycila license-maven-plugin **/org/eclipse/che/commons/lang/FlushingStreamWriter.java ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/Deserializer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import java.util.Map; /** A deserializer that resolve system properties to allow runtime configuration. */ public class Deserializer { /** * Resolve the variables of type ${my.var} for the current context from the system properties * * @param input the input value * @return the resolve value */ public static String resolveVariables(String input) { return resolveVariables(input, null, true); } /** * Resolve the variables of type ${my.var} for the current context which is composed only of the * given settings * * @param input the input value * @param props a set of parameters to add for the variable resolution * @return the resolve value */ public static String resolveVariables(String input, Map props) { return resolveVariables(input, props, true); } /** * Resolve the variables of type ${my.var} for the current context which is composed of the system * properties and the given settings * * @param input the input value * @param props a set of parameters to add for the variable resolution * @return the resolve value */ public static String resolveVariables( String input, Map props, boolean includeSysProps) { final int NORMAL = 0; final int SEEN_DOLLAR = 1; final int IN_BRACKET = 2; if (input == null) { return input; } if (!includeSysProps && (props == null || props.size() == 0)) { return input; } char[] chars = input.toCharArray(); StringBuffer buffer = new StringBuffer(); boolean properties = false; int state = NORMAL; int start = 0; for (int i = 0; i < chars.length; ++i) { char c = chars[i]; if (c == '$' && state != IN_BRACKET) { state = SEEN_DOLLAR; } else if (c == '{' && state == SEEN_DOLLAR) { buffer.append(input.substring(start, i - 1)); state = IN_BRACKET; start = i - 1; } else if (state == SEEN_DOLLAR) { state = NORMAL; } else if (c == '}' && state == IN_BRACKET) { if (start + 2 == i) { buffer.append("${}"); } else { String value = null; String key = input.substring(start + 2, i); if (props != null) { // Some parameters have been given thus we need to check // inside first String sValue = props.get(key); value = sValue == null || sValue.length() == 0 ? null : sValue; } if (value == null && includeSysProps) { // try to get it from the system properties value = System.getProperty(key); } if (value != null) { properties = true; buffer.append(value); } } start = i + 1; state = NORMAL; } } if (properties == false) { return input; } if (start != chars.length) { buffer.append(input.substring(start, chars.length)); } return buffer.toString(); } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/FlushingStreamWriter.java ================================================ /* * Copyright 2003-2007 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eclipse.che.commons.lang; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; /** * Stream writer which flushes after each write operation. * * @author Guillaume Laforge */ public class FlushingStreamWriter extends OutputStreamWriter { public FlushingStreamWriter(OutputStream out) { super(out); } public void write(char[] cbuf, int off, int len) throws IOException { super.write(cbuf, off, len); flush(); } public void write(int c) throws IOException { super.write(c); flush(); } public void write(String str, int off, int len) throws IOException { super.write(str, off, len); flush(); } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/IoUtil.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import static java.nio.file.FileVisitResult.CONTINUE; import static java.nio.file.FileVisitResult.TERMINATE; import jakarta.ws.rs.HttpMethod; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.channels.FileChannel; import java.nio.file.FileSystem; import java.nio.file.FileSystemAlreadyExistsException; import java.nio.file.FileSystems; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.nio.file.ProviderNotFoundException; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.security.DigestInputStream; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class IoUtil { private static final Logger LOG = LoggerFactory.getLogger(IoUtil.class); private IoUtil() {} /** Represents filter what select any file */ public static final FilenameFilter ANY_FILTER = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return true; } }; /** Represent filter, that excludes .git entries. */ public static final FilenameFilter GIT_FILTER = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return !(".git".equals(name)); } }; /** * Reads bytes from input stream and builds a string from them. * * @param inputStream source stream * @return string * @throws java.io.IOException if any i/o error occur */ public static String readStream(InputStream inputStream) throws IOException { if (inputStream == null) { return null; } ByteArrayOutputStream bout = new ByteArrayOutputStream(); byte[] buf = new byte[8192]; int r; while ((r = inputStream.read(buf)) != -1) { bout.write(buf, 0, r); } return bout.toString("UTF-8"); } /** * Reads bytes from input stream and builds a string from them. InputStream closed after * consumption. * * @param inputStream source stream * @return string * @throws java.io.IOException if any i/o error occur */ public static String readAndCloseQuietly(InputStream inputStream) throws IOException { try { return readStream(inputStream); } catch (IOException e) { LOG.error(e.getLocalizedMessage(), e); throw e; } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { LOG.error(e.getLocalizedMessage(), e); } } } } /** * Looking for resource by given path. If no file exist by this path, method will try to find it * in context. * * @param resource - path to resource * @return - InputStream of resource * @throws IOException when resource is not a file or resource not found */ public static InputStream getResource(String resource) throws IOException { File resourceFile = new File(resource); if (resourceFile.exists() && !resourceFile.isFile()) { throw new IOException(String.format("%s is not a file. ", resourceFile.getAbsolutePath())); } InputStream is = resourceFile.exists() ? new FileInputStream(resourceFile) : Thread.currentThread().getContextClassLoader().getResourceAsStream(resource); if (is == null) { throw new FileNotFoundException(String.format("Resource %s is not found", resource)); } return is; } /** * Lists all children resources. * * @param parent the root path represented in {@link URI} format * @param consumer consumer for children resources * @throws java.io.IOException if any i/o error occur * @throws ProviderNotFoundException if a provider supporting the URI scheme is not installed */ public static void listResources(URI parent, Consumer consumer) throws IOException { FileSystem fileSystem = null; try { if (!"file".equals(parent.getScheme())) { try { fileSystem = FileSystems.newFileSystem(parent, Collections.emptyMap()); } catch (FileSystemAlreadyExistsException ignore) { } } Path root = Paths.get(parent); Files.list(root).forEach(consumer); } finally { // close FS only if only it has been initialized here if (fileSystem != null) { fileSystem.close(); } } } /** Remove directory and all its sub-resources with specified path */ public static boolean removeDirectory(String pathToDir) { return deleteRecursive(new File(pathToDir)); } /** * Remove specified file or directory. * * @param fileOrDirectory the file or directory to cancel * @return true if specified File was deleted and false otherwise */ public static boolean deleteRecursive(File fileOrDirectory) { if (fileOrDirectory.isDirectory()) { File[] list = fileOrDirectory.listFiles(); if (list == null) { return false; } for (File f : list) { if (!deleteRecursive(f)) { return false; } } } if (!fileOrDirectory.delete()) { if (fileOrDirectory.exists()) { return false; } } return true; } /** * Remove specified file or directory. * * @param fileOrDirectory the file or directory to cancel * @param followLinks are symbolic links followed or not? * @return true if specified File was deleted and false otherwise */ public static boolean deleteRecursive(File fileOrDirectory, boolean followLinks) { if (fileOrDirectory.isDirectory()) { // If fileOrDirectory represents a symbolic link to a folder, // do not read a target folder content. Just remove this symbolic link. if (!followLinks && java.nio.file.Files.isSymbolicLink(fileOrDirectory.toPath())) { return !fileOrDirectory.exists() || fileOrDirectory.delete(); } File[] list = fileOrDirectory.listFiles(); if (list == null) { return false; } for (File f : list) { if (!deleteRecursive(f, followLinks)) { return false; } } } if (!fileOrDirectory.delete()) { if (fileOrDirectory.exists()) { return false; } } return true; } /** * Download file. * * @param parent parent directory, may be null then use 'java.io.tmpdir' * @param prefix prefix of temporary file name, may not be null and must be at least * three characters long * @param suffix suffix of temporary file name, may be null * @param url URL for download * @return downloaded file * @throws java.io.IOException if any i/o error occurs */ public static File downloadFile(File parent, String prefix, String suffix, URL url) throws IOException { File file = File.createTempFile(prefix, suffix, parent); URLConnection conn = null; final String protocol = url.getProtocol().toLowerCase(Locale.ENGLISH); try { conn = url.openConnection(); if ("http".equals(protocol) || "https".equals(protocol)) { HttpURLConnection http = (HttpURLConnection) conn; http.setInstanceFollowRedirects(false); http.setRequestMethod(HttpMethod.GET); } try (InputStream input = conn.getInputStream(); FileOutputStream fOutput = new FileOutputStream(file)) { byte[] b = new byte[8192]; int r; while ((r = input.read(b)) != -1) { fOutput.write(b, 0, r); } } } finally { if (conn != null && ("http".equals(protocol) || "https".equals(protocol))) { ((HttpURLConnection) conn).disconnect(); } } return file; } /** * Download file with redirection if got status 301, 302, 303. Will useful in case redirection * http -> https * * @param parent parent directory, may be null then use 'java.io.tmpdir' * @param prefix prefix of temporary file name, may not be null and must be at least * three characters long * @param suffix suffix of temporary file name, may be null * @param url URL for download * @return downloaded file * @throws java.io.IOException if any i/o error occurs */ public static File downloadFileWithRedirect(File parent, String prefix, String suffix, URL url) throws IOException { File file = File.createTempFile(prefix, suffix, parent); URLConnection conn = null; final String protocol = url.getProtocol().toLowerCase(Locale.ENGLISH); try { conn = url.openConnection(); boolean redirect = false; if ("http".equals(protocol) || "https".equals(protocol)) { HttpURLConnection http = (HttpURLConnection) conn; http.setRequestMethod(HttpMethod.GET); int status = http.getResponseCode(); if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_SEE_OTHER) { redirect = true; } if (redirect) { String newUrl = conn.getHeaderField("Location"); // open the new connection again http.disconnect(); conn = new URL(newUrl).openConnection(); http = (HttpURLConnection) conn; http.setRequestMethod(HttpMethod.GET); } } try (InputStream input = conn.getInputStream(); FileOutputStream fOutput = new FileOutputStream(file)) { byte[] b = new byte[8192]; int r; while ((r = input.read(b)) != -1) { fOutput.write(b, 0, r); } } } finally { if (conn != null && ("http".equals(protocol) || "https".equals(protocol))) { ((HttpURLConnection) conn).disconnect(); } } return file; } /** * Copy file or directory to the specified destination. Existed files in destination directory * will be overwritten. * * @param source copy source * @param target copy destination * @param filter copy filter * @throws java.io.IOException if any i/o error occurs */ public static void copy(File source, File target, FilenameFilter filter) throws IOException { copy(source, target, filter, false, true); } /** * Copy file or directory to the specified destination. Existed files in destination directory * will be overwritten. * *

This method use java.nio for coping files. * * @param source copy source * @param target copy destination * @param filter copy filter * @throws java.io.IOException if any i/o error occurs */ public static void nioCopy(File source, File target, FilenameFilter filter) throws IOException { copy(source, target, filter, true, true); } /** * Copy file or directory to the specified destination. * * @param source copy source * @param target copy destination * @param filter copy filter * @param replaceIfExists if true existed files in destination directory will be * overwritten * @throws java.io.IOException if any i/o error occurs */ public static void copy(File source, File target, FilenameFilter filter, boolean replaceIfExists) throws IOException { copy(source, target, filter, false, replaceIfExists); } /** * Copy file or directory to the specified destination. * *

This method use java.nio for coping files. * * @param source copy source * @param target copy destination * @param filter copy filter * @param replaceIfExists if true existed files in destination directory will be * overwritten * @throws java.io.IOException if any i/o error occurs */ public static void nioCopy( File source, File target, FilenameFilter filter, boolean replaceIfExists) throws IOException { copy(source, target, filter, true, replaceIfExists); } private static void copy( File source, File target, FilenameFilter filter, boolean nio, boolean replaceIfExists) throws IOException { if (source.isDirectory()) { if (!(target.exists() || target.mkdirs())) { throw new IOException( String.format("Unable create directory '%s'. ", target.getAbsolutePath())); } if (filter == null) { filter = ANY_FILTER; } String sourceRoot = source.getAbsolutePath(); LinkedList q = new LinkedList<>(); q.add(source); while (!q.isEmpty()) { File current = q.pop(); File[] list = current.listFiles(); if (list != null) { for (File f : list) { if (!filter.accept(current, f.getName())) { continue; } File newFile = new File(target, f.getAbsolutePath().substring(sourceRoot.length() + 1)); if (f.isDirectory()) { if (!(newFile.exists() || newFile.mkdirs())) { throw new IOException( String.format("Unable create directory '%s'. ", newFile.getAbsolutePath())); } if (!f.equals(target)) { q.push(f); } } else { if (nio) { nioCopyFile(f, newFile, replaceIfExists); } else { copyFile(f, newFile, replaceIfExists); } } } } } } else { File parent = target.getParentFile(); if (!(parent.exists() || parent.mkdirs())) { throw new IOException( String.format("Unable create directory '%s'. ", parent.getAbsolutePath())); } if (nio) { nioCopyFile(source, target, replaceIfExists); } else { copyFile(source, target, replaceIfExists); } } } private static void copyFile(File source, File target, boolean replaceIfExists) throws IOException { if (!target.createNewFile()) { if (target.exists() && !replaceIfExists) { throw new IOException( String.format("File '%s' already exists. ", target.getAbsolutePath())); } } byte[] b = new byte[8192]; try (FileInputStream in = new FileInputStream(source); FileOutputStream out = new FileOutputStream(target)) { int r; while ((r = in.read(b)) != -1) { out.write(b, 0, r); } } } private static void nioCopyFile(File source, File target, boolean replaceIfExists) throws IOException { if (!target.createNewFile()) // atomic { if (target.exists() && !replaceIfExists) { throw new IOException( String.format("File '%s' already exists. ", target.getAbsolutePath())); } } try (FileInputStream sourceStream = new FileInputStream(source); FileOutputStream targetStream = new FileOutputStream(target); FileChannel sourceChannel = sourceStream.getChannel(); FileChannel targetChannel = targetStream.getChannel()) { final long size = sourceChannel.size(); long transferred = 0L; while (transferred < size) { transferred += targetChannel.transferFrom(sourceChannel, transferred, (size - transferred)); } } } public static List list(File dir, FilenameFilter filter) { if (!dir.isDirectory()) { throw new IllegalArgumentException("Not a directory. "); } if (filter == null) { filter = ANY_FILTER; } List files = new ArrayList<>(); LinkedList q = new LinkedList<>(); q.add(dir); while (!q.isEmpty()) { File current = q.pop(); File[] list = current.listFiles(); if (list != null) { for (File f : list) { if (!filter.accept(current, f.getName())) { continue; } if (f.isDirectory()) { q.push(f); } else { files.add(f); } } } } return files; } public static String countFileHash(File file, MessageDigest digest) throws IOException { byte[] b = new byte[8192]; try (DigestInputStream dis = new DigestInputStream(new FileInputStream(file), digest)) { while (dis.read(b) != -1) ; return toHex(digest.digest()); } } private static final char[] HEX = "0123456789abcdef".toCharArray(); public static String toHex(byte[] hash) { StringBuilder b = new StringBuilder(); for (int i = 0; i < hash.length; i++) { b.append(HEX[(hash[i] >> 4) & 0x0f]); b.append(HEX[hash[i] & 0x0f]); } return b.toString(); } /** * Detects and returns {@code Path} to file by name pattern. * * @param pattern file name pattern * @param folder path to folder that contains project sources * @return pom.xml path * @throws java.io.IOException if an I/O error is thrown while finding pom.xml * @throws IllegalArgumentException if pom.xml not found */ public static File findFile(String pattern, File folder) throws IOException { Finder finder = new Finder(pattern); Files.walkFileTree( folder.toPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, finder); if (finder.getFirstMatchedFile() == null) { throw new IllegalArgumentException("File not found."); } return finder.getFirstMatchedFile().toFile(); } /** A {@code FileVisitor} that finds first file that match the specified pattern. */ private static class Finder extends SimpleFileVisitor { private final PathMatcher matcher; private Path firstMatchedFile; Finder(String pattern) { matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern); } /** {@inheritDoc} */ @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Path fileName = file.getFileName(); if (fileName != null && matcher.matches(fileName)) { firstMatchedFile = file; return TERMINATE; } return CONTINUE; } /** Returns the first matched {@link java.nio.file.Path}. */ Path getFirstMatchedFile() { return firstMatchedFile; } } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/NameGenerator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import java.security.SecureRandom; import java.util.Random; public class NameGenerator { private static final Random RANDOM = new SecureRandom(); private static final char[] CHARS = new char[36]; static { int i = 0; // [0..9] for (int c = 48; c <= 57; c++) { CHARS[i++] = (char) c; } // [a-z] for (int c = 97; c <= 122; c++) { CHARS[i++] = (char) c; } } public static String generate(String prefix, int length) { return generate(prefix, null, length); } public static String generate(String prefix, String suffix, int length) { int bufLength = length; if (prefix != null) { bufLength += prefix.length(); } if (suffix != null) { bufLength += suffix.length(); } final StringBuilder buf = new StringBuilder(bufLength); if (prefix != null && !prefix.isEmpty()) { buf.append(prefix); } for (int i = 0; i < length; i++) { buf.append(CHARS[RANDOM.nextInt(CHARS.length)]); } if (suffix != null && !suffix.isEmpty()) { buf.append(suffix); } return buf.toString(); } private NameGenerator() {} } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/Pair.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; /** * @author andrew00x */ public final class Pair { public static Pair of(K k, V v) { return new Pair<>(k, v); } public final A first; public final B second; private final int hashCode; public Pair(A first, B second) { this.first = first; this.second = second; int hashCode = 7; hashCode = hashCode * 31 + (first == null ? 0 : first.hashCode()); hashCode = hashCode * 31 + (second == null ? 0 : second.hashCode()); this.hashCode = hashCode; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Pair)) { return false; } final Pair other = (Pair) o; return (first == null ? other.first == null : first.equals(other.first)) && (second == null ? other.second == null : second.equals(other.second)); } @Override public int hashCode() { return hashCode; } @Override public String toString() { return "{first=" + first + ", second=" + second + '}'; } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/PathUtil.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import java.io.File; import java.io.IOException; import java.util.function.Supplier; /** Set of util methods for path strings. */ public class PathUtil { /** * Converts path to canonical form via traversing '..' and eliminating '.' and removing duplicate * separators. Note: This method works for Unix paths only. */ public static String toCanonicalPath(String path, boolean removeLastSlash) { if (path == null || path.isEmpty()) { return path; } if (path.charAt(0) == '.') { if (path.length() == 1) { return ""; } char c = path.charAt(1); if (c == '/') { path = path.substring(2); } } int index = -1; do { index = path.indexOf('/', index + 1); char next = index == path.length() - 1 ? 0 : path.charAt(index + 1); if (next == '.' || next == '/') { break; } } while (index != -1); if (index == -1) { if (removeLastSlash) { int start = processRoot(path, NullWriter.INSTANCE); int slashIndex = path.lastIndexOf('/'); return slashIndex != -1 && slashIndex > start ? StringUtils.trimEnd(path, '/') : path; } return path; } String finalPath = path; Supplier canonicalPath = () -> { try { return new File(finalPath).getCanonicalPath(); } catch (IOException ignore) { return toCanonicalPath(finalPath, removeLastSlash); } }; StringBuilder result = new StringBuilder(path.length()); int start = processRoot(path, result); int dots = 0; boolean separator = true; for (int i = start; i < path.length(); ++i) { char c = path.charAt(i); if (c == '/') { if (!separator) { if (!processDots(result, dots, start)) { return canonicalPath.get(); } dots = 0; } separator = true; } else if (c == '.') { if (separator || dots > 0) { ++dots; } else { result.append('.'); } separator = false; } else { if (dots > 0) { StringUtils.repeatSymbol(result, '.', dots); dots = 0; } result.append(c); separator = false; } } if (dots > 0) { if (!processDots(result, dots, start)) { return canonicalPath.get(); } } int lastChar = result.length() - 1; if (removeLastSlash && lastChar >= 0 && result.charAt(lastChar) == '/' && lastChar > start) { result.deleteCharAt(lastChar); } return result.toString(); } private static boolean processDots(StringBuilder result, int dots, int start) { if (dots == 2) { int pos = -1; if (!StringUtils.endWith(result, "/../") && !StringUtils.equals(result, "../")) { pos = StringUtils.lastIndexOf(result, '/', start, result.length() - 1); if (pos >= 0) { ++pos; } else if (start > 0) { pos = start; } else if (result.length() > 0) { pos = 0; } } if (pos >= 0) { result.delete(pos, result.length()); } else { result.append("../"); } } else if (dots != 1) { StringUtils.repeatSymbol(result, '.', dots); result.append('/'); } return true; } private static int processRoot(String path, Appendable appendable) { try { if (!path.isEmpty() && path.charAt(0) == '/') { appendable.append('/'); return 1; } if (path.length() > 2 && path.charAt(1) == ':' && path.charAt(2) == '/') { appendable.append(path, 0, 3); return 3; } } catch (IOException e) { throw new RuntimeException(e); } return 0; } private static final class NullWriter implements Appendable { public static final NullWriter INSTANCE = new NullWriter(); @Override public Appendable append(CharSequence csq) throws IOException { return this; } @Override public Appendable append(CharSequence csq, int start, int end) throws IOException { return this; } @Override public Appendable append(char c) throws IOException { return this; } } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/Size.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Helper for converting memory size to human readable representation and back, e.g 12K, 12kB, 1M, * etc. * * @author andrew00x */ public class Size { /** * Convert memory to human readable representation, e.g. 1 kB * * @param sizeInBytes size in bytes * @return memory in human readable format * @throws java.lang.IllegalArgumentException if {@code sizeInBytes} is negative */ public static String toHumanSize(long sizeInBytes) { if (sizeInBytes < 0) { throw new IllegalArgumentException(String.format("Negative size: %d", sizeInBytes)); } if (sizeInBytes < K) { return String.format("%d B", sizeInBytes); } float size = 0.0f; String suffix = "PB"; for (int i = 0, l = SIZE_UNITS.length; i < l; i++) { Pair sizeUnit = SIZE_UNITS[i]; if (sizeInBytes >= sizeUnit.first) { size = (float) sizeInBytes / sizeUnit.first; suffix = sizeUnit.second; break; } } return String.format((size % 1.0f == 0) ? "%.0f %s" : "%.1f %s", size, suffix); } /** * Parse human readable size string to long size in bytes. * * @param humanSize human readable size string * @return long size in bytes * @throws IllegalArgumentException if {@code humanSize} has incorrect format */ public static long parseSize(String humanSize) { return parseAndConvertToBytes(humanSize); } /** * Parse human readable size string to long size in megabytes. * * @param humanSize human readable size string * @return long size in megabytes * @throws IllegalArgumentException if {@code humanSize} has incorrect format */ public static long parseSizeToMegabytes(String humanSize) { return parseAndConvertToBytes(humanSize) / M; } private static final long K = 1024; private static final long M = K * K; private static final long G = M * K; private static final long T = G * K; private static final long P = T * K; @SuppressWarnings("unchecked") private static Pair[] SIZE_UNITS = new Pair[] { Pair.of(P, "PB"), Pair.of(T, "TB"), Pair.of(G, "GB"), Pair.of(M, "MB"), Pair.of(K, "kB") }; private static final Pattern HUMAN_SIZE_PATTERN = Pattern.compile("^([0-9]*(\\.[0-9]+)?)\\s*(\\S+)?$"); private static long parseAndConvertToBytes(String sizeString) { final Matcher matcher; if ((matcher = HUMAN_SIZE_PATTERN.matcher(sizeString)).matches()) { final float size = Float.parseFloat(matcher.group(1)); final String suffix = matcher.group(3); if (suffix == null) { return (long) size; } final String suffixL = suffix.toLowerCase(Locale.ENGLISH); switch (suffixL) { case "b": return (long) (size); case "k": case "kb": case "kib": return (long) (size * K); case "m": case "mb": case "mib": return (long) (size * M); case "g": case "gb": case "gib": return (long) (size * G); case "t": case "tb": case "tib": return (long) (size * T); case "p": case "pb": case "pib": return (long) (size * P); } } throw new IllegalArgumentException("Invalid size: " + sizeString); } private Size() {} } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/StringUtils.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.base.Splitter; import com.google.common.collect.Sets; import java.util.Collections; import java.util.Set; /** Set of useful String methods */ public class StringUtils { /** * Trim last char of the string if string ends with that char. * * @param s the string to trim * @param suffix the suffix * @return trimmed string */ public static String trimEnd(String s, char suffix) { if (endWithChar(s, suffix)) { return s.substring(0, s.length() - 1); } return s; } /** Check if string ends with char. */ public static boolean endWithChar(String s, char suffix) { return s != null && !s.isEmpty() && s.charAt(s.length() - 1) == suffix; } /** Add to builder 'times' symbol 'symbol' */ public static void repeatSymbol(StringBuilder builder, char symbol, int times) { for (int i = 0; i < times; i++) { builder.append(symbol); } } /** Check if CharSequence end with suffix */ public static boolean endWith(CharSequence builder, CharSequence suffix) { int bl = builder.length(); int sl = suffix.length(); if (bl < sl) { return false; } for (int i = bl - 1; i >= bl - sl; i--) { if (builder.charAt(i) != suffix.charAt(i + sl - bl)) { return false; } } return true; } /** Check that char sequences are equal */ public static boolean equals(CharSequence c1, CharSequence c2) { if (c1 == null ^ c2 == null) { return false; } if (c1 == null) { return true; } if (c1.length() != c2.length()) { return false; } for (int i = 0; i < c1.length(); i++) { if (c1.charAt(i) != c2.charAt(i)) { return false; } } return true; } /** * Returns the index within this string of the last occurrence of the specified char, searching in * specified range */ public static int lastIndexOf(CharSequence s, char c, int start, int end) { start = Math.max(start, 0); for (int i = Math.min(end, s.length()) - 1; i >= start; i--) { if (s.charAt(i) == c) { return i; } } return -1; } /** Parse string to set of strings. String should be comma separated. Whitespaces are trimmed. */ public static Set strToSet(String str) { return strToSet(str, ","); } /** * Parse string to set of strings using the specified separator. Whitespaces are trimmed. * * @param str the string to parse * @param delimiter the delimiter to split on * @return set of strings */ public static Set strToSet(String str, String delimiter) { if (!isNullOrEmpty(str)) { return Sets.newHashSet(Splitter.on(delimiter).trimResults().omitEmptyStrings().split(str)); } else { return Collections.emptySet(); } } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/TopologicalSort.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import static com.google.common.collect.Maps.newLinkedHashMapWithExpectedSize; import static java.util.Collections.emptyList; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; import java.util.function.Function; import java.util.stream.Collectors; /** * This is an implementation of a stable topological sort on a directed graph. * *

The sorting does not pose any requirements on the types being sorted. Instead, the * implementation merely requires a function to provide it with a set of predecessors of a certain * "node". The implementation of the function is completely in the hands of the caller. * *

Additionally, a function to extract an "ID" from a node is required. The reasoning behind this * is that it usually is easier to work with identifiers when establishing the predecessors than * with the full node instances. That said, nothing prevents the caller from using the actual node * instance as its ID if the caller so wishes. The consequence of this is that, as a side-effect of * the sorting, the duplicates, as determined by the equality of {@code ID} instances, are removed * from the resulting sorted list. * * @param the type of nodes * @param the type of a node ID */ public final class TopologicalSort { private final Function identityExtractor; private final Function> directPredecessorsExtractor; /** * @param identityExtractor a function to extract some kind of value uniquely identifying a node * amongst the others. * @param directPredecessorsExtractor a function returning a list of ids of direct predecessors of * a node */ public TopologicalSort( Function identityExtractor, Function> directPredecessorsExtractor) { this.identityExtractor = identityExtractor; this.directPredecessorsExtractor = directPredecessorsExtractor; } /** * Given the function for determining the predecessors of the nodes, return the list of the nodes * in topological order. I.e. all predecessors will be placed sooner in the list than their * successors. Note that the input collection is assumed to contain no duplicate entries as * determined by the equality of the {@code ID} type. If such duplicates are present in the input * collection, the output list will only contain the first instance of the duplicates from the * input collection. * *

The implemented sort algorithm is stable. If there is no relationship between 2 nodes, they * retain the relative position to each other as they had in the provided collection (e.g. if "a" * preceded "b" in the original collection and there is no relationship between them (as * determined by the predecessor function), the "a" will still precede "b" in the resulting list. * Other nodes may be inserted in between them though in the result). * *

The cycles in the graph determined by the predecessor function are ignored and nodes in the * cycle are placed into the output list in the source order. * * @param nodes the collection of nodes * @return the list of nodes sorted in topological order */ public List sort(Collection nodes) { // the linked hashmap is important to retain the original order of elements unless required // by the dependencies between nodes LinkedHashMap> nodeInfos = newLinkedHashMapWithExpectedSize(nodes.size()); List> results = new ArrayList<>(nodes.size()); int pos = 0; boolean needsSorting = false; for (N node : nodes) { ID nodeID = identityExtractor.apply(node); // we need the set to be modifiable, so let's make our own Set preds = new HashSet<>(directPredecessorsExtractor.apply(node)); needsSorting = needsSorting || !preds.isEmpty(); NodeInfo nodeInfo = nodeInfos.computeIfAbsent(nodeID, __ -> new NodeInfo<>()); nodeInfo.id = nodeID; nodeInfo.predecessors = preds; nodeInfo.sourcePosition = pos++; nodeInfo.node = node; for (ID pred : preds) { // note that this means that we're inserting the nodeinfos into the map in an incorrect // order and will have to sort them in the source order before we do the actual topo sort. // We take that cost because we gamble on there being no dependencies in the nodes as a // common case. NodeInfo predNode = nodeInfos.computeIfAbsent(pred, __ -> new NodeInfo<>()); if (predNode.successors == null) { predNode.successors = new HashSet<>(); } predNode.successors.add(nodeID); } } if (needsSorting) { // because of the predecessors, we have put the nodeinfos in the map in an incorrect order. // we need to correct that before we try to sort... TreeSet> tmp = new TreeSet<>(Comparator.comparingInt(a -> a.sourcePosition)); tmp.addAll(nodeInfos.values()); nodeInfos.clear(); tmp.forEach(ni -> nodeInfos.put(ni.id, ni)); // now we're ready to produce the results sort(nodeInfos, results); } else { // we don't need to sort, but we need to keep the expected behavior of removing the duplicates results = new ArrayList<>(nodeInfos.values()); } return results.stream().map(ni -> ni.node).collect(Collectors.toList()); } private void sort(LinkedHashMap> nodes, List> results) { while (!nodes.isEmpty()) { NodeInfo curr = removeFirstIndependent(nodes); if (curr != null) { // yay, simple. Just add the found independent node to the results. results.add(curr); } else { // ok, there is a cycle in the graph. Let's remove all the nodes in the first cycle we find // from our predecessors map, add them to the result in their original order and try to // continue normally // find the first cycle in the predecessors (in the original list order) Iterator> nexts = nodes.values().iterator(); List> cycle; do { curr = nexts.next(); cycle = findCycle(curr, nodes); } while (cycle.isEmpty() && nexts.hasNext()); // If we ever find a graph that doesn't have any independent node, yet we fail to find a // cycle in it, the universe must be broken. if (cycle.isEmpty()) { throw new IllegalStateException( String.format( "Failed to find a cycle in a graph that doesn't seem to have any independent" + " node. This should never happen. Please file a bug. Current state of the" + " sorting is: nodes=%s, results=%s", nodes.toString(), results.toString())); } cycle.sort(Comparator.comparingInt(a -> a.sourcePosition)); for (NodeInfo n : cycle) { removePredecessorMapping(nodes, n); results.add(n); } } } } private void removePredecessorMapping(Map> nodes, NodeInfo node) { forgetNodeInSuccessors(node, nodes); nodes.remove(node.id); } private void forgetNodeInSuccessors(NodeInfo node, Map> nodes) { if (node.successors != null) { for (ID succ : node.successors) { NodeInfo succNode = nodes.get(succ); if (succNode != null) { succNode.predecessors.remove(node.id); } } } } private List> findCycle(NodeInfo node, Map> nodes) { // bail out quickly if there are no preds - should be fairly common occurrence hopefully Set preds = node.predecessors; if (preds == null || preds.isEmpty()) { return emptyList(); } List> ret = new ArrayList<>(); List todo = new ArrayList<>(preds); Set visited = new HashSet<>(); while (!todo.isEmpty()) { ID n = todo.remove(0); if (visited.contains(n)) { continue; } visited.add(n); NodeInfo predNode = nodes.get(n); if (predNode != null) { todo.addAll(predNode.predecessors); ret.add(predNode); if (predNode.equals(node)) { // we found the cycle to our original node return ret; } } } return emptyList(); } private NodeInfo removeFirstIndependent(Map> nodes) { Iterator>> it = nodes.entrySet().iterator(); while (it.hasNext()) { Entry> e = it.next(); if (e.getValue().predecessors.isEmpty()) { it.remove(); NodeInfo ret = e.getValue(); forgetNodeInSuccessors(ret, nodes); return ret; } } return null; } private static final class NodeInfo { private ID id; private int sourcePosition; private Set predecessors; private Set successors; private N node; } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/URLEncodedUtils.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.*; /** A collection of utilities for encoding URLs. */ public class URLEncodedUtils { private static final String PARAMETER_SEPARATOR = "&"; private static final String NAME_VALUE_SEPARATOR = "="; /** * Returns Map> as built from the URI's query portion. For example, a URI of * http://example.org/path/to/file?a=1&b=2&c=3&c=4 would return a Map three key is a name name of * parameter Set is a values, a={1}, one for b={2} and two for c={3,4}. * *

* *

This is typically useful while parsing an HTTP PUT. * * @param uri uri to parse * @param encoding encoding to use while parsing the query. */ public static Map> parse(final URI uri, final String encoding) { Map> result = Collections.emptyMap(); final String query = uri.getRawQuery(); if (query != null && query.length() > 0) { result = new HashMap<>(); parse(result, new Scanner(query), encoding, true); } return result; } /** * Returns Map> as built from the URI's query portion. Parameters encoding * does not performed. For example, a URI of http://example.org/path/to/file?a=1&b=2&c=3&c=4 would * return a Map three key is a name name of parameter Set is a values, a={1}, one for * b={2} and two for c={3,4}. * *

* *

This is typically useful while parsing an HTTP PUT. * * @param uri uri to parse */ public static Map> parse(final URI uri) { return parse(uri, true); } /** * Returns Map> as built from the URI's query portion. Parameters encoding * does not performed. For example, a URI of http://example.org/path/to/file?a=1&b=2&c=3&c=4 would * return a Map three key is a name name of parameter Set is a values, a={1}, one for * b={2} and two for c={3,4}. * *

* *

This is typically useful while parsing an HTTP PUT. * * @param uri uri to parse * @param decodeQueryParam decode query parameter or not. */ public static Map> parse(final URI uri, boolean decodeQueryParam) { Map> result = Collections.emptyMap(); final String query = uri.getRawQuery(); if (query != null && query.length() > 0) { result = new HashMap<>(); parse(result, new Scanner(query), null, decodeQueryParam); } return result; } /** * Adds all parameters within the Scanner to the list of parameters, as encoded by * encoding. If encoding is null encoding does not performed. For * example, a scanner containing the string a=1&b=2&c=3 would add the Map> a=1, b=2, and c=3 to the list of parameters. * * @param parameters List to add parameters to. * @param scanner Input that contains the parameters to parse. * @param encoding Encoding to use when decoding the parameters. If encoding is null encoding does * not performed. */ private static void parse( final Map> parameters, final Scanner scanner, final String encoding, boolean decodeQueryParam) { scanner.useDelimiter(PARAMETER_SEPARATOR); while (scanner.hasNext()) { final String[] nameValue = scanner.next().split(NAME_VALUE_SEPARATOR); if (nameValue.length == 0 || nameValue.length > 2) throw new IllegalArgumentException("bad parameter"); final String name = decodeQueryParam ? decode(nameValue[0], encoding) : nameValue[0]; String value = null; if (nameValue.length == 2) value = decodeQueryParam ? decode(nameValue[1], encoding) : nameValue[1]; Set values = parameters.get(name); if (values == null) { values = new LinkedHashSet<>(); parameters.put(name, values); } if (value != null) { values.add(value); } } } /** * Returns a String that is suitable for use as an application/x-www-form-urlencoded * list of parameters in an HTTP PUT or HTTP POST. * * @param parameters The parameters to include. * @param encoding The encoding to use. */ public static String format(final Map> parameters, final String encoding) { final StringBuilder result = new StringBuilder(); for (Map.Entry> parameter : parameters.entrySet()) { final String encodedName = encode(parameter.getKey(), encoding); final Set values = parameter.getValue(); if (values == null || values.size() == 0) { result.append(encodedName); result.append(NAME_VALUE_SEPARATOR); result.append(""); } else { for (String value : values) { final String encodedValue = value != null ? encode(value, encoding) : ""; if (result.length() > 0) result.append(PARAMETER_SEPARATOR); result.append(encodedName); result.append(NAME_VALUE_SEPARATOR); result.append(encodedValue); } } } return result.toString(); } private static String decode(final String content, final String encoding) { try { return URLDecoder.decode(content, encoding != null ? encoding : "UTF-8"); } catch (UnsupportedEncodingException problem) { throw new IllegalArgumentException(problem); } } private static String encode(final String content, final String encoding) { try { return URLEncoder.encode(content, encoding != null ? encoding : "UTF-8"); } catch (UnsupportedEncodingException problem) { throw new IllegalArgumentException(problem); } } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/UrlUtils.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import static com.google.common.base.Strings.isNullOrEmpty; import jakarta.ws.rs.core.UriInfo; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** TODO replace this class with URLEncodedUtils */ public class UrlUtils { /** * Retrieve query parameters map from String representation of url * * @param url * @return - Map with parameters names as map keys and parameters values as map * values * @throws UnsupportedEncodingException */ public static Map> getQueryParameters(URL url) throws UnsupportedEncodingException { Map> params = new HashMap<>(); String query = url.getQuery(); if (query != null) { for (String param : query.split("&")) { String pair[] = param.split("="); String key = URLDecoder.decode(pair[0], "UTF-8"); String value = null; if (pair.length > 1) { value = URLDecoder.decode(pair[1], "UTF-8"); } List values = params.get(key); if (values == null) { values = new ArrayList<>(); params.put(key, values); } values.add(value); } } return params; } /** * Get state query parameter from request url. * * @return state parameter encoded in application/x-www-form-urlencoded MIME format */ public static String getState(URL requestUrl) { final String query = requestUrl.getQuery(); if (!isNullOrEmpty(query)) { int start = query.indexOf("state="); if (start < 0) { return null; } int end = query.indexOf('&', start); if (end < 0) { end = query.length(); } // {@code OAuthAuthenticator} encodes state parameter in UTF-8 return URLDecoder.decode(query.substring(start + 6, end), StandardCharsets.UTF_8); } return null; } /** * Retrieve parameters map from state parameter. * * @param state state parameter encoded in application/x-www-form-urlencoded MIME format * @return - Map with parameters names as map keys and parameters values as map * values */ public static Map> getQueryParametersFromState(String state) { Map> params = new HashMap<>(); if (isNullOrEmpty(state)) { return params; } try { for (String pair : URLDecoder.decode(state, "UTF-8").split("&")) { if (pair.isEmpty()) { continue; } String name; String value; int eq = pair.indexOf('='); if (eq < 0) { name = pair; value = ""; } else { name = pair.substring(0, eq); value = pair.substring(eq + 1); } List paramValues = params.computeIfAbsent(name, values -> new ArrayList<>()); paramValues.add(value); } } catch (UnsupportedEncodingException ignored) { // should never happen, UTF-8 supported. } return params; } /** * Extract parameter value from given parameters map. * * @param parameters Map with parameters names as map keys and parameters values as * map values * @param name name of the requested parameter * @return parameter value or null if it was not found in parameters map */ public static String getParameter(Map> parameters, String name) { List l = parameters.get(name); if (!(l == null || l.isEmpty())) { return l.get(0); } return null; } /** Extract URL from given URI information. */ public static URL getRequestUrl(UriInfo uriInfo) { try { return uriInfo.getRequestUri().toURL(); } catch (MalformedURLException e) { // should never happen throw new RuntimeException(e.getMessage(), e); } } private UrlUtils() {} } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/ZipUtils.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.function.Consumer; import java.util.regex.Pattern; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; /** * Utils for ZIP. * * @author Eugene Voevodin * @author Sergii Kabashniuk * @author Thomas Mäder */ public class ZipUtils { private static final int BUF_SIZE = 4096; private static class ExceptionWrapper extends RuntimeException { private static final long serialVersionUID = 1L; public ExceptionWrapper(Throwable cause) { super(cause); } } /** * Creates a {@link ZipOutputStream} with proper buffering and options * * @param zip * @return a newly opened stream * @throws FileNotFoundException */ public static ZipOutputStream stream(Path zip) throws FileNotFoundException { ZipOutputStream result = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zip.toFile()))); result.setLevel(0); return result; } /** * This is equivalent to writing {@code #add(out, f, f.getParent())} * * @param out the stream to write to * @param f the file or directory * @throws IOException */ public static void add(ZipOutputStream out, Path f) throws IOException { add(out, f, f.getParent()); } /** * Recursively add the contents of the given file or directory to a {@link ZipOutputStream} * * @param out The zip file to add to * @param f the file or directory to add to * @param rootPath The path the zip entries are relative to. If f is /a/b/c and rootPath is /a, * the entry corresponding to f will be named b/c. Must be a prefix of f. * @throws IOException */ public static void add(ZipOutputStream out, Path f, Path rootPath) throws IOException { if (!f.startsWith(rootPath)) { throw new IllegalArgumentException( "'" + String.valueOf(rootPath) + "' is not a prefix of '" + String.valueOf(f) + "'"); } if (Files.isDirectory(f)) { addDirectory(out, f, rootPath); } else { addFileEntry(out, relativePath(rootPath, f), f.toFile()); } } private static void addDirectory(ZipOutputStream out, Path d, Path root) throws IOException { if (!root.equals(d)) { addDirectoryEntry(out, relativePath(root, d)); } try (Stream entries = Files.list(d)) { entries.forEach( path -> { try { add(out, path, root); } catch (IOException e) { throw new ExceptionWrapper(e); } }); } catch (ExceptionWrapper e) { throw (IOException) e.getCause(); } } private static String relativePath(Path root, Path f) { return root.relativize(f).toString().replaceAll(File.pathSeparator, "/"); } private static void addDirectoryEntry(ZipOutputStream zipOut, String entryName) throws IOException { final ZipEntry zipEntry = new ZipEntry(entryName.endsWith("/") ? entryName : (entryName + '/')); zipOut.putNextEntry(zipEntry); zipOut.closeEntry(); } private static void addFileEntry(ZipOutputStream zipOut, String entryName, File file) throws IOException { final ZipEntry zipEntryEntry = new ZipEntry(entryName); zipOut.putNextEntry(zipEntryEntry); try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { final byte[] buf = new byte[BUF_SIZE]; int r; while ((r = in.read(buf)) != -1) { zipOut.write(buf, 0, r); } } zipOut.closeEntry(); } public static Collection listEntries(File zip) throws IOException { try (InputStream in = new FileInputStream(zip)) { return listEntries(in); } } public static Collection listEntries(InputStream in) throws IOException { final List list = new LinkedList<>(); final ZipInputStream zipIn = new ZipInputStream(in); ZipEntry zipEntry; while ((zipEntry = zipIn.getNextEntry()) != null) { if (!zipEntry.isDirectory()) { list.add(zipEntry.getName()); } zipIn.closeEntry(); } return list; } public static void unzip(File zip, File targetDir) throws IOException { try (InputStream in = new FileInputStream(zip)) { unzip(in, targetDir); } } public static void unzip(InputStream in, File targetDir) throws IOException { final ZipInputStream zipIn = new ZipInputStream(in); final byte[] b = new byte[BUF_SIZE]; ZipEntry zipEntry; while ((zipEntry = zipIn.getNextEntry()) != null) { final File file = new File(targetDir, zipEntry.getName()); if (!zipEntry.isDirectory()) { final File parent = file.getParentFile(); if (!parent.exists()) { if (!parent.mkdirs()) { throw new IOException("Unable to create parent folder " + parent.getAbsolutePath()); } } try (FileOutputStream fos = new FileOutputStream(file)) { int r; while ((r = zipIn.read(b)) != -1) { fos.write(b, 0, r); } } } else { if (!file.exists()) { if (!file.mkdirs()) { throw new IOException("Unable to create folder " + file.getAbsolutePath()); } } } zipIn.closeEntry(); } } /** * Provides streams to all resources matching {@code filter} criteria inside the archive. * * @param zip zip file to get resources from * @param filter the search criteria * @throws IOException */ public static void getResources(ZipFile zip, Pattern filter, Consumer consumer) throws IOException { Enumeration zipEntries = zip.entries(); while (zipEntries.hasMoreElements()) { ZipEntry zipEntry = zipEntries.nextElement(); final String name = zipEntry.getName(); if (filter.matcher(name).matches()) { try (InputStream in = zip.getInputStream(zipEntry)) { consumer.accept(in); } } } } /** * Checks is specified file is zip file or not. Zip file headers description. */ public static boolean isZipFile(File file) throws IOException { if (file.isDirectory()) { return false; } // NOTE: little-indian bytes order! final byte[] bytes = new byte[4]; try (FileInputStream fIn = new FileInputStream(file)) { if (fIn.read(bytes) != bytes.length) { return false; } } ByteBuffer zipFileHeaderSignature = ByteBuffer.wrap(bytes); zipFileHeaderSignature.order(ByteOrder.LITTLE_ENDIAN); return 0x04034b50 == zipFileHeaderSignature.getInt(); } private ZipUtils() {} } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/concurrent/CopyThreadLocalCallable.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.concurrent; import java.util.Map; import java.util.concurrent.Callable; import org.slf4j.MDC; /** * @author andrew00x */ class CopyThreadLocalCallable implements Callable { private final Callable wrapped; private final ThreadLocalPropagateContext.ThreadLocalState threadLocalState; private Map currentMdcState; CopyThreadLocalCallable(Callable wrapped) { // Called from main thread. Copy the current values of all the ThreadLocal variables which // registered in ThreadLocalPropagateContext. this.wrapped = wrapped; this.threadLocalState = ThreadLocalPropagateContext.currentThreadState(); this.currentMdcState = MDC.getCopyOfContextMap(); } @Override public T call() throws Exception { try { threadLocalState.propagate(); if (currentMdcState != null) { MDC.setContextMap(currentMdcState); } return wrapped.call(); } finally { threadLocalState.cleanup(); MDC.clear(); } } @Override public String toString() { return "CopyThreadLocalCallable{ " + wrapped.toString() + " }"; } public Callable getWrapped() { return wrapped; } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/concurrent/CopyThreadLocalRunnable.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.concurrent; import java.util.Map; import org.slf4j.MDC; /** * @author andrew00x */ class CopyThreadLocalRunnable implements Runnable { private final Runnable wrapped; private final ThreadLocalPropagateContext.ThreadLocalState threadLocalState; private Map currentMdcState; CopyThreadLocalRunnable(Runnable wrapped) { // Called from main thread. Copy the current values of all the ThreadLocal variables which // registered in ThreadLocalPropagateContext. this.wrapped = wrapped; this.threadLocalState = ThreadLocalPropagateContext.currentThreadState(); this.currentMdcState = MDC.getCopyOfContextMap(); } @Override public void run() { try { threadLocalState.propagate(); if (currentMdcState != null) { MDC.setContextMap(currentMdcState); } wrapped.run(); } finally { threadLocalState.cleanup(); MDC.clear(); } } @Override public String toString() { return "CopyThreadLocalRunnable{ " + wrapped.toString() + " }"; } public Runnable getWrapped() { return wrapped; } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/concurrent/LoggingUncaughtExceptionHandler.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.concurrent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Writes uncaught exceptions in threads being run by {@link java.util.concurrent.ExecutorService} * into application log. * * @author Max Shaposhnik */ public class LoggingUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { private static final Logger LOG = LoggerFactory.getLogger(LoggingUncaughtExceptionHandler.class); private static final LoggingUncaughtExceptionHandler INSTANCE = new LoggingUncaughtExceptionHandler(); @Override public void uncaughtException(Thread t, Throwable e) { LOG.error( String.format( "Runtime exception caught in thread %s. Message: %s", t.getName(), e.getLocalizedMessage()), e); } public static LoggingUncaughtExceptionHandler getInstance() { return INSTANCE; } private LoggingUncaughtExceptionHandler() {} } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/concurrent/PropagatedThreadLocalsProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.concurrent; /** * Helps to identify classes which need propagating ThreadLocal variables with * ThreadLocalPropagateContext. Useful to avoid manual registration of ThreadLocal in * ThreadLocalPropagateContext. * *

 * Set<PropagatedThreadLocalsProvider> ps = ... // Look up all implementations of PropagatedThreadLocalsProvider
 * for (PropagatedThreadLocalsProvider p : ps) {
 *     for (ThreadLocal tl : p.getThreadLocals()) {
 *         ThreadLocalPropagateContext.addThreadLocal(tl);
 *     }
 * }
 * 
* * @author andrew00x */ public interface PropagatedThreadLocalsProvider { ThreadLocal[] getThreadLocals(); } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/concurrent/StripedLocks.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.concurrent; import com.google.common.util.concurrent.Striped; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; /** * Helper class to use striped locks in try-with-resources construction. Examples of usage: * *
{@code
 * StripedLocks stripedLocks = new StripedLocks(16);
 * try (Unlocker u = stripedLocks.writeLock(myKey)) {
 *     syncedObject.write();
 * }
 *
 * try (Unlocker u = stripedLocks.readLock(myKey)) {
 *     syncedObject.read();
 * }
 *
 * try (Unlocker u = stripedLocks.writeAllLock(myKey)) {
 *     for (ObjectToSync objectToSync : allObjectsToSync) {
 *         objectToSync.write();
 *     }
 * }
 * }
* * @author Alexander Garagatyi * @author Sergii Leschenko * @author Yevhenii Voevodin */ public class StripedLocks { private final Striped striped; public StripedLocks(int stripesCount) { striped = Striped.readWriteLock(stripesCount); } /** Acquire read lock for provided key. */ public Unlocker readLock(String key) { Lock lock = striped.get(key).readLock(); lock.lock(); return new LockUnlocker(lock); } /** Acquire write lock for provided key. */ public Unlocker writeLock(String key) { Lock lock = striped.get(key).writeLock(); lock.lock(); return new LockUnlocker(lock); } /** Acquire write lock for all possible keys. */ public Unlocker writeAllLock() { Lock[] locks = getAllWriteLocks(); for (Lock lock : locks) { lock.lock(); } return new LocksUnlocker(locks); } private Lock[] getAllWriteLocks() { Lock[] locks = new Lock[striped.size()]; for (int i = 0; i < striped.size(); i++) { locks[i] = striped.getAt(i).writeLock(); } return locks; } private static class LockUnlocker implements Unlocker { private final Lock lock; private LockUnlocker(Lock lock) { this.lock = lock; } @Override public void unlock() { lock.unlock(); } } private static class LocksUnlocker implements Unlocker { private final Lock[] locks; private LocksUnlocker(Lock[] locks) { this.locks = locks; } @Override public void unlock() { for (Lock lock : locks) { lock.unlock(); } } } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/concurrent/ThreadLocalPropagateContext.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.concurrent; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; /** * Helps propagating ThreadLocal variables to the child Threads, e.g. when {@link * java.util.concurrent.ExecutorService} is in use. * *

Usage example: * *

 * private static ThreadLocal<MyClass> myThreadLocal = new ThreadLocal<>();
 * static {
 *    // register ThreadLocal variable
 *    ThreadLocalPropagateContext.addThreadLocal(myThreadLocal);
 * }
 *
 * private ExecutorService executor = ...;
 *
 * ...
 *
 * // If need to start Runnable task with executor.
 * myThreadLocal.set(new MyClass()); // initialize ThreadLocal in 'main' thread
 * Runnable myRunnable = new Runnable() {
 *     public void run() {
 *         MyClass v = myThreadLocal.get(); // get ThreadLocal in 'child' thread and do something with it
 *     }
 * }
 * executor.submit(ThreadLocalPropagateContext.wrap(myRunnable)); // wrap Runnable and submit it to executor
 * 
* * @author andrew00x */ public class ThreadLocalPropagateContext { private static CopyOnWriteArrayList> toPropagate = new CopyOnWriteArrayList<>(); /** * Register ThreadLocal in this context. After registration value of ThreadLocal from parent * Thread is copied to the child thread when method {@link #wrap(Runnable)} or {@link * #wrap(java.util.concurrent.Callable)}. */ public static void addThreadLocal(ThreadLocal threadLocal) { if (threadLocal == null) { throw new IllegalArgumentException(); } toPropagate.addIfAbsent(threadLocal); } /** * Get list of all registered ThreadLocal. * * @return list of all registered ThreadLocal */ public static ThreadLocal[] getThreadLocals() { return toPropagate.toArray(new ThreadLocal[toPropagate.size()]); } /** * Register ThreadLocal from this context. * * @see #addThreadLocal(ThreadLocal) */ public static void removeThreadLocal(ThreadLocal threadLocal) { if (threadLocal == null) { return; } toPropagate.remove(threadLocal); } /** Clear all registered ThreadLocal variables. */ public void clear() { toPropagate.clear(); } public static Runnable wrap(Runnable task) { return new CopyThreadLocalRunnable(task); } public static Callable wrap(Callable task) { return new CopyThreadLocalCallable<>(task); } static ThreadLocalState currentThreadState() { final ThreadLocal[] threadLocals = toPropagate.toArray(new ThreadLocal[toPropagate.size()]); final Object[] values = new Object[threadLocals.length]; for (int i = 0, l = threadLocals.length; i < l; i++) { values[i] = threadLocals[i].get(); } return new ThreadLocalState(threadLocals, values); } static class ThreadLocalState { private final ThreadLocal[] threadLocals; private final Object[] values; private Object[] previousValues; private ThreadLocalState(ThreadLocal[] threadLocals, Object[] values) { this.threadLocals = threadLocals; this.values = values; } @SuppressWarnings("unchecked") void propagate() { previousValues = new Object[threadLocals.length]; for (int i = 0, l = values.length; i < l; i++) { previousValues[i] = threadLocals[i].get(); threadLocals[i].set(values[i]); } } @SuppressWarnings("unchecked") void cleanup() { if (previousValues == null) { return; // method propagate wasn't called } for (int i = 0, l = previousValues.length; i < l; i++) { threadLocals[i].set(previousValues[i]); } } } private ThreadLocalPropagateContext() {} } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/concurrent/Unlocker.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.concurrent; /** * An interface that allows implementations to enclose locked instance and unlock it later by * calling {@link #unlock()}. * *

This is designed to be used in try-with-resources statement. * *

The example: * *

 *     try (@SuppressWarnings("unused") Unlocker u = customLocks.lock("key")) {
 *         // do something in lock
 *     }
 * 
* * @author Sergii Leschenko * @author Yevhenii Voevodin */ public interface Unlocker extends AutoCloseable { /** Unlocks the corresponding lock in implementation specific manner. */ void unlock(); @Override default void close() { unlock(); } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/CommandLine.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Manages command line options and create native OS process. Also manages creating {@link Process} * object by {@link CommandLine#createProcess()}. All options represented as-is, without any * escaping or encoding. * * @author Evgen Vidolob */ public class CommandLine { private String executablePath; private boolean redirectErrorStream; private File workingDirectory; private Map environment = new HashMap<>(); private List parameters = new ArrayList<>(); public CommandLine() {} public CommandLine(String... command) { this(Arrays.asList(command)); } public CommandLine(List command) { if (command.size() > 0) { executablePath = command.get(0); if (command.size() > 1) { addParameters(command.subList(0, command.size())); } } } public String getExecutablePath() { return executablePath; } public void setExecutablePath(String executablePath) { this.executablePath = executablePath; } public void addParameters(List parameters) { this.parameters.addAll(parameters); } public File getWorkingDirectory() { return workingDirectory; } public void setWorkingDirectory(File workingDirectory) { this.workingDirectory = workingDirectory; } public void setWorkingDirectory(String path) { if (path != null && !path.isEmpty()) { workingDirectory = new File(path); } } public Map getEnvironment() { return environment; } public boolean isRedirectErrorStream() { return redirectErrorStream; } public void setRedirectErrorStream(boolean redirectErrorStream) { this.redirectErrorStream = redirectErrorStream; } public Process createProcess() throws ExecutionException { List commandLine = createCommandLine(); try { return runProcess(commandLine); } catch (IOException e) { throw new ExecutionException(e); } } private Process runProcess(List commandLine) throws IOException { ProcessBuilder processBuilder = new ProcessBuilder(commandLine); Map environment = processBuilder.environment(); environment.clear(); environment.putAll(this.environment); processBuilder.directory(workingDirectory); processBuilder.redirectErrorStream(redirectErrorStream); return processBuilder.start(); } private List createCommandLine() { List commandLine = new ArrayList<>(parameters.size() + 1); commandLine.add(executablePath); commandLine.addAll(parameters); return commandLine; } public void addParameter(String parameter) { parameters.add(parameter); } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/ExecutionException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; /** * Exception throw by {@link ProcessExecutor} * * @author Evgen Vidolob */ public class ExecutionException extends Exception { public ExecutionException(String message) { super(message); } public ExecutionException(String message, Throwable cause) { super(message, cause); } public ExecutionException(Throwable cause) { super(cause); } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/Executor.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; import java.util.concurrent.Future; /** * Executor for asynchronous execution. * * @author Evgen Vidolob */ public interface Executor { Future execute(Runnable runnable); } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/ExecutorServiceBuilder.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; import java.time.Duration; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Helper class to build {@link ExecutorService} from parts like corePoolSize, maxPoolSize, * threadFactory, etc. * * @author Sergii Kabashniuk */ public class ExecutorServiceBuilder { private int corePoolSize; private int maxPoolSize; private boolean allowCoreThreadTimeOut; private Duration keepAliveTime; private BlockingQueue workQueue; private ThreadFactory threadFactory; private RejectedExecutionHandler handler; public ExecutorServiceBuilder() { this.corePoolSize = 0; this.maxPoolSize = 1; this.allowCoreThreadTimeOut = false; this.keepAliveTime = Duration.ofSeconds(60); this.workQueue = new LinkedBlockingQueue<>(); this.threadFactory = Executors.defaultThreadFactory(); this.handler = new ThreadPoolExecutor.AbortPolicy(); } /** * @param corePoolSize - configure corePoolSize the number of threads to keep in the pool, even if * they are idle, unless {@code allowCoreThreadTimeOut} is set. */ public ExecutorServiceBuilder corePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; return this; } /** * @param maxPoolSize - configure the maximum number of threads to allow in the pool. */ public ExecutorServiceBuilder maxPoolSize(int maxPoolSize) { if (maxPoolSize < corePoolSize) { throw new IllegalArgumentException("maxPoolSize must be greater than corePoolSize"); } this.maxPoolSize = maxPoolSize; return this; } /** * @param allowCoreThreadTimeOut - allow core threads to time out an terminate. */ public ExecutorServiceBuilder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) { this.allowCoreThreadTimeOut = allowCoreThreadTimeOut; return this; } /** * @param time - configure keepAliveTime parameter of {@code ThreadPoolExecutor} */ public ExecutorServiceBuilder keepAliveTime(Duration time) { this.keepAliveTime = time; return this; } /** * @param workQueue - configure the type of the task queue that would be used in {@code * ThreadPoolExecutor} */ public ExecutorServiceBuilder workQueue(BlockingQueue workQueue) { this.workQueue = workQueue; return this; } /** * @param queueCapacity - configure {@code LinkedBlockingQueue} with queueCapacity capacity to be * used in {@code ThreadPoolExecutor} */ public ExecutorServiceBuilder queueCapacity(int queueCapacity) { this.workQueue = new LinkedBlockingQueue<>(queueCapacity); return this; } /** * @param handler - configure instance of {@code RejectedExecutionHandler} to be used in {@code * ThreadPoolExecutor} */ public ExecutorServiceBuilder rejectedExecutionHandler(RejectedExecutionHandler handler) { this.handler = handler; return this; } /** * @param threadFactory - configure instance of {@code ThreadFactory} to be used in {@code * ThreadPoolExecutor} */ public ExecutorServiceBuilder threadFactory(ThreadFactory threadFactory) { this.threadFactory = threadFactory; return this; } public ExecutorService build() { final ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maxPoolSize, keepAliveTime.toMillis(), TimeUnit.MILLISECONDS, workQueue, threadFactory, handler); executor.allowCoreThreadTimeOut(allowCoreThreadTimeOut); return executor; } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/JavaParameters.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Holds all parameters that needed to start new JVM instance.
* Set all needed parameters and call {@link #createCommand()} to receive command line. * * @author Evgen Vidolob */ public class JavaParameters { private String workingDirectory; private String javaExecutable; private String jarPath; private String mainClassName; private List classPath = new ArrayList<>(); private List vmParameters = new ArrayList<>(); private Map enviroment = new HashMap<>(); private ParametersList parametersList = new ParametersList(); public String getJavaExecutable() { return javaExecutable; } public String getMainClassName() { return mainClassName; } public List getClassPath() { return classPath; } public List getVmParameters() { return vmParameters; } public String getWorkingDirectory() { return workingDirectory; } public ParametersList getParametersList() { return parametersList; } public void setWorkingDirectory(String workingDirectory) { this.workingDirectory = workingDirectory; } public void setJavaExecutable(String javaExecutable) { this.javaExecutable = javaExecutable; } public void setMainClassName(String mainClassName) { this.mainClassName = mainClassName; } public String getJarPath() { return jarPath; } public void setJarPath(String jarPath) { this.jarPath = jarPath; } public Map getEnviroment() { return enviroment; } public CommandLine createCommand() { CommandLine result = new CommandLine(javaExecutable); result.getEnvironment().putAll(enviroment); result.addParameters(vmParameters); result.addParameter("-classpath"); result.addParameter(createClasspath(classPath)); if (mainClassName != null) { result.addParameter(mainClassName); } else if (jarPath != null) { result.addParameter("-jar"); result.addParameter(jarPath); } result.addParameters(parametersList.getParameters()); result.setWorkingDirectory(workingDirectory); return result; } private String createClasspath(List classPath) { return classPath.stream().reduce((s, s2) -> s + ":" + s2).get(); } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/OutputReader.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; import java.io.IOException; import java.io.Reader; import java.util.concurrent.Future; import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Asynchronously non blocking read process output. Class use external executor to run read thread * * @author Evgen Vidolob */ public class OutputReader { private static final Logger LOG = LoggerFactory.getLogger(OutputReader.class); private static final int SLEEP_WHEN_WAS_ACTIVE = 1; private static final int SLEEP_WHEN_IDLE = 5; private final Executor executor; private final Consumer textConsumer; private final Reader reader; private final char[] buffer = new char[8192]; private final StringBuilder lineBuilder = new StringBuilder(); private Future readingFuture; private volatile boolean isStopped; public OutputReader(Reader reader, Executor executor, Consumer textConsumer) { this.executor = executor; this.textConsumer = textConsumer; this.reader = reader; } /** Start reading thread */ public void start() { if (readingFuture == null) { readingFuture = executor.execute(() -> doStart()); } } public void stop() { isStopped = true; } public void waitFor() throws InterruptedException { try { readingFuture.get(); } catch (java.util.concurrent.ExecutionException e) { LOG.error(e.getMessage(), e); } } private void doStart() { try { while (true) { boolean active = readIfAvailable(); if (isStopped) { break; } try { Thread.sleep(getTimeToSleep(active)); } catch (InterruptedException ignored) { } } } catch (Exception e) { LOG.error(e.getMessage(), e); } finally { close(); } } private void close() { try { reader.close(); } catch (IOException e) { LOG.error("Cannot close stream", e); } } private int getTimeToSleep(boolean active) { return active ? SLEEP_WHEN_WAS_ACTIVE : SLEEP_WHEN_IDLE; } private boolean readIfAvailable() throws IOException { boolean read = false; int charCount; while (reader.ready() && (charCount = reader.read(buffer)) > 1) { read = true; processRead(lineBuilder, buffer, charCount); } if (lineBuilder.length() > 0) { consumeLine(lineBuilder); } return read; } private void consumeLine(StringBuilder lineBuilder) { textConsumer.accept(lineBuilder.toString()); lineBuilder.setLength(0); } private void processRead(StringBuilder lineBuilder, char[] buffer, int charCount) { for (int i = 0; i < charCount; i++) { char c = buffer[i]; lineBuilder.append(c); if (c == '\n') { consumeLine(lineBuilder); } } } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/ParametersList.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; import static java.util.Collections.unmodifiableList; import java.util.ArrayList; import java.util.List; /** Represent and configure program parameters */ public class ParametersList { private final List parameters = new ArrayList<>(); public void add(String name, String value) { parameters.add(name); parameters.add(value); } /** * Adds a parameter without name. * * @param value value of the parameter */ public void add(String value) { parameters.add(value); } public List getParameters() { return unmodifiableList(parameters); } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/ProcessEvent.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; import java.util.EventObject; /** * @author Evgen Vidolob */ public class ProcessEvent extends EventObject { private String text; private int exitCode; /** * Constructs a prototypical Event. * * @param source The object on which the Event initially occurred. * @exception IllegalArgumentException if source is null. */ public ProcessEvent(ProcessHandler source) { super(source); } public ProcessEvent(ProcessHandler source, String text) { super(source); this.text = text; } public ProcessEvent(ProcessHandler source, String text, int exitCode) { super(source); this.text = text; this.exitCode = exitCode; } public ProcessEvent(ProcessHandler source, int exitCode) { super(source); this.exitCode = exitCode; } public String getText() { return text; } public int getExitCode() { return exitCode; } public ProcessHandler getProcessHandler() { return (ProcessHandler) source; } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/ProcessExecutor.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; /** * External process executor * * @author Evgen Vidolob */ public interface ProcessExecutor { ProcessHandler execute() throws ExecutionException; } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/ProcessHandler.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Handler for native OS process * * @author Evgen Vidolob */ public class ProcessHandler implements Executor { private static final Logger LOG = LoggerFactory.getLogger(ProcessHandler.class); private final Process process; private final WaitForProcessEnd waitForProcess; private final ExecutorService executorService; private final ProcessListener listenerNotifier; private final TerminatingTaskRunner terminatingListener; private final CountDownLatch latch; private final List listeners = new CopyOnWriteArrayList<>(); private volatile ProcessState state = ProcessState.INITIAL; public ProcessHandler(Process process) { executorService = createExecutorService(); this.process = process; waitForProcess = new WaitForProcessEnd(process, this); listenerNotifier = createNotifier(); terminatingListener = new TerminatingTaskRunner(); addProcessListener(terminatingListener); latch = new CountDownLatch(1); } private ProcessListener createNotifier() { InvocationHandler invocationHandler = (proxy, method, args) -> { // call method over all listeners for (ProcessListener listener : listeners) { method.invoke(listener, args); } return null; }; return (ProcessListener) Proxy.newProxyInstance( ProcessListener.class.getClassLoader(), new Class[] {ProcessListener.class}, invocationHandler); } private ExecutorService createExecutorService() { return new ThreadPoolExecutor( 10, Integer.MAX_VALUE, 1, TimeUnit.MINUTES, new SynchronousQueue<>(), (r -> new Thread(r, "Native process polled Thread"))); } public boolean isProcessTerminating() { return false; } public boolean isProcessTerminated() { return false; } public void addProcessListener(ProcessListener processListener) { listeners.add(processListener); } public void startNotify() { addProcessListener( new ProcessListener() { @Override public void onStart(ProcessEvent event) { try { OutputReader stdOutReader = createStdOutReader(); stdOutReader.start(); OutputReader stdErrReader = createStdErrReader(); stdErrReader.start(); waitForProcess.setEndCallback( exitCode -> { try { stdErrReader.stop(); stdOutReader.stop(); try { stdErrReader.waitFor(); stdOutReader.waitFor(); } catch (InterruptedException ignore) { } } finally { ProcessHandler.this.onProcessTerminated(exitCode); latch.countDown(); } }); } finally { removeProcessListener(this); } } @Override public void onText(ProcessEvent event, ProcessOutputType outputType) {} @Override public void onProcessTerminated(ProcessEvent event) {} @Override public void onProcessWillTerminate(ProcessEvent event) {} }); state = ProcessState.RUNNING; listenerNotifier.onStart(new ProcessEvent(this)); } private void onProcessTerminated(int exitCode) { terminatingListener.runTask( () -> { if (state == ProcessState.RUNNING) { state = ProcessState.TERMINATING; notifyOnTerminating(); } if (state == ProcessState.TERMINATING) { state = ProcessState.TERMINATED; listenerNotifier.onProcessTerminated(new ProcessEvent(ProcessHandler.this, exitCode)); } }); } private void removeProcessListener(ProcessListener listener) { listeners.remove(listener); } private OutputReader createStdErrReader() { return new OutputReader( new InputStreamReader(process.getErrorStream()), this, (s -> notifyOnText(s, ProcessOutputType.STDERR))); } private void notifyOnTerminating() { listenerNotifier.onProcessWillTerminate(new ProcessEvent(this)); } private void notifyOnText(String text, ProcessOutputType type) { listenerNotifier.onText(new ProcessEvent(this, text), type); } private OutputReader createStdOutReader() { return new OutputReader( new InputStreamReader(process.getInputStream()), this, (s -> { notifyOnText(s, ProcessOutputType.STDOUT); })); } @Override public Future execute(Runnable runnable) { return executorService.submit(runnable); } public boolean isStarted() { return state != ProcessState.INITIAL; } public void destroyProcess() { terminatingListener.runTask( () -> { if (state == ProcessState.RUNNING) { state = ProcessState.TERMINATING; notifyOnTerminating(); try { closeStream(); } finally { process.destroy(); } } }); } private void closeStream() { try { process.getOutputStream().close(); } catch (IOException e) { LOG.error(e.getMessage(), e); } } public boolean waitFor() { try { latch.await(); return true; } catch (InterruptedException ignored) { return false; } } private enum ProcessState { INITIAL, RUNNING, TERMINATING, TERMINATED } private class TerminatingTaskRunner implements ProcessListener { private List tasks = new ArrayList<>(); @Override public void onStart(ProcessEvent event) { removeProcessListener(this); runAllTasks(); } public void runTask(Runnable task) { if (isStarted()) { task.run(); } else { synchronized (tasks) { tasks.add(task); } if (isStarted()) { runAllTasks(); } } } private void runAllTasks() { List taskList; synchronized (tasks) { taskList = new ArrayList<>(tasks); tasks.clear(); } taskList.forEach(Runnable::run); } @Override public void onText(ProcessEvent event, ProcessOutputType outputType) {} @Override public void onProcessTerminated(ProcessEvent event) {} @Override public void onProcessWillTerminate(ProcessEvent event) {} } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/ProcessListener.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; import java.util.EventListener; /** * Listener for {@link ProcessHandler} * * @author Evgen Vidolob */ public interface ProcessListener extends EventListener { void onStart(ProcessEvent event); void onText(ProcessEvent event, ProcessOutputType outputType); void onProcessTerminated(ProcessEvent event); void onProcessWillTerminate(ProcessEvent event); } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/ProcessOutputType.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; /** * Contains native OS process output types * * @author Evgen Vidolob */ public enum ProcessOutputType { SYSTEM, STDOUT, STDERR } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/execution/WaitForProcessEnd.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Future; import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Wait until the {@code Process} has terminated. * * @author Evgen Vidolob */ public class WaitForProcessEnd { private static final Logger LOG = LoggerFactory.getLogger(WaitForProcessEnd.class); private final Future waitFor; private final BlockingQueue> endCallback = new ArrayBlockingQueue>(1); public WaitForProcessEnd(Process process, Executor executor) { waitFor = executor.execute( () -> { int exitCode = 0; try { while (true) { try { exitCode = process.waitFor(); break; } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } } } finally { try { endCallback.take().accept(exitCode); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } } }); } public void setEndCallback(Consumer callback) { endCallback.offer(callback); } public void stop() { waitFor.cancel(true); } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/os/WindowsPathEscaper.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.os; /** * Escapes Windows path to unix-style path. * * @author Alexander Garagatyi */ public class WindowsPathEscaper { /** Implements singleton pattern. */ private static final WindowsPathEscaper escaper = new WindowsPathEscaper(); /** Does nothing. Can be used for mocking purposes. */ public WindowsPathEscaper() {} /** * Static version of method that escapes paths on Windows. It is discouraged to use this method * because it is too hard to mock it. Use {@link #escapePath(String)} instead. * * @param path path on Windows. Can be unix-style path also. * @return unix-style path * @see #escapePath(String) */ public static String escapePathStatic(String path) { return escaper.escapePath(path); } /** * Escapes Windows path to unix-style path. * * @param path path on Windows. Can be unix-style path also. * @return unix-style path */ public String escapePath(String path) { String esc; if (path.indexOf(":") == 1) { // check and replace only occurrence of ":" after disk label on Windows host (e.g. C:/) // but keep other occurrences it can be marker for docker mount volumes // (e.g. /path/dir/from/host:/name/of/dir/in/container // ) esc = path.replaceFirst(":", "").replace('\\', '/'); esc = Character.toLowerCase(esc.charAt(0)) + esc.substring(1); // letter of disk mark must be lower case } else { esc = path.replace('\\', '/'); } if (!esc.startsWith("/")) { esc = "/" + esc; } return esc; } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/reflect/ParameterizedTypeImpl.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.reflect; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; /** * Provides runtime information about parameterized type. * * @author andrew00x */ public final class ParameterizedTypeImpl implements ParameterizedType { private final Type[] typeArguments; private final Class rawType; public ParameterizedTypeImpl(Class rawType, Type... typeArguments) { this.rawType = rawType; this.typeArguments = new Type[typeArguments.length]; System.arraycopy(typeArguments, 0, this.typeArguments, 0, this.typeArguments.length); } @Override public Type[] getActualTypeArguments() { return Arrays.copyOf(typeArguments, typeArguments.length); } @Override public Type getRawType() { return rawType; } @Override public Type getOwnerType() { return null; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(rawType.getName()); builder.append('<'); for (int i = 0, length = typeArguments.length; i < length; i++) { if (i > 0) { builder.append(", "); } builder.append( typeArguments[i] instanceof Class ? ((Class) typeArguments[i]).getName() : typeArguments[i].toString()); } builder.append('>'); return builder.toString(); } @Override public int hashCode() { int hashCode = 7; hashCode = 31 * hashCode + rawType.hashCode(); hashCode = 31 * hashCode + Arrays.hashCode(typeArguments); return hashCode; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ParameterizedType)) { return false; } ParameterizedType other = (ParameterizedType) o; return rawType.equals(other.getRawType()) && Arrays.equals(typeArguments, other.getActualTypeArguments()); } } ================================================ FILE: core/commons/che-core-commons-lang/src/main/java/org/eclipse/che/commons/lang/ws/rs/ExtMediaType.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.ws.rs; import jakarta.ws.rs.core.MediaType; /** * Extended media type. * * @author Tareq Sharafy (tareq.sharafy@sap.com) */ public interface ExtMediaType { /** A {@code String} constant representing "{@value #APPLICATION_ZIP}" media type. */ public static final String APPLICATION_ZIP = "application/zip"; /** A {@link MediaType} constant representing "{@value #APPLICATION_ZIP}" media type. */ public static final MediaType APPLICATION_ZIP_TYPE = new MediaType("application", "zip"); /** A {@code String} constant representing "{@value #APPLICATION_X_TAR}" media type. */ String APPLICATION_X_TAR = "application/x-tar"; /** A {@link MediaType} constant representing "{@value #APPLICATION_X_TAR}" media type. */ MediaType APPLICATION_X_TAR_TYPE = new MediaType("application", "x-tar"); } ================================================ FILE: core/commons/che-core-commons-lang/src/test/java/org/eclipse/che/commons/lang/IoUtilTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import static org.testng.Assert.assertTrue; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.testng.annotations.Test; /** * @author Anatolii Bazko */ public class IoUtilTest { @Test public void shouldListFileResources() throws Exception { List resources = new ArrayList<>(); IoUtil.listResources( getClass().getResource("/").toURI(), path -> resources.add(path.getFileName().toString())); assertTrue(resources.contains("logback-test.xml")); assertTrue(resources.contains("findbugs-exclude.xml")); } @Test public void shouldListChildrenResourcesInJar() throws Exception { URL testJar = Thread.currentThread().getContextClassLoader().getResource("che/che.jar"); URI codenvyDir = URI.create("jar:" + testJar + "!/codenvy"); List resources = new ArrayList<>(); IoUtil.listResources(codenvyDir, path -> resources.add(path.getFileName().toString())); assertTrue(resources.contains("a.json")); assertTrue(resources.contains("b.json")); } @Test public void shouldListParentResourcesInJar() throws Exception { URL testJar = Thread.currentThread().getContextClassLoader().getResource("che/che.jar"); URI codenvyDir = URI.create("jar:" + testJar + "!/"); List resources = new ArrayList<>(); IoUtil.listResources( codenvyDir, path -> resources.add(path.getFileName().toString().replace("/", ""))); assertTrue(resources.contains("codenvy")); } } ================================================ FILE: core/commons/che-core-commons-lang/src/test/java/org/eclipse/che/commons/lang/PathUtilTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import org.testng.annotations.Test; /** Tests for {@link PathUtil} */ public class PathUtilTest { @Test public void testCanonicalPath() throws Exception { String path = PathUtil.toCanonicalPath("/foo/../bar", false); assertNotNull(path); assertFalse(path.isEmpty()); assertEquals(path, "/bar"); } @Test public void testRemoveLastSlash() throws Exception { String path = PathUtil.toCanonicalPath("/foo/bar/", true); assertNotNull(path); assertFalse(path.isEmpty()); assertEquals(path, "/foo/bar"); } @Test public void testEliminationDot() throws Exception { String path = PathUtil.toCanonicalPath("./bar", false); assertNotNull(path); assertFalse(path.isEmpty()); assertEquals(path, "bar"); } @Test public void testCanonicalPathWithFile() throws Exception { String path = PathUtil.toCanonicalPath("/foo/../bar/pom.xml", false); assertNotNull(path); assertFalse(path.isEmpty()); assertEquals(path, "/bar/pom.xml"); } } ================================================ FILE: core/commons/che-core-commons-lang/src/test/java/org/eclipse/che/commons/lang/SizeTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import java.text.DecimalFormat; import org.testng.Assert; import org.testng.annotations.Test; /** * @author andrew00x */ public class SizeTest { char sep = new DecimalFormat().getDecimalFormatSymbols().getDecimalSeparator(); @Test public void testToHumanSize() { Assert.assertEquals(Size.toHumanSize(1024), "1 kB"); Assert.assertEquals(Size.toHumanSize(1000), "1000 B"); Assert.assertEquals(Size.toHumanSize(1024 * 1024), "1 MB"); Assert.assertEquals(Size.toHumanSize(5 * 1024 * 1024), "5 MB"); Assert.assertEquals(Size.toHumanSize(5L * 1024 * 1024 * 1024), "5 GB"); Assert.assertEquals(Size.toHumanSize(7539480), "7" + sep + "2 MB"); Assert.assertEquals(Size.toHumanSize(10226124), "9" + sep + "8 MB"); } @Test public void testParseToByte() { Assert.assertEquals(Size.parseSize("1 kB"), 1024); Assert.assertEquals(Size.parseSize("1000B"), 1000); Assert.assertEquals(Size.parseSize("1000"), 1000); Assert.assertEquals(Size.parseSize("1 MB"), 1024 * 1024); Assert.assertEquals(Size.parseSize("5 mb"), 5 * 1024 * 1024); Assert.assertEquals(Size.parseSize("9.8M"), Float.valueOf(9.8f * 1024 * 1024).longValue()); } @Test public void testParseToMegabyte() { Assert.assertEquals(Size.parseSizeToMegabytes("10485760"), 10); Assert.assertEquals(Size.parseSizeToMegabytes("12 MB"), 12); Assert.assertEquals(Size.parseSizeToMegabytes("1GB"), 1024); } @Test(expectedExceptions = IllegalArgumentException.class) public void testParseErrorInvalidSuffix() { Size.parseSize("1 xx"); } @Test(expectedExceptions = IllegalArgumentException.class) public void testParseErrorInvalidNumber() { Size.parseSize("1.x kB"); } } ================================================ FILE: core/commons/che-core-commons-lang/src/test/java/org/eclipse/che/commons/lang/TestURLEncodedUtils.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import static org.testng.Assert.*; import java.net.URI; import java.util.*; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class TestURLEncodedUtils { static final int SWISS_GERMAN_HELLO[] = { 0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4 }; static final int RUSSIAN_HELLO[] = { 0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438, 0x432, 0x435, 0x442 }; private static String constructString(int[] unicodeChars) { StringBuffer buffer = new StringBuffer(); if (unicodeChars != null) { for (int i = 0; i < unicodeChars.length; i++) { buffer.append((char) unicodeChars[i]); } } return buffer.toString(); } private static void assertNameValuePair( final Map> parameter, final String expectedName, final String... values) { Set actualValues = parameter.get(expectedName); assertNotNull(actualValues); assertEquals(actualValues.size(), values.length); for (String value : values) { assertTrue(actualValues.contains(value)); } } @Test public void shouldBeAbleToParseEmptyURI() throws Exception { // given // when Map> parameters = parse("", null); // then assertTrue(parameters.isEmpty()); } @Test(dataProvider = "uris") public void shouldParseURI(String url, String expectedName, String[] expectedValues) throws Exception { assertNameValuePair(parse(url, "UTF-8"), expectedName, expectedValues); } @Test(dataProvider = "uris") public void shouldFormatURI(String url, String expectedName, String[] expectedValues) throws Exception { // given Map> parameters = new HashMap<>(); parameters.put(expectedName, new LinkedHashSet<>(Arrays.asList(expectedValues))); // when String actual = URLEncodedUtils.format(parameters, "UTF-8"); // then if ("Name3".equals(url)) { assertEquals(actual, url + "="); } else { assertEquals(actual, url); } } @Test public void shouldFormatURIWithMultipleParams() throws Exception { // given Map> parameters = new LinkedHashMap<>(); parameters.put("Name5", new LinkedHashSet<>(Arrays.asList(new String[] {"aaa"}))); parameters.put("Name6", new LinkedHashSet<>(Arrays.asList(new String[] {"bbb"}))); // when String actual = URLEncodedUtils.format(parameters, "UTF-8"); // then assertEquals(actual, "Name5=aaa&Name6=bbb"); } @Test public void shouldParseURIWithMultipleParams() throws Exception { Map> parameters = parse("Name5=aaa&Name6=bbb", null); assertNameValuePair(parameters, "Name5", "aaa"); assertNameValuePair(parameters, "Name6", "bbb"); } @DataProvider(name = "uris") public Object[][] uris() { return new Object[][] { {"Name1=Value1", "Name1", new String[] {"Value1"}}, {"Name2=", "Name2", new String[] {}}, {"Name3", "Name3", new String[] {}}, {"Name4=Value+4%21", "Name4", new String[] {"Value 4!"}}, {"Name4=Value%2B4%21", "Name4", new String[] {"Value+4!"}}, {"Name4=Value+4%21+%214", "Name4", new String[] {"Value 4! !4"}}, {"Name7=aaa&Name7=b%2Cb&Name7=ccc", "Name7", new String[] {"aaa", "b,b", "ccc"}}, {"Name8=xx%2C++yy++%2Czz", "Name8", new String[] {"xx, yy ,zz"}}, }; } @Test public void shouldFormatUTF8() { // given String ru_hello = constructString(RUSSIAN_HELLO); String ch_hello = constructString(SWISS_GERMAN_HELLO); Map> parameters = new LinkedHashMap<>(); parameters.put("russian", new LinkedHashSet<>(Arrays.asList(new String[] {ru_hello}))); parameters.put("swiss", new LinkedHashSet<>(Arrays.asList(new String[] {ch_hello}))); // when String actual = URLEncodedUtils.format(parameters, "UTF-8"); // then assertEquals( actual, "russian=%D0%92%D1%81%D0%B5%D0%BC_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82" + "&swiss=Gr%C3%BCezi_z%C3%A4m%C3%A4"); } @Test public void shouldParseUTF8() { // given String ru_hello = constructString(RUSSIAN_HELLO); String ch_hello = constructString(SWISS_GERMAN_HELLO); // when Map> parameters = parse( "russian=%D0%92%D1%81%D0%B5%D0%BC_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82" + "&swiss=Gr%C3%BCezi_z%C3%A4m%C3%A4", "UTF-8"); // then assertNameValuePair(parameters, "russian", ru_hello); assertNameValuePair(parameters, "swiss", ch_hello); } @Test public void shouldParseWithoutDecoding() { // given String ru_hello = "%D0%92%D1%81%D0%B5%D0%BC_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82"; String ch_hello = "Gr%C3%BCezi_z%C3%A4m%C3%A4"; // when Map> parameters = URLEncodedUtils.parse( URI.create("http://hc.apache.org/params?russian=" + ru_hello + "&swiss=" + ch_hello), false); // then assertNameValuePair(parameters, "russian", ru_hello); assertNameValuePair(parameters, "swiss", ch_hello); } private Map> parse(final String params, final String encoding) { return URLEncodedUtils.parse(URI.create("http://hc.apache.org/params?" + params), encoding); } } ================================================ FILE: core/commons/che-core-commons-lang/src/test/java/org/eclipse/che/commons/lang/TopologicalSortTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.testng.Assert.assertEquals; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class TopologicalSortTest { @Test(dataProvider = "sortingCases") public void shouldApplySorting(List list, List expectedSorting) throws Exception { // when sort(list); // then assertEquals(list, expectedSorting); } @DataProvider public static Object[][] sortingCases() { return new Object[][] { // keep order if no dependencies new Object[] { vars(var('b', "."), var('a', "."), var('c', ".")), vars(var('b', "."), var('a', "."), var('c', ".")) }, // sort dependents after dependencies new Object[] { vars(var('a', "b"), var('b', "."), var('c', ".")), vars(var('b', "."), var('a', "b"), var('c', ".")) }, // sort dependents after dependencies, multiple dependencies new Object[] { vars(var('a', "bc"), var('b', "."), var('c', ".")), vars(var('b', "."), var('c', "."), var('a', "bc")) }, // sort dependents after dependencies, check dependee moved after dependencies new Object[] { vars(var('d', "."), var('a', "c"), var('b', "."), var('c', ".")), vars(var('d', "."), var('b', "."), var('c', "."), var('a', "c")) }, // test the robustness against cycles // dependent directly on itself new Object[] {vars(var('a', "a")), vars(var('a', "a"))}, // two mutually dependent nodes new Object[] {vars(var('a', "b"), var('b', "a")), vars(var('a', "b"), var('b', "a"))}, // independent node mixed inside a cycle new Object[] { vars(var('b', "c"), var('d', "."), var('a', "b"), var('c', "a")), vars(var('d', "."), var('b', "c"), var('a', "b"), var('c', "a")) }, // cycle (a-b-c) with one of the nodes also depending on another node (a-d), // mixed with a "chain" (f-e-b) new Object[] { vars( var('f', "e"), var('a', "bd"), var('b', "c"), var('e', "b"), var('c', "a"), var('d', ".")), vars( var('d', "."), var('a', "bd"), var('b', "c"), var('c', "a"), var('e', "b"), var('f', "e")) }, // removes duplicates new Object[] { vars(var('a', "."), var('a', "."), var('b', ".")), vars(var('a', "."), var('b', ".")) } }; } private static Var var(char name, String value) { return new Var(name, value.chars().mapToObj(c -> (char) c).collect(toSet())); } private static List vars(Var... vars) { return Stream.of(vars).collect(toList()); } private static void sort(List vars) { Function idFunction = v -> v.name; Function> predecessors = v -> v.dependencies.stream().filter(Character::isAlphabetic).collect(toSet()); TopologicalSort sort = new TopologicalSort<>(idFunction, predecessors); List newVars = sort.sort(vars); vars.clear(); vars.addAll(newVars); } private static final class Var { private final char name; private final Set dependencies; private Var(char name, Set dependencies) { this.name = name; this.dependencies = dependencies; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Var var = (Var) o; return name == var.name && dependencies.equals(var.dependencies); } @Override public int hashCode() { return Objects.hash(name, dependencies); } @Override public String toString() { return "Var{" + "name='" + name + '\'' + ", deps='" + dependencies + '\'' + '}'; } } } ================================================ FILE: core/commons/che-core-commons-lang/src/test/java/org/eclipse/che/commons/lang/UrlUtilsTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import static org.testng.Assert.*; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.testng.annotations.Test; public class UrlUtilsTest { @Test public void shouldExtractParametersWithoutValue() throws Exception { Map> params = UrlUtils.getQueryParameters(new URL("http://codenvy.com/factory?v")); assertTrue(params.containsKey("v")); assertNull(params.get("v").iterator().next()); } @Test public void shouldExtractParametersWithMultipleValues() throws Exception { Map> expectedParams = new HashMap<>(); List v = new LinkedList<>(); v.add("123"); v.add("qwe"); v.add("www"); expectedParams.put("v", v); Map> params = UrlUtils.getQueryParameters(new URL("http://codenvy.com/factory?v=123&v=qwe&v=www")); assertEquals(params, expectedParams); } @Test public void shouldExtractParametersWithMultipleValuesDividedAnotherParameters() throws Exception { Map> expectedParams = new HashMap<>(); List v = new LinkedList<>(); v.add("123"); v.add("qwe"); v.add("www"); List par = new LinkedList<>(); par.add("test"); expectedParams.put("v", v); expectedParams.put("par", par); Map> params = UrlUtils.getQueryParameters( new URL("http://codenvy.com/factory?v=123&par=test&v=qwe&v=www")); assertEquals(params, expectedParams); } @Test public void shouldIgnoreSlashAtTheEndOfPath() throws Exception { Map> expectedParams = new HashMap<>(); List v = new LinkedList<>(); v.add("123"); v.add("qwe"); v.add("www"); List par = new LinkedList<>(); par.add("test"); expectedParams.put("v", v); expectedParams.put("par", par); Map> params = UrlUtils.getQueryParameters( new URL("http://codenvy.com/factory/?v=123&par=test&v=qwe&v=www")); assertEquals(params, expectedParams); } @Test public void shouldExtractEncodedParameters() throws Exception { Map> expectedParams = new HashMap<>(); List vcsurl = new LinkedList<>(); vcsurl.add("http://github/some/path?somequery=qwe&somequery=sss&somequery=rty"); expectedParams.put("vcsurl", vcsurl); Map> params = UrlUtils.getQueryParameters( new URL( "http://codenvy.com/factory?vcsurl=" + URLEncoder.encode( "http://github/some/path?somequery=qwe&somequery=sss&somequery=rty", "UTF-8"))); assertEquals(params, expectedParams); } } ================================================ FILE: core/commons/che-core-commons-lang/src/test/java/org/eclipse/che/commons/lang/ZipUtilsTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Random; import java.util.function.Consumer; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class ZipUtilsTest { public static final String FILE_NAME = "test"; private File zipFile; private byte[] testData; @BeforeMethod public void setUp() throws IOException { zipFile = File.createTempFile("test", "zip"); zipFile.deleteOnExit(); testData = new byte[2048]; Random random = new Random(); random.nextBytes(testData); try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { ZipEntry entry = new ZipEntry(FILE_NAME); entry.setSize(testData.length); zos.putNextEntry(entry); zos.write(testData); zos.closeEntry(); zos.close(); } } @Test public void shouldBeAbleToDetectZipFile() throws IOException { Assert.assertTrue(ZipUtils.isZipFile(zipFile)); } @Test public void testGetResources() throws Exception { URL testJar = ZipUtilsTest.class.getResource("/che/che.jar"); @SuppressWarnings("unchecked") Consumer consumer = mock(Consumer.class); ZipUtils.getResources( new ZipFile(testJar.getFile()), Pattern.compile(".*[//]?codenvy/[^//]+[.]json"), consumer); verify(consumer, times(2)).accept(any(InputStream.class)); } @Test public void testUnzipFile() throws Exception { Path targetDir = Paths.get(zipFile.getParent()); ZipUtils.unzip(zipFile, targetDir.toFile()); Path unzippedFile = Paths.get(targetDir.toString(), FILE_NAME); Assert.assertEquals(testData, Files.readAllBytes(unzippedFile)); Files.delete(unzippedFile); } } ================================================ FILE: core/commons/che-core-commons-lang/src/test/java/org/eclipse/che/commons/lang/ZipUtilsWriteTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang; import static org.eclipse.che.commons.lang.ZipUtils.add; import static org.eclipse.che.commons.lang.ZipUtils.listEntries; import static org.eclipse.che.commons.lang.ZipUtils.stream; import static org.testng.Assert.assertTrue; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collection; import java.util.Random; import java.util.zip.ZipOutputStream; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class ZipUtilsWriteTest { private Path zipFile; private Path tempDir; private static Random random = new Random(); @BeforeMethod public void setUp() throws IOException { this.zipFile = Files.createTempFile("ZipUtilsTest", ".zip"); this.tempDir = createZipDir(); } private Path createZipDir() throws IOException { Path rootDir = Files.createTempDirectory("ZipUtilTest"); createFile(rootDir, "foo.bar"); createFile(rootDir, "inner/temp.bla"); return rootDir; } private void createFile(Path rootDir, String relativePath) throws IOException { Path file = rootDir.resolve(relativePath); Files.createDirectories(file.getParent()); byte[] buf = new byte[1024]; random.nextBytes(buf); Files.write(file, buf); } @AfterMethod public void tearDown() throws IOException { Files.delete(zipFile); Files.walkFileTree( tempDir, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { if (e == null) { Files.delete(dir); return FileVisitResult.CONTINUE; } else { throw e; } } }); } @Test public void testAddFileWithParent() throws IOException { try (ZipOutputStream out = stream(zipFile)) { add(out, tempDir, tempDir); } Collection entries = listEntries(zipFile.toFile()); assertTrue(entries.contains("foo.bar")); assertTrue(entries.contains(Paths.get("inner/temp.bla").toString())); } @Test public void testAddFile() throws IOException { try (ZipOutputStream out = stream(zipFile)) { add(out, tempDir); } Collection entries = listEntries(zipFile.toFile()); String tempFileName = tempDir.getFileName().toString(); assertTrue(entries.contains(Paths.get(tempFileName + "/foo.bar").toString())); assertTrue(entries.contains(Paths.get(tempFileName + "/inner/temp.bla").toString())); } } ================================================ FILE: core/commons/che-core-commons-lang/src/test/java/org/eclipse/che/commons/lang/concurrent/ThreadLocalPropagateContextTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.concurrent; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.testng.Assert; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; /** * @author andrew00x */ public class ThreadLocalPropagateContextTest { private static ThreadLocal tl1 = new ThreadLocal<>(); private ExecutorService exec; private final String tlValue = "my value"; @BeforeTest public void setUp() { tl1.set(tlValue); ThreadLocalPropagateContext.addThreadLocal(tl1); Assert.assertEquals(ThreadLocalPropagateContext.getThreadLocals().length, 1); exec = Executors.newSingleThreadExecutor(); } @AfterTest public void tearDown() { if (exec != null) { exec.shutdownNow(); } ThreadLocalPropagateContext.removeThreadLocal(tl1); Assert.assertEquals(ThreadLocalPropagateContext.getThreadLocals().length, 0); tl1.remove(); } @Test public void testRunnableWithoutThreadLocalPropagateContext() throws Exception { final String[] holder = new String[1]; exec.submit( new Runnable() { @Override public void run() { holder[0] = tl1.get(); } }) .get(); Assert.assertNull(holder[0]); } @Test public void testRunnableWithThreadLocalPropagateContext() throws Exception { final String[] holder = new String[1]; exec.submit( ThreadLocalPropagateContext.wrap( new Runnable() { @Override public void run() { holder[0] = tl1.get(); } })) .get(); Assert.assertEquals(holder[0], tlValue); } @Test public void testCallableWithoutThreadLocalPropagateContext() throws Exception { final String v = exec.submit( new Callable() { @Override public String call() { return tl1.get(); } }) .get(); Assert.assertNull(v); } @Test public void testCallableWithThreadLocalPropagateContext() throws Exception { final String v = exec.submit( ThreadLocalPropagateContext.wrap( new Callable() { @Override public String call() { return tl1.get(); } })) .get(); Assert.assertEquals(v, tlValue); } } ================================================ FILE: core/commons/che-core-commons-lang/src/test/java/org/eclipse/che/commons/lang/execution/ExecutorServiceBuilderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.execution; import static org.testng.Assert.*; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.testng.annotations.Test; public class ExecutorServiceBuilderTest { ThreadFactory threadFactory = new ThreadFactoryBuilder() .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setNameFormat(ExecutorServiceBuilderTest.class + "-%d") .setDaemon(true) .build(); @Test public void testBuild() { ExecutorService executorService = new ExecutorServiceBuilder() .corePoolSize(6) .maxPoolSize(12) .queueCapacity(5000) .threadFactory(threadFactory) .build(); assertTrue(executorService instanceof ThreadPoolExecutor); ThreadPoolExecutor threadPoolExecutorService = (ThreadPoolExecutor) executorService; assertEquals(threadPoolExecutorService.getCorePoolSize(), 6); assertEquals(threadPoolExecutorService.getMaximumPoolSize(), 12); assertEquals(threadPoolExecutorService.getThreadFactory(), threadFactory); } @Test( expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "maxPoolSize must be greater than corePoolSize") public void testSetMaxLowerThenCore() { new ExecutorServiceBuilder().corePoolSize(6).maxPoolSize(1).build(); } } ================================================ FILE: core/commons/che-core-commons-lang/src/test/java/org/eclipse/che/commons/lang/os/WindowsPathEscaperTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.lang.os; import static org.testng.Assert.assertEquals; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ public class WindowsPathEscaperTest { /** * E.g from * https://github.com/boot2docker/boot2docker/blob/master/README.md#virtualbox-guest-additions * *

Users should be /Users /Users should be /Users c/Users should be /c/Users /c/Users should be * /c/Users c:/Users should be /c/Users */ @Test(dataProvider = "pathProvider") public void shouldStaticallyEscapePathForWindowsHost(String windowsPath, String expectedPath) { assertEquals(WindowsPathEscaper.escapePathStatic(windowsPath), expectedPath); } /** * E.g from * https://github.com/boot2docker/boot2docker/blob/master/README.md#virtualbox-guest-additions * *

Users should be /Users /Users should be /Users c/Users should be /c/Users /c/Users should be * /c/Users c:/Users should be /c/Users */ @Test(dataProvider = "pathProvider") public void shouldNonStaticallyEscapePathForWindowsHost(String windowsPath, String expectedPath) { WindowsPathEscaper windowsPathEscaper = new WindowsPathEscaper(); assertEquals(windowsPathEscaper.escapePath(windowsPath), expectedPath); } @DataProvider(name = "pathProvider") public static Object[][] pathProvider() { return new Object[][] { {"Users", "/Users"}, {"/Users", "/Users"}, {"c/Users", "/c/Users"}, {"/c/Users", "/c/Users"}, {"c:/Users", "/c/Users"}, {"C:/Users", "/c/Users"}, { "C:/Users/path/dir/from/host:/name/of/dir/in/container", "/c/Users/path/dir/from/host:/name/of/dir/in/container" } }; } } ================================================ FILE: core/commons/che-core-commons-lang/src/test/resources/findbugs-exclude.xml ================================================ ================================================ FILE: core/commons/che-core-commons-lang/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: core/commons/che-core-commons-observability/pom.xml ================================================ 4.0.0 che-core-commons-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-commons-observability Che Core :: Commons :: Tracing and Monitoring wrapper com.google.guava guava com.google.inject guice io.micrometer micrometer-core io.opentracing opentracing-api io.opentracing.contrib opentracing-concurrent jakarta.inject jakarta.inject-api jakarta.validation jakarta.validation-api org.eclipse.che.core che-core-commons-lang org.eclipse.che.core che-core-commons-schedule org.slf4j slf4j-api ch.qos.logback logback-classic test org.eclipse.che.core che-core-commons-test test org.testng testng test ================================================ FILE: core/commons/che-core-commons-observability/src/main/java/io/micrometer/core/instrument/internal/TimedCronExecutorService.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package io.micrometer.core.instrument.internal; import static java.util.stream.Collectors.toList; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Timer; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.che.commons.schedule.executor.CronExecutorService; import org.eclipse.che.commons.schedule.executor.CronExpression; /** * A {@link CronExecutorService} that is timed. It provides the same metrics as {@link * io.micrometer.core.instrument.internal.TimedScheduledExecutorService} plus adds one extra counter * {@code executor.scheduled.cron}. The counter represents the number of invocations of method * {@link CronExecutorService#schedule(Runnable, CronExpression)} * * @author Sergii Kabashniuk */ public class TimedCronExecutorService implements CronExecutorService { private final CronExecutorService delegate; private final Counter scheduledCron; private final MeterRegistry registry; private final Timer executionTimer; private final Timer idleTimer; private final Counter scheduledOnce; private final Counter scheduledRepetitively; public TimedCronExecutorService( MeterRegistry registry, CronExecutorService delegate, String executorServiceName, Iterable tags) { this.registry = registry; this.delegate = delegate; this.executionTimer = registry.timer("executor", Tags.concat(tags, "name", executorServiceName)); this.idleTimer = registry.timer("executor.idle", Tags.concat(tags, "name", executorServiceName)); this.scheduledOnce = registry.counter("executor.scheduled.once", Tags.concat(tags, "name", executorServiceName)); this.scheduledRepetitively = registry.counter( "executor.scheduled.repetitively", Tags.concat(tags, "name", executorServiceName)); this.scheduledCron = registry.counter("executor.scheduled.cron", Tags.concat(tags, "name", executorServiceName)); } @Override public void shutdown() { delegate.shutdown(); } @Override public List shutdownNow() { return delegate.shutdownNow(); } @Override public boolean isShutdown() { return delegate.isShutdown(); } @Override public boolean isTerminated() { return delegate.isTerminated(); } @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { return delegate.awaitTermination(timeout, unit); } @Override public Future submit(Callable task) { return delegate.submit(wrap(task)); } @Override public Future submit(Runnable task, T result) { return delegate.submit(wrap(task), result); } @Override public Future submit(Runnable task) { return delegate.submit(wrap(task)); } @Override public List> invokeAll(Collection> tasks) throws InterruptedException { return delegate.invokeAll(wrapAll(tasks)); } @Override public List> invokeAll( Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { return delegate.invokeAll(wrapAll(tasks), timeout, unit); } @Override public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { return delegate.invokeAny(wrapAll(tasks)); } @Override public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return delegate.invokeAny(wrapAll(tasks), timeout, unit); } @Override public void execute(Runnable command) { delegate.execute(wrap(command)); } @Override public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { scheduledOnce.increment(); return delegate.schedule(executionTimer.wrap(command), delay, unit); } @Override public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { scheduledOnce.increment(); return delegate.schedule(executionTimer.wrap(callable), delay, unit); } @Override public ScheduledFuture scheduleAtFixedRate( Runnable command, long initialDelay, long period, TimeUnit unit) { scheduledRepetitively.increment(); return delegate.scheduleAtFixedRate(executionTimer.wrap(command), initialDelay, period, unit); } @Override public ScheduledFuture scheduleWithFixedDelay( Runnable command, long initialDelay, long delay, TimeUnit unit) { scheduledRepetitively.increment(); return delegate.scheduleWithFixedDelay(executionTimer.wrap(command), initialDelay, delay, unit); } private Runnable wrap(Runnable task) { return new TimedRunnable(registry, executionTimer, idleTimer, task); } private Callable wrap(Callable task) { return new TimedCallable<>(registry, executionTimer, idleTimer, task); } private Collection> wrapAll(Collection> tasks) { return tasks.stream().map(this::wrap).collect(toList()); } @Override public Future schedule(Runnable task, CronExpression expression) { scheduledCron.increment(); return delegate.schedule(executionTimer.wrap(task), expression); } } ================================================ FILE: core/commons/che-core-commons-observability/src/main/java/org/eclipse/che/commons/observability/CountedRejectedExecutionHandler.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.binder.BaseUnits; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; /** * Wrapper of {@link RejectedExecutionHandler} that reports to {@link MeterRegistry} number of * rejections. */ public class CountedRejectedExecutionHandler implements RejectedExecutionHandler { private final RejectedExecutionHandler delegate; private final Counter counter; public CountedRejectedExecutionHandler( RejectedExecutionHandler delegate, MeterRegistry registry, String name, Iterable tags) { this.delegate = delegate; this.counter = Counter.builder("executor.rejected") .tags(Tags.concat(tags, "name", name)) .description("The number of tasks not accepted for execution") .baseUnit(BaseUnits.TASKS) .register(registry); } @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { counter.increment(); delegate.rejectedExecution(r, executor); } public static void monitorRejections( MeterRegistry registry, ThreadPoolExecutor executor, String name, Iterable tags) { executor.setRejectedExecutionHandler( new CountedRejectedExecutionHandler( executor.getRejectedExecutionHandler(), registry, name, tags)); } } ================================================ FILE: core/commons/che-core-commons-observability/src/main/java/org/eclipse/che/commons/observability/CountedThreadFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.binder.BaseUnits; import jakarta.validation.constraints.NotNull; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicInteger; /** A {@link ThreadFactory} that monitors the number of threads created, running and terminated. */ public class CountedThreadFactory implements ThreadFactory { private final ThreadFactory delegate; private final Counter created; private final AtomicInteger running = new AtomicInteger(0); private final Counter terminated; /** * Wraps a {@link ThreadFactory} with an explicit name and records the number of created, running * and terminated threads. * * @param delegate {@link ThreadFactory} to wrap. * @param registry {@link MeterRegistry} that will contain the metrics. * @param name name for this delegate. * @param tags tags that can provide additional context. */ public CountedThreadFactory( ThreadFactory delegate, MeterRegistry registry, String name, Iterable tags) { this.delegate = delegate; this.created = Counter.builder("thread.factory.created") .tags(Tags.concat(tags, "name", name)) .description( "The approximate number of threads which were created with a thread factory") .baseUnit(BaseUnits.THREADS) .register(registry); this.terminated = Counter.builder("thread.factory.terminated") .tags(Tags.concat(tags, "name", name)) .description("The approximate number of threads which have finished execution") .baseUnit(BaseUnits.THREADS) .register(registry); Gauge.builder("thread.factory.running", running, AtomicInteger::get) .tags(Tags.concat(tags, "name", name)) .description( "The approximate number of threads which have started to execute, but have not terminated") .baseUnit(BaseUnits.THREADS) .register(registry); } /** {@inheritDoc} */ @Override public Thread newThread(@NotNull Runnable runnable) { Thread thread = delegate.newThread( () -> { running.incrementAndGet(); try { runnable.run(); } finally { running.decrementAndGet(); terminated.increment(); } }); created.increment(); return thread; } public static void monitorThreads( MeterRegistry registry, ThreadPoolExecutor executor, String name, Iterable tags) { executor.setThreadFactory( new CountedThreadFactory(executor.getThreadFactory(), registry, name, tags)); } } ================================================ FILE: core/commons/che-core-commons-observability/src/main/java/org/eclipse/che/commons/observability/ExecutorServiceWrapper.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import com.google.common.annotations.Beta; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import org.eclipse.che.commons.schedule.executor.CronExecutorService; /** * Wrapper of different implemntations of {@link ExecutorService}. At this moment supported: {@link * ExecutorService}, {@link ScheduledExecutorService} and {@link CronExecutorService}. * *

Depending on implementation and environment configuration wrapper can add to the original * class different capabilities like monitoring or tracing. * * @author Sergii Kabashniuk */ @Beta public interface ExecutorServiceWrapper { /** * Creates wrapper for the given executor. * * @param executor {@link ExecutorService} that has to be wrapped. * @param name unique name that can identify concrete instance of executor. * @param tags key/value pairs that gives some context about provided executor. * @return wrapped instance of given executor. */ ExecutorService wrap(ExecutorService executor, String name, String... tags); /** * Creates wrapper for the given executor. * * @param executor {@link ScheduledExecutorService} that has to be wrapped. * @param name unique name that can identify concrete instance of executor. * @param tags key/value pairs that gives some context about provided executor. * @return wrapped instance of given executor. */ ScheduledExecutorService wrap(ScheduledExecutorService executor, String name, String... tags); /** * Creates wrapper for the given executor. * * @param executor {@link CronExecutorService} that has to be wrapped. * @param name unique name that can identify concrete instance of executor. * @param tags key/value pairs that gives some context about provided executor. * @return wrapped instance of given executor. */ CronExecutorService wrap(CronExecutorService executor, String name, String... tags); } ================================================ FILE: core/commons/che-core-commons-observability/src/main/java/org/eclipse/che/commons/observability/MeteredAndTracedExecutorServiceWrapper.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.commons.schedule.executor.CronExecutorService; /** * Implementation of {@code ExecutorServiceWrapper} that add all sort of monitoring and tracing * capabilities. Monitoring allayed first, tracing second. */ @Singleton public class MeteredAndTracedExecutorServiceWrapper implements ExecutorServiceWrapper { private final MeteredExecutorServiceWrapper meteredExecutorServiceWrapper; private final TracedExecutorServiceWrapper tracedExecutorServiceWrapper; @Inject public MeteredAndTracedExecutorServiceWrapper( MeteredExecutorServiceWrapper meteredExecutorServiceWrapper, TracedExecutorServiceWrapper tracedExecutorServiceWrapper) { this.meteredExecutorServiceWrapper = meteredExecutorServiceWrapper; this.tracedExecutorServiceWrapper = tracedExecutorServiceWrapper; } @Override public ExecutorService wrap(ExecutorService executor, String name, String... tags) { return tracedExecutorServiceWrapper.wrap( meteredExecutorServiceWrapper.wrap(executor, name, tags), name, tags); } @Override public ScheduledExecutorService wrap( ScheduledExecutorService executor, String name, String... tags) { return tracedExecutorServiceWrapper.wrap( meteredExecutorServiceWrapper.wrap(executor, name, tags), name, tags); } @Override public CronExecutorService wrap(CronExecutorService executor, String name, String... tags) { return tracedExecutorServiceWrapper.wrap( meteredExecutorServiceWrapper.wrap(executor, name, tags), name, tags); } } ================================================ FILE: core/commons/che-core-commons-observability/src/main/java/org/eclipse/che/commons/observability/MeteredExecutorServiceWrapper.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics; import io.micrometer.core.instrument.internal.TimedCronExecutorService; import io.micrometer.core.lang.Nullable; import java.lang.reflect.Field; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.commons.schedule.executor.CronExecutorService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of {@code ExecutorServiceWrapper} that add all sort of monitoring capabilities * from {@code ExecutorServiceMetrics}. * *

Also in case if a provided executor is an instance of {@code ThreadPoolExecutor} it will add * metrics provided by {@code CountedThreadFactory} and {@code CountedRejectedExecutionHandler}. In * case if {@code ExecutorService} provided by {@code Executors} class are the instances of * java.util.concurrent.Executors$DelegatedScheduledExecutorService or * java.util.concurrent.Executors$FinalizableDelegatedExecutorService there would be an attempt to * unwrap them to get underlying {@code ThreadPoolExecutor} to be able to provide {@code * CountedThreadFactory} and {@code CountedRejectedExecutionHandler} statistics. Failed unwrapping * attempt would be only logged, no exception would be raised and no additional statistic would be * published. */ @Singleton public class MeteredExecutorServiceWrapper implements ExecutorServiceWrapper { private static final Logger LOG = LoggerFactory.getLogger(MeteredExecutorServiceWrapper.class); private final MeterRegistry meterRegistry; @Inject public MeteredExecutorServiceWrapper(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } @Override public ScheduledExecutorService wrap( ScheduledExecutorService executor, String name, String... tags) { monitorThreadPoolExecutor(executor, name, tags); return ExecutorServiceMetrics.monitor(meterRegistry, executor, name, Tags.of(tags)); } @Override public ExecutorService wrap(ExecutorService executor, String name, String... tags) { monitorThreadPoolExecutor(executor, name, tags); return ExecutorServiceMetrics.monitor(meterRegistry, executor, name, Tags.of(tags)); } @Override public CronExecutorService wrap(CronExecutorService executor, String name, String... tags) { monitorThreadPoolExecutor(executor, name, tags); new io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics( executor, name, Tags.of(tags)) .bindTo(meterRegistry); return new TimedCronExecutorService(meterRegistry, executor, name, Tags.of(tags)); } private void monitorThreadPoolExecutor(ExecutorService executor, String name, String... tags) { String className = executor.getClass().getName(); ThreadPoolExecutor unwrappedThreadPoolExecutor = null; if (executor instanceof ThreadPoolExecutor) { unwrappedThreadPoolExecutor = (ThreadPoolExecutor) executor; } else if (className.equals( "java.util.concurrent.Executors$DelegatedScheduledExecutorService")) { unwrappedThreadPoolExecutor = unwrapThreadPoolExecutor(executor, executor.getClass()); } else if (className.equals( "java.util.concurrent.Executors$FinalizableDelegatedExecutorService")) { unwrappedThreadPoolExecutor = unwrapThreadPoolExecutor(executor, executor.getClass().getSuperclass()); } if (unwrappedThreadPoolExecutor != null) { CountedThreadFactory.monitorThreads( meterRegistry, unwrappedThreadPoolExecutor, name, Tags.of(tags)); CountedRejectedExecutionHandler.monitorRejections( meterRegistry, unwrappedThreadPoolExecutor, name, Tags.of(tags)); } } /** * Every ScheduledThreadPoolExecutor created by {@link Executors} is wrapped. Also, {@link * Executors#newSingleThreadExecutor()} wrap a regular {@link ThreadPoolExecutor}. */ @Nullable private ThreadPoolExecutor unwrapThreadPoolExecutor(ExecutorService executor, Class wrapper) { try { Field e = wrapper.getDeclaredField("e"); e.setAccessible(true); return (ThreadPoolExecutor) e.get(executor); } catch (NoSuchFieldException | IllegalAccessException e) { LOG.error( String.format( "Unable to unwrap ThreadPoolExecutor from %s instance of %s." + " CountedThreadFactory and CountedThreadFactory statistic would be omitted", executor, wrapper), e); } return null; } } ================================================ FILE: core/commons/che-core-commons-observability/src/main/java/org/eclipse/che/commons/observability/NoopExecutorServiceWrapper.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import javax.inject.Singleton; import org.eclipse.che.commons.schedule.executor.CronExecutorService; /** * Implementation of {@link ExecutorServiceWrapper} that adds nothing. All wrap methods return the * same instance as result. * * @author Sergii Kabashniuk */ @Singleton public class NoopExecutorServiceWrapper implements ExecutorServiceWrapper { @Override public ExecutorService wrap(ExecutorService executor, String name, String... tags) { return executor; } @Override public ScheduledExecutorService wrap( ScheduledExecutorService executor, String name, String... tags) { return executor; } @Override public CronExecutorService wrap(CronExecutorService executor, String name, String... tags) { return executor; } } ================================================ FILE: core/commons/che-core-commons-observability/src/main/java/org/eclipse/che/commons/observability/ObservableThreadPullLauncher.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import com.google.common.util.concurrent.ThreadFactoryBuilder; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.eclipse.che.commons.schedule.executor.CronThreadPoolExecutor; import org.eclipse.che.commons.schedule.executor.ThreadPullLauncher; /** Monitored and traced implementation of {@code ThreadPullLauncher}. */ @Singleton public class ObservableThreadPullLauncher extends ThreadPullLauncher { @Inject public ObservableThreadPullLauncher( ExecutorServiceWrapper wrapper, @Named("schedule.core_pool_size") Integer corePoolSize) { super( wrapper.wrap( new CronThreadPoolExecutor( corePoolSize, new ThreadFactoryBuilder() .setNameFormat("Annotated-scheduler-%d") .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setDaemon(false) .build()), CronThreadPoolExecutor.class.getName())); } } ================================================ FILE: core/commons/che-core-commons-observability/src/main/java/org/eclipse/che/commons/observability/TracedCronExecutorService.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import io.opentracing.Tracer; import io.opentracing.contrib.concurrent.TracedRunnable; import io.opentracing.contrib.concurrent.TracedScheduledExecutorService; import java.util.concurrent.Future; import javax.inject.Inject; import org.eclipse.che.commons.schedule.executor.CronExecutorService; import org.eclipse.che.commons.schedule.executor.CronExpression; /** * Executor which propagates span from parent thread to submitted. Optionally it creates parent span * if traceWithActiveSpanOnly = false. */ public class TracedCronExecutorService extends TracedScheduledExecutorService implements CronExecutorService { private final CronExecutorService delegate; @Inject public TracedCronExecutorService(CronExecutorService delegate, Tracer tracer) { super(delegate, tracer); this.delegate = delegate; } @Override public Future schedule(Runnable task, CronExpression expression) { return delegate.schedule( tracer.activeSpan() == null ? task : new TracedRunnable(task, tracer), expression); } } ================================================ FILE: core/commons/che-core-commons-observability/src/main/java/org/eclipse/che/commons/observability/TracedExecutorServiceWrapper.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import io.opentracing.Tracer; import io.opentracing.contrib.concurrent.TracedExecutorService; import io.opentracing.contrib.concurrent.TracedScheduledExecutorService; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.commons.schedule.executor.CronExecutorService; /** * Implementation of {@code ExecutorServiceWrapper} that add all sort of tracing capabilities with * help of traced implementation. */ @Singleton public class TracedExecutorServiceWrapper implements ExecutorServiceWrapper { private final Tracer tracer; @Inject public TracedExecutorServiceWrapper(Tracer tracer) { this.tracer = tracer; } @Override public ExecutorService wrap(ExecutorService executor, String name, String... tags) { return new TracedExecutorService(executor, tracer); } @Override public ScheduledExecutorService wrap( ScheduledExecutorService executor, String name, String... tags) { return new TracedScheduledExecutorService(executor, tracer); } @Override public CronExecutorService wrap(CronExecutorService executor, String name, String... tags) { return new TracedCronExecutorService(executor, tracer); } } ================================================ FILE: core/commons/che-core-commons-observability/src/main/java/org/eclipse/che/commons/observability/deploy/ExecutorWrapperModule.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability.deploy; import com.google.inject.AbstractModule; import org.eclipse.che.commons.observability.*; import org.eclipse.che.commons.schedule.executor.ThreadPullLauncher; public class ExecutorWrapperModule extends AbstractModule { @Override protected void configure() { if (Boolean.parseBoolean(System.getenv("CHE_METRICS_ENABLED"))) { if (Boolean.parseBoolean(System.getenv("CHE_TRACING_ENABLED"))) { bind(ExecutorServiceWrapper.class) .to(MeteredAndTracedExecutorServiceWrapper.class) .asEagerSingleton(); } else { bind(ExecutorServiceWrapper.class) .to(MeteredExecutorServiceWrapper.class) .asEagerSingleton(); } } else { if (Boolean.parseBoolean(System.getenv("CHE_TRACING_ENABLED"))) { bind(ExecutorServiceWrapper.class) .to(TracedExecutorServiceWrapper.class) .asEagerSingleton(); } else { bind(ExecutorServiceWrapper.class).to(NoopExecutorServiceWrapper.class).asEagerSingleton(); } } bind(ThreadPullLauncher.class).to(ObservableThreadPullLauncher.class); } } ================================================ FILE: core/commons/che-core-commons-observability/src/test/java/io/micrometer/core/instrument/internal/TimedCronExecutorServiceTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package io.micrometer.core.instrument.internal; import static org.eclipse.che.commons.test.AssertRetry.assertWithRetry; import io.micrometer.core.instrument.MockClock; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.simple.SimpleConfig; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.text.ParseException; import java.util.concurrent.CountDownLatch; import org.eclipse.che.commons.schedule.executor.CronExpression; import org.eclipse.che.commons.schedule.executor.CronThreadPoolExecutor; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class TimedCronExecutorServiceTest { private SimpleMeterRegistry registry; private Iterable userTags = Tags.of("userTagKey", "userTagValue"); @BeforeMethod public void setup() { registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); } @Test public void executor() throws InterruptedException, ParseException { // given TimedCronExecutorService executorService = new TimedCronExecutorService( registry, new CronThreadPoolExecutor(1), TimedCronExecutorServiceTest.class.getName(), userTags); CountDownLatch lock = new CountDownLatch(1); // when executorService.schedule( () -> { lock.countDown(); }, // one time per second new CronExpression(" * * * ? * * *")); lock.await(); // then assertWithRetry( () -> registry .get("executor.scheduled.cron") .tags(userTags) .tag("name", TimedCronExecutorServiceTest.class.getName()) .counter() .count(), 1.0, 10, 50); assertWithRetry( () -> registry .get("executor") .tags(userTags) .tag("name", TimedCronExecutorServiceTest.class.getName()) .timer() .count(), 1L, 10, 50); } } ================================================ FILE: core/commons/che-core-commons-observability/src/test/java/org/eclipse/che/commons/observability/CountedRejectedExecutionHandlerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import static org.testng.Assert.assertEquals; import io.micrometer.core.instrument.MockClock; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.simple.SimpleConfig; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.concurrent.CountDownLatch; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class CountedRejectedExecutionHandlerTest { private SimpleMeterRegistry registry; private Iterable userTags = Tags.of("userTagKey", "userTagValue"); @BeforeMethod public void setup() { registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); } @Test public void countRejections() { // given ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new SynchronousQueue<>()); executor.setRejectedExecutionHandler((r, executor1) -> {}); CountedRejectedExecutionHandler.monitorRejections( registry, executor, CountedRejectedExecutionHandler.class.getName(), userTags); CountDownLatch runnableTaskComplete = new CountDownLatch(1); Runnable stub = () -> { try { runnableTaskComplete.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new IllegalStateException("runnable interrupted before completion"); } }; executor.submit(stub); // then for (int i = 0; i < 14; i++) { executor.submit( () -> { // do nothing. Task has to be rejected. }); } // when assertEquals( registry .get("executor.rejected") .tags(userTags) .tag("name", CountedRejectedExecutionHandler.class.getName()) .counter() .count(), 14.0); // cleanup runnableTaskComplete.countDown(); executor.shutdownNow(); } } ================================================ FILE: core/commons/che-core-commons-observability/src/test/java/org/eclipse/che/commons/observability/CountedThreadFactoryTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import io.micrometer.core.instrument.MockClock; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.simple.SimpleConfig; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class CountedThreadFactoryTest { private SimpleMeterRegistry registry; private Iterable userTags = Tags.of("userTagKey", "userTagValue"); @BeforeMethod public void setup() { registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); } @Test public void shouldCountCreatedThreads() { // given ThreadFactory factory = new CountedThreadFactory( Executors.defaultThreadFactory(), registry, CountedThreadFactoryTest.class.getName(), userTags); // when factory.newThread(() -> {}); // then assertEquals( registry .get("thread.factory.created") .tags(userTags) .tag("name", CountedThreadFactoryTest.class.getName()) .counter() .count(), 1.0); } @Test public void shouldCountRunningThreads() throws InterruptedException { // given ThreadFactory factory = new CountedThreadFactory( Executors.defaultThreadFactory(), registry, CountedThreadFactoryTest.class.getName(), userTags); CountDownLatch runnableTaskStart = new CountDownLatch(1); CountDownLatch runnableTaskComplete = new CountDownLatch(1); Runnable task = () -> { runnableTaskStart.countDown(); try { Assert.assertTrue(runnableTaskComplete.await(10, TimeUnit.SECONDS)); } catch (InterruptedException e) { throw new IllegalStateException("runnable interrupted before completion"); } }; // when Thread thread = factory.newThread(task); thread.start(); // then runnableTaskStart.await(); assertEquals( registry .get("thread.factory.running") .tags(userTags) .tag("name", CountedThreadFactoryTest.class.getName()) .gauge() .value(), 1.0); runnableTaskComplete.countDown(); thread.join(); assertEquals( registry .get("thread.factory.running") .tags(userTags) .tag("name", CountedThreadFactoryTest.class.getName()) .gauge() .value(), 0.0); // put here to ensure that thread are not GCd assertFalse(thread.isAlive()); // put here to ensure that factory are not GCd assertNotNull(factory); } @Test public void shouldCountRunningAndTerminatedThreadsInExecutorPool() throws InterruptedException, TimeoutException, ExecutionException { // given JoinableThreadFactory factory = new JoinableThreadFactory( new CountedThreadFactory( Executors.defaultThreadFactory(), registry, CountedThreadFactoryTest.class.getName(), userTags)); ExecutorService executor = Executors.newCachedThreadPool(factory); CountDownLatch runnableTaskStart = new CountDownLatch(10); CountDownLatch runnableTaskComplete = new CountDownLatch(1); Runnable task = () -> { runnableTaskStart.countDown(); try { Assert.assertTrue(runnableTaskComplete.await(10, TimeUnit.SECONDS)); } catch (InterruptedException e) { throw new IllegalStateException("runnable interrupted before completion"); } }; List futures = new ArrayList<>(10); for (int i = 0; i < 10; i++) { futures.add(executor.submit(task)); } runnableTaskStart.await(); assertEquals( registry .get("thread.factory.running") .tags(userTags) .tag("name", CountedThreadFactoryTest.class.getName()) .gauge() .value(), 10.0); assertEquals( registry .get("thread.factory.terminated") .tags(userTags) .tag("name", CountedThreadFactoryTest.class.getName()) .counter() .count(), 0.0); runnableTaskComplete.countDown(); for (Future future : futures) { future.get(1, TimeUnit.MINUTES); } executor.shutdownNow(); Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); factory.joinAll(); assertEquals( registry .get("thread.factory.running") .tags(userTags) .tag("name", CountedThreadFactoryTest.class.getName()) .gauge() .value(), 0.0); assertEquals( registry .get("thread.factory.terminated") .tags(userTags) .tag("name", CountedThreadFactoryTest.class.getName()) .counter() .count(), 10.0); // put here to ensure that factory are not GCd assertNotNull(factory); } public static class JoinableThreadFactory implements ThreadFactory { private final ThreadFactory delegate; private final List threads; public JoinableThreadFactory(ThreadFactory delegate) { this.delegate = delegate; this.threads = new ArrayList<>(); } @Override public Thread newThread(Runnable r) { Thread result = delegate.newThread(r); threads.add(result); return result; } public void joinAll() throws InterruptedException { for (Thread thread : threads) { if (thread.isAlive()) { thread.join(); } } } } } ================================================ FILE: core/commons/che-core-commons-observability/src/test/java/org/eclipse/che/commons/observability/MeteredExecutorServiceWrapperTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import static org.eclipse.che.commons.test.AssertRetry.assertWithRetry; import static org.testng.Assert.assertTrue; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MockClock; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.simple.SimpleConfig; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.text.ParseException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.che.commons.schedule.executor.CronExecutorService; import org.eclipse.che.commons.schedule.executor.CronExpression; import org.eclipse.che.commons.schedule.executor.CronThreadPoolExecutor; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Ignore; import org.testng.annotations.Test; @Ignore public class MeteredExecutorServiceWrapperTest { private MeterRegistry registry; private ExecutorServiceWrapper executorServiceWrapper; private Iterable userTags = Tags.of("userTagKey", "userTagValue"); private ExecutorService executor; @BeforeMethod public void setup() { registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); executorServiceWrapper = new MeteredExecutorServiceWrapper(registry); } @AfterMethod public void cleanup() { if (executor == null) return; // Tell threads to finish off. executor.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being cancelled if (!executor.awaitTermination(60, TimeUnit.SECONDS)) System.out.println("Pool did not terminate"); } } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted executor.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); } } @Test public void shouldRecordExecutorServiceMetrics() throws InterruptedException, TimeoutException, ExecutionException { // given executor = executorServiceWrapper.wrap( Executors.newSingleThreadExecutor(), MeteredExecutorServiceWrapperTest.class.getName(), "userTagKey", "userTagValue"); CountDownLatch runnableTaskStart = new CountDownLatch(1); // when Future future = executor.submit(runnableTaskStart::countDown); // then runnableTaskStart.await(10, TimeUnit.SECONDS); future.get(1, TimeUnit.MINUTES); assertCounter("thread.factory.terminated", 0.0); assertGauge("thread.factory.running", 1.0); assertCounter("thread.factory.created", 1.0); assertCounter("executor.rejected", 0.0); assertGauge("executor.pool.size", 1.0); assertFunctionCounter("executor.completed", 1.0); assertGauge("executor.queued", 0.0); assertTimerCount("executor.idle", 1L); assertGauge("executor.queue.remaining", (double) Integer.MAX_VALUE); assertTimerCount("executor", 1L); assertGauge("executor.active", 0.0); } @Test public void shouldRecordScheduledExecutorServiceMetrics() throws InterruptedException { // given executor = executorServiceWrapper.wrap( Executors.newSingleThreadScheduledExecutor(), MeteredExecutorServiceWrapperTest.class.getName(), "userTagKey", "userTagValue"); CountDownLatch runnableTaskStart = new CountDownLatch(1); // when ((ScheduledExecutorService) executor) .scheduleAtFixedRate(runnableTaskStart::countDown, 0, 100, TimeUnit.SECONDS); // then runnableTaskStart.await(10, TimeUnit.SECONDS); assertCounter("thread.factory.terminated", 0.0); assertGauge("thread.factory.running", 1.0); assertCounter("thread.factory.created", 1.0); assertCounter("executor.rejected", 0.0); assertGauge("executor.pool.size", 1.0); assertFunctionCounter("executor.completed", 1.0); assertGauge("executor.queued", 1.0); assertTimerCount("executor.idle", 0L); assertGauge("executor.queue.remaining", (double) Integer.MAX_VALUE); assertTimerCount("executor", 1L); assertGauge("executor.active", 0.0); assertCounter("executor.scheduled.once", 0.0); assertCounter("executor.scheduled.repetitively", 1.0); } @Test public void shouldRecordCronExecutorServiceMetrics() throws InterruptedException, ParseException { // given CronExecutorService executor = executorServiceWrapper.wrap( new CronThreadPoolExecutor(1), MeteredExecutorServiceWrapperTest.class.getName(), "userTagKey", "userTagValue"); CountDownLatch runnableTaskStart = new CountDownLatch(1); // when executor.schedule(runnableTaskStart::countDown, new CronExpression(" * * * ? * * *")); // then runnableTaskStart.await(10, TimeUnit.SECONDS); assertCounter("thread.factory.terminated", 0.0); assertGauge("thread.factory.running", 2.0); assertCounter("thread.factory.created", 2.0); assertCounter("executor.rejected", 0.0); assertGauge("executor.pool.size", 2.0); assertFunctionCounter("executor.completed", 1.0); assertGauge("executor.queued", 1.0); assertTimerCount("executor.idle", 0L); assertTrue( registry .get("executor.queue.remaining") .tag("name", MeteredExecutorServiceWrapperTest.class.getName()) .tags(userTags) .gauge() .value() > 0.0); assertTimerCount("executor", 1L); assertGauge("executor.active", 1.0); assertCounter("executor.scheduled.once", 0.0); assertCounter("executor.scheduled.repetitively", 0.0); assertCounter("executor.scheduled.cron", 1.0); } public void assertCounter(String counterName, Double value) throws InterruptedException { assertWithRetry( () -> registry .get(counterName) .tag("name", MeteredExecutorServiceWrapperTest.class.getName()) .tags(userTags) .counter() .count(), value, 20, 50); } public void assertFunctionCounter(String counterName, Double value) throws InterruptedException { assertWithRetry( () -> registry .get(counterName) .tag("name", MeteredExecutorServiceWrapperTest.class.getName()) .tags(userTags) .functionCounter() .count(), value, 20, 50); } public void assertGauge(String gaugeName, Double value) throws InterruptedException { assertWithRetry( () -> registry .get(gaugeName) .tag("name", MeteredExecutorServiceWrapperTest.class.getName()) .tags(userTags) .gauge() .value(), value, 20, 50); } public void assertTimerCount(String timerName, Long value) throws InterruptedException { assertWithRetry( () -> registry .get(timerName) .tag("name", MeteredExecutorServiceWrapperTest.class.getName()) .tags(userTags) .timer() .count(), value, 20, 50); } } ================================================ FILE: core/commons/che-core-commons-observability/src/test/java/org/eclipse/che/commons/observability/NoopExecutorServiceWrapperTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.observability; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import org.eclipse.che.commons.schedule.executor.CronExecutorService; import org.eclipse.che.commons.schedule.executor.CronThreadPoolExecutor; import org.testng.Assert; import org.testng.annotations.Test; public class NoopExecutorServiceWrapperTest { NoopExecutorServiceWrapper noopExecutorServiceWrapper = new NoopExecutorServiceWrapper(); @Test public void testWrapExecutorService() { // given ExecutorService executorService = Executors.newSingleThreadExecutor(); // when ExecutorService result = noopExecutorServiceWrapper.wrap( executorService, NoopExecutorServiceWrapper.class.getName(), "key", "value"); // then Assert.assertSame(result, executorService); } @Test public void testWrapScheduledExecutorService() { // given ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); // when ScheduledExecutorService result = noopExecutorServiceWrapper.wrap( executorService, NoopExecutorServiceWrapper.class.getName(), "key", "value"); // then Assert.assertSame(result, executorService); } @Test public void testWrapCronExecutorService() { // given CronExecutorService executorService = new CronThreadPoolExecutor(1); // when CronExecutorService result = noopExecutorServiceWrapper.wrap( executorService, NoopExecutorServiceWrapper.class.getName(), "key", "value"); // then Assert.assertSame(result, executorService); } } ================================================ FILE: core/commons/che-core-commons-observability/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: core/commons/che-core-commons-schedule/README.md ================================================ # Che job scheduling framework ## About There is such a common programming use case when you need to execute some method periodically. Usually it is implemented with some sort of a ThreadPoolExecutor. However, often developers don't pay enough attention to thread start stop routine. As a result we have an unnamed thread or thread that never stops, etc. Scheduling framework will take away all threading routine away from developer and add a couple of new features. ## Features - Run job with a fixed rate - Run job with a fixed delay - Run job according to the cron expression - Error logging - Container configuration - Automatic job discovering - Automatic thread pull start and shutdown. ## TODO - Ability to run demon jobs (can be terminated during JVM shutdown) - Metrics and statistic - Ability to control thread names - Time by UTC - Do not interrupt future jobs on exceptions - Ability to disable task. ## How to use ### Installation There is a couple of steps you need to do before start. Usually you need to do it once, in target war. First: add maven dependency. ```xml org.eclipse.che.core che-core-commons-schedule ``` Second: You need to install Guice module ```java install(new org.eclipse.che.commons.schedule.executor.ScheduleModule()); ``` Thread: You need to configure core pool size. This is the minimum number of workers to keep alive. ```java @Named("schedule.core_pool_size") Integer corePoolSize ``` Note: actual number of threads will be corePoolSize+1. One thread is needed to monitor cron jobs. ### Implementations notes Framework can execute methods with any visibility and any name. But method must have 0 parameters. If method that need to be executed is ```java void run() ``` and class implements java.lang.Runnable then this method will be executed without reflection that suppose to be faster then using reflections. Best practices is to implement Runnable interface and schedule method run. Classes must be annotated with javax.inject.Singleton or com.google.inject.Singleton and binded in guice as Eager singleton. ### Run job with fixed rate If you would like to execute some method with fixed rate. You can mark it with annotation for execution a periodic action that becomes enabled first after the given initial delay, and subsequently with the given period; that is executions will commence after initialDelay then initialDelay+period, then initialDelay + 2 * period, and so on. If any execution of the task encounters an exception, subsequent executions are suppressed. Otherwise, the task will only terminate via cancellation or termination of the executor. If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute. Analogue of java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate Example 1: Given method scheduleBackup will be executed once a minute after 1 minute initial delay. ```java @Singleton public class WorkspaceFsBackupScheduler { ... @ScheduleRate(initialDelay = 1, period = 1, unit = TimeUnit.MINUTES) public void scheduleBackup() { ... } ``` Example 2: Same as example 1, but timings configured over container named parameters. ```java @Singleton public class WorkspaceFsBackupScheduler { ... @ScheduleRate(initialDelayParameterName = "fs.backup.init_dalay", periodParameterName = "fs.backup.period", unit = TimeUnit.MINUTES) public void scheduleBackup() { ... } ```

NOTE: if initialDelay and initialDelayParameterName configured at the same time, initialDelayParameterName has greater weight when statically configured value. Same for period and periodParameterName.

### Run job with fixed delay If you would like to execute some method with fixed delay you can mark method with annotation for execution periodic action that becomes enabled first after the given initial delay, and subsequently with the given delay between the termination of one execution and the commencement of the next. Analogue of java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate Example 1: Given method registerRoutingRules will be executed with minute after 1 daley between end and start of new job after 1 minute initial delay. ```java @Singleton // should be eager public class RouterRulesRegistry { @ScheduleDelay(initialDelay = 1, delay = 1, unit = TimeUnit.MINUTES) private void registerRoutingRules() throws Exception { ... } ``` Example 2: Same as example 1, but timings configured over container named parameters. ```java @Singleton // should be eager public class RouterRulesRegistry { @ScheduleDelay(initialDelayParameterName = "router.rules.initialDelay", delayParameterName = "router.rules.delay", unit = TimeUnit.MINUTES) private void registerRoutingRules() throws Exception { ... } ```

NOTE: if initialDelayParameterName and initialDelay configured at the same time, initialDelayParameterName has greater weight when statically configured value. Same for delay and delayParameterName.

### Disabling scheduling by rate or period If a scheduled method has been configured through `delayParameterName` or `periodParameterName`, it is possible to disable the scheduling by setting a non positive value to the parameter. Example: Disabling scheduling of this `registerRoutingRules` method: ```java @Singleton // should be eager public class RouterRulesRegistry { @ScheduleDelay(initialDelayParameterName = "router.rules.initialDelay", delayParameterName = "router.rules.delay", unit = TimeUnit.MINUTES) private void registerRoutingRules() throws Exception { ... } ``` In che.properties: ``` router.rules.delay=-1 ``` will disable the scheduling ### Run job according to the cron expression If you would like to execute some method according to the cron expression you can mark method with annotation @ScheduleCron Example 1 : Send each Sunday at 1:00 AM. ```java @Singleton public class ReportSender { @ScheduleCron(cron = "0 0 1 ? * SUN *") // public void sendWeeklyReports() { ... } ``` Example 2 : Same as example 1, but cron expression configured over container named parameter "report.sender.cron". ```java @Singleton public class ReportSender { @ScheduleCron(cronParameterName = "report.sender.cron") // public void sendWeeklyReports() { ... } ```

NOTE: if cronParameterName and cron configured at the same time, cronParameterName has grater weight when statically configured value.

#### Cron expression syntax.
Cron expressions provide the ability to specify complex time combinations such as "At 8:00am every Monday through Friday" or "At 1:30am every last Friday of the month".

Cron expressions are comprised of 6 required fields and one optional field separated by white space. The fields respectively are described as follows: *
Field Name   Allowed Values   Allowed Special Characters
Seconds   0-59   , - * /
Minutes   0-59   , - * /
Hours   0-23   , - * /
Day-of-month   1-31   , - * ? / L W
Month   1-12 or JAN-DEC   , - * /
Day-of-Week   1-7 or SUN-SAT   , - * ? / L #
Year (Optional)   empty, 1970-2199   , - * /

The '*' character is used to specify all values. For example, "*" in the minute field means "every minute".

The '?' character is allowed for the day-of-month and day-of-week fields. It is used to specify 'no specific value'. This is useful when you need to specify something in one of the two fields, but not the other.

The '-' character is used to specify ranges For example "10-12" in the hour field means "the hours 10, 11 and 12".

The ',' character is used to specify additional values. For example "MON,WED,FRI" in the day-of-week field means "the days Monday, Wednesday, and Friday".

The '/' character is used to specify increments. For example "0/15" in the seconds field means "the seconds 0, 15, 30, and 45". And "5/15" in the seconds field means "the seconds 5, 20, 35, and 50". Specifying '*' before the '/' is equivalent to specifying 0 is the value to start with. Essentially, for each field in the expression, there is a set of numbers that can be turned on or off. For seconds and minutes, the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to 31, and for months 1 to 12. The "/" character simply helps you turn on every "nth" value in the given set. Thus "7/6" in the month field only turns on month "7", it does NOT mean every 6th month, please note that subtlety.

The 'L' character is allowed for the day-of-month and day-of-week fields. This character is short-hand for "last", but it has different meaning in each of the two fields. For example, the value "L" in the day-of-month field means "the last day of the month" - day 31 for January, day 28 for February on non-leap years. If used in the day-of-week field by itself, it simply means "7" or "SAT". But if used in the day-of-week field after another value, it means "the last xxx day of the month" - for example "6L" means "the last friday of the month". You can also specify an offset from the last day of the month, such as "L-3" which would mean the third-to-last day of the calendar month. When using the 'L' option, it is important not to specify lists, or ranges of values, as you'll get confusing/unexpected results.

The 'W' character is allowed for the day-of-month field. This character is used to specify the weekday (Monday-Friday) nearest the given day. As an example, if you were to specify "15W" as the value for the day-of-month field, the meaning is: "the nearest weekday to the 15th of the month". So if the 15th is a Saturday, the trigger will fire on Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. However if you specify "1W" as the value for day-of-month, and the 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not 'jump' over the boundary of a month's days. The 'W' character can only be specified when the day-of-month is a single day, not a range or list of days.

The 'L' and 'W' characters can also be combined for the day-of-month expression to yield 'LW', which translates to "last weekday of the month".

The '#' character is allowed for the day-of-week field. This character is used to specify "the nth" XXX day of the month. For example, the value of "6#3" in the day-of-week field means the third Friday of the month (day 6 = Friday and "#3" = the 3rd one in the month). Other examples: "2#1" = the first Monday of the month and "4#5" = the fifth Wednesday of the month. Note that if you specify "#5" and there is not 5 of the given day-of-week in the month, then no firing will occur that month. If the '#' character is used, there can only be one expression in the day-of-week field ("3#1,6#3" is not valid, since there are two expressions).

The legal characters and the names of months and days of the week are not case sensitive. *

NOTES:

  • Support for specifying both a day-of-week and a day-of-month value is not complete (you'll need to use the '?' character in one of these fields).
  • Overflowing ranges is supported - that is, having a larger number on the left hand side than the right. You might do 22-2 to catch 10 o'clock at night until 2 o'clock in the morning, or you might have NOV-FEB. It is very important to note that overuse of overflowing ranges creates ranges that don't make sense and no effort has been made to determine which interpretation CronExpression chooses. An example would be "0 0 14-6 ? * FRI-MON".

================================================ FILE: core/commons/che-core-commons-schedule/pom.xml ================================================ 4.0.0 che-core-commons-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-commons-schedule jar Che Core :: Commons :: Scheduler service ${project.build.testSourceDirectory}/../resources/findbugs-exclude.xml com.google.guava guava com.google.inject guice jakarta.annotation jakarta.annotation-api jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-commons-inject org.eclipse.che.core che-core-commons-lang org.slf4j slf4j-api ch.qos.logback logback-classic test org.testng testng test com.mycila license-maven-plugin **/**/CronExpression*.java ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/commons/schedule/Launcher.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.schedule; import java.util.concurrent.TimeUnit; /** * Launcher of periodic jobs. * * @author Sergii Kabashniuk */ public interface Launcher { /** * execution periodic action according to the cron expression. See more {@link * org.eclipse.che.commons.schedule.executor.CronExpression} */ void scheduleCron(Runnable runnable, String cron); /** * Execute periodic action that becomes enabled first after the given initial delay, and * subsequently with the given delay between the termination of one execution and the commencement * of the next. * *

Analogue of {@link * java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate(Runnable, long, long, * java.util.concurrent.TimeUnit)} } */ void scheduleWithFixedDelay(Runnable runnable, long initialDelay, long delay, TimeUnit unit); /** * Execute a periodic action that becomes enabled first after the given initial delay, and * subsequently with the given period; that is executions will commence after initialDelay then * initialDelay+period, then initialDelay + 2 * period, and so on. If any execution of the task * encounters an exception, subsequent executions are suppressed. Otherwise, the task will only * terminate via cancellation or termination of the executor. If any execution of this task takes * longer than its period, then subsequent executions may start late, but will not concurrently * execute. * *

Analogue of {@link * java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate(Runnable, long, long, * java.util.concurrent.TimeUnit)} } */ void scheduleAtFixedRate(Runnable runnable, long initialDelay, long period, TimeUnit unit); } ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/commons/schedule/ScheduleCron.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.schedule; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Mark method for execution periodic action according to the cron expression. See more {@link * org.eclipse.che.commons.schedule.executor.CronExpression} */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ScheduleCron { /** * @return - cron expression. */ String cron() default ""; /** * @return name of guice parameter with cron expression. */ String cronParameterName() default ""; } ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/commons/schedule/ScheduleDelay.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.schedule; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit; /** * Mark method for execution periodic action that becomes enabled first after the given initial * delay, and subsequently with the given delay between the termination of one execution and the * commencement of the next. * *

Analogue of {@link java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate(Runnable, * long, long, java.util.concurrent.TimeUnit)} } * * @author Sergii Kabashniuk */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ScheduleDelay { /** * @return - the time to delay first execution */ long initialDelay() default 0; /** * @return the delay between the termination of one execution and the commencement of the next */ long delay() default 0; /** * @return the time unit of the initialDelay and delay parameters */ TimeUnit unit() default TimeUnit.SECONDS; /** * @return - name of configuration parameter for initialDelay */ String initialDelayParameterName() default ""; /** * @return - name of configuration parameter for delay. A non positive delay value will disable * the scheduling of the method. */ String delayParameterName() default ""; } ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/commons/schedule/ScheduleRate.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.schedule; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit; /** * Mark method for execution a periodic action that becomes enabled first after the given initial * delay, and subsequently with the given period; that is executions will commence after * initialDelay then initialDelay+period, then initialDelay + 2 * period, and so on. If any * execution of the task encounters an exception, subsequent executions are suppressed. Otherwise, * the task will only terminate via cancellation or termination of the executor. If any execution of * this task takes longer than its period, then subsequent executions may start late, but will not * concurrently execute. * *

Analogue of {@link java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate(Runnable, * long, long, java.util.concurrent.TimeUnit)} } * * @author Sergii Kabashniuk */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ScheduleRate { /** * @return - the time to delay first execution */ long initialDelay() default 0; /** * @return the period between successive executions */ long period() default 0; /** * @return the time unit of the initialDelay and period parameters */ TimeUnit unit() default TimeUnit.SECONDS; /** * @return - name of configuration parameter for initialDelay */ String initialDelayParameterName() default ""; /** * @return - name of configuration parameter for period. A non positive period value will disable * the scheduling of the method. */ String periodParameterName() default ""; } ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/commons/schedule/executor/CronExecutorService.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.schedule.executor; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; /** Executor service that schedules a task for execution via a cron expression. */ public interface CronExecutorService extends ScheduledExecutorService { /** * Schedules the specified task to execute according to the specified cron expression. * * @param task the Runnable task to schedule * @param expression a cron expression */ Future schedule(Runnable task, CronExpression expression); } ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/commons/schedule/executor/CronExpression.java ================================================ /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * */ package org.eclipse.che.commons.schedule.executor; import java.io.Serializable; import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.SortedSet; import java.util.StringTokenizer; import java.util.TimeZone; import java.util.TreeSet; /** * Provides a parser and evaluator for unix-like cron expressions. Cron expressions provide the * ability to specify complex time combinations such as "At 8:00am every Monday through * Friday" or "At 1:30am every last Friday of the month". * *

Cron expressions are comprised of 6 required fields and one optional field separated by white * space. The fields respectively are described as follows: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Field Name Allowed Values Allowed Special Characters
Seconds  * 0-59  * , - * /
Minutes  * 0-59  * , - * /
Hours  * 0-23  * , - * /
Day-of-month  * 1-31  * , - * ? / L W
Month  * 1-12 or JAN-DEC  * , - * /
Day-of-Week  * 1-7 or SUN-SAT  * , - * ? / L #
Year (Optional)  * empty, 1970-2199  * , - * /
* *

The '*' character is used to specify all values. For example, "*" in the minute * field means "every minute". * *

The '?' character is allowed for the day-of-month and day-of-week fields. It is used to * specify 'no specific value'. This is useful when you need to specify something in one of the two * fields, but not the other. * *

The '-' character is used to specify ranges For example "10-12" in the hour field * means "the hours 10, 11 and 12". * *

The ',' character is used to specify additional values. For example "MON,WED,FRI" in * the day-of-week field means "the days Monday, Wednesday, and Friday". * *

The '/' character is used to specify increments. For example "0/15" in the seconds * field means "the seconds 0, 15, 30, and 45". And "5/15" in the seconds field * means "the seconds 5, 20, 35, and 50". Specifying '*' before the '/' is equivalent to * specifying 0 is the value to start with. Essentially, for each field in the expression, there is * a set of numbers that can be turned on or off. For seconds and minutes, the numbers range from 0 * to 59. For hours 0 to 23, for days of the month 0 to 31, and for months 1 to 12. The * "/" character simply helps you turn on every "nth" value in the given set. * Thus "7/6" in the month field only turns on month "7", it does NOT mean every * 6th month, please note that subtlety. * *

The 'L' character is allowed for the day-of-month and day-of-week fields. This character is * short-hand for "last", but it has different meaning in each of the two fields. For * example, the value "L" in the day-of-month field means "the last day of the * month" - day 31 for January, day 28 for February on non-leap years. If used in the * day-of-week field by itself, it simply means "7" or "SAT". But if used in the * day-of-week field after another value, it means "the last xxx day of the month" - for * example "6L" means "the last friday of the month". You can also specify an * offset from the last day of the month, such as "L-3" which would mean the third-to-last day of * the calendar month. When using the 'L' option, it is important not to specify lists, or ranges * of values, as you'll get confusing/unexpected results. * *

The 'W' character is allowed for the day-of-month field. This character is used to specify the * weekday (Monday-Friday) nearest the given day. As an example, if you were to specify * "15W" as the value for the day-of-month field, the meaning is: "the nearest * weekday to the 15th of the month". So if the 15th is a Saturday, the trigger will fire on * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the 16th. If the 15th * is a Tuesday, then it will fire on Tuesday the 15th. However if you specify "1W" as the * value for day-of-month, and the 1st is a Saturday, the trigger will fire on Monday the 3rd, as it * will not 'jump' over the boundary of a month's days. The 'W' character can only be specified when * the day-of-month is a single day, not a range or list of days. * *

The 'L' and 'W' characters can also be combined for the day-of-month expression to yield 'LW', * which translates to "last weekday of the month". * *

The '#' character is allowed for the day-of-week field. This character is used to specify * "the nth" XXX day of the month. For example, the value of "6#3" in the * day-of-week field means the third Friday of the month (day 6 = Friday and "#3" = the * 3rd one in the month). Other examples: "2#1" = the first Monday of the month and * "4#5" = the fifth Wednesday of the month. Note that if you specify "#5" and * there is not 5 of the given day-of-week in the month, then no firing will occur that month. If * the '#' character is used, there can only be one expression in the day-of-week field * ("3#1,6#3" is not valid, since there are two expressions). * *

* * *

The legal characters and the names of months and days of the week are not case sensitive. * *

NOTES: * *

    *
  • Support for specifying both a day-of-week and a day-of-month value is not complete (you'll * need to use the '?' character in one of these fields). *
  • Overflowing ranges is supported - that is, having a larger number on the left hand side * than the right. You might do 22-2 to catch 10 o'clock at night until 2 o'clock in the * morning, or you might have NOV-FEB. It is very important to note that overuse of * overflowing ranges creates ranges that don't make sense and no effort has been made to * determine which interpretation CronExpression chooses. An example would be "0 0 14-6 ? * * FRI-MON". *
* * @author Sharada Jambula, James House * @author Contributions from Mads Henderson * @author Refactoring from CronTrigger to CronExpression by Aaron Craven */ public final class CronExpression implements Serializable, Cloneable { private static final long serialVersionUID = 12423409423L; protected static final int SECOND = 0; protected static final int MINUTE = 1; protected static final int HOUR = 2; protected static final int DAY_OF_MONTH = 3; protected static final int MONTH = 4; protected static final int DAY_OF_WEEK = 5; protected static final int YEAR = 6; protected static final int ALL_SPEC_INT = 99; // '*' protected static final int NO_SPEC_INT = 98; // '?' protected static final Integer ALL_SPEC = ALL_SPEC_INT; protected static final Integer NO_SPEC = NO_SPEC_INT; protected static final Map monthMap = new HashMap(20); protected static final Map dayMap = new HashMap(60); static { monthMap.put("JAN", 0); monthMap.put("FEB", 1); monthMap.put("MAR", 2); monthMap.put("APR", 3); monthMap.put("MAY", 4); monthMap.put("JUN", 5); monthMap.put("JUL", 6); monthMap.put("AUG", 7); monthMap.put("SEP", 8); monthMap.put("OCT", 9); monthMap.put("NOV", 10); monthMap.put("DEC", 11); dayMap.put("SUN", 1); dayMap.put("MON", 2); dayMap.put("TUE", 3); dayMap.put("WED", 4); dayMap.put("THU", 5); dayMap.put("FRI", 6); dayMap.put("SAT", 7); } private final String cronExpression; private TimeZone timeZone = null; protected transient TreeSet seconds; protected transient TreeSet minutes; protected transient TreeSet hours; protected transient TreeSet daysOfMonth; protected transient TreeSet months; protected transient TreeSet daysOfWeek; protected transient TreeSet years; protected transient boolean lastdayOfWeek = false; protected transient int nthdayOfWeek = 0; protected transient boolean lastdayOfMonth = false; protected transient boolean nearestWeekday = false; protected transient int lastdayOffset = 0; protected transient boolean expressionParsed = false; public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100; /** * Constructs a new CronExpression based on the specified parameter. * * @param cronExpression String representation of the cron expression the new object should * represent * @throws java.text.ParseException if the string expression cannot be parsed into a valid * CronExpression */ public CronExpression(String cronExpression) throws ParseException { if (cronExpression == null) { throw new IllegalArgumentException("cronExpression cannot be null"); } this.cronExpression = cronExpression.toUpperCase(Locale.US); buildExpression(this.cronExpression); } /** * Constructs a new {@code CronExpression} as a copy of an existing instance. * * @param expression The existing cron expression to be copied */ public CronExpression(CronExpression expression) { /* * We don't call the other constructor here since we need to swallow the * ParseException. We also elide some of the sanity checking as it is * not logically trippable. */ this.cronExpression = expression.getCronExpression(); try { buildExpression(cronExpression); } catch (ParseException ex) { throw new AssertionError(); } if (expression.getTimeZone() != null) { setTimeZone((TimeZone) expression.getTimeZone().clone()); } } /** * Indicates whether the given date satisfies the cron expression. Note that milliseconds are * ignored, so two Dates falling on different milliseconds of the same second will always have the * same result here. * * @param date the date to evaluate * @return a boolean indicating whether the given date satisfies the cron expression */ public boolean isSatisfiedBy(Date date) { Calendar testDateCal = Calendar.getInstance(getTimeZone()); testDateCal.setTime(date); testDateCal.set(Calendar.MILLISECOND, 0); Date originalDate = testDateCal.getTime(); testDateCal.add(Calendar.SECOND, -1); Date timeAfter = getTimeAfter(testDateCal.getTime()); return ((timeAfter != null) && (timeAfter.equals(originalDate))); } /** * Returns the next date/time after the given date/time which satisfies the cron * expression. * * @param date the date/time at which to begin the search for the next valid date/time * @return the next valid date/time */ public Date getNextValidTimeAfter(Date date) { return getTimeAfter(date); } /** * Returns the next date/time after the given date/time which does not satisfy the * expression * * @param date the date/time at which to begin the search for the next invalid date/time * @return the next valid date/time */ public Date getNextInvalidTimeAfter(Date date) { long difference = 1000; // move back to the nearest second so differences will be accurate Calendar adjustCal = Calendar.getInstance(getTimeZone()); adjustCal.setTime(date); adjustCal.set(Calendar.MILLISECOND, 0); Date lastDate = adjustCal.getTime(); Date newDate; // FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. // Performance will be very bad here, depending on the cron expression. It is, however A // solution. // keep getting the next included time until it's farther than one second // apart. At that point, lastDate is the last valid fire time. We return // the second immediately following it. while (difference == 1000) { newDate = getTimeAfter(lastDate); if (newDate == null) break; difference = newDate.getTime() - lastDate.getTime(); if (difference == 1000) { lastDate = newDate; } } return new Date(lastDate.getTime() + 1000); } /** Returns the time zone for which this CronExpression will be resolved. */ public TimeZone getTimeZone() { if (timeZone == null) { timeZone = TimeZone.getDefault(); } return timeZone; } /** Sets the time zone for which this CronExpression will be resolved. */ public void setTimeZone(TimeZone timeZone) { this.timeZone = timeZone; } /** * Returns the string representation of the CronExpression * * @return a string representation of the CronExpression */ @Override public String toString() { return cronExpression; } /** * Indicates whether the specified cron expression can be parsed into a valid cron expression * * @param cronExpression the expression to evaluate * @return a boolean indicating whether the given expression is a valid cron expression */ public static boolean isValidExpression(String cronExpression) { try { new CronExpression(cronExpression); } catch (ParseException pe) { return false; } return true; } public static void validateExpression(String cronExpression) throws ParseException { new CronExpression(cronExpression); } //////////////////////////////////////////////////////////////////////////// // // Expression Parsing Functions // //////////////////////////////////////////////////////////////////////////// protected void buildExpression(String expression) throws ParseException { expressionParsed = true; try { if (seconds == null) { seconds = new TreeSet(); } if (minutes == null) { minutes = new TreeSet(); } if (hours == null) { hours = new TreeSet(); } if (daysOfMonth == null) { daysOfMonth = new TreeSet(); } if (months == null) { months = new TreeSet(); } if (daysOfWeek == null) { daysOfWeek = new TreeSet(); } if (years == null) { years = new TreeSet(); } int exprOn = SECOND; StringTokenizer exprsTok = new StringTokenizer(expression, " \t", false); while (exprsTok.hasMoreTokens() && exprOn <= YEAR) { String expr = exprsTok.nextToken().trim(); // throw an exception if L is used with other days of the month if (exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { throw new ParseException( "Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1); } // throw an exception if L is used with other days of the week if (exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { throw new ParseException( "Support for specifying 'L' with other days of the week is not implemented", -1); } if (exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') + 1) != -1) { throw new ParseException( "Support for specifying multiple \"nth\" days is not implemented.", -1); } StringTokenizer vTok = new StringTokenizer(expr, ","); while (vTok.hasMoreTokens()) { String v = vTok.nextToken(); storeExpressionVals(0, v, exprOn); } exprOn++; } if (exprOn <= DAY_OF_WEEK) { throw new ParseException("Unexpected end of expression.", expression.length()); } if (exprOn <= YEAR) { storeExpressionVals(0, "*", YEAR); } TreeSet dow = getSet(DAY_OF_WEEK); TreeSet dom = getSet(DAY_OF_MONTH); // Copying the logic from the UnsupportedOperationException below boolean dayOfMSpec = !dom.contains(NO_SPEC); boolean dayOfWSpec = !dow.contains(NO_SPEC); if (!dayOfMSpec || dayOfWSpec) { if (!dayOfWSpec || dayOfMSpec) { throw new ParseException( "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0); } } } catch (ParseException pe) { throw pe; } catch (Exception e) { throw new ParseException("Illegal cron expression format (" + e.toString() + ")", 0); } } protected int storeExpressionVals(int pos, String s, int type) throws ParseException { int incr = 0; int i = skipWhiteSpace(pos, s); if (i >= s.length()) { return i; } char c = s.charAt(i); if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) { String sub = s.substring(i, i + 3); int sval = -1; int eval = -1; if (type == MONTH) { sval = getMonthNumber(sub) + 1; if (sval <= 0) { throw new ParseException("Invalid Month value: '" + sub + "'", i); } if (s.length() > i + 3) { c = s.charAt(i + 3); if (c == '-') { i += 4; sub = s.substring(i, i + 3); eval = getMonthNumber(sub) + 1; if (eval <= 0) { throw new ParseException("Invalid Month value: '" + sub + "'", i); } } } } else if (type == DAY_OF_WEEK) { sval = getDayOfWeekNumber(sub); if (sval < 0) { throw new ParseException("Invalid Day-of-Week value: '" + sub + "'", i); } if (s.length() > i + 3) { c = s.charAt(i + 3); if (c == '-') { i += 4; sub = s.substring(i, i + 3); eval = getDayOfWeekNumber(sub); if (eval < 0) { throw new ParseException("Invalid Day-of-Week value: '" + sub + "'", i); } } else if (c == '#') { try { i += 4; nthdayOfWeek = Integer.parseInt(s.substring(i)); if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { throw new Exception(); } } catch (Exception e) { throw new ParseException( "A numeric value between 1 and 5 must follow the '#' option", i); } } else if (c == 'L') { lastdayOfWeek = true; i++; } } } else { throw new ParseException("Illegal characters for this position: '" + sub + "'", i); } if (eval != -1) { incr = 1; } addToSet(sval, eval, incr, type); return (i + 3); } if (c == '?') { i++; if ((i + 1) < s.length() && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) { throw new ParseException("Illegal character after '?': " + s.charAt(i), i); } if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) { throw new ParseException("'?' can only be specfied for Day-of-Month or Day-of-Week.", i); } if (type == DAY_OF_WEEK && !lastdayOfMonth) { int val = daysOfMonth.last(); if (val == NO_SPEC_INT) { throw new ParseException( "'?' can only be specfied for Day-of-Month -OR- Day-of-Week.", i); } } addToSet(NO_SPEC_INT, -1, 0, type); return i; } if (c == '*' || c == '/') { if (c == '*' && (i + 1) >= s.length()) { addToSet(ALL_SPEC_INT, -1, incr, type); return i + 1; } else if (c == '/' && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t')) { throw new ParseException("'/' must be followed by an integer.", i); } else if (c == '*') { i++; } c = s.charAt(i); if (c == '/') { // is an increment specified? i++; if (i >= s.length()) { throw new ParseException("Unexpected end of string.", i); } incr = getNumericValue(s, i); i++; if (incr > 10) { i++; } if (incr > 59 && (type == SECOND || type == MINUTE)) { throw new ParseException("Increment > 60 : " + incr, i); } else if (incr > 23 && (type == HOUR)) { throw new ParseException("Increment > 24 : " + incr, i); } else if (incr > 31 && (type == DAY_OF_MONTH)) { throw new ParseException("Increment > 31 : " + incr, i); } else if (incr > 7 && (type == DAY_OF_WEEK)) { throw new ParseException("Increment > 7 : " + incr, i); } else if (incr > 12 && (type == MONTH)) { throw new ParseException("Increment > 12 : " + incr, i); } } else { incr = 1; } addToSet(ALL_SPEC_INT, -1, incr, type); return i; } else if (c == 'L') { i++; if (type == DAY_OF_MONTH) { lastdayOfMonth = true; } if (type == DAY_OF_WEEK) { addToSet(7, 7, 0, type); } if (type == DAY_OF_MONTH && s.length() > i) { c = s.charAt(i); if (c == '-') { ValueSet vs = getValue(0, s, i + 1); lastdayOffset = vs.value; if (lastdayOffset > 30) throw new ParseException("Offset from last day must be <= 30", i + 1); i = vs.pos; } if (s.length() > i) { c = s.charAt(i); if (c == 'W') { nearestWeekday = true; i++; } } } return i; } else if (c >= '0' && c <= '9') { int val = Integer.parseInt(String.valueOf(c)); i++; if (i >= s.length()) { addToSet(val, -1, -1, type); } else { c = s.charAt(i); if (c >= '0' && c <= '9') { ValueSet vs = getValue(val, s, i); val = vs.value; i = vs.pos; } i = checkNext(i, s, val, type); return i; } } else { throw new ParseException("Unexpected character: " + c, i); } return i; } protected int checkNext(int pos, String s, int val, int type) throws ParseException { int end = -1; int i = pos; if (i >= s.length()) { addToSet(val, end, -1, type); return i; } char c = s.charAt(pos); if (c == 'L') { if (type == DAY_OF_WEEK) { if (val < 1 || val > 7) throw new ParseException("Day-of-Week values must be between 1 and 7", -1); lastdayOfWeek = true; } else { throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i); } TreeSet set = getSet(type); set.add(val); i++; return i; } if (c == 'W') { if (type == DAY_OF_MONTH) { nearestWeekday = true; } else { throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i); } if (val > 31) throw new ParseException( "The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i); TreeSet set = getSet(type); set.add(val); i++; return i; } if (c == '#') { if (type != DAY_OF_WEEK) { throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i); } i++; try { nthdayOfWeek = Integer.parseInt(s.substring(i)); if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { throw new Exception(); } } catch (Exception e) { throw new ParseException("A numeric value between 1 and 5 must follow the '#' option", i); } TreeSet set = getSet(type); set.add(val); i++; return i; } if (c == '-') { i++; c = s.charAt(i); int v = Integer.parseInt(String.valueOf(c)); end = v; i++; if (i >= s.length()) { addToSet(val, end, 1, type); return i; } c = s.charAt(i); if (c >= '0' && c <= '9') { ValueSet vs = getValue(v, s, i); end = vs.value; i = vs.pos; } if (i < s.length() && ((c = s.charAt(i)) == '/')) { i++; c = s.charAt(i); int v2 = Integer.parseInt(String.valueOf(c)); i++; if (i >= s.length()) { addToSet(val, end, v2, type); return i; } c = s.charAt(i); if (c >= '0' && c <= '9') { ValueSet vs = getValue(v2, s, i); int v3 = vs.value; addToSet(val, end, v3, type); i = vs.pos; return i; } else { addToSet(val, end, v2, type); return i; } } else { addToSet(val, end, 1, type); return i; } } if (c == '/') { i++; c = s.charAt(i); int v2 = Integer.parseInt(String.valueOf(c)); i++; if (i >= s.length()) { addToSet(val, end, v2, type); return i; } c = s.charAt(i); if (c >= '0' && c <= '9') { ValueSet vs = getValue(v2, s, i); int v3 = vs.value; addToSet(val, end, v3, type); i = vs.pos; return i; } else { throw new ParseException("Unexpected character '" + c + "' after '/'", i); } } addToSet(val, end, 0, type); i++; return i; } public String getCronExpression() { return cronExpression; } public String getExpressionSummary() { StringBuilder buf = new StringBuilder(); buf.append("seconds: "); buf.append(getExpressionSetSummary(seconds)); buf.append("\n"); buf.append("minutes: "); buf.append(getExpressionSetSummary(minutes)); buf.append("\n"); buf.append("hours: "); buf.append(getExpressionSetSummary(hours)); buf.append("\n"); buf.append("daysOfMonth: "); buf.append(getExpressionSetSummary(daysOfMonth)); buf.append("\n"); buf.append("months: "); buf.append(getExpressionSetSummary(months)); buf.append("\n"); buf.append("daysOfWeek: "); buf.append(getExpressionSetSummary(daysOfWeek)); buf.append("\n"); buf.append("lastdayOfWeek: "); buf.append(lastdayOfWeek); buf.append("\n"); buf.append("nearestWeekday: "); buf.append(nearestWeekday); buf.append("\n"); buf.append("NthDayOfWeek: "); buf.append(nthdayOfWeek); buf.append("\n"); buf.append("lastdayOfMonth: "); buf.append(lastdayOfMonth); buf.append("\n"); buf.append("years: "); buf.append(getExpressionSetSummary(years)); buf.append("\n"); return buf.toString(); } protected String getExpressionSetSummary(java.util.Set set) { if (set.contains(NO_SPEC)) { return "?"; } if (set.contains(ALL_SPEC)) { return "*"; } StringBuilder buf = new StringBuilder(); Iterator itr = set.iterator(); boolean first = true; while (itr.hasNext()) { Integer iVal = itr.next(); String val = iVal.toString(); if (!first) { buf.append(","); } buf.append(val); first = false; } return buf.toString(); } protected String getExpressionSetSummary(java.util.ArrayList list) { if (list.contains(NO_SPEC)) { return "?"; } if (list.contains(ALL_SPEC)) { return "*"; } StringBuilder buf = new StringBuilder(); Iterator itr = list.iterator(); boolean first = true; while (itr.hasNext()) { Integer iVal = itr.next(); String val = iVal.toString(); if (!first) { buf.append(","); } buf.append(val); first = false; } return buf.toString(); } protected int skipWhiteSpace(int i, String s) { for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) { ; } return i; } protected int findNextWhiteSpace(int i, String s) { for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) { ; } return i; } protected void addToSet(int val, int end, int incr, int type) throws ParseException { TreeSet set = getSet(type); if (type == SECOND || type == MINUTE) { if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) { throw new ParseException("Minute and Second values must be between 0 and 59", -1); } } else if (type == HOUR) { if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) { throw new ParseException("Hour values must be between 0 and 23", -1); } } else if (type == DAY_OF_MONTH) { if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) { throw new ParseException("Day of month values must be between 1 and 31", -1); } } else if (type == MONTH) { if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) { throw new ParseException("Month values must be between 1 and 12", -1); } } else if (type == DAY_OF_WEEK) { if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) { throw new ParseException("Day-of-Week values must be between 1 and 7", -1); } } if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) { if (val != -1) { set.add(val); } else { set.add(NO_SPEC); } return; } int startAt = val; int stopAt = end; if (val == ALL_SPEC_INT && incr <= 0) { incr = 1; set.add(ALL_SPEC); // put in a marker, but also fill values } if (type == SECOND || type == MINUTE) { if (stopAt == -1) { stopAt = 59; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 0; } } else if (type == HOUR) { if (stopAt == -1) { stopAt = 23; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 0; } } else if (type == DAY_OF_MONTH) { if (stopAt == -1) { stopAt = 31; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 1; } } else if (type == MONTH) { if (stopAt == -1) { stopAt = 12; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 1; } } else if (type == DAY_OF_WEEK) { if (stopAt == -1) { stopAt = 7; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 1; } } else if (type == YEAR) { if (stopAt == -1) { stopAt = MAX_YEAR; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 1970; } } // if the end of the range is before the start, then we need to overflow into // the next day, month etc. This is done by adding the maximum amount for that // type, and using modulus max to determine the value being added. int max = -1; if (stopAt < startAt) { switch (type) { case SECOND: max = 60; break; case MINUTE: max = 60; break; case HOUR: max = 24; break; case MONTH: max = 12; break; case DAY_OF_WEEK: max = 7; break; case DAY_OF_MONTH: max = 31; break; case YEAR: throw new IllegalArgumentException("Start year must be less than stop year"); default: throw new IllegalArgumentException("Unexpected type encountered"); } stopAt += max; } for (int i = startAt; i <= stopAt; i += incr) { if (max == -1) { // ie: there's no max to overflow over set.add(i); } else { // take the modulus to get the real value int i2 = i % max; // 1-indexed ranges should not include 0, and should include their max if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH)) { i2 = max; } set.add(i2); } } } TreeSet getSet(int type) { switch (type) { case SECOND: return seconds; case MINUTE: return minutes; case HOUR: return hours; case DAY_OF_MONTH: return daysOfMonth; case MONTH: return months; case DAY_OF_WEEK: return daysOfWeek; case YEAR: return years; default: return null; } } protected ValueSet getValue(int v, String s, int i) { char c = s.charAt(i); StringBuilder s1 = new StringBuilder(String.valueOf(v)); while (c >= '0' && c <= '9') { s1.append(c); i++; if (i >= s.length()) { break; } c = s.charAt(i); } ValueSet val = new ValueSet(); val.pos = (i < s.length()) ? i : i + 1; val.value = Integer.parseInt(s1.toString()); return val; } protected int getNumericValue(String s, int i) { int endOfVal = findNextWhiteSpace(i, s); String val = s.substring(i, endOfVal); return Integer.parseInt(val); } protected int getMonthNumber(String s) { Integer integer = monthMap.get(s); if (integer == null) { return -1; } return integer; } protected int getDayOfWeekNumber(String s) { Integer integer = dayMap.get(s); if (integer == null) { return -1; } return integer; } //////////////////////////////////////////////////////////////////////////// // // Computation Functions // //////////////////////////////////////////////////////////////////////////// public Date getTimeAfter(Date afterTime) { // Computation is based on Gregorian year only. Calendar cl = new java.util.GregorianCalendar(getTimeZone()); // move ahead one second, since we're computing the time *after* the // given time afterTime = new Date(afterTime.getTime() + 1000); // CronTrigger does not deal with milliseconds cl.setTime(afterTime); cl.set(Calendar.MILLISECOND, 0); boolean gotOne = false; // loop until we've computed the next time, or we've past the endTime while (!gotOne) { // if (endTime != null && cl.getTime().after(endTime)) return null; if (cl.get(Calendar.YEAR) > 2999) { // prevent endless loop... return null; } SortedSet st = null; int t = 0; int sec = cl.get(Calendar.SECOND); int min = cl.get(Calendar.MINUTE); // get second................................................. st = seconds.tailSet(sec); if (st != null && st.size() != 0) { sec = st.first(); } else { sec = seconds.first(); min++; cl.set(Calendar.MINUTE, min); } cl.set(Calendar.SECOND, sec); min = cl.get(Calendar.MINUTE); int hr = cl.get(Calendar.HOUR_OF_DAY); t = -1; // get minute................................................. st = minutes.tailSet(min); if (st != null && st.size() != 0) { t = min; min = st.first(); } else { min = minutes.first(); hr++; } if (min != t) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, min); setCalendarHour(cl, hr); continue; } cl.set(Calendar.MINUTE, min); hr = cl.get(Calendar.HOUR_OF_DAY); int day = cl.get(Calendar.DAY_OF_MONTH); t = -1; // get hour................................................... st = hours.tailSet(hr); if (st != null && st.size() != 0) { t = hr; hr = st.first(); } else { hr = hours.first(); day++; } if (hr != t) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.DAY_OF_MONTH, day); setCalendarHour(cl, hr); continue; } cl.set(Calendar.HOUR_OF_DAY, hr); day = cl.get(Calendar.DAY_OF_MONTH); int mon = cl.get(Calendar.MONTH) + 1; // '+ 1' because calendar is 0-based for this field, and we are // 1-based t = -1; int tmon = mon; // get day................................................... boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC); boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC); if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule st = daysOfMonth.tailSet(day); if (lastdayOfMonth) { if (!nearestWeekday) { t = day; day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); day -= lastdayOffset; if (t > day) { mon++; if (mon > 12) { mon = 1; tmon = 3333; // ensure test of mon != tmon further below fails cl.add(Calendar.YEAR, 1); } day = 1; } } else { t = day; day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); day -= lastdayOffset; java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone()); tcal.set(Calendar.SECOND, 0); tcal.set(Calendar.MINUTE, 0); tcal.set(Calendar.HOUR_OF_DAY, 0); tcal.set(Calendar.DAY_OF_MONTH, day); tcal.set(Calendar.MONTH, mon - 1); tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); int dow = tcal.get(Calendar.DAY_OF_WEEK); if (dow == Calendar.SATURDAY && day == 1) { day += 2; } else if (dow == Calendar.SATURDAY) { day -= 1; } else if (dow == Calendar.SUNDAY && day == ldom) { day -= 2; } else if (dow == Calendar.SUNDAY) { day += 1; } tcal.set(Calendar.SECOND, sec); tcal.set(Calendar.MINUTE, min); tcal.set(Calendar.HOUR_OF_DAY, hr); tcal.set(Calendar.DAY_OF_MONTH, day); tcal.set(Calendar.MONTH, mon - 1); Date nTime = tcal.getTime(); if (nTime.before(afterTime)) { day = 1; mon++; } } } else if (nearestWeekday) { t = day; day = daysOfMonth.first(); java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone()); tcal.set(Calendar.SECOND, 0); tcal.set(Calendar.MINUTE, 0); tcal.set(Calendar.HOUR_OF_DAY, 0); tcal.set(Calendar.DAY_OF_MONTH, day); tcal.set(Calendar.MONTH, mon - 1); tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); int dow = tcal.get(Calendar.DAY_OF_WEEK); if (dow == Calendar.SATURDAY && day == 1) { day += 2; } else if (dow == Calendar.SATURDAY) { day -= 1; } else if (dow == Calendar.SUNDAY && day == ldom) { day -= 2; } else if (dow == Calendar.SUNDAY) { day += 1; } tcal.set(Calendar.SECOND, sec); tcal.set(Calendar.MINUTE, min); tcal.set(Calendar.HOUR_OF_DAY, hr); tcal.set(Calendar.DAY_OF_MONTH, day); tcal.set(Calendar.MONTH, mon - 1); Date nTime = tcal.getTime(); if (nTime.before(afterTime)) { day = daysOfMonth.first(); mon++; } } else if (st != null && st.size() != 0) { t = day; day = st.first(); // make sure we don't over-run a short month, such as february int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); if (day > lastDay) { day = daysOfMonth.first(); mon++; } } else { day = daysOfMonth.first(); mon++; } if (day != t || mon != tmon) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, day); cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar is 0-based for this field, and we // are 1-based continue; } } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule if (lastdayOfWeek) { // are we looking for the last XXX day of // the month? int dow = daysOfWeek.first(); // desired // d-o-w int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w int daysToAdd = 0; if (cDow < dow) { daysToAdd = dow - cDow; } if (cDow > dow) { daysToAdd = dow + (7 - cDow); } int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); if (day + daysToAdd > lDay) { // did we already miss the // last one? cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, 1); cl.set(Calendar.MONTH, mon); // no '- 1' here because we are promoting the month continue; } // find date of last occurrence of this day in this month... while ((day + daysToAdd + 7) <= lDay) { daysToAdd += 7; } day += daysToAdd; if (daysToAdd > 0) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, day); cl.set(Calendar.MONTH, mon - 1); // '- 1' here because we are not promoting the month continue; } } else if (nthdayOfWeek != 0) { // are we looking for the Nth XXX day in the month? int dow = daysOfWeek.first(); // desired // d-o-w int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w int daysToAdd = 0; if (cDow < dow) { daysToAdd = dow - cDow; } else if (cDow > dow) { daysToAdd = dow + (7 - cDow); } boolean dayShifted = false; if (daysToAdd > 0) { dayShifted = true; } day += daysToAdd; int weekOfMonth = day / 7; if (day % 7 > 0) { weekOfMonth++; } daysToAdd = (nthdayOfWeek - weekOfMonth) * 7; day += daysToAdd; if (daysToAdd < 0 || day > getLastDayOfMonth(mon, cl.get(Calendar.YEAR))) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, 1); cl.set(Calendar.MONTH, mon); // no '- 1' here because we are promoting the month continue; } else if (daysToAdd > 0 || dayShifted) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, day); cl.set(Calendar.MONTH, mon - 1); // '- 1' here because we are NOT promoting the month continue; } } else { int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w int dow = daysOfWeek.first(); // desired // d-o-w st = daysOfWeek.tailSet(cDow); if (st != null && st.size() > 0) { dow = st.first(); } int daysToAdd = 0; if (cDow < dow) { daysToAdd = dow - cDow; } if (cDow > dow) { daysToAdd = dow + (7 - cDow); } int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); if (day + daysToAdd > lDay) { // will we pass the end of // the month? cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, 1); cl.set(Calendar.MONTH, mon); // no '- 1' here because we are promoting the month continue; } else if (daysToAdd > 0) { // are we swithing days? cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd); cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar is 0-based for this field, // and we are 1-based continue; } } } else { // dayOfWSpec && !dayOfMSpec throw new UnsupportedOperationException( "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."); } cl.set(Calendar.DAY_OF_MONTH, day); mon = cl.get(Calendar.MONTH) + 1; // '+ 1' because calendar is 0-based for this field, and we are // 1-based int year = cl.get(Calendar.YEAR); t = -1; // test for expressions that never generate a valid fire date, // but keep looping... if (year > MAX_YEAR) { return null; } // get month................................................... st = months.tailSet(mon); if (st != null && st.size() != 0) { t = mon; mon = st.first(); } else { mon = months.first(); year++; } if (mon != t) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, 1); cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar is 0-based for this field, and we are // 1-based cl.set(Calendar.YEAR, year); continue; } cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar is 0-based for this field, and we are // 1-based year = cl.get(Calendar.YEAR); t = -1; // get year................................................... st = years.tailSet(year); if (st != null && st.size() != 0) { t = year; year = st.first(); } else { return null; // ran out of years... } if (year != t) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, 1); cl.set(Calendar.MONTH, 0); // '- 1' because calendar is 0-based for this field, and we are // 1-based cl.set(Calendar.YEAR, year); continue; } cl.set(Calendar.YEAR, year); gotOne = true; } // while( !done ) return cl.getTime(); } /** * Advance the calendar to the particular hour paying particular attention to daylight saving * problems. * * @param cal the calendar to operate on * @param hour the hour to set */ protected void setCalendarHour(Calendar cal, int hour) { cal.set(java.util.Calendar.HOUR_OF_DAY, hour); if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) { cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1); } } /** * NOT YET IMPLEMENTED: Returns the time before the given time that the CronExpression * matches. */ public Date getTimeBefore(Date endTime) { // FUTURE_TODO: implement QUARTZ-423 return null; } /** * NOT YET IMPLEMENTED: Returns the final time that the CronExpression will match. */ public Date getFinalFireTime() { // FUTURE_TODO: implement QUARTZ-423 return null; } protected boolean isLeapYear(int year) { return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); } protected int getLastDayOfMonth(int monthNum, int year) { switch (monthNum) { case 1: return 31; case 2: return (isLeapYear(year)) ? 29 : 28; case 3: return 31; case 4: return 30; case 5: return 31; case 6: return 30; case 7: return 31; case 8: return 31; case 9: return 30; case 10: return 31; case 11: return 30; case 12: return 31; default: throw new IllegalArgumentException("Illegal month number: " + monthNum); } } private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException { stream.defaultReadObject(); try { buildExpression(cronExpression); } catch (Exception ignore) { } // never happens } @Override @Deprecated public Object clone() { return new CronExpression(this); } } class ValueSet { public int value; public int pos; } ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/commons/schedule/executor/CronThreadPoolExecutor.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.schedule.executor; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Scheduled thread-pool executor implementation that leverages a CronExpression to calculate future * execution times for scheduled tasks. */ public class CronThreadPoolExecutor extends ScheduledThreadPoolExecutor implements CronExecutorService { private static final Logger LOG = LoggerFactory.getLogger(CronThreadPoolExecutor.class); private final List cronJobWatchDogs; /** * Constructs a new CronThreadPoolExecutor. * * @param corePoolSize the pool size */ public CronThreadPoolExecutor(int corePoolSize) { super(corePoolSize); this.cronJobWatchDogs = new ArrayList<>(); this.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); } /** * Constructs a new CronThreadPoolExecutor. * * @param corePoolSize the pool size * @param threadFactory the thread factory */ public CronThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) { super(corePoolSize, threadFactory); this.cronJobWatchDogs = new ArrayList<>(); this.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); } /** * Constructs a new CronThreadPoolExecutor. * * @param corePoolSize the pool size * @param handler the handler for rejected executions */ public CronThreadPoolExecutor(int corePoolSize, RejectedExecutionHandler handler) { super(corePoolSize, handler); this.cronJobWatchDogs = new ArrayList<>(); this.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); } /** * Constructs a new CronThreadPoolExecutor. * * @param corePoolSize the pool size * @param handler the handler for rejecting executions * @param threadFactory the thread factory */ public CronThreadPoolExecutor( int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) { super(corePoolSize, threadFactory, handler); this.cronJobWatchDogs = new ArrayList<>(); } @Override public Future schedule(final Runnable task, final CronExpression expression) { if (task == null) { throw new NullPointerException(); } setCorePoolSize(getCorePoolSize() + 1); Runnable scheduleTask = new Runnable() { @Override public void run() { CountDownLatch countDownLatch = new CountDownLatch(1); cronJobWatchDogs.add(countDownLatch); Date now = new Date(); Date time = expression.getNextValidTimeAfter(now); try { while (time != null) { CronThreadPoolExecutor.this.schedule( task, time.getTime() - now.getTime(), TimeUnit.MILLISECONDS); while (now.before(time)) { LOG.debug("Cron watch dog wait {} ", time.getTime() - now.getTime()); if (countDownLatch.await(time.getTime() - now.getTime(), TimeUnit.MILLISECONDS)) { LOG.debug("Stopping cron watch dog."); return; } now = new Date(); } time = expression.getNextValidTimeAfter(now); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (RejectedExecutionException | CancellationException e) { LOG.error(e.getMessage(), e); } } }; return this.submit(scheduleTask); } @Override public void shutdown() { for (CountDownLatch cronJobWatchDog : cronJobWatchDogs) { cronJobWatchDog.countDown(); } cronJobWatchDogs.clear(); super.shutdown(); LOG.debug( "Active {} Pool {}, CEPTAS {} , EEDTAS {} , Task count {} , queue size {}", getActiveCount(), getPoolSize(), getContinueExistingPeriodicTasksAfterShutdownPolicy(), getExecuteExistingDelayedTasksAfterShutdownPolicy(), getTaskCount(), getQueue().size()); } @Override public List shutdownNow() { for (CountDownLatch cronJobWatchDog : cronJobWatchDogs) { cronJobWatchDog.countDown(); } cronJobWatchDogs.clear(); LOG.debug( "Active {} Pool {}, CEPTAS {} , EEDTAS {} , Task count {} , queue size {}", getActiveCount(), getPoolSize(), getContinueExistingPeriodicTasksAfterShutdownPolicy(), getExecuteExistingDelayedTasksAfterShutdownPolicy(), getTaskCount(), getQueue().size()); return super.shutdownNow(); } } ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/commons/schedule/executor/LoggedRunnable.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.schedule.executor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Invoke given method of given object. * * @author Sergii Kabashniuk */ public class LoggedRunnable implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(LoggedRunnable.class); private final Object object; private final Method method; public LoggedRunnable(Object object, Method method) { this.object = object; this.method = method; } @Override public void run() { long startTime = System.currentTimeMillis(); try { if (object instanceof Runnable && method.getName().equals("run") && method.getParameterTypes().length == 0) { LOG.debug( "Invoking method 'run' of class '{}' instance '{}'", object.getClass().getName(), object); ((Runnable) object).run(); LOG.debug( "Method of class '{}' instance '{}' is completed in {} sec", object.getClass().getName(), object, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - startTime)); } else { try { LOG.debug( "Invoking method '{}' of class '{}' instance '{}'", method.getName(), object.getClass().getName(), object); method.invoke(object); LOG.debug( "Method of class '{}' instance '{}' is completed in {} sec", object.getClass().getName(), object, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - startTime)); } catch (InvocationTargetException | IllegalAccessException e) { LOG.error( "Error occurred during invocation of method '{}#{}'. Instance: '{}'. Error: {}", object.getClass().getName(), method.getName(), object, e.getMessage(), e); } } } catch (Exception e) { LOG.error( "Error occurred during invocation of method '{}#{}'. Instance: '{}'. Error: {}", object.getClass().getName(), method.getName(), object, e.getMessage(), e); throw e; } } @Override public String toString() { return "LoggedRunnable{" + "methodToInvoke=" + object.getClass().getName() + '#' + method.getName() + '}'; } } ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/commons/schedule/executor/ScheduleModule.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.schedule.executor; import com.google.inject.Binder; import com.google.inject.Module; import org.eclipse.che.commons.schedule.Launcher; import org.eclipse.che.inject.lifecycle.InternalScheduleModule; /** * Guice deployment module. * * @author Sergii Kabashniuk */ public class ScheduleModule implements Module { @Override public void configure(Binder binder) { binder.bind(Launcher.class).to(ThreadPullLauncher.class).asEagerSingleton(); binder.install(new InternalScheduleModule()); } } ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/commons/schedule/executor/ThreadPullLauncher.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.schedule.executor; import com.google.common.util.concurrent.ThreadFactoryBuilder; import jakarta.annotation.PreDestroy; import java.text.ParseException; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.eclipse.che.commons.schedule.Launcher; import org.eclipse.che.inject.ConfigurationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Execute method marked with @ScheduleCron @ScheduleDelay and @ScheduleRate annotations using * CronThreadPoolExecutor. * * @author Sergii Kabashniuk */ @Singleton public class ThreadPullLauncher implements Launcher { private static final Logger LOG = LoggerFactory.getLogger(CronThreadPoolExecutor.class); private final CronExecutorService service; /** * @param corePoolSize the number of threads to keep in the pool, even if they are idle, unless * {@code allowCoreThreadTimeOut} is set */ @Inject public ThreadPullLauncher(@Named("schedule.core_pool_size") Integer corePoolSize) { this( new CronThreadPoolExecutor( corePoolSize, new ThreadFactoryBuilder() .setNameFormat("Annotated-scheduler-%d") .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setDaemon(false) .build())); } protected ThreadPullLauncher(CronExecutorService service) { this.service = service; } @PreDestroy public void shutdown() throws InterruptedException { // Tell threads to finish off. service.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!service.awaitTermination(60, TimeUnit.SECONDS)) { service.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being cancelled if (!service.awaitTermination(60, TimeUnit.SECONDS)) LOG.warn("Pool did not terminate"); } } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted service.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); } } @Override public void scheduleCron(Runnable runnable, String cron) { if (cron == null || cron.isEmpty()) { throw new ConfigurationException("Cron parameter can't be null"); } try { CronExpression expression = new CronExpression(cron); service.schedule(runnable, expression); LOG.debug("Schedule method {} with cron {} schedule", runnable, cron); } catch (ParseException e) { LOG.error(e.getLocalizedMessage(), e); throw new ConfigurationException(e.getLocalizedMessage()); } } @Override public void scheduleWithFixedDelay( Runnable runnable, long initialDelay, long delay, TimeUnit unit) { if (delay <= 0) { LOG.debug( "Method {} has not been scheduled (delay <= 0). Initial delay {} delay {} unit {}", runnable, initialDelay, delay, unit); return; } service.scheduleWithFixedDelay(runnable, initialDelay, delay, unit); LOG.debug( "Schedule method {} with fixed initial delay {} delay {} unit {}", runnable, initialDelay, delay, unit); } @Override public void scheduleAtFixedRate( Runnable runnable, long initialDelay, long period, TimeUnit unit) { if (period <= 0) { LOG.debug( "Method {} with fixed rate has not been scheduled (period <= 0). Initial delay {} period {} unit {}", runnable, initialDelay, period, unit); return; } service.scheduleAtFixedRate(runnable, initialDelay, period, unit); LOG.debug( "Schedule method {} with fixed rate. Initial delay {} period {} unit {}", runnable, initialDelay, period, unit); } } ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/inject/lifecycle/InternalScheduleModule.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject.lifecycle; import com.google.inject.Injector; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; import org.eclipse.che.commons.schedule.Launcher; /** * Launch method marked with @ScheduleCron @ScheduleDelay and @ScheduleRate annotations using * Launcher * *

Note do not inject this module. Use {@link * org.eclipse.che.commons.schedule.executor.ScheduleModule} * * @author Sergii Kabashniuk */ public class InternalScheduleModule extends LifecycleModule { @Override protected void configure() { bindListener( Matchers.any(), new ScheduleTypeListener(getProvider(Launcher.class), getProvider(Injector.class))); } private static class ScheduleTypeListener implements TypeListener { private final Provider launcher; private final Provider injector; private ScheduleTypeListener(Provider launcher, Provider injector) { this.launcher = launcher; this.injector = injector; } @Override public void hear(TypeLiteral type, TypeEncounter encounter) { encounter.register(new ScheduleInjectionListener(launcher, injector)); } } } ================================================ FILE: core/commons/che-core-commons-schedule/src/main/java/org/eclipse/che/inject/lifecycle/ScheduleInjectionListener.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.inject.lifecycle; import com.google.inject.ConfigurationException; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.ProvisionException; import com.google.inject.matcher.Matcher; import com.google.inject.matcher.Matchers; import com.google.inject.name.Names; import com.google.inject.spi.InjectionListener; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import org.eclipse.che.commons.schedule.Launcher; import org.eclipse.che.commons.schedule.ScheduleCron; import org.eclipse.che.commons.schedule.ScheduleDelay; import org.eclipse.che.commons.schedule.ScheduleRate; import org.eclipse.che.commons.schedule.executor.LoggedRunnable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Listen guice injections and launch method marked with schedule annotations. * * @author Sergii Kabashniuk */ public class ScheduleInjectionListener extends LifecycleModule implements InjectionListener { private static final Logger LOG = LoggerFactory.getLogger(ScheduleInjectionListener.class); private final Matcher javaxSingleton = Matchers.annotatedWith(javax.inject.Singleton.class); private final Matcher googleSingleton = Matchers.annotatedWith(com.google.inject.Singleton.class); private final Provider launcherProvider; private final Provider injectorProvider; public ScheduleInjectionListener( Provider launcherProvider, Provider injectorProvider) { this.launcherProvider = launcherProvider; this.injectorProvider = injectorProvider; } private T getValue(Class configurationType, String configurationKey) { try { return injectorProvider .get() .getInstance(Key.get(configurationType, Names.named(configurationKey))); } catch (ConfigurationException | ProvisionException e) { return null; } } private long getValue(String configurationKey) { String stringValue = getValue(String.class, configurationKey); if (stringValue != null) { long result = Long.parseLong(stringValue); if (result == 0) { throw new RuntimeException("Invalid value 0 for parameter " + configurationKey); } return result; } Long longValue = getValue(Long.class, configurationKey); if (longValue != null) { long result = longValue.longValue(); if (result == 0) { throw new RuntimeException("Invalid value 0 for parameter " + configurationKey); } return result; } Integer intValue = getValue(Integer.class, configurationKey); if (intValue != null) { long result = intValue.longValue(); if (result == 0) { throw new RuntimeException("Invalid value 0 for parameter " + configurationKey); } return result; } throw new RuntimeException("Parameter " + configurationKey + " is not configured"); } private void launch(Object object, Method method, ScheduleCron annotation) { Launcher launcher = launcherProvider.get(); launcher.scheduleCron( new LoggedRunnable(object, method), annotation.cronParameterName().isEmpty() ? annotation.cron() : getValue(String.class, annotation.cronParameterName())); } private void launch(Object object, Method method, ScheduleDelay annotation) { if (annotation.delayParameterName().isEmpty() && annotation.delay() == 0) { throw new RuntimeException("Delay parameter is not configured"); } Launcher launcher = launcherProvider.get(); launcher.scheduleWithFixedDelay( new LoggedRunnable(object, method), annotation.initialDelayParameterName().isEmpty() ? annotation.initialDelay() : getValue(annotation.initialDelayParameterName()), annotation.delayParameterName().isEmpty() ? annotation.delay() : getValue(annotation.delayParameterName()), annotation.unit()); } private void launch(Object object, Method method, ScheduleRate annotation) { if (annotation.periodParameterName().isEmpty() && annotation.period() == 0) { throw new RuntimeException("Period parameter is not configured"); } Launcher launcher = launcherProvider.get(); launcher.scheduleAtFixedRate( new LoggedRunnable(object, method), annotation.initialDelayParameterName().isEmpty() ? annotation.initialDelay() : getValue(annotation.initialDelayParameterName()), annotation.periodParameterName().isEmpty() ? annotation.period() : getValue(annotation.periodParameterName()), annotation.unit()); } private void launch(Object object, Class annotationType) { boolean isSingleton = javaxSingleton.matches(object.getClass()) || googleSingleton.matches(object.getClass()); for (Method method : get(object.getClass(), annotationType)) { if (!isSingleton) { throw new RuntimeException( "Scheduled class " + object.getClass() + " should be marked as singleton"); } if (annotationType.equals(ScheduleRate.class)) { launch(object, method, method.getAnnotation(ScheduleRate.class)); } else if (annotationType.equals(ScheduleDelay.class)) { launch(object, method, method.getAnnotation(ScheduleDelay.class)); } else if (annotationType.equals(ScheduleCron.class)) { launch(object, method, method.getAnnotation(ScheduleCron.class)); } } } @Override public void afterInjection(T injectee) { launch(injectee, ScheduleCron.class); launch(injectee, ScheduleRate.class); launch(injectee, ScheduleDelay.class); } @Override protected void configure() {} } ================================================ FILE: core/commons/che-core-commons-schedule/src/test/java/org/eclipse/che/commons/schedule/executor/CronExpressionTest.java ================================================ /* * Copyright 2001-2009 Terracotta, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package org.eclipse.che.commons.schedule.executor; import static org.testng.Assert.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.Test; public class CronExpressionTest { private static final Logger LOG = LoggerFactory.getLogger(CronExpressionTest.class); private static final String[] VERSIONS = new String[] {"1.5.2"}; private static final TimeZone EST_TIME_ZONE = TimeZone.getTimeZone("US/Eastern"); /** * Get the object to serialize when generating serialized file for future tests, and against which * to validate deserialized object. */ protected Object getTargetObject() throws ParseException { CronExpression cronExpression = new CronExpression("0 15 10 * * ? 2005"); cronExpression.setTimeZone(EST_TIME_ZONE); return cronExpression; } /** Get the Quartz versions for which we should verify serialization backwards compatibility. */ protected String[] getVersions() { return VERSIONS; } /** Verify that the target object and the object we just deserialized match. */ protected void verifyMatch(Object target, Object deserialized) { CronExpression targetCronExpression = (CronExpression) target; CronExpression deserializedCronExpression = (CronExpression) deserialized; assertNotNull(deserializedCronExpression); assertEquals( targetCronExpression.getCronExpression(), deserializedCronExpression.getCronExpression()); assertEquals(targetCronExpression.getTimeZone(), deserializedCronExpression.getTimeZone()); } /* * Test method for 'org.quartz.CronExpression.isSatisfiedBy(Date)'. */ @Test public void testIsSatisfiedBy() throws Exception { CronExpression cronExpression = new CronExpression("0 15 10 * * ? 2005"); Calendar cal = Calendar.getInstance(); cal.set(2005, Calendar.JUNE, 1, 10, 15, 0); assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(Calendar.YEAR, 2006); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal = Calendar.getInstance(); cal.set(2005, Calendar.JUNE, 1, 10, 16, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cal = Calendar.getInstance(); cal.set(2005, Calendar.JUNE, 1, 10, 14, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); } @Test public void testLastDayOffset() throws Exception { CronExpression cronExpression = new CronExpression("0 15 10 L-2 * ? 2010"); Calendar cal = Calendar.getInstance(); cal.set(2010, Calendar.OCTOBER, 29, 10, 15, 0); // last day - 2 assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cal.set(2010, Calendar.OCTOBER, 28, 10, 15, 0); assertFalse(cronExpression.isSatisfiedBy(cal.getTime())); cronExpression = new CronExpression("0 15 10 L-5W * ? 2010"); cal.set(2010, Calendar.OCTOBER, 26, 10, 15, 0); // last day - 5 assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cronExpression = new CronExpression("0 15 10 L-1 * ? 2010"); cal.set(2010, Calendar.OCTOBER, 30, 10, 15, 0); // last day - 1 assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); cronExpression = new CronExpression("0 15 10 L-1W * ? 2010"); cal.set( 2010, Calendar.OCTOBER, 29, 10, 15, 0); // nearest weekday to last day - 1 (29th is a friday in 2010) assertTrue(cronExpression.isSatisfiedBy(cal.getTime())); } /* * QUARTZ-571: Showing that expressions with months correctly serialize. */ @Test public void testQuartz571() throws Exception { CronExpression cronExpression = new CronExpression("19 15 10 4 Apr ? "); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(cronExpression); oos.flush(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); CronExpression newExpression = (CronExpression) ois.readObject(); assertEquals(newExpression.getCronExpression(), cronExpression.getCronExpression()); // if broken, this will throw an exception newExpression.getNextValidTimeAfter(new Date()); } /** QTZ-259 : last day offset causes repeating fire time */ @Test public void testQtz259() throws Exception { CronExpression cronExpression = new CronExpression("0 0 0 L-2 * ? *"); int i = 0; Date pdate = cronExpression.getNextValidTimeAfter(new Date()); while (++i < 26) { Date date = cronExpression.getNextValidTimeAfter(pdate); LOG.debug("fireTime: " + date + ", previousFireTime: " + pdate); assertFalse(pdate.equals(date), "Next fire time is the same as previous fire time!"); pdate = date; } } /** QTZ-259 : last day offset causes repeating fire time */ @Test public void testQtz259LW() throws Exception { CronExpression cronExpression = new CronExpression("0 0 0 LW * ? *"); int i = 0; Date pdate = cronExpression.getNextValidTimeAfter(new Date()); while (++i < 26) { Date date = cronExpression.getNextValidTimeAfter(pdate); LOG.debug("fireTime: " + date + ", previousFireTime: " + pdate); assertFalse(pdate.equals(date), "Next fire time is the same as previous fire time!"); pdate = date; } } /* * QUARTZ-574: Showing that storeExpressionVals correctly calculates the month number */ @Test public void testQuartz574() { try { new CronExpression("* * * * Foo ? "); fail("Expected ParseException did not fire for non-existent month"); } catch (ParseException pe) { assertTrue( pe.getMessage().startsWith("Invalid Month value:"), "Incorrect ParseException thrown"); } try { new CronExpression("* * * * Jan-Foo ? "); fail("Expected ParseException did not fire for non-existent month"); } catch (ParseException pe) { assertTrue( pe.getMessage().startsWith("Invalid Month value:"), "Incorrect ParseException thrown"); } } @Test public void testQuartz621() { try { new CronExpression("0 0 * * * *"); fail("Expected ParseException did not fire for wildcard day-of-month and day-of-week"); } catch (ParseException pe) { assertTrue( pe.getMessage() .startsWith( "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."), "Incorrect ParseException thrown"); } try { new CronExpression("0 0 * 4 * *"); fail( "Expected ParseException did not fire for specified day-of-month and wildcard day-of-week"); } catch (ParseException pe) { assertTrue( pe.getMessage() .startsWith( "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."), "Incorrect ParseException thrown"); } try { new CronExpression("0 0 * * * 4"); fail( "Expected ParseException did not fire for wildcard day-of-month and specified day-of-week"); } catch (ParseException pe) { assertTrue( pe.getMessage() .startsWith( "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."), "Incorrect ParseException thrown"); } } @Test public void testQuartz640() throws ParseException { try { new CronExpression("0 43 9 1,5,29,L * ?"); fail("Expected ParseException did not fire for L combined with other days of the month"); } catch (ParseException pe) { assertTrue( pe.getMessage() .startsWith( "Support for specifying 'L' and 'LW' with other days of the month is not implemented"), "Incorrect ParseException thrown"); } try { new CronExpression("0 43 9 ? * SAT,SUN,L"); fail("Expected ParseException did not fire for L combined with other days of the week"); } catch (ParseException pe) { assertTrue( pe.getMessage() .startsWith( "Support for specifying 'L' with other days of the week is not implemented"), "Incorrect ParseException thrown"); } try { new CronExpression("0 43 9 ? * 6,7,L"); fail("Expected ParseException did not fire for L combined with other days of the week"); } catch (ParseException pe) { assertTrue( pe.getMessage() .startsWith( "Support for specifying 'L' with other days of the week is not implemented"), "Incorrect ParseException thrown"); } try { new CronExpression("0 43 9 ? * 5L"); } catch (ParseException pe) { fail("Unexpected ParseException thrown for supported '5L' expression."); } } @Test public void testQtz96() throws ParseException { try { new CronExpression("0/5 * * 32W 1 ?"); fail("Expected ParseException did not fire for W with value larger than 31"); } catch (ParseException pe) { assertTrue( pe.getMessage().startsWith("The 'W' option does not make sense with values larger than"), "Incorrect ParseException thrown"); } } @Test public void testQtz395_CopyConstructorMustPreserveTimeZone() throws ParseException { TimeZone nonDefault = TimeZone.getTimeZone("Europe/Brussels"); if (nonDefault.equals(TimeZone.getDefault())) { nonDefault = EST_TIME_ZONE; } CronExpression cronExpression = new CronExpression("0 15 10 * * ? 2005"); cronExpression.setTimeZone(nonDefault); CronExpression copyCronExpression = new CronExpression(cronExpression); assertEquals(nonDefault, copyCronExpression.getTimeZone()); } // // execute with version number to generate a new version's serialized form // public static void main(String[] args) throws Exception { // new CronExpressionTest().writeJobDataFile("1.5.2"); // } } ================================================ FILE: core/commons/che-core-commons-schedule/src/test/resources/findbugs-exclude.xml ================================================ ================================================ FILE: core/commons/che-core-commons-schedule/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: core/commons/che-core-commons-test/pom.xml ================================================ 4.0.0 che-core-commons-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-commons-test jar Che Core :: Commons :: Utilities for tests true org.mockito mockito-core org.testng testng com.google.guava guava provided com.google.inject guice provided com.google.inject.extensions guice-persist provided jakarta.inject jakarta.inject-api provided jakarta.servlet jakarta.servlet-api provided org.eclipse.persistence jakarta.persistence provided ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/AssertRetry.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test; import java.util.function.Supplier; import org.testng.Assert; public class AssertRetry { /** * Assert that is making several attempts with pauses to match expected value with the value * provided by Supplier. */ public static void assertWithRetry( Supplier predicate, V expected, int times, int pause_millis) throws InterruptedException { for (int i = 0; i <= times; i++) { V actual = predicate.get(); if (expected.equals(actual)) { return; } else if (i + 1 <= times) { Thread.sleep(pause_millis); } } Assert.fail("Unable to obtain the expected value " + expected + " after " + times + " retries"); } } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/SystemPropertiesHelper.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test; import java.util.HashMap; import java.util.Map; /** * Example of usage in tests: * *

{@code
 * SystemPropertiesHelper systemPropertiesHelper;
 *
 * public void setUp() {
 *     systemPropertiesHelper = SystemPropertiesHelper.overrideSystemProperties()
 *         .property("name1", "value1")
 *         .property("name2", "value2");
 * }
 *
 * public void tearDown() {
 *     systemPropertiesHelper.restoreFromBackup();
 * }
 * }
*/ public class SystemPropertiesHelper { private Map backup; private SystemPropertiesHelper() { backup = new HashMap<>(); } public static SystemPropertiesHelper overrideSystemProperties() { return new SystemPropertiesHelper(); } public SystemPropertiesHelper property(String name, String value) { backupCurrentValue(name); System.setProperty(name, value); return this; } public SystemPropertiesHelper restoreFromBackup() { for (Map.Entry entry : backup.entrySet()) { if (entry.getValue() == null) { System.clearProperty(entry.getKey()); } else { System.setProperty(entry.getKey(), entry.getValue()); } } backup.clear(); return this; } private void backupCurrentValue(String name) { String value = System.getProperty(name); backup.put(name, value); } } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/mockito/answer/SelfReturningAnswer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.mockito.answer; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; /** * Allows create mocks for builder-like classes easily * *

If builder-like classes needs to be mocked then: * *

    *
  • methods of that class returns the same object *
  • not all methods can be called *
  • methods can be called in different order *
* * In that case it is hard to mock that class. With the help of this class it can be achieved as: * *

 *     HttpJsonRequest httpJsonRequest = mock(HttpJsonRequest.class, new SelfReturningAnswer());
 * 
* * In that case all methods of httpJsonRequest that returns HttpJsonRequest will return * httpJsonRequest; * * @author Alexander Garagatyi */ public class SelfReturningAnswer implements Answer { public Object answer(InvocationOnMock invocation) throws Throwable { Object mock = invocation.getMock(); if (invocation.getMethod().getReturnType().isInstance(mock)) { return mock; } else { return Mockito.RETURNS_DEFAULTS.answer(invocation); } } } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/mockito/answer/WaitingAnswer.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.mockito.answer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; /** * Answer class that helps to lock execution of test in separate threads for testing purposes. * *

It can hold waiting thread until this answer is called.
* It can hold waiting thread that uses mock until answering to mock call is allowed. * *

Here is an example of complex test that ensures that (for example) locked area is not called * when other thread try to access it. * *


 *     // given
 *     WaitingAnswer waitingAnswer = new WaitingAnswer<>();
 *     doAnswer(waitingAnswer).when(someClassUsedInTestedClass).someMethod(eq(param1), eq(param2));
 *
 *     // start doing something in a separate thread
 *     executor.execute(() -> testedClass.doSomething());
 *     // wait until separate thread call answer to find the moment when critical area is occupied
 *     // to make test fast wait not more that provided timeout
 *     waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS);
 *
 *     // when
 *     try {
 *         // start doing something in current thread
 *         testedClass.doSomething()
 *         // this area should not be reachable until answer is completed!
 *         fail("Error message");
 *     } finally {
 *         // In this case exception can be suppressed
 *         // Test is simplified to provide clean example
 *
 *         // then
 *         // complete waiting answer
 *         waitingAnswer.completeAnswer();
 *         // ensure that someMethod is called only once to confirm that testedClass.doSomething()
 *         // doesn't call it this particular test
 *         verify(someClassUsedInTestedClass, timeout(100).times(1)).someMethod(any(), any());
 *     }
 * 
* * @author Alexander Garagatyi */ public class WaitingAnswer implements Answer { private final CountDownLatch answerIsCalledLatch; private final CountDownLatch answerResultIsUnlockedLatch; private long maxWaitingTime; private TimeUnit maxWaitingUnit; private T result; private volatile String error; public WaitingAnswer() { this.maxWaitingTime = 1; this.maxWaitingUnit = TimeUnit.SECONDS; this.result = null; this.answerIsCalledLatch = new CountDownLatch(1); this.answerResultIsUnlockedLatch = new CountDownLatch(1); this.error = null; } public WaitingAnswer(long maxWaitingTime, TimeUnit maxWaitingUnit) { this(); this.maxWaitingTime = maxWaitingTime; this.maxWaitingUnit = maxWaitingUnit; } public WaitingAnswer(T result) { this(); this.result = result; } public WaitingAnswer(T result, long maxWaitingTime, TimeUnit maxWaitingUnit) { this(); this.result = result; this.maxWaitingTime = maxWaitingTime; this.maxWaitingUnit = maxWaitingUnit; } /** * Waits until answer is called in method {@link #answer(InvocationOnMock)}. * * @param maxWaitingTime max time to wait * @param maxWaitingUnit time unit of the max waiting time argument * @throws Exception if the waiting time elapsed before this answer is called * @see #answer(InvocationOnMock) */ public void waitAnswerCall(long maxWaitingTime, TimeUnit maxWaitingUnit) throws Exception { if (!answerIsCalledLatch.await(maxWaitingTime, maxWaitingUnit)) { error = "Waiting time elapsed but answer is not called"; throw new Exception(error); } } /** * Stops process of waiting returning result of answer in method {@link * #answer(InvocationOnMock)}. * * @throws Exception if this answer waiting time elapsed before this method is called * @see #answer(InvocationOnMock) */ public void completeAnswer() throws Exception { answerResultIsUnlockedLatch.countDown(); if (error != null) { throw new Exception(error); } } /** * Stops waiting until answer is called in method {@link #waitAnswerCall(long, TimeUnit)} and then * waits until method {@link #completeAnswer()} is called. * * @param invocationOnMock see {@link Answer#answer(InvocationOnMock)} * @return returns answer result if provided in constructor or null otherwise * @throws Exception if answer call or answer result waiting time is elapsed * @throws Throwable in the same cases as in {@link Answer#answer(InvocationOnMock)} * @see #waitAnswerCall(long, TimeUnit) * @see #completeAnswer() * @see Answer#answer(InvocationOnMock) */ @Override public T answer(InvocationOnMock invocationOnMock) throws Throwable { // report start of answer call answerIsCalledLatch.countDown(); if (error != null) { throw new Exception(error); } // wait until another thread unlocks returning of answer if (!answerResultIsUnlockedLatch.await(maxWaitingTime, maxWaitingUnit)) { error = "Waiting time elapsed but completeAnswer is not called"; throw new Exception(error); } return result; } } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/servlet/MockServletInputStream.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.servlet; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; import java.io.IOException; import java.io.InputStream; /** Utility class for mocking {@link ServletInputStream} */ public class MockServletInputStream extends ServletInputStream { private final InputStream data; public MockServletInputStream(InputStream data) { this.data = data; } @Override public int read() throws IOException { return this.data.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) {} } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/JpaCleaner.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck; import javax.inject.Inject; import javax.inject.Provider; import javax.persistence.EntityManagerFactory; /** * This class is designed to close {@link EntityManagerFactory} on finish of tck test with jpa * implementation. * *

Examples of usage:
* bind(TckResourcesCleaner.class).to(JpaCleaner.class)
* * bind(TckResourcesCleaner.class).annotatedWith(Names.named(MyTckTest.class.getName())).to(JpaCleaner.class); * * * @author Sergii Leschenko */ public class JpaCleaner implements TckResourcesCleaner { @Inject private Provider entityManagerFactory; @Override public void clean() { entityManagerFactory.get().close(); } } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/TckListener.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck; import static java.lang.String.format; import com.google.inject.AbstractModule; import com.google.inject.ConfigurationException; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.name.Names; import java.util.Iterator; import java.util.ServiceLoader; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.testng.ITestContext; import org.testng.ITestNGMethod; /** * The listener is designed to instantiate {@link TckModule tck modules} using {@link ServiceLoader} * mechanism. The components provided by those modules will be injected into a test class whether * it's necessary to do so. For each test class will be used new instance of injector. Listener * requires tck test to be in own separated suite, if it finds more tests in suite it'll throw * {@link IllegalArgumentException} on suite start. After test suite is finished listener'll try to * find test specific or common instance of {@link TckResourcesCleaner}. It is optional and can be * bound in modules. * *

The listener expects at least one implementation of {@code TckModule} to be configured, if it * doesn't find any of the {@code TckModule} implementations then it will report an appropriate * exception and TckTest will fail(as it requires components to be injected into it). If it finds * more than one {@code TckModule} implementation it will use all of the found. * *

The usage example: * *

 * package org.eclipse.mycomponent;
 *
 * @org.testng.annotations.Listeners(TckListener)
 * // Tck test must have own suite because of cleaning resources on suite finishing
 * @org.testng.annotations.Test(suiteName = "MySuite")
 * class SubjectTest {
 *
 *     @javax.inject.Inject
 *     private Component1 component1.
 *     @javax.inject.Inject
 *     private Component2 component2;
 *
 *     @org.testng.annotations.Test
 *     public void test() {
 *          // use components
 *     }
 * }
 *
 * class MyTckModule extends TckModule {
 *     public void configure() {
 *         bind(Component1.class).to(...);
 *         bind(Component2.class).toInstance(new Component2(() -> testContext.getAttribute("server_url").toString()));
 *         bind(TckResourcesCleaner.class).to(...);
 *         bind(TckResourcesCleaner.class).annotatedWith(Names.named(SubjectTest.class.getName())).to(...);
 *     }
 * }
 *
 * // Allows to add pre/post test actions like db server start/stop
 * class DBServerListener implements ITestListener {
 *      // ...
 *      public void onStart(ITestContext context) {
 *          String url = dbServer.start();
 *          context.setAttribute("server_url", url)l
 *      }
 *
 *      public void onFinish(ITestContext context) {
 *          dbServer.stop();
 *      }
 *      // ...
 * }
 * 
* *

Configuring: * *

 * META-INF/services/org.eclipse.che.commons.test.tck.TckModule
 * org.eclipse.mycomponent.MyTckModule
 *
 * META-INF/services/org.testng.ITestNGListener
 * org.eclipse.mycomponent.DBServerListener
 * 
* * @author Yevhenii Voevodin * @author Sergii Leschenko * @see org.testng.annotations.Listeners * @see org.testng.IInvokedMethodListener * @see TckResourcesCleaner */ public class TckListener extends TestListenerAdapter { private Injector injector; private Object instance; @Override public void onStart(ITestContext context) { final Set instances = Stream.of(context.getAllTestMethods()) .map(ITestNGMethod::getInstance) .collect(Collectors.toSet()); if (instances.size() != 1) { throw new IllegalStateException("Tck test should be one and only one in suite."); } instance = instances.iterator().next(); injector = Guice.createInjector(createModule(context, instance.getClass().getName())); injector.injectMembers(instance); } @Override public void onFinish(ITestContext context) { if (injector == null || instance == null) { throw new IllegalStateException("Looks like onFinish method is invoked before onStart."); } // try to get test specific resources cleaner TckResourcesCleaner resourcesCleaner = getResourcesCleaner( injector, Key.get(TckResourcesCleaner.class, Names.named(instance.getClass().getName()))); if (resourcesCleaner == null) { // try to get common resources cleaner resourcesCleaner = getResourcesCleaner(injector, Key.get(TckResourcesCleaner.class)); } if (resourcesCleaner != null) { resourcesCleaner.clean(); } } private TckResourcesCleaner getResourcesCleaner(Injector injector, Key key) { try { return injector.getInstance(key); } catch (ConfigurationException ignored) { } return null; } private Module createModule(ITestContext testContext, String name) { final Iterator moduleIterator = ServiceLoader.load(TckModule.class).iterator(); if (!moduleIterator.hasNext()) { throw new IllegalStateException( format( "Couldn't find a TckModule configuration. " + "You probably forgot to configure resources/META-INF/services/%s, or even " + "provide an implementation of the TckModule which is required by the jpa test class %s", TckModule.class.getName(), name)); } return new CompoundModule(testContext, moduleIterator); } private static class CompoundModule extends AbstractModule { private final ITestContext testContext; private final Iterator moduleIterator; private CompoundModule(ITestContext testContext, Iterator moduleIterator) { this.testContext = testContext; this.moduleIterator = moduleIterator; } @Override protected void configure() { bind(ITestContext.class).toInstance(testContext); while (moduleIterator.hasNext()) { final TckModule module = moduleIterator.next(); module.setTestContext(testContext); install(module); } } } } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/TckModule.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck; import com.google.inject.AbstractModule; import com.google.inject.Module; import java.util.ServiceLoader; import org.testng.ITestContext; /** * Abstract class for those Guice {@link Module modules} which provide TCK tests components, which * will be injected directly into the test class. * *

The {@link ServiceLoader} mechanism is used for loading such modules and for injecting them * later. So each module which is TCK module must provide the implementations list(as described by * {@code ServiceLoader} mechanism) in the file named * org.eclipse.che.commons.test.tck.TckModule usually under * test/resources/META-INF/services directory, then the {@link TckListener} will recognise * and load it. * * @author Yevhenii Voevodin * @see TckListener */ public abstract class TckModule extends AbstractModule { /** * It is guaranteed that this field is always present and can be reused by implementation, it will * be set by {@link TckListener} immediately after module implementation is loaded by {@link * ServiceLoader}. */ private ITestContext testContext; /** Returns the {@link ITestContext context} of currently executing test suite. */ protected ITestContext getTestContext() { return testContext; } /** * Sets the context of currently executing test suite. This method designed to be used by {@link * TckListener} for setting the context before installing modules. */ void setTestContext(ITestContext testContext) { this.testContext = testContext; } } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/TckResourcesCleaner.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck; /** * This class is designed to clean up resources after tck test. * *

Implementation should be defined in {@link TckModule}. It can be defined common for all tests * or test specific by using @Named annotation. Cleaning of resources is invoked after finish of all * tests methods from one test suite that should contains one and only one tck test class. * *

The usage example: * *

 * class MyTckModule extends TckModule {
 *     public void configure() {
 *         bind(TckResourcesCleaner.class).to(...);
 *         bind(TckResourcesCleaner.class).annotatedWith(Names.named(SomeTest.class.getName())).to(...);
 *     }
 * }
 * 
* * @author Sergii Leschenko * @see TckListener */ public interface TckResourcesCleaner { /** * Clean up resources. * *

Note: it is invoked after finish of all methods from one test suite. */ void clean(); } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/TestListenerAdapter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck; import org.testng.ITestContext; import org.testng.ITestListener; import org.testng.ITestResult; /** * Skeletal implementation of the {@link ITestListener}. In most cases only 2 methods are needed * {@link #onStart(ITestContext)} and {@link #onFinish(ITestContext)}. * * @author Yevhenii Voevodin */ public abstract class TestListenerAdapter implements ITestListener { @Override public void onTestStart(ITestResult result) {} @Override public void onTestSuccess(ITestResult result) {} @Override public void onTestFailure(ITestResult result) {} @Override public void onTestSkipped(ITestResult result) {} @Override public void onTestFailedButWithinSuccessPercentage(ITestResult result) {} @Override public void onStart(ITestContext context) {} @Override public void onFinish(ITestContext context) {} } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/repository/JpaTckRepository.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck.repository; import static java.lang.String.format; import com.google.inject.Inject; import com.google.inject.persist.UnitOfWork; import java.util.Collection; import javax.inject.Provider; import javax.persistence.Entity; import javax.persistence.EntityManager; /** * Simplifies implementation for Jpa repository in general case. * *

Expected usage: * *

 *      class MyTckModule extends TckModule {
 *          @Override configure() {
 *              bind(new TypeLiteral<TckRepository<UserImpl>>() {})
 *                  .toInstance(new JpaTckRepository(Concrete.class));
 *          }
 *      }
 * 
* * @param type of the entity * @author Yevhenii Voevodin */ public class JpaTckRepository implements TckRepository { @Inject protected Provider managerProvider; @Inject protected UnitOfWork uow; private final Class entityClass; public JpaTckRepository(Class entityClass) { this.entityClass = entityClass; } @Override public void createAll(Collection entities) throws TckRepositoryException { uow.begin(); final EntityManager manager = managerProvider.get(); try { manager.getTransaction().begin(); for (T entity : entities) { manager.persist(entity); manager.flush(); } manager.getTransaction().commit(); } catch (RuntimeException x) { if (manager.getTransaction().isActive()) { manager.getTransaction().rollback(); } throw new TckRepositoryException(x.getLocalizedMessage(), x); } finally { uow.end(); } } @Override public void removeAll() throws TckRepositoryException { uow.begin(); final EntityManager manager = managerProvider.get(); try { manager.getTransaction().begin(); // The query 'DELETE FROM Entity' won't be correct as it will ignore orphanRemoval // and may also ignore some configuration options, while EntityManager#remove won't manager .createQuery(format("SELECT e FROM %s e", getEntityName(entityClass)), entityClass) .getResultList() .forEach(manager::remove); manager.getTransaction().commit(); } catch (RuntimeException x) { if (manager.getTransaction().isActive()) { manager.getTransaction().rollback(); } throw new TckRepositoryException(x.getLocalizedMessage(), x); } finally { uow.end(); } } private String getEntityName(Class clazz) { if (!clazz.isAnnotationPresent(Entity.class)) { return clazz.getSimpleName(); } final Entity entity = clazz.getAnnotation(Entity.class); if (entity.name().isEmpty()) { return clazz.getSimpleName(); } return entity.name(); } } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/repository/TckRepository.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck.repository; import java.util.Collection; /** * The interface which allows to create TCK tests for DAO interfaces by providing operations for * creating/removing batch of elements. The interface is designed to work with entities, which means * that entity marshaling and unmarshalling to/from db objects must be tested by implementation * separately. * * @param the type of the object managed by the repository * @author Yevhenii Voevodin */ public interface TckRepository { /** * Creates all the given {@code entities} in the storage. * *

Note that implementation must fail if it is impossible to create any of the given entities. * * @param entities elements to create * @throws TckRepositoryException when any error occurs during the storing */ void createAll(Collection entities) throws TckRepositoryException; /** * Clears the storage. * *

Note that implementation must fail if it is impossible to remove all the entities. * * @throws TckRepositoryException when any error occurs during the clearing */ void removeAll() throws TckRepositoryException; } ================================================ FILE: core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/repository/TckRepositoryException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck.repository; import java.util.Collection; /** * Thrown when any error occurs during {@link TckRepository#createAll(Collection)} or {@link * TckRepository#removeAll()} invocation. Usually wraps exceptions occurred during the * storing/removing. * * @author Yevhenii Voevodin */ public class TckRepositoryException extends Exception { public TckRepositoryException(String message) { super(message); } public TckRepositoryException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: core/commons/che-core-commons-test/src/main/resources/org/eclipse/che/commons/test/db/persistence.xml.template ================================================ org.eclipse.persistence.jpa.PersistenceProvider $! Add entity classes as fqn.MyEntity.class !$ $if(entity_classes)$ $trunc(entity_classes) : {class | $class$ }$ $last(entity_classes)$ $endif$ $! Add properties as !$ $if(properties)$ $trunc(properties) : {prop | }$ $endif$ ================================================ FILE: core/commons/che-core-commons-test/src/test/java/org/eclipse/che/commons/test/AssertRetryTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test; import static org.testng.Assert.*; import java.util.concurrent.atomic.AtomicBoolean; import org.testng.annotations.Test; public class AssertRetryTest { @Test public void testAssertWithRetry() throws InterruptedException { // Given AtomicBoolean atomicBoolean = new AtomicBoolean(false); // When new Thread( () -> { try { Thread.sleep(100); atomicBoolean.getAndSet(true); } catch (InterruptedException e) { } }) .start(); // then AssertRetry.assertWithRetry(() -> atomicBoolean.get(), Boolean.TRUE, 100, 10); } @Test(expectedExceptions = AssertionError.class) public void testAssertFailWithRetry() throws InterruptedException { // Given AtomicBoolean atomicBoolean = new AtomicBoolean(false); // When new Thread( () -> { try { Thread.sleep(100); atomicBoolean.getAndSet(true); } catch (InterruptedException e) { } }) .start(); // then AssertRetry.assertWithRetry(() -> atomicBoolean.get(), Boolean.TRUE, 2, 10); } } ================================================ FILE: core/commons/che-core-commons-test/src/test/java/org/eclipse/che/commons/test/tck/DBServerListener.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck; import org.testng.ITestContext; /** * Listener representing fake db server url injection for testing "attributes sharing" using {@link * ITestContext} test suite instance. * * @author Yevhenii Voevodin */ public class DBServerListener extends TestListenerAdapter { public static final String DB_SERVER_URL_ATTRIBUTE_NAME = "db_server_url"; public static final String DB_SERVER_URL = "localhost:12345"; @Override public void onStart(ITestContext context) { context.setAttribute(DB_SERVER_URL_ATTRIBUTE_NAME, DB_SERVER_URL); } } ================================================ FILE: core/commons/che-core-commons-test/src/test/java/org/eclipse/che/commons/test/tck/TckComponentsTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import javax.inject.Inject; import org.eclipse.che.commons.test.tck.repository.TckRepository; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests for {@code org.eclipse.che.commons.test.tck.*} package. * * @author Yevhenii Voevodin */ @Listeners(TckListener.class) @Test(suiteName = "tck") public class TckComponentsTest { @Inject private TckRepository tckRepository; @Inject private DBUrlProvider dbUrlProvider; @Test public void testComponentsAreInjected() { assertNotNull(tckRepository, "TckRepository is not injected"); assertNotNull(dbUrlProvider, "DBUrlProvider is not injected"); assertEquals( dbUrlProvider.getUrl(), DBServerListener.DB_SERVER_URL, "Value is set to ITestContext"); } public interface Entity {} public interface DBUrlProvider { String getUrl(); } } ================================================ FILE: core/commons/che-core-commons-test/src/test/java/org/eclipse/che/commons/test/tck/TestModule1.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck; import com.google.inject.TypeLiteral; import java.util.Collection; import org.eclipse.che.commons.test.tck.TckComponentsTest.Entity; import org.eclipse.che.commons.test.tck.repository.TckRepository; import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; /** * @author Yevhenii Voevodin */ public class TestModule1 extends TckModule { @Override public void configure() { bind(new TypeLiteral>() {}) .toInstance( new TckRepository() { @Override public void createAll(Collection entities) throws TckRepositoryException {} @Override public void removeAll() throws TckRepositoryException {} }); } } ================================================ FILE: core/commons/che-core-commons-test/src/test/java/org/eclipse/che/commons/test/tck/TestModule2.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.test.tck; import static org.eclipse.che.commons.test.tck.DBServerListener.DB_SERVER_URL_ATTRIBUTE_NAME; import org.eclipse.che.commons.test.tck.TckComponentsTest.DBUrlProvider; /** * @author Yevhenii Voevodin */ public class TestModule2 extends TckModule { @Override public void configure() { bind(DBUrlProvider.class) .toInstance(() -> getTestContext().getAttribute(DB_SERVER_URL_ATTRIBUTE_NAME).toString()); } } ================================================ FILE: core/commons/che-core-commons-test/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule ================================================ org.eclipse.che.commons.test.tck.TestModule1 org.eclipse.che.commons.test.tck.TestModule2 ================================================ FILE: core/commons/che-core-commons-test/src/test/resources/META-INF/services/org.testng.ITestNGListener ================================================ org.eclipse.che.commons.test.tck.DBServerListener ================================================ FILE: core/commons/che-core-commons-test/src/test/resources/org/eclipse/che/commons/test/db/test-persistence-1.xml ================================================ org.eclipse.persistence.jpa.PersistenceProvider org.eclipse.che.commons.test.db.PersistTestModuleBuilderTest$MyEntity1 org.eclipse.che.commons.test.db.PersistTestModuleBuilderTest$MyEntity2 ================================================ FILE: core/commons/che-core-commons-tracing/pom.xml ================================================ 4.0.0 che-core-commons-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-commons-tracing Che Core :: Commons :: Tracing io.opentracing opentracing-api org.eclipse.che.core che-core-commons-annotations com.google.guava guava provided com.google.inject guice provided ================================================ FILE: core/commons/che-core-commons-tracing/src/main/java/org/eclipse/che/commons/tracing/AnnotationAwareBooleanTag.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.tracing; import com.google.common.annotations.Beta; import io.opentracing.tag.BooleanTag; import java.util.function.Supplier; import org.eclipse.che.commons.annotation.Traced; /** * A specialization of the {@link BooleanTag} that adds support for setting a tag in a {@link * Traced @Traced} method. */ @Beta public class AnnotationAwareBooleanTag extends BooleanTag { public AnnotationAwareBooleanTag(String key) { super(key); } /** * Sets the value of the tag for the span of the {@link Traced @Traced} method. * * @param value the value to set */ public void set(Boolean value) { set(() -> value); } /** * Sets the value of the tag for the span of the {@link Traced @Traced} method. * * @param value the supplier of the value to set */ public void set(Supplier value) { Traced.Tags.addBoolean(getKey(), value); } } ================================================ FILE: core/commons/che-core-commons-tracing/src/main/java/org/eclipse/che/commons/tracing/AnnotationAwareIntTag.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.tracing; import com.google.common.annotations.Beta; import io.opentracing.tag.IntTag; import java.util.function.Supplier; import org.eclipse.che.commons.annotation.Traced; /** * A specialization of the {@link IntTag} that adds support for setting a tag in a {@link * Traced @Traced} method. */ @Beta public class AnnotationAwareIntTag extends IntTag { public AnnotationAwareIntTag(String key) { super(key); } /** * Sets the value of the tag for the span of the {@link Traced @Traced} method. * * @param value the value to set */ public void set(Integer value) { set(() -> value); } /** * Sets the value of the tag for the span of the {@link Traced @Traced} method. * * @param value the supplier of the value to set */ public void set(Supplier value) { Traced.Tags.addInteger(getKey(), value); } } ================================================ FILE: core/commons/che-core-commons-tracing/src/main/java/org/eclipse/che/commons/tracing/AnnotationAwareStringTag.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.tracing; import com.google.common.annotations.Beta; import io.opentracing.tag.StringTag; import java.util.function.Supplier; import org.eclipse.che.commons.annotation.Traced; /** * A specialization of the {@link StringTag} that adds support for setting a tag in a {@link * Traced @Traced} method. */ @Beta public class AnnotationAwareStringTag extends StringTag { public AnnotationAwareStringTag(String key) { super(key); } /** * Sets the value of the tag for the span of the {@link Traced @Traced} method. * * @param value the value to set */ public void set(String value) { set(() -> value); } /** * Sets the value of the tag for the span of the {@link Traced @Traced} method. * * @param value the supplier of the value to set */ public void set(Supplier value) { Traced.Tags.addString(getKey(), value); } } ================================================ FILE: core/commons/che-core-commons-tracing/src/main/java/org/eclipse/che/commons/tracing/TracingTags.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.commons.tracing; import static com.google.common.base.MoreObjects.firstNonNull; import com.google.common.annotations.Beta; import io.opentracing.Span; import io.opentracing.tag.Tags; /** The standard tags used in Che server. */ @Beta public final class TracingTags { /** The id of the workspace the span is related to. */ public static final AnnotationAwareStringTag WORKSPACE_ID = new AnnotationAwareStringTag("workspace.id"); /** The name of the machine (container) the span is related to. */ public static final AnnotationAwareStringTag MACHINE_NAME = new AnnotationAwareStringTag("machine.name"); /** The id of the user the span is related to. */ public static final AnnotationAwareStringTag USER_ID = new AnnotationAwareStringTag("user.id"); /** The id of the stack the span is related to. */ public static final AnnotationAwareStringTag STACK_ID = new AnnotationAwareStringTag("stack.id"); /** * The entity that stopped workspace, which can be either user ID, or name of component that * stopped it (e.g. activity checker) . */ public static final AnnotationAwareStringTag STOPPED_BY = new AnnotationAwareStringTag("stopped_by"); /** * This is the standard {@link Tags#ERROR} "reexported" as an annotation aware tag so that it can * be easily set in the {@link org.eclipse.che.commons.annotation.Traced @Traced} methods. */ public static final AnnotationAwareBooleanTag ERROR = new AnnotationAwareBooleanTag(Tags.ERROR.getKey()); /** We can record the reason for an error in this tag. */ public static final AnnotationAwareStringTag ERROR_REASON = new AnnotationAwareStringTag("error.reason"); /** * If some asynchronous job has been cancelled due to some reason (but itself didn't fail) one can * use this tag instead of the "error" tag. */ public static final AnnotationAwareBooleanTag CANCELLED = new AnnotationAwareBooleanTag("cancelled"); /** A place to report the reason for the cancellation as a tag on a span */ public static final AnnotationAwareStringTag CANCELLED_REASON = new AnnotationAwareStringTag("cancelled.reason"); /** * This is the standard {@link Tags#SAMPLING_PRIORITY} "reexported" as an annotation aware tag so * that it can be easily set in the {@link org.eclipse.che.commons.annotation.Traced @Traced} * methods. */ public static final AnnotationAwareIntTag SAMPLING_PRIORITY = new AnnotationAwareIntTag(Tags.SAMPLING_PRIORITY.getKey()); /** Set error status and associated tags on a span, given a throwable */ public static void setErrorStatus(Span span, Throwable e) { TracingTags.ERROR.set(span, true); TracingTags.ERROR_REASON.set(span, firstNonNull(e.getMessage(), "Unknown reason")); TracingTags.SAMPLING_PRIORITY.set(span, 1); } private TracingTags() {} } ================================================ FILE: core/commons/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT ../pom.xml che-core-commons-parent pom Che Core :: Commons :: Parent che-core-commons-annotations che-core-commons-lang che-core-commons-inject che-core-commons-json che-core-commons-schedule che-core-commons-test che-core-commons-j2ee che-core-commons-tracing che-core-commons-observability ================================================ FILE: core/pom.xml ================================================ 4.0.0 che-server org.eclipse.che 7.118.0-SNAPSHOT ../pom.xml org.eclipse.che.core che-core-parent pom Che Core Parent commons che-core-api-dto che-core-api-dto-maven-plugin che-core-typescript-dto-maven-plugin che-core-api-core che-core-api-model che-core-logback che-core-tracing-core che-core-tracing-web che-core-metrics-core che-core-tracing-metrics ================================================ FILE: deploy/cert-manager/ca-cert-generator-role-binding.yml ================================================ --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: ca-cert-generator-role-binding namespace: cert-manager subjects: - kind: ServiceAccount name: ca-cert-generator apiGroup: '' roleRef: kind: Role name: ca-cert-generator-role apiGroup: '' ================================================ FILE: deploy/cert-manager/ca-cert-generator-role.yml ================================================ --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: ca-cert-generator-role namespace: cert-manager rules: - apiGroups: - '' resources: - secrets verbs: - create ================================================ FILE: deploy/cert-manager/che-certificate.yml ================================================ --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: che-certificate namespace: che spec: secretName: che-tls issuerRef: name: che-cluster-issuer kind: ClusterIssuer # This is a template and it will be set from --domain parameter # For example: '*.192.168.99.100.nip.io' commonName: '*.' dnsNames: - '*.' ================================================ FILE: deploy/cert-manager/che-cluster-issuer.yml ================================================ --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: che-cluster-issuer namespace: cert-manager spec: ca: secretName: ca ================================================ FILE: devfile.yaml ================================================ schemaVersion: 2.3.0 metadata: name: che-server generateName: che-server- components: - container: image: quay.io/devfile/universal-developer-image:ubi9-latest memoryLimit: 12G memoryRequest: 512Mi cpuRequest: 1000m cpuLimit: 5000m mountSources: true volumeMounts: - name: m2 path: /home/user/.m2 name: tools - name: m2 volume: {} commands: - id: build-sources exec: label: "1. Build sources" component: tools workingDir: ${PROJECT_SOURCE} commandLine: | mvn clean install -V -e -Pfast -DskipTests -Dskip-validate-sources -Denforcer.skip=true group: kind: build isDefault: true - id: build-image exec: label: "2. Build image" component: tools workingDir: ${PROJECT_SOURCE} commandLine: | ./build/build.sh group: kind: build isDefault: true - id: install-chectl exec: label: "3. Install chectl" component: tools group: kind: build workingDir: ${HOME} commandLine: | get_arch() { case "$(uname -m)" in "x86_64") echo "x64" ;; "aarch64") echo "arm64" ;; "armv7l") echo "arm" ;; "ppc64le") echo "ppc64le" ;; "s390x") echo "s390x" ;; "arm64") echo "arm64" ;; *) error "unsupported arch: $(uname -m)" return 1 ;; esac } get_operating_system() { SHORT_UNAME=$(uname -s) if [ "$(uname)" == "Darwin" ]; then echo "darwin" elif [ "${SHORT_UNAME:0:5}" == "Linux" ]; then echo "linux" else error "This installer is only supported on Linux and macOS. Found $(uname)" return 1 fi } curl -LJO $(curl -fsSL https://che-incubator.github.io/chectl/download-link/stable-$(get_operating_system)-$(get_arch)) tar -xf chectl-linux-x64.tar.gz -C $HOME/ echo 'export PATH=$HOME/chectl/bin/:$PATH' >> $HOME/.bashrc - id: install-claude exec: label: "4. Install Claude CLI" commandLine: curl -fsSL https://claude.ai/install.sh -o claude-install.sh && chmod +x claude-install.sh && ./claude-install.sh component: tools - id: run-claude-yolo exec: label: "5. Run Claude YOLO" commandLine: claude --dangerously-skip-permissions component: tools ================================================ FILE: docs/README.md ================================================ Docs are located at [https://github.com/eclipse/che-docs](https://github.com/eclipse/che-docs). ================================================ FILE: docs/grafana/dashboard.json ================================================ { "annotations": { "list": [ { "builtIn": 1, "datasource": "-- Grafana --", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "limit": 100, "name": "Annotations & Alerts", "showIn": 0, "type": "dashboard" }, { "datasource": null, "enable": true, "expr": "resets(process_uptime_seconds{application=\"$application\", instance=\"$instance\"}[1m]) > 0", "iconColor": "rgba(255, 96, 96, 1)", "name": "Restart Detection", "showIn": 0, "step": "1m", "tagKeys": "restart-tag", "textFormat": "uptime reset", "titleFormat": "Restart" } ] }, "description": "Che Server JVM", "editable": true, "gnetId": 4701, "graphTooltip": 1, "iteration": 1559737330617, "links": [], "panels": [ { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 125, "panels": [], "repeat": null, "title": "Quick Facts", "type": "row" }, { "cacheTimeout": null, "colorBackground": false, "colorValue": true, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)" ], "datasource": null, "decimals": 1, "editable": true, "error": false, "format": "s", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 3, "w": 6, "x": 0, "y": 1 }, "height": "", "id": 63, "interval": null, "links": [], "mappingType": 1, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "options": {}, "postfix": "", "postfixFontSize": "50%", "prefix": "", "prefixFontSize": "70%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" } ], "sparkline": { "fillColor": "rgba(31, 118, 189, 0.18)", "full": false, "lineColor": "rgb(31, 120, 193)", "show": false }, "tableColumn": "", "targets": [ { "expr": "process_uptime_seconds{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "", "metric": "", "refId": "A", "step": 14400 } ], "thresholds": "", "title": "Uptime", "type": "singlestat", "valueFontSize": "80%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" } ], "valueName": "current" }, { "cacheTimeout": null, "colorBackground": false, "colorValue": true, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)" ], "datasource": null, "decimals": null, "editable": true, "error": false, "format": "dateTimeAsIso", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 3, "w": 6, "x": 6, "y": 1 }, "height": "", "id": 92, "interval": null, "links": [], "mappingType": 1, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "options": {}, "postfix": "", "postfixFontSize": "50%", "prefix": "", "prefixFontSize": "70%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" } ], "sparkline": { "fillColor": "rgba(31, 118, 189, 0.18)", "full": false, "lineColor": "rgb(31, 120, 193)", "show": false }, "tableColumn": "", "targets": [ { "expr": "process_start_time_seconds{application=\"$application\", instance=\"$instance\"}*1000", "format": "time_series", "intervalFactor": 2, "legendFormat": "", "metric": "", "refId": "A", "step": 14400 } ], "thresholds": "", "title": "Start time", "type": "singlestat", "valueFontSize": "70%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" } ], "valueName": "current" }, { "cacheTimeout": null, "colorBackground": false, "colorValue": true, "colors": [ "rgba(50, 172, 45, 0.97)", "rgba(237, 129, 40, 0.89)", "rgba(245, 54, 54, 0.9)" ], "datasource": null, "decimals": 2, "editable": true, "error": false, "format": "percent", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 3, "w": 6, "x": 12, "y": 1 }, "id": 65, "interval": null, "links": [], "mappingType": 1, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "options": {}, "postfix": "", "postfixFontSize": "50%", "prefix": "", "prefixFontSize": "70%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" } ], "sparkline": { "fillColor": "rgba(31, 118, 189, 0.18)", "full": false, "lineColor": "rgb(31, 120, 193)", "show": false }, "tableColumn": "", "targets": [ { "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"heap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "", "refId": "A", "step": 14400 } ], "thresholds": "70,90", "title": "Heap used", "type": "singlestat", "valueFontSize": "80%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" } ], "valueName": "current" }, { "cacheTimeout": null, "colorBackground": false, "colorValue": true, "colors": [ "rgba(50, 172, 45, 0.97)", "rgba(237, 129, 40, 0.89)", "rgba(245, 54, 54, 0.9)" ], "datasource": null, "decimals": 2, "editable": true, "error": false, "format": "percent", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 3, "w": 6, "x": 18, "y": 1 }, "id": 75, "interval": null, "links": [], "mappingType": 2, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "options": {}, "postfix": "", "postfixFontSize": "50%", "prefix": "", "prefixFontSize": "70%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" }, { "from": "-99999999999999999999999999999999", "text": "N/A", "to": "0" } ], "sparkline": { "fillColor": "rgba(31, 118, 189, 0.18)", "full": false, "lineColor": "rgb(31, 120, 193)", "show": false }, "tableColumn": "", "targets": [ { "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"nonheap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "", "refId": "A", "step": 14400 } ], "thresholds": "70,90", "title": "Non-Heap used", "type": "singlestat", "valueFontSize": "80%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" }, { "op": "=", "text": "x", "value": "" } ], "valueName": "current" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 4 }, "id": 127, "panels": [], "repeat": null, "title": "JVM Memory", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 0, "y": 5 }, "id": 24, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 2400 }, { "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "committed", "refId": "B", "step": 2400 }, { "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "max", "refId": "C", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "JVM Heap", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 6, "y": 5 }, "id": 25, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", "format": "time_series", "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 2400 }, { "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "committed", "refId": "B", "step": 2400 }, { "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "max", "refId": "C", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "JVM Non-Heap", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 12, "y": 5 }, "id": 26, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 2400 }, { "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "committed", "refId": "B", "step": 2400 }, { "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "max", "refId": "C", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "JVM Total", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": "", "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 18, "y": 5 }, "id": 86, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "process_memory_vss_bytes{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "hide": true, "intervalFactor": 2, "legendFormat": "vss", "metric": "", "refId": "A", "step": 2400 }, { "expr": "process_memory_rss_bytes{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "rss", "refId": "B" }, { "expr": "process_memory_pss_bytes{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "pss", "refId": "C" }, { "expr": "process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "swap", "refId": "D" }, { "expr": "process_memory_swappss_bytes{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "swappss", "refId": "E" }, { "expr": "process_memory_pss_bytes{application=\"$application\", instance=\"$instance\"} + process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "phys (pss+swap)", "refId": "F" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "JVM Process Memory", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": "", "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 12 }, "id": 128, "panels": [], "repeat": null, "title": "JVM Misc", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 0, "y": 13 }, "id": 106, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "system_cpu_usage{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "hide": false, "intervalFactor": 1, "legendFormat": "system", "metric": "", "refId": "A", "step": 2400 }, { "expr": "process_cpu_usage{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "hide": false, "intervalFactor": 1, "legendFormat": "process", "refId": "B" }, { "expr": "avg_over_time(process_cpu_usage{application=\"$application\", instance=\"$instance\"}[1h])", "format": "time_series", "hide": false, "intervalFactor": 1, "legendFormat": "process-1h", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "CPU", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 1, "format": "percentunit", "label": "", "logBase": 1, "max": "1", "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 6, "y": 13 }, "id": 93, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "system_load_average_1m{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "system-1m", "metric": "", "refId": "A", "step": 2400 }, { "expr": "", "format": "time_series", "intervalFactor": 2, "refId": "B" }, { "expr": "system_cpu_count{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "cpu", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Load", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 1, "format": "short", "label": "", "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 12, "y": 13 }, "id": 32, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_threads_live{application=\"$application\", instance=\"$instance\"} or jvm_threads_live_threads{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "live", "metric": "", "refId": "A", "step": 2400 }, { "expr": "jvm_threads_daemon{application=\"$application\", instance=\"$instance\"} or jvm_threads_daemon_threads{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "daemon", "metric": "", "refId": "B", "step": 2400 }, { "expr": "jvm_threads_peak{application=\"$application\", instance=\"$instance\"} or jvm_threads_peak_threads{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "peak", "refId": "C", "step": 2400 }, { "expr": "process_threads{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "interval": "", "intervalFactor": 2, "legendFormat": "process", "refId": "D", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Threads", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 0, "format": "short", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": { "blocked": "#bf1b00", "new": "#fce2de", "runnable": "#7eb26d", "terminated": "#511749", "timed-waiting": "#c15c17", "waiting": "#eab839" }, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "fill": 1, "gridPos": { "h": 7, "w": 6, "x": 18, "y": 13 }, "id": 124, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_threads_states_threads{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "{{state}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Thread States", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": { "debug": "#1F78C1", "error": "#BF1B00", "info": "#508642", "trace": "#6ED0E0", "warn": "#EAB839" }, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 18, "x": 0, "y": 20 }, "height": "", "id": 91, "legend": { "alignAsTable": false, "avg": false, "current": true, "hideEmpty": false, "hideZero": false, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": true, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [ { "alias": "error", "yaxis": 1 }, { "alias": "warn", "yaxis": 1 } ], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "increase(logback_events_total{application=\"$application\", instance=\"$instance\"}[1m])", "format": "time_series", "interval": "", "intervalFactor": 2, "legendFormat": "{{level}}", "metric": "", "refId": "A", "step": 1200 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Log Events (1m)", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 0, "format": "short", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 18, "y": 20 }, "id": 61, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "process_open_fds{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "hide": false, "intervalFactor": 2, "legendFormat": "open", "metric": "", "refId": "A", "step": 2400 }, { "expr": "process_max_fds{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "hide": false, "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "B", "step": 2400 }, { "expr": "process_files_open{application=\"$application\", instance=\"$instance\"} or process_files_open_files{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "open", "refId": "C" }, { "expr": "process_files_max{application=\"$application\", instance=\"$instance\"} or process_files_max_files{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "max", "refId": "D" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "File Descriptors", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 0, "format": "short", "label": null, "logBase": 10, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 27 }, "id": 129, "panels": [], "repeat": "persistence_counts", "title": "JVM Memory Pools (Heap)", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 8, "x": 0, "y": 28 }, "id": 3, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "maxPerRow": 3, "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": "jvm_memory_pool_heap", "scopedVars": { "jvm_memory_pool_heap": { "selected": false, "text": "PS Eden Space", "value": "PS Eden Space" } }, "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 1800 }, { "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "commited", "metric": "", "refId": "B", "step": 1800 }, { "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "C", "step": 1800 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "$jvm_memory_pool_heap", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 8, "x": 8, "y": 28 }, "id": 134, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "maxPerRow": 3, "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": null, "repeatIteration": 1559737330617, "repeatPanelId": 3, "scopedVars": { "jvm_memory_pool_heap": { "selected": false, "text": "PS Old Gen", "value": "PS Old Gen" } }, "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 1800 }, { "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "commited", "metric": "", "refId": "B", "step": 1800 }, { "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "C", "step": 1800 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "$jvm_memory_pool_heap", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 8, "x": 16, "y": 28 }, "id": 135, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "maxPerRow": 3, "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": null, "repeatIteration": 1559737330617, "repeatPanelId": 3, "scopedVars": { "jvm_memory_pool_heap": { "selected": false, "text": "PS Survivor Space", "value": "PS Survivor Space" } }, "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 1800 }, { "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "commited", "metric": "", "refId": "B", "step": 1800 }, { "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_heap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "C", "step": 1800 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "$jvm_memory_pool_heap", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 35 }, "id": 130, "panels": [], "repeat": null, "title": "JVM Memory Pools (Non-Heap)", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 8, "x": 0, "y": 36 }, "id": 78, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "maxPerRow": 3, "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": "jvm_memory_pool_nonheap", "scopedVars": { "jvm_memory_pool_nonheap": { "selected": false, "text": "Metaspace", "value": "Metaspace" } }, "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 1800 }, { "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "commited", "metric": "", "refId": "B", "step": 1800 }, { "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "C", "step": 1800 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "$jvm_memory_pool_nonheap", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 8, "x": 8, "y": 36 }, "id": 136, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "maxPerRow": 3, "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": null, "repeatIteration": 1559737330617, "repeatPanelId": 78, "scopedVars": { "jvm_memory_pool_nonheap": { "selected": false, "text": "Compressed Class Space", "value": "Compressed Class Space" } }, "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 1800 }, { "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "commited", "metric": "", "refId": "B", "step": 1800 }, { "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "C", "step": 1800 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "$jvm_memory_pool_nonheap", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 8, "x": 16, "y": 36 }, "id": 137, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "maxPerRow": 3, "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": null, "repeatIteration": 1559737330617, "repeatPanelId": 78, "scopedVars": { "jvm_memory_pool_nonheap": { "selected": false, "text": "Code Cache", "value": "Code Cache" } }, "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 1800 }, { "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "commited", "metric": "", "refId": "B", "step": 1800 }, { "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=~\"$jvm_memory_pool_nonheap\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "C", "step": 1800 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "$jvm_memory_pool_nonheap", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 43 }, "id": 131, "panels": [], "repeat": null, "title": "Garbage Collection", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "fill": 1, "gridPos": { "h": 7, "w": 8, "x": 0, "y": 44 }, "id": 98, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])", "format": "time_series", "hide": false, "intervalFactor": 2, "legendFormat": "{{action}} ({{cause}})", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "Collections", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ops", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": "", "logBase": 1, "max": null, "min": null, "show": true } ] }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "fill": 1, "gridPos": { "h": 7, "w": 8, "x": 8, "y": 44 }, "id": 101, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "rate(jvm_gc_pause_seconds_sum{application=\"$application\", instance=\"$instance\"}[1m])/rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])", "format": "time_series", "hide": false, "instant": false, "intervalFactor": 1, "legendFormat": "avg {{action}} ({{cause}})", "refId": "A" }, { "expr": "jvm_gc_pause_seconds_max{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "hide": false, "instant": false, "intervalFactor": 1, "legendFormat": "max {{action}} ({{cause}})", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "Pause Durations", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "s", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": "", "logBase": 1, "max": null, "min": null, "show": true } ] }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "fill": 1, "gridPos": { "h": 7, "w": 8, "x": 16, "y": 44 }, "id": 99, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "rate(jvm_gc_memory_allocated_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])", "format": "time_series", "interval": "", "intervalFactor": 1, "legendFormat": "allocated", "refId": "A" }, { "expr": "rate(jvm_gc_memory_promoted_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])", "format": "time_series", "interval": "", "intervalFactor": 1, "legendFormat": "promoted", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "Allocated/Promoted", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "Bps", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 51 }, "id": 132, "panels": [], "repeat": null, "title": "Classloading", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 12, "x": 0, "y": 52 }, "id": 37, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_classes_loaded{application=\"$application\", instance=\"$instance\"} or jvm_classes_loaded_classes{application=\"$application\", instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "loaded", "metric": "", "refId": "A", "step": 1200 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "Classes loaded", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 52 }, "id": 38, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "delta(jvm_classes_loaded{application=\"$application\",instance=\"$instance\"}[5m]) or delta(jvm_classes_loaded_classes{application=\"$application\",instance=\"$instance\"}[5m])", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "delta", "metric": "", "refId": "A", "step": 1200 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "Class delta (5m)", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "ops", "short" ], "yaxes": [ { "decimals": null, "format": "short", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 59 }, "id": 133, "panels": [], "repeat": null, "title": "Buffer Pools", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 0, "y": 60 }, "id": 33, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"direct\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 2400 }, { "expr": "jvm_buffer_total_capacity_bytes{application=\"$application\", instance=\"$instance\", id=\"direct\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "capacity", "metric": "", "refId": "B", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "Direct Buffers", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 6, "y": 60 }, "id": 83, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_buffer_count{application=\"$application\", instance=\"$instance\", id=\"direct\"} or jvm_buffer_count_buffers{application=\"$application\", instance=\"$instance\", id=\"direct\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "count", "metric": "", "refId": "A", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "Direct Buffers", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 0, "format": "short", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 12, "y": 60 }, "id": 85, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 2400 }, { "expr": "jvm_buffer_total_capacity_bytes{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "capacity", "metric": "", "refId": "B", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "Mapped Buffers", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 18, "y": 60 }, "id": 84, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": {}, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "jvm_buffer_count{application=\"$application\", instance=\"$instance\", id=\"mapped\"} or jvm_buffer_count_buffers{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "count", "metric": "", "refId": "A", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeShift": null, "title": "Mapped Buffers", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 0, "format": "short", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ] } ], "refresh": "15m", "schemaVersion": 18, "style": "dark", "tags": [], "templating": { "list": [ { "allValue": null, "current": { "isNone": true, "text": "None", "value": "" }, "datasource": null, "definition": "", "hide": 0, "includeAll": false, "label": "Application", "multi": false, "name": "application", "options": [], "query": "label_values(application)", "refresh": 2, "regex": "", "skipUrlSync": false, "sort": 0, "tagValuesQuery": "", "tags": [], "tagsQuery": "", "type": "query", "useTags": false }, { "allFormat": "glob", "allValue": null, "datasource": null, "definition": "", "hide": 0, "includeAll": false, "label": "Instance", "multi": false, "multiFormat": "glob", "name": "instance", "options": [], "query": "label_values(jvm_memory_used_bytes{application=\"$application\"}, instance)", "refresh": 2, "regex": "", "skipUrlSync": false, "sort": 0, "tagValuesQuery": "", "tags": [], "tagsQuery": "", "type": "query", "useTags": false }, { "allFormat": "glob", "allValue": null, "current": { "text": "All", "value": "$__all" }, "datasource": null, "definition": "", "hide": 0, "includeAll": true, "label": "JVM Memory Pools Heap", "multi": false, "multiFormat": "glob", "name": "jvm_memory_pool_heap", "options": [], "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"},id)", "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 1, "tagValuesQuery": "", "tags": [], "tagsQuery": "", "type": "query", "useTags": false }, { "allFormat": "glob", "allValue": null, "current": { "text": "All", "value": "$__all" }, "datasource": null, "definition": "", "hide": 0, "includeAll": true, "label": "JVM Memory Pools Non-Heap", "multi": false, "multiFormat": "glob", "name": "jvm_memory_pool_nonheap", "options": [], "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"},id)", "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 2, "tagValuesQuery": "", "tags": [], "tagsQuery": "", "type": "query", "useTags": false }, { "hide": 0, "includeAll": false, "label": null, "multi": false, "name": "datasource", "options": [], "query": "prometheus", "refresh": 1, "regex": ".*che.*", "skipUrlSync": false, "type": "datasource" } ] }, "time": { "from": "now-24h", "to": "now" }, "timepicker": { "now": true, "refresh_intervals": [ "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d" ], "time_options": [ "5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d" ] }, "timezone": "browser", "title": "Che Server JVM", "uid": "Jv-xDZrmk", "version": 1 } ================================================ FILE: docs/grafana/openshift-console-dashboard.json ================================================ { "annotations": { "list": [ { "builtIn": 1, "datasource": "-- Grafana --", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "limit": 100, "name": "Annotations & Alerts", "showIn": 0, "type": "dashboard" }, { "datasource": null, "enable": true, "expr": "resets(process_uptime_seconds{instance=\"$instance\"}[1m]) > 0", "iconColor": "rgba(255, 96, 96, 1)", "name": "Restart Detection", "showIn": 0, "step": "1m", "tagKeys": "restart-tag", "textFormat": "uptime reset", "titleFormat": "Restart" } ] }, "description": "Che Server JVM", "editable": true, "gnetId": 4701, "graphTooltip": 1, "id": 6, "iteration": 1681311119023, "links": [], "rows": [ { "collapse": false, "height": "100px", "panels": [ { "cacheTimeout": null, "colorBackground": false, "colorValue": true, "colors": [ "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)" ], "datasource": null, "decimals": 1, "editable": true, "error": false, "format": "", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 3, "w": 8, "x": 0, "y": 1 }, "height": "", "id": 63, "interval": null, "links": [], "mappingType": 1, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "postfix": "hours", "postfixFontSize": "80%", "prefix": "", "prefixFontSize": "70%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" } ], "sparkline": { "fillColor": "rgba(31, 118, 189, 0.18)", "full": false, "lineColor": "rgb(31, 120, 193)", "show": false }, "tableColumn": "", "span": 2, "targets": [ { "expr": "process_uptime_seconds{instance=\"$instance\"} / (60 * 60)", "format": "time_series", "intervalFactor": 2, "legendFormat": "", "metric": "", "refId": "A", "step": 14400 } ], "thresholds": "", "title": "Uptime", "type": "singlestat", "valueFontSize": "80%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" } ], "valueName": "current" }, { "cacheTimeout": null, "colorBackground": false, "colorValue": true, "colors": [ "rgba(50, 172, 45, 0.97)", "rgba(237, 129, 40, 0.89)", "rgba(245, 54, 54, 0.9)" ], "datasource": null, "decimals": 2, "editable": true, "error": false, "format": "none", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 3, "w": 8, "x": 8, "y": 1 }, "id": 65, "interval": null, "links": [], "mappingType": 1, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "postfix": "%", "postfixFontSize": "80%", "prefix": "", "prefixFontSize": "70%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" } ], "sparkline": { "fillColor": "rgba(31, 118, 189, 0.18)", "full": false, "lineColor": "rgb(31, 120, 193)", "show": false }, "tableColumn": "", "span": 2, "targets": [ { "expr": "sum(jvm_memory_used_bytes{instance=\"$instance\", area=\"heap\"})*100/sum(jvm_memory_max_bytes{instance=\"$instance\", area=\"heap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "", "refId": "A", "step": 14400 } ], "thresholds": "70,90", "title": "Heap used", "type": "singlestat", "valueFontSize": "80%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" } ], "valueName": "current" }, { "cacheTimeout": null, "colorBackground": false, "colorValue": true, "colors": [ "rgba(50, 172, 45, 0.97)", "rgba(237, 129, 40, 0.89)", "rgba(245, 54, 54, 0.9)" ], "datasource": null, "decimals": 2, "editable": true, "error": false, "format": "none", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 3, "w": 8, "x": 16, "y": 1 }, "id": 75, "interval": null, "links": [], "mappingType": 2, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "postfix": "%", "postfixFontSize": "80%", "prefix": "", "prefixFontSize": "70%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" }, { "from": "-99999999999999999999999999999999", "text": "N/A", "to": "0" } ], "sparkline": { "fillColor": "rgba(31, 118, 189, 0.18)", "full": false, "lineColor": "rgb(31, 120, 193)", "show": false }, "tableColumn": "", "span": 2, "targets": [ { "expr": "sum(jvm_memory_used_bytes{instance=\"$instance\", area=\"nonheap\"})*100/sum(jvm_memory_max_bytes{instance=\"$instance\", area=\"nonheap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "", "refId": "A", "step": 14400 } ], "thresholds": "70,90", "title": "Non-Heap used", "type": "singlestat", "valueFontSize": "80%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" }, { "op": "=", "text": "x", "value": "" } ], "valueName": "current" } ], "repeat": null, "repeatIteration": null, "repeatRowId": null, "showTitle": true, "title": "Quick Facts", "titleSize": "h6" }, { "collapse": false, "height": "250px", "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 0, "y": 5 }, "hiddenSeries": false, "id": 24, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "sum(jvm_memory_used_bytes{instance=\"$instance\", area=\"heap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 2400 }, { "expr": "sum(jvm_memory_committed_bytes{instance=\"$instance\", area=\"heap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "committed", "refId": "B", "step": 2400 }, { "expr": "sum(jvm_memory_max_bytes{instance=\"$instance\", area=\"heap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "max", "refId": "C", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "JVM Heap", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 6, "y": 5 }, "hiddenSeries": false, "id": 25, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "sum(jvm_memory_used_bytes{instance=\"$instance\", area=\"nonheap\"})", "format": "time_series", "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 2400 }, { "expr": "sum(jvm_memory_committed_bytes{instance=\"$instance\", area=\"nonheap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "committed", "refId": "B", "step": 2400 }, { "expr": "sum(jvm_memory_max_bytes{instance=\"$instance\", area=\"nonheap\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "max", "refId": "C", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "JVM Non-Heap", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 12, "y": 5 }, "hiddenSeries": false, "id": 26, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "sum(jvm_memory_used_bytes{instance=\"$instance\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 2400 }, { "expr": "sum(jvm_memory_committed_bytes{instance=\"$instance\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "committed", "refId": "B", "step": 2400 }, { "expr": "sum(jvm_memory_max_bytes{instance=\"$instance\"})", "format": "time_series", "intervalFactor": 2, "legendFormat": "max", "refId": "C", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "JVM Total", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": "", "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 18, "y": 5 }, "hiddenSeries": false, "id": 86, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "process_memory_vss_bytes{instance=\"$instance\"}", "format": "time_series", "hide": true, "intervalFactor": 2, "legendFormat": "vss", "metric": "", "refId": "A", "step": 2400 }, { "expr": "process_memory_rss_bytes{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "rss", "refId": "B" }, { "expr": "process_memory_pss_bytes{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "pss", "refId": "C" }, { "expr": "process_memory_swap_bytes{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "swap", "refId": "D" }, { "expr": "process_memory_swappss_bytes{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "swappss", "refId": "E" }, { "expr": "process_memory_pss_bytes{instance=\"$instance\"} + process_memory_swap_bytes{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "phys (pss+swap)", "refId": "F" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "JVM Process Memory", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": "", "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "repeat": null, "repeatIteration": null, "repeatRowId": null, "showTitle": true, "title": "JVM Memory", "titleSize": "h6" }, { "collapse": false, "height": "250px", "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 0, "y": 13 }, "hiddenSeries": false, "id": 106, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "system_cpu_usage{instance=\"$instance\"} * 100", "format": "time_series", "hide": false, "intervalFactor": 1, "legendFormat": "system", "metric": "", "refId": "A", "step": 2400 }, { "expr": "process_cpu_usage{instance=\"$instance\"} * 100", "format": "time_series", "hide": false, "intervalFactor": 1, "legendFormat": "process", "refId": "B" }, { "expr": "avg_over_time(process_cpu_usage{instance=\"$instance\"}[1h]) * 100", "format": "time_series", "hide": false, "intervalFactor": 1, "legendFormat": "process-1h", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "CPU Usage (%)", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 1, "format": "percentunit", "label": "", "logBase": 1, "max": "1", "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 6, "y": 13 }, "hiddenSeries": false, "id": 93, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "system_load_average_1m{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "system-1m", "metric": "", "refId": "A", "step": 2400 }, { "expr": "system_cpu_count{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "cpu", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Load", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 1, "format": "short", "label": "", "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 12, "y": 13 }, "hiddenSeries": false, "id": 32, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_threads_live{instance=\"$instance\"} or jvm_threads_live_threads{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "live", "metric": "", "refId": "A", "step": 2400 }, { "expr": "jvm_threads_daemon{instance=\"$instance\"} or jvm_threads_daemon_threads{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "daemon", "metric": "", "refId": "B", "step": 2400 }, { "expr": "jvm_threads_peak{instance=\"$instance\"} or jvm_threads_peak_threads{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "peak", "refId": "C", "step": 2400 }, { "expr": "process_threads{instance=\"$instance\"}", "format": "time_series", "interval": "", "intervalFactor": 2, "legendFormat": "process", "refId": "D", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Threads", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 0, "format": "short", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": { "blocked": "#bf1b00", "new": "#fce2de", "runnable": "#7eb26d", "terminated": "#511749", "timed-waiting": "#c15c17", "waiting": "#eab839" }, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 6, "x": 18, "y": 13 }, "hiddenSeries": false, "id": 124, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_threads_states_threads{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "{{state}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Thread States", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": { "debug": "#1F78C1", "error": "#BF1B00", "info": "#508642", "trace": "#6ED0E0", "warn": "#EAB839" }, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 18, "x": 0, "y": 20 }, "height": "", "hiddenSeries": false, "id": 91, "legend": { "alignAsTable": false, "avg": false, "current": true, "hideEmpty": false, "hideZero": false, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": true, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [ { "alias": "error", "yaxis": 1 }, { "alias": "warn", "yaxis": 1 } ], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "increase(logback_events_total{instance=\"$instance\"}[1m])", "format": "time_series", "interval": "", "intervalFactor": 2, "legendFormat": "{{level}}", "metric": "", "refId": "A", "step": 1200 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Log Events (1m)", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 0, "format": "short", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 18, "y": 20 }, "hiddenSeries": false, "id": 61, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "process_open_fds{instance=\"$instance\"}", "format": "time_series", "hide": false, "intervalFactor": 2, "legendFormat": "open", "metric": "", "refId": "A", "step": 2400 }, { "expr": "process_max_fds{instance=\"$instance\"}", "format": "time_series", "hide": false, "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "B", "step": 2400 }, { "expr": "process_files_open{instance=\"$instance\"} or process_files_open_files{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "open", "refId": "C" }, { "expr": "process_files_max{instance=\"$instance\"} or process_files_max_files{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "max", "refId": "D" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "File Descriptors", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 0, "format": "short", "label": null, "logBase": 10, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "repeat": null, "repeatIteration": null, "repeatRowId": null, "showTitle": true, "title": "JVM Misc", "titleSize": "h6" }, { "collapse": false, "height": "250px", "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 8, "x": 0, "y": 28 }, "hiddenSeries": false, "id": 3, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "maxPerRow": 3, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": null, "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_memory_used_bytes{instance=\"$instance\", id=\"Eden Space\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 1800 }, { "expr": "jvm_memory_committed_bytes{instance=\"$instance\", id=~\"Eden Space\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "committed", "metric": "", "refId": "B", "step": 1800 }, { "expr": "jvm_memory_max_bytes{instance=\"$instance\", id=\"Eden Space\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "C", "step": 1800 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Eden Space", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 8, "x": 8, "y": 28 }, "hiddenSeries": false, "id": 134, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "maxPerRow": 3, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": null, "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_memory_used_bytes{instance=\"$instance\", id=\"Survivor Space\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 1800 }, { "expr": "jvm_memory_committed_bytes{instance=\"$instance\", id=\"Survivor Space\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "committed", "metric": "", "refId": "B", "step": 1800 }, { "expr": "jvm_memory_max_bytes{instance=\"$instance\", id=\"Survivor Space\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "C", "step": 1800 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Survivor Space", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 8, "x": 16, "y": 28 }, "hiddenSeries": false, "id": 135, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "maxPerRow": 3, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": null, "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_memory_used_bytes{instance=\"$instance\", id=\"Tenured Gen\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 1800 }, { "expr": "jvm_memory_committed_bytes{instance=\"$instance\", id=\"Tenured Gen\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "committed", "metric": "", "refId": "B", "step": 1800 }, { "expr": "jvm_memory_max_bytes{instance=\"$instance\", id=\"Tenured Gen\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "C", "step": 1800 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Tenured Gen", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "repeat": null, "repeatIteration": null, "repeatRowId": null, "showTitle": true, "title": "JVM Memory Pools (Heap)", "titleSize": "h6" }, { "collapse": false, "height": "250px", "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 8, "x": 0, "y": 36 }, "hiddenSeries": false, "id": 78, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "maxPerRow": 3, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": "null", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_memory_used_bytes{instance=\"$instance\", id=\"Metaspace\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 1800 }, { "expr": "jvm_memory_committed_bytes{instance=\"$instance\", id=\"Metaspace\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "committed", "metric": "", "refId": "B", "step": 1800 }, { "expr": "jvm_memory_max_bytes{instance=\"$instance\", id=\"Metaspace\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "C", "step": 1800 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Metaspace", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 8, "x": 8, "y": 36 }, "hiddenSeries": false, "id": 136, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": true, "min": false, "rightSide": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], "maxPerRow": 3, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "repeat": null, "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_memory_used_bytes{instance=\"$instance\", id=\"Compressed Class Space\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 1800 }, { "expr": "jvm_memory_committed_bytes{instance=\"$instance\", id=\"Compressed Class Space\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "committed", "metric": "", "refId": "B", "step": 1800 }, { "expr": "jvm_memory_max_bytes{instance=\"$instance\", id=\"Compressed Class Space\"}", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "max", "metric": "", "refId": "C", "step": 1800 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Compressed Class Space", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "mbytes", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 8, "x": 16, "y": 36 }, "hiddenSeries": false, "id": 138, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_memory_used_bytes{instance=\"$instance\", id=\"CodeHeap 'profiled nmethods'\"}", "interval": "", "intervalFactor": 2, "legendFormat": "used", "refId": "A" }, { "expr": "jvm_memory_committed_bytes{instance=\"$instance\", id=\"CodeHeap 'profiled nmethods'\"}", "interval": "", "intervalFactor": 2, "legendFormat": "committed", "refId": "B" }, { "expr": "jvm_memory_max_bytes{instance=\"$instance\", id=\"CodeHeap 'profiled nmethods'\"}", "interval": "", "intervalFactor": 2, "legendFormat": "max", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "CodeHeap 'profiled nmethods'", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "fill": 1, "fillGradient": 0, "gridPos": { "h": 6, "w": 8, "x": 0, "y": 43 }, "hiddenSeries": false, "id": 140, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_memory_used_bytes{instance=\"$instance\", id=\"CodeHeap 'non-profiled nmethods'\"}", "interval": "", "intervalFactor": 2, "legendFormat": "used", "refId": "A" }, { "expr": "jvm_memory_committed_bytes{instance=\"$instance\", id=\"CodeHeap 'non-profiled nmethods'\"}", "interval": "", "intervalFactor": 2, "legendFormat": "committed", "refId": "B" }, { "expr": "jvm_memory_max_bytes{instance=\"$instance\", id=\"CodeHeap 'non-profiled nmethods'\"}", "interval": "", "intervalFactor": 2, "legendFormat": "max", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "CodeHeap 'non-profiled nmethods'", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "fill": 1, "fillGradient": 0, "gridPos": { "h": 6, "w": 8, "x": 8, "y": 43 }, "hiddenSeries": false, "id": 142, "legend": { "avg": false, "current": true, "max": true, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_memory_used_bytes{instance=\"$instance\", id=\"CodeHeap 'non-nmethods'\"}", "interval": "", "intervalFactor": 2, "legendFormat": "used", "refId": "A" }, { "expr": "jvm_memory_committed_bytes{instance=\"$instance\", id=\"CodeHeap 'non-nmethods'\"}", "interval": "", "intervalFactor": 2, "legendFormat": "committed", "refId": "B" }, { "expr": "jvm_memory_max_bytes{instance=\"$instance\", id=\"CodeHeap 'non-nmethods'\"}", "interval": "", "intervalFactor": 2, "legendFormat": "max", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "CodeHeap 'non-nmethods'", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "repeat": null, "repeatIteration": null, "repeatRowId": null, "showTitle": true, "title": "JVM Memory Pools (Non-Heap)", "titleSize": "h6" }, { "collapse": false, "height": "250px", "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 8, "x": 0, "y": 50 }, "hiddenSeries": false, "id": 98, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "rate(jvm_gc_pause_seconds_count{instance=\"$instance\"}[1m])", "format": "time_series", "hide": false, "intervalFactor": 2, "legendFormat": "{{action}} ({{cause}})", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Collections (ops/s)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ops", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": "", "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 8, "x": 8, "y": 50 }, "hiddenSeries": false, "id": 101, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "rate(jvm_gc_pause_seconds_sum{instance=\"$instance\"}[1m])/rate(jvm_gc_pause_seconds_count{instance=\"$instance\"}[1m])", "format": "time_series", "hide": false, "instant": false, "intervalFactor": 1, "legendFormat": "avg {{action}} ({{cause}})", "refId": "A" }, { "expr": "jvm_gc_pause_seconds_max{instance=\"$instance\"}", "format": "time_series", "hide": false, "instant": false, "intervalFactor": 1, "legendFormat": "max {{action}} ({{cause}})", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Pause Durations", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "s", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": "", "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 8, "x": 16, "y": 50 }, "hiddenSeries": false, "id": 99, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "rate(jvm_gc_memory_allocated_bytes_total{instance=\"$instance\"}[1m])", "format": "time_series", "interval": "", "intervalFactor": 1, "legendFormat": "allocated", "refId": "A" }, { "expr": "rate(jvm_gc_memory_promoted_bytes_total{instance=\"$instance\"}[1m])", "format": "time_series", "interval": "", "intervalFactor": 1, "legendFormat": "promoted", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Allocated/Promoted", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "Bps", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "repeat": null, "repeatIteration": null, "repeatRowId": null, "showTitle": true, "title": "Garbage Collection", "titleSize": "h6" }, { "collapse": false, "height": "250px", "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 12, "x": 0, "y": 58 }, "hiddenSeries": false, "id": 37, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_classes_loaded{instance=\"$instance\"} or jvm_classes_loaded_classes{instance=\"$instance\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "loaded", "metric": "", "refId": "A", "step": 1200 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Classes loaded", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 58 }, "hiddenSeries": false, "id": 38, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "delta(jvm_classes_loaded{instance=\"$instance\"}[5m]) or delta(jvm_classes_loaded_classes{instance=\"$instance\"}[5m])", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 2, "legendFormat": "delta", "metric": "", "refId": "A", "step": 1200 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Class delta (5m)", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "ops", "short" ], "yaxes": [ { "decimals": null, "format": "short", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "repeat": null, "repeatIteration": null, "repeatRowId": null, "showTitle": true, "title": "Classloading", "titleSize": "h6" }, { "collapse": false, "height": "250px", "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 0, "y": 66 }, "hiddenSeries": false, "id": 33, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_buffer_memory_used_bytes{instance=\"$instance\", id=\"direct\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 2400 }, { "expr": "jvm_buffer_total_capacity_bytes{instance=\"$instance\", id=\"direct\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "capacity", "metric": "", "refId": "B", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Direct Buffers", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 6, "y": 66 }, "hiddenSeries": false, "id": 83, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_buffer_count{instance=\"$instance\", id=\"direct\"} or jvm_buffer_count_buffers{instance=\"$instance\", id=\"direct\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "count", "metric": "", "refId": "A", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Direct Buffers", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 0, "format": "short", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 12, "y": 66 }, "hiddenSeries": false, "id": 85, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_buffer_memory_used_bytes{instance=\"$instance\", id=\"mapped\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "used", "metric": "", "refId": "A", "step": 2400 }, { "expr": "jvm_buffer_total_capacity_bytes{instance=\"$instance\", id=\"mapped\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "capacity", "metric": "", "refId": "B", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Mapped Buffers", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "format": "bytes", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "editable": true, "error": false, "fill": 1, "fillGradient": 0, "grid": { "leftLogBase": 1, "leftMax": null, "leftMin": null, "rightLogBase": 1, "rightMax": null, "rightMin": null }, "gridPos": { "h": 7, "w": 6, "x": 18, "y": 66 }, "hiddenSeries": false, "id": 84, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 5, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "span": 6, "targets": [ { "expr": "jvm_buffer_count{instance=\"$instance\", id=\"mapped\"} or jvm_buffer_count_buffers{instance=\"$instance\", id=\"mapped\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "count", "metric": "", "refId": "A", "step": 2400 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Mapped Buffers", "tooltip": { "msResolution": false, "shared": true, "sort": 0, "value_type": "cumulative" }, "type": "graph", "x-axis": true, "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "y-axis": true, "y_formats": [ "short", "short" ], "yaxes": [ { "decimals": 0, "format": "short", "label": null, "logBase": 1, "max": null, "min": 0, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "repeat": null, "repeatIteration": null, "repeatRowId": null, "showTitle": true, "title": "Buffer Pools", "titleSize": "h6" } ], "refresh": "15m", "schemaVersion": 22, "style": "dark", "tags": [ "dev-spaces" ], "templating": { "list": [ { "allFormat": "glob", "allValue": null, "current": { "text": "10.128.2.176:8087", "value": "10.128.2.176:8087" }, "datasource": null, "definition": "", "hide": 0, "includeAll": false, "index": -1, "label": "Instance", "multi": false, "multiFormat": "glob", "name": "instance", "options": [], "query": "label_values(jvm_memory_used_bytes, instance)", "refresh": 2, "regex": "", "skipUrlSync": false, "sort": 0, "tagValuesQuery": "", "tags": [], "tagsQuery": "", "type": "query", "useTags": false } ] }, "time": { "from": "now-24h", "to": "now" }, "timepicker": { "now": true, "refresh_intervals": [ "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d" ], "time_options": [ "5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d" ] }, "timezone": "browser", "title": "Che Server JVM", "uid": "Jv-xDZrmk", "variables": { "list": [] }, "version": 1 } ================================================ FILE: infrastructures/infrastructure-distributed/pom.xml ================================================ 4.0.0 che-infrastructures-parent org.eclipse.che.infrastructure 7.118.0-SNAPSHOT infrastructure-distributed jar Infrastructure :: Distributed components com.google.inject guice jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-commons-lang org.eclipse.che.infrastructure infrastructure-kubernetes org.jgroups jgroups org.slf4j slf4j-api ================================================ FILE: infrastructures/infrastructure-distributed/src/main/java/org/eclipse/che/multiuser/api/distributed/WorkspaceStopPropagator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.distributed; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.multiuser.api.distributed.cache.JGroupsWorkspaceStatusCache; import org.eclipse.che.multiuser.api.distributed.cache.StatusChangeListener; import org.eclipse.che.workspace.infrastructure.kubernetes.event.KubernetesRuntimeStoppedEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.event.KubernetesRuntimeStoppingEvent; /** * Propagate workspace status changes. * *

It's needed to synchronize interrupt/stop operations between Che Servers instances. There can * be two Che Servers in case of Rolling Update. * * @author Sergii Leshchenko * @see org.eclipse.che.workspace.infrastructure.kubernetes.StartSynchronizer */ @Singleton public class WorkspaceStopPropagator implements StatusChangeListener { private final EventService eventService; @Inject public WorkspaceStopPropagator( EventService eventService, JGroupsWorkspaceStatusCache statusCache) { this.eventService = eventService; statusCache.subscribe(this); } @Override public void statusChanged(String workspaceId, WorkspaceStatus status) { switch (status) { case STOPPING: // is supposed to notify start synchronizer that workspace is stopping // so, if runtime is starting then start will be interrupted eventService.publish(new KubernetesRuntimeStoppingEvent(workspaceId)); break; case STOPPED: // is supposed to notify start synchronizer that workspace is stopped // so, if runtime start was interrupting then interruption will be considered as finished eventService.publish(new KubernetesRuntimeStoppedEvent(workspaceId)); break; default: // there is no needed to publish other statuses } } } ================================================ FILE: infrastructures/infrastructure-distributed/src/main/java/org/eclipse/che/multiuser/api/distributed/cache/JGroupsWorkspaceStatusCache.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.distributed.cache; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.workspace.server.WorkspaceStatusCache; import org.jgroups.JChannel; import org.jgroups.blocks.ReplicatedHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * JGroups based implementation of {@link WorkspaceStatusCache}. * * @author Anton Korneta */ @Singleton public class JGroupsWorkspaceStatusCache implements WorkspaceStatusCache { private static final String CHANNEL_NAME = "WorkspaceStateCache"; private static final Logger LOG = LoggerFactory.getLogger(JGroupsWorkspaceStatusCache.class); private final ReplicatedHashMap delegate; @Inject public JGroupsWorkspaceStatusCache(@Named("jgroups.config.file") String confFile) { try { JChannel channel = new JChannel(confFile).connect(CHANNEL_NAME); delegate = new ReplicatedHashMap<>(channel); delegate.setBlockingUpdates(true); delegate.start(5000); } catch (Exception ex) { throw new RuntimeException("Jgroups cache creation failed. Cause :" + ex.getMessage()); } } @Override public WorkspaceStatus get(String workspaceId) { return delegate.get(workspaceId); } @Override public WorkspaceStatus replace(String workspaceId, WorkspaceStatus newStatus) { return delegate.replace(workspaceId, newStatus); } @Override public boolean replace( String workspaceId, WorkspaceStatus prevStatus, WorkspaceStatus newStatus) { return delegate.replace(workspaceId, prevStatus, newStatus); } @Override public WorkspaceStatus remove(String workspaceId) { return delegate.remove(workspaceId); } @Override public WorkspaceStatus putIfAbsent(String workspaceId, WorkspaceStatus status) { return delegate.putIfAbsent(workspaceId, status); } @Override public Map asMap() { return new HashMap<>(delegate); } /** * Subscribes status changes listener. * * @param listener listener instance that will receive status changed events */ public void subscribe(StatusChangeListener listener) { delegate.addNotifier( new ReplicatedMapNotificationAdapter() { @Override public void entrySet(Object workspaceId, Object workspaceStatus) { listener.statusChanged((String) workspaceId, (WorkspaceStatus) workspaceStatus); } @Override public void entryRemoved(Object workspaceId) { listener.statusChanged((String) workspaceId, WorkspaceStatus.STOPPED); } }); } /** Stops workspace status cache. */ public void shutdown() { try { delegate.close(); } catch (IOException | RuntimeException ex) { LOG.error("Failed to stop workspace status cache. Cause: " + ex.getMessage()); } } } ================================================ FILE: infrastructures/infrastructure-distributed/src/main/java/org/eclipse/che/multiuser/api/distributed/cache/ReplicatedMapNotificationAdapter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.distributed.cache; import java.util.List; import java.util.Map; import org.jgroups.View; import org.jgroups.blocks.ReplicatedHashMap; /** * Purpose: Provides an empty implementation of {@link ReplicatedHashMap.Notification}. Users * who do not require to be notified about all map changes can subclass this class and implement * only the methods required. * * @author Sergii Leshchenko */ public class ReplicatedMapNotificationAdapter implements ReplicatedHashMap.Notification { @Override public void entrySet(Object key, Object value) {} @Override public void entryRemoved(Object key) {} @Override public void contentsSet(Map new_entries) {} @Override public void contentsCleared() {} @Override public void viewChange(View view, List mbrs_joined, List mbrs_left) {} } ================================================ FILE: infrastructures/infrastructure-distributed/src/main/java/org/eclipse/che/multiuser/api/distributed/cache/StatusChangeListener.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.distributed.cache; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; /** * Listener interface for being notified about workspace status changes in {@link * JGroupsWorkspaceStatusCache}. * *

Note that JGroupsWorkspaceStatusCache is distributed cache and listener will receiver changes * that are made by this instance or any another is a cluster. * * @author Sergii Leshchenko */ public interface StatusChangeListener { void statusChanged(String workspaceId, WorkspaceStatus status); } ================================================ FILE: infrastructures/infrastructure-distributed/src/main/java/org/eclipse/che/multiuser/api/distributed/lock/JGroupsWorkspaceLockService.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.distributed.lock; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.concurrent.locks.Lock; import javax.inject.Named; import org.eclipse.che.api.workspace.server.WorkspaceLockService; import org.eclipse.che.commons.lang.concurrent.Unlocker; import org.jgroups.JChannel; import org.jgroups.blocks.locking.LockService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * JGroups based implementation of {@link WorkspaceLockService}. * * @author Anton Korneta */ @Singleton public class JGroupsWorkspaceLockService implements WorkspaceLockService { private static final Logger LOG = LoggerFactory.getLogger(JGroupsWorkspaceLockService.class); private static final String CHANNEL_NAME = "WorkspaceLocks"; private final LockService lockService; private final JChannel channel; @Inject public JGroupsWorkspaceLockService(@Named("jgroups.config.file") String confFile) { try { this.channel = new JChannel(confFile); this.lockService = new LockService(channel); channel.connect(CHANNEL_NAME); } catch (Exception ex) { throw new RuntimeException(ex); } } @Override public Unlocker readLock(String key) { // JGroups lock service does not contain an associated pair of read/write locks, that's why // there no way provide a reliable version of a read lock. return writeLock(key); } @Override public Unlocker writeLock(String key) { final Lock lock = lockService.getLock(key); lock.lock(); return new UnlockerImpl(lock); } private class UnlockerImpl implements Unlocker { private final Lock lock; public UnlockerImpl(Lock lock) { this.lock = lock; } @Override public void unlock() { lock.unlock(); } } /** Stops the workspace lock service. */ public void shutdown() { try { channel.close(); } catch (RuntimeException ex) { LOG.error("Failed to stop workspace locks service. Cause: " + ex.getMessage()); } } } ================================================ FILE: infrastructures/infrastructure-distributed/src/main/java/org/eclipse/che/multiuser/api/distributed/subscription/DistributedRemoteSubscriptionStorage.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.distributed.subscription; import static org.slf4j.LoggerFactory.getLogger; import com.google.inject.Singleton; import java.util.Collections; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.notification.RemoteSubscriptionContext; import org.eclipse.che.api.core.notification.RemoteSubscriptionStorage; import org.jgroups.JChannel; import org.jgroups.blocks.ReplicatedHashMap; import org.jgroups.blocks.locking.LockService; import org.slf4j.Logger; /** * Replicated map-based implementation of {@link RemoteSubscriptionStorage} * * @author Max Shaposhnik (mshaposh@redhat.com) */ @Singleton public class DistributedRemoteSubscriptionStorage implements RemoteSubscriptionStorage { private static final Logger LOG = getLogger(DistributedRemoteSubscriptionStorage.class); private static final String CHANNEL_NAME = "RemoteSubscriptionChannel"; private final ReplicatedHashMap> subscriptions; private final LockService lockService; private final JChannel channel; @Inject public DistributedRemoteSubscriptionStorage(@Named("jgroups.config.file") String confFile) throws Exception { try { channel = new JChannel(confFile); this.lockService = new LockService(channel); channel.connect(CHANNEL_NAME); subscriptions = new ReplicatedHashMap<>(channel); subscriptions.setBlockingUpdates(true); subscriptions.start(5000); } catch (Exception e) { LOG.error("Unable to create distributed event subscriptions map.", e); throw e; } } @Override public Set getByMethod(String method) { return subscriptions.getOrDefault(method, Collections.emptySet()); } @Override public void addSubscription(String method, RemoteSubscriptionContext remoteSubscriptionContext) { Lock lock = lockService.getLock(method); lock.lock(); try { Set existing = subscriptions.getOrDefault(method, ConcurrentHashMap.newKeySet(1)); existing.add(remoteSubscriptionContext); subscriptions.put(method, existing); } finally { lock.unlock(); } } @Override public void removeSubscription(String method, String endpointId) { Lock lock = lockService.getLock(method); lock.lock(); try { Set existing = subscriptions.get(method); if (existing == null) { return; } existing.removeIf( remoteSubscriptionContext -> Objects.equals(remoteSubscriptionContext.getEndpointId(), endpointId)); if (existing.isEmpty()) { subscriptions.remove(method); } else { subscriptions.put(method, existing); } } finally { lock.unlock(); } } /** Stops remote subscription storage. */ public void shutdown() { try { channel.close(); } catch (RuntimeException ex) { LOG.error("Failed to stop remote subscription storage. Cause: " + ex.getMessage()); } } } ================================================ FILE: infrastructures/infrastructure-distributed/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex target/log/test.log %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: infrastructures/infrastructure-factory/pom.xml ================================================ 4.0.0 che-infrastructures-parent org.eclipse.che.infrastructure 7.118.0-SNAPSHOT infrastructure-factory jar Infrastructure :: Factory components com.google.code.gson gson com.google.guava guava com.google.inject guice io.fabric8 kubernetes-client-api io.fabric8 kubernetes-model-core jakarta.inject jakarta.inject-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-factory org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang org.eclipse.che.infrastructure infrastructure-kubernetes org.slf4j slf4j-api ch.qos.logback logback-classic test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test ================================================ FILE: infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/KubernetesScmModule.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import com.google.inject.AbstractModule; import org.eclipse.che.api.factory.server.scm.kubernetes.KubernetesAuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.kubernetes.KubernetesGitCredentialManager; import org.eclipse.che.api.factory.server.scm.kubernetes.KubernetesPersonalAccessTokenManager; public class KubernetesScmModule extends AbstractModule { @Override protected void configure() { bind(GitCredentialManager.class).to(KubernetesGitCredentialManager.class); bind(PersonalAccessTokenManager.class).to(KubernetesPersonalAccessTokenManager.class); bind(AuthorisationRequestManager.class).to(KubernetesAuthorisationRequestManager.class); } } ================================================ FILE: infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesAuthorisationRequestManager.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.kubernetes; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.commons.lang.UrlUtils.getParameter; import static org.eclipse.che.commons.lang.UrlUtils.getQueryParametersFromState; import static org.eclipse.che.commons.lang.UrlUtils.getRequestUrl; import static org.eclipse.che.commons.lang.UrlUtils.getState; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME; import com.google.gson.Gson; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.base.PatchContext; import io.fabric8.kubernetes.client.dsl.base.PatchType; import jakarta.ws.rs.core.UriInfo; import java.net.URL; import java.util.HashSet; import java.util.List; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Store and retrieve rejected authorisation requests in the Kubernetes ConfigMap. */ @Singleton public class KubernetesAuthorisationRequestManager implements AuthorisationRequestManager { private static final Logger LOG = LoggerFactory.getLogger(KubernetesAuthorisationRequestManager.class); private final KubernetesNamespaceFactory namespaceFactory; private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private static final String SKIP_AUTHORISATION_MAP_KEY = "skip-authorisation"; @Inject public KubernetesAuthorisationRequestManager( KubernetesNamespaceFactory namespaceFactory, CheServerKubernetesClientFactory cheServerKubernetesClientFactory) { this.namespaceFactory = namespaceFactory; this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; } @Override public void store(String scmProviderName) { if (isStored(scmProviderName)) { return; } ConfigMap configMap = getConfigMap(); HashSet fromJson = getSkipAuthorisationValues(); fromJson.add(scmProviderName); configMap.setData(Map.of(SKIP_AUTHORISATION_MAP_KEY, fromJson.toString())); patchConfigMap(configMap); } @Override public void remove(String scmProviderName) { if (!isStored(scmProviderName)) { return; } ConfigMap configMap = getConfigMap(); HashSet fromJson = getSkipAuthorisationValues(); fromJson.remove(scmProviderName); configMap.setData(Map.of(SKIP_AUTHORISATION_MAP_KEY, fromJson.toString())); patchConfigMap(configMap); } @Override public boolean isStored(String scmProviderName) { try { return getSkipAuthorisationValues().contains(scmProviderName); } catch (Exception e) { // In environments where the Kubernetes configmap cannot be read (e.g. when running // che-server standalone without a properly configured service account), treat the // preference as "not stored" so the factory resolver can continue processing. LOG.debug( "Cannot read authorisation preferences configmap ({}); defaulting to false.", e.getMessage()); return false; } } @Override public void callback(UriInfo uriInfo, List errorValues) { URL requestUrl = getRequestUrl(uriInfo); Map> params = getQueryParametersFromState(getState(requestUrl)); errorValues = errorValues == null ? uriInfo.getQueryParameters().get("error") : errorValues; if (errorValues != null && errorValues.contains("access_denied")) { String oauthProvider = getParameter(params, "oauth_provider"); if (!isNullOrEmpty(oauthProvider)) { store(oauthProvider); } } } private ConfigMap getConfigMap() { try (KubernetesClient kubernetesClient = cheServerKubernetesClientFactory.create()) { String namespace = getFirstNamespace(); return kubernetesClient .configMaps() .inNamespace(namespace) .withName(PREFERENCES_CONFIGMAP_NAME) .get(); } catch (UnsatisfiedScmPreconditionException | ScmConfigurationPersistenceException | InfrastructureException e) { throw new RuntimeException(e); } } private void patchConfigMap(ConfigMap configMap) { try (KubernetesClient kubernetesClient = cheServerKubernetesClientFactory.create()) { kubernetesClient .configMaps() .inNamespace(getFirstNamespace()) .withName(PREFERENCES_CONFIGMAP_NAME) .patch(PatchContext.of(PatchType.STRATEGIC_MERGE), configMap); } catch (UnsatisfiedScmPreconditionException | ScmConfigurationPersistenceException | InfrastructureException e) { throw new RuntimeException(e); } } private HashSet getSkipAuthorisationValues() { String data = getConfigMap().getData().get(SKIP_AUTHORISATION_MAP_KEY); return new Gson().fromJson(isNullOrEmpty(data) ? "[]" : data, HashSet.class); } private String getFirstNamespace() throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException { try { return namespaceFactory.list().stream() .map(KubernetesNamespaceMeta::getName) .findFirst() .orElseThrow( () -> new UnsatisfiedScmPreconditionException( "No user namespace found. Cannot read SCM credentials.")); } catch (InfrastructureException e) { throw new ScmConfigurationPersistenceException(e.getMessage(), e); } } } ================================================ FILE: infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesGitCredentialManager.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.kubernetes; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_AUTOMOUNT; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_DEV_WORKSPACE_MOUNT_PATH; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_GIT_CREDENTIALS; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_AS; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_PATH; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.DEV_WORKSPACE_PREFIX; import com.google.common.collect.ImmutableMap; import com.google.common.net.PercentEscaper; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Optional; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.GitCredentialManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.lang.StringUtils; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; /** * Creates or updates Git credentials secret in user's namespace to allow Git operations on private * repositories. */ @Singleton public class KubernetesGitCredentialManager implements GitCredentialManager { public static final String NAME_PATTERN = "git-credentials-secret-"; public static final String ANNOTATION_SCM_URL = "che.eclipse.org/scm-url"; public static final String ANNOTATION_SCM_USERNAME = "che.eclipse.org/scm-username"; public static final String ANNOTATION_CHE_USERID = "che.eclipse.org/che-userid"; public static final String CREDENTIALS_MOUNT_PATH = "/.git-credentials"; public static final String LABEL_DEV_WORKSPACE_CREDENTIAL = DEV_WORKSPACE_PREFIX + "/git-credential"; public static final String LABEL_DEV_WORKSPACE_WATCH_SECRET = "controller.devfile.io/watch-secret"; // Labels that that are use to search for already existing secret. private static final Map SEARCH_LABELS = ImmutableMap.of( "app.kubernetes.io/part-of", "che.eclipse.org", "app.kubernetes.io/component", "workspace-secret"); // Labels that will be added to newly created secret. private static final Map NEW_SECRET_LABELS = ImmutableMap.builder() .putAll(SEARCH_LABELS) .put(LABEL_DEV_WORKSPACE_CREDENTIAL, "true") .put(LABEL_DEV_WORKSPACE_WATCH_SECRET, "true") .build(); static final Map DEFAULT_SECRET_ANNOTATIONS = ImmutableMap.of( ANNOTATION_AUTOMOUNT, "true", ANNOTATION_MOUNT_PATH, CREDENTIALS_MOUNT_PATH, ANNOTATION_MOUNT_AS, "file", ANNOTATION_GIT_CREDENTIALS, "true", ANNOTATION_DEV_WORKSPACE_MOUNT_PATH, CREDENTIALS_MOUNT_PATH); private final KubernetesNamespaceFactory namespaceFactory; private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Inject public KubernetesGitCredentialManager( KubernetesNamespaceFactory namespaceFactory, CheServerKubernetesClientFactory cheServerKubernetesClientFactory) { this.namespaceFactory = namespaceFactory; this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; } @Override public void createOrReplace(PersonalAccessToken personalAccessToken) throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException { try { final String namespace = getFirstNamespace(); final KubernetesClient client = cheServerKubernetesClientFactory.create(); // to avoid duplicating secrets we try to reuse existing one by matching // hostname/username if possible, and update it. Otherwise, create new one. Optional existing = client .secrets() .inNamespace(namespace) .withLabels(SEARCH_LABELS) .list() .getItems() .stream() .filter(s -> s.getMetadata().getAnnotations() != null) .filter( s -> Boolean.parseBoolean( s.getMetadata().getAnnotations().get(ANNOTATION_GIT_CREDENTIALS)) && personalAccessToken .getScmProviderUrl() .equals( StringUtils.trimEnd( s.getMetadata().getAnnotations().get(ANNOTATION_SCM_URL), '/')) && personalAccessToken .getCheUserId() .equals(s.getMetadata().getAnnotations().get(ANNOTATION_CHE_USERID))) .findFirst(); Secret secret = existing.orElseGet( () -> { Map annotations = new HashMap<>(DEFAULT_SECRET_ANNOTATIONS); annotations.put(ANNOTATION_SCM_URL, personalAccessToken.getScmProviderUrl()); annotations.put(ANNOTATION_CHE_USERID, personalAccessToken.getCheUserId()); ObjectMeta meta = new ObjectMetaBuilder() .withName(NameGenerator.generate(NAME_PATTERN, 5)) .withAnnotations(annotations) .withLabels(NEW_SECRET_LABELS) .build(); return new SecretBuilder().withMetadata(meta).build(); }); URL scmUrl = new URL(personalAccessToken.getScmProviderUrl()); secret.setData( Map.of( "credentials", Base64.getEncoder() .encodeToString( format( "%s://%s:%s@%s%s", scmUrl.getProtocol(), getUsernameSegment(personalAccessToken), URLEncoder.encode(personalAccessToken.getToken(), UTF_8), scmUrl.getHost(), scmUrl.getPort() != 80 && scmUrl.getPort() != -1 ? ":" + scmUrl.getPort() : "") .getBytes()))); client.secrets().inNamespace(namespace).createOrReplace(secret); } catch (InfrastructureException | MalformedURLException e) { throw new ScmConfigurationPersistenceException(e.getMessage(), e); } } /** * Returns username URL segment for git credentials. For OAuth2 tokens it is "oauth2", for others * - {@param personalAccessToken#getScmUserName()} or just "username" string if the token has a * non-null {@param personalAccessToken#getScmOrganization()}. This is needed to support providers * that do not have username in their user object. Such providers have an additional organization * field. */ private String getUsernameSegment(PersonalAccessToken personalAccessToken) { // Special characters are not allowed in URL username segment, so we need to escape them. PercentEscaper percentEscaper = new PercentEscaper("", false); return personalAccessToken.getScmTokenName().startsWith(OAUTH_2_PREFIX) // Most of the git providers work with git credentials with OAuth token in format // "ouath2:" // but bitbucket requires username to be explicitly set: ": // TODO: needs to be moved to the specific bitbucket implementation. && !personalAccessToken.getScmProviderName().equals("bitbucket") ? "oauth2" : isNullOrEmpty(personalAccessToken.getScmOrganization()) ? percentEscaper.escape(personalAccessToken.getScmUserName()) : "username"; } /** * It is not guaranteed that this code will always return same namespace, but since credentials * are now added into manually pre-created user namespace, we can expect always the same result * will be returned. */ private String getFirstNamespace() throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException { try { Optional namespace = namespaceFactory.list().stream().map(KubernetesNamespaceMeta::getName).findFirst(); if (namespace.isEmpty()) { throw new UnsatisfiedScmPreconditionException( "No user namespace found. Cannot read SCM credentials."); } return namespace.get(); } catch (InfrastructureException e) { throw new ScmConfigurationPersistenceException(e.getMessage(), e); } } } ================================================ FILE: infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.kubernetes; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.LabelSelector; import io.fabric8.kubernetes.api.model.LabelSelectorBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.KubernetesClientException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.GitCredentialManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.ScmPersonalAccessTokenFetcher; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Manages personal access token secrets used for private repositories authentication. */ @Singleton public class KubernetesPersonalAccessTokenManager implements PersonalAccessTokenManager { public static final Map SECRET_LABELS = ImmutableMap.of( "app.kubernetes.io/part-of", "che.eclipse.org", "app.kubernetes.io/component", "scm-personal-access-token"); public static final LabelSelector KUBERNETES_PERSONAL_ACCESS_TOKEN_LABEL_SELECTOR = new LabelSelectorBuilder().withMatchLabels(SECRET_LABELS).build(); public static final String NAME_PATTERN = "personal-access-token-"; public static final String ANNOTATION_CHE_USERID = "che.eclipse.org/che-userid"; public static final String ANNOTATION_SCM_PROVIDER_NAME = "che.eclipse.org/scm-provider-name"; public static final String ANNOTATION_SCM_ORGANIZATION = "che.eclipse.org/scm-organization"; public static final String ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID = "che.eclipse.org/scm-personal-access-token-id"; public static final String ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME = "che.eclipse.org/scm-personal-access-token-name"; public static final String ANNOTATION_SCM_URL = "che.eclipse.org/scm-url"; public static final String TOKEN_DATA_FIELD = "token"; private final KubernetesNamespaceFactory namespaceFactory; private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private final ScmPersonalAccessTokenFetcher scmPersonalAccessTokenFetcher; private final GitCredentialManager gitCredentialManager; private static final Logger LOG = LoggerFactory.getLogger(KubernetesPersonalAccessTokenManager.class); @Inject public KubernetesPersonalAccessTokenManager( KubernetesNamespaceFactory namespaceFactory, CheServerKubernetesClientFactory cheServerKubernetesClientFactory, ScmPersonalAccessTokenFetcher scmPersonalAccessTokenFetcher, GitCredentialManager gitCredentialManager) { this.namespaceFactory = namespaceFactory; this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; this.scmPersonalAccessTokenFetcher = scmPersonalAccessTokenFetcher; this.gitCredentialManager = gitCredentialManager; } @Override public void store(PersonalAccessToken personalAccessToken) throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException { try { String namespace = getFirstNamespace(); ObjectMeta meta = new ObjectMetaBuilder() .withName(NameGenerator.generate(NAME_PATTERN, 5)) .withAnnotations( new ImmutableMap.Builder() .put(ANNOTATION_CHE_USERID, personalAccessToken.getCheUserId()) .put(ANNOTATION_SCM_URL, personalAccessToken.getScmProviderUrl()) .put(ANNOTATION_SCM_PROVIDER_NAME, personalAccessToken.getScmProviderName()) .put( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID, personalAccessToken.getScmTokenId()) .put( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, personalAccessToken.getScmTokenName()) .build()) .withLabels(SECRET_LABELS) .build(); Secret secret = new SecretBuilder() .withMetadata(meta) .withData( Map.of( TOKEN_DATA_FIELD, Base64.getEncoder() .encodeToString( personalAccessToken.getToken().getBytes(StandardCharsets.UTF_8)))) .build(); cheServerKubernetesClientFactory .create() .secrets() .inNamespace(namespace) .createOrReplace(secret); } catch (KubernetesClientException | InfrastructureException e) { throw new ScmConfigurationPersistenceException(e.getMessage(), e); } } @Override public void remove(String scmServerUrl) { try { for (KubernetesNamespaceMeta namespaceMeta : getKubernetesNamespaceMetas(null)) { List secrets = doGetPersonalAccessTokenSecrets(namespaceMeta); Optional optionalSecret = secrets.stream() .filter( secret -> isSecretMatchesSearchCriteria( EnvironmentContext.getCurrent().getSubject(), null, scmServerUrl, secret)) .findFirst(); if (optionalSecret.isPresent() && isSecretMatchesSearchCriteria( EnvironmentContext.getCurrent().getSubject(), null, scmServerUrl, optionalSecret.get())) { cheServerKubernetesClientFactory .create() .secrets() .inNamespace(namespaceMeta.getName()) .delete(optionalSecret.get()); } } } catch (InfrastructureException | ScmConfigurationPersistenceException e) { LOG.debug("Failed to remove personal access token", e); } } @Override public PersonalAccessToken fetchAndSave(Subject cheUser, String scmServerUrl) throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException, ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { PersonalAccessToken personalAccessToken = scmPersonalAccessTokenFetcher.fetchPersonalAccessToken(cheUser, scmServerUrl); store(personalAccessToken); return personalAccessToken; } @Override public PersonalAccessToken get(String scmServerUrl) throws ScmConfigurationPersistenceException, ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException, UnsatisfiedScmPreconditionException { Subject subject = EnvironmentContext.getCurrent().getSubject(); Optional tokenOptional = doGetPersonalAccessTokens(subject, null, scmServerUrl, null).stream().findFirst(); if (tokenOptional.isPresent()) { return tokenOptional.get(); } else { // try to authenticate for the given URL return fetchAndSave(subject, scmServerUrl); } } @Override public Optional get( Subject cheUser, String oAuthProviderName, @Nullable String scmServerUrl, @Nullable String namespaceName) throws ScmConfigurationPersistenceException, ScmCommunicationException { return doGetPersonalAccessTokens(cheUser, oAuthProviderName, scmServerUrl, namespaceName) .stream() .findFirst(); } private List doGetPersonalAccessTokens( Subject cheUser, @Nullable String oAuthProviderName, @Nullable String scmServerUrl, @Nullable String namespaceName) throws ScmConfigurationPersistenceException, ScmCommunicationException { List result = new ArrayList<>(); try { LOG.debug( "Fetching personal access token for user {} and OAuth provider {}", cheUser.getUserId(), oAuthProviderName); for (KubernetesNamespaceMeta namespaceMeta : getKubernetesNamespaceMetas(namespaceName)) { List secrets = doGetPersonalAccessTokenSecrets(namespaceMeta); for (Secret secret : secrets) { LOG.debug("Checking secret {}", secret.getMetadata().getName()); if (deleteSecretIfMisconfigured(secret)) { LOG.debug("Secret {} is misconfigured and was deleted", secret.getMetadata().getName()); continue; } if (isSecretMatchesSearchCriteria(cheUser, oAuthProviderName, scmServerUrl, secret)) { LOG.debug("Iterating over secret {}", secret.getMetadata().getName()); PersonalAccessTokenParams personalAccessTokenParams = this.secret2PersonalAccessTokenParams(secret); Optional scmUsername = scmPersonalAccessTokenFetcher.getScmUsername(personalAccessTokenParams); if (scmUsername.isPresent()) { LOG.debug( "Creating personal access token for user {} and OAuth provider {}", cheUser.getUserId(), oAuthProviderName); Map secretAnnotations = secret.getMetadata().getAnnotations(); PersonalAccessToken personalAccessToken = new PersonalAccessToken( personalAccessTokenParams.getScmProviderUrl(), getScmProviderName(personalAccessTokenParams), secretAnnotations.get(ANNOTATION_CHE_USERID), personalAccessTokenParams.getOrganization(), scmUsername.get(), personalAccessTokenParams.getScmTokenName(), personalAccessTokenParams.getScmTokenId(), personalAccessTokenParams.getToken()); result.add(personalAccessToken); continue; } // Removing token that is no longer valid. If several tokens exist the next one could // be valid. If no valid token can be found, the caller should react in the same way // as it reacts if no token exists. Usually, that means that process of new token // retrieval would be initiated. cheServerKubernetesClientFactory .create() .secrets() .inNamespace(namespaceMeta.getName()) .delete(secret); LOG.debug("Secret {} is misconfigured and was deleted", secret.getMetadata().getName()); } } } } catch (InfrastructureException | UnknownScmProviderException e) { LOG.debug("Failed to get personal access token", e); throw new ScmConfigurationPersistenceException(e.getMessage(), e); } return result; } /** * Returns the list of namespaces to search for the personal access token secrets. * * @param namespaceName the user's namespace name */ private List getKubernetesNamespaceMetas(@Nullable String namespaceName) throws InfrastructureException { if (namespaceName != null) { return namespaceFactory .fetchNamespace(namespaceName) .map(List::of) .orElseGet(Collections::emptyList); } else { return namespaceFactory.list(); } } private List doGetPersonalAccessTokenSecrets(KubernetesNamespaceMeta namespaceMeta) throws ScmConfigurationPersistenceException { try { List secrets = namespaceFactory .access(null, namespaceMeta.getName()) .secrets() .get(KUBERNETES_PERSONAL_ACCESS_TOKEN_LABEL_SELECTOR); // sort secrets to get the newest one first // Assign to new list to avoid UnsupportedOperationException (ImmutableList) secrets = secrets.stream() .sorted(Comparator.comparing(secret -> secret.getMetadata().getCreationTimestamp())) .collect(Collectors.toList()); Collections.reverse(secrets); return secrets; } catch (InfrastructureException e) { LOG.debug("Failed to get personal access token secret", e); throw new ScmConfigurationPersistenceException(e.getMessage(), e); } } /** * Returns the name of the SCM provider. If the name is not set, the name of the token is used. * This is used to support back compatibility with the old token secrets, which do not have the * 'che.eclipse.org/scm-provider-name' annotation. * * @param params the parameters of the personal access token * @return the name of the SCM provider */ private String getScmProviderName(PersonalAccessTokenParams params) { return isNullOrEmpty(params.getScmProviderName()) ? params.getScmTokenName() : params.getScmProviderName(); } private boolean deleteSecretIfMisconfigured(Secret secret) throws InfrastructureException { Map secretAnnotations = secret.getMetadata().getAnnotations(); LOG.debug("Secret annotations: {}", secretAnnotations); String configuredScmServerUrl = secretAnnotations.get(ANNOTATION_SCM_URL); LOG.debug("SCM server URL: {}", configuredScmServerUrl); String configuredCheUserId = secretAnnotations.get(ANNOTATION_CHE_USERID); LOG.debug("Che user ID: {}", configuredCheUserId); String configuredOAuthProviderName = secretAnnotations.get(ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME); LOG.debug("OAuth provider name: {}", configuredOAuthProviderName); // if any of the required annotations is missing, the secret is not valid if (isNullOrEmpty(configuredScmServerUrl) || isNullOrEmpty(configuredCheUserId) || isNullOrEmpty(configuredOAuthProviderName)) { LOG.debug("deleting secret {}", secret.getMetadata().getName()); cheServerKubernetesClientFactory .create() .secrets() .inNamespace(secret.getMetadata().getNamespace()) .delete(secret); return true; } return false; } private PersonalAccessTokenParams secret2PersonalAccessTokenParams(Secret secret) { Map secretAnnotations = secret.getMetadata().getAnnotations(); String token = new String(Base64.getDecoder().decode(secret.getData().get("token"))).trim(); String configuredOAuthTokenName = secretAnnotations.get(ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME); String configuredTokenId = secretAnnotations.get(ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID); String configuredScmOrganization = secretAnnotations.get(ANNOTATION_SCM_ORGANIZATION); String configuredScmServerUrl = secretAnnotations.get(ANNOTATION_SCM_URL); String configuredScmProviderName = secretAnnotations.get(ANNOTATION_SCM_PROVIDER_NAME); return new PersonalAccessTokenParams( trimEnd(configuredScmServerUrl, '/'), configuredScmProviderName, configuredOAuthTokenName, configuredTokenId, token, configuredScmOrganization); } private boolean isSecretMatchesSearchCriteria( Subject cheUser, @Nullable String oAuthProviderName, @Nullable String scmServerUrl, Secret secret) { Map secretAnnotations = secret.getMetadata().getAnnotations(); String configuredScmServerUrl = secretAnnotations.get(ANNOTATION_SCM_URL); String configuredCheUserId = secretAnnotations.get(ANNOTATION_CHE_USERID); String configuredOAuthProviderName = secretAnnotations.get(ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME); return (configuredCheUserId.equals(cheUser.getUserId())) && (oAuthProviderName == null || oAuthProviderName.equals(configuredOAuthProviderName)) && (scmServerUrl == null || trimEnd(configuredScmServerUrl, '/').equals(trimEnd(scmServerUrl, '/'))); } @Override public PersonalAccessToken getAndStore(String scmServerUrl) throws ScmCommunicationException, ScmConfigurationPersistenceException, UnknownScmProviderException, UnsatisfiedScmPreconditionException, ScmUnauthorizedException { PersonalAccessToken personalAccessToken = get(scmServerUrl); gitCredentialManager.createOrReplace(personalAccessToken); return personalAccessToken; } @Override public void forceRefreshPersonalAccessToken(String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException, UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException { Subject subject = EnvironmentContext.getCurrent().getSubject(); PersonalAccessToken personalAccessToken = scmPersonalAccessTokenFetcher.refreshPersonalAccessToken(subject, scmServerUrl); gitCredentialManager.createOrReplace(personalAccessToken); store(personalAccessToken); removePreviousTokenSecretsIfPresent(scmServerUrl); } private void removePreviousTokenSecretsIfPresent(String scmServerUrl) throws ScmConfigurationPersistenceException, UnsatisfiedScmPreconditionException { try { for (KubernetesNamespaceMeta namespaceMeta : namespaceFactory.list()) { List secrets = doGetPersonalAccessTokenSecrets(namespaceMeta); for (int i = 1; i < secrets.size(); i++) { Secret secret = secrets.get(i); if (secret.getMetadata().getAnnotations().get(ANNOTATION_SCM_URL).equals(scmServerUrl)) { cheServerKubernetesClientFactory .create() .secrets() .inNamespace(getFirstNamespace()) .delete(secret); } } } } catch (InfrastructureException e) { throw new ScmConfigurationPersistenceException(e.getMessage(), e); } } @Override public void storeGitCredentials(String scmServerUrl) throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException, ScmCommunicationException, ScmUnauthorizedException { Subject subject = EnvironmentContext.getCurrent().getSubject(); Optional tokenOptional = doGetPersonalAccessTokens(subject, null, scmServerUrl, null).stream().findFirst(); if (tokenOptional.isPresent()) { PersonalAccessToken personalAccessToken = tokenOptional.get(); gitCredentialManager.createOrReplace(personalAccessToken); } } private String getFirstNamespace() throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException { try { return namespaceFactory.list().stream() .map(KubernetesNamespaceMeta::getName) .findFirst() .orElseThrow( () -> new UnsatisfiedScmPreconditionException( "No user namespace found. Cannot read SCM credentials.")); } catch (InfrastructureException e) { throw new ScmConfigurationPersistenceException(e.getMessage(), e); } } } ================================================ FILE: infrastructures/infrastructure-factory/src/test/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesAuthorisationRequestManagerTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.kubernetes; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapList; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import java.util.Collections; import java.util.Map; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class KubernetesAuthorisationRequestManagerTest { @Mock private KubernetesNamespaceFactory namespaceFactory; @Mock private MixedOperation> configMapsMixedOperation; @Mock NonNamespaceOperation> nonNamespaceOperation; @Mock private Resource configMapResource; @Mock private ConfigMap configMap; @Mock private KubernetesClient kubeClient; @Mock private CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private KubernetesAuthorisationRequestManager authorisationRequestManager; @BeforeMethod protected void init() throws Exception { KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(Collections.singletonList(meta)); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.configMaps()).thenReturn(configMapsMixedOperation); when(configMapsMixedOperation.inNamespace(eq(meta.getName()))) .thenReturn(nonNamespaceOperation); when(nonNamespaceOperation.withName(eq(PREFERENCES_CONFIGMAP_NAME))) .thenReturn(configMapResource); when(configMapResource.get()).thenReturn(configMap); authorisationRequestManager = new KubernetesAuthorisationRequestManager( namespaceFactory, cheServerKubernetesClientFactory); } @Test public void shouldStoreAuthorisationRequestToEmptyConfigMap() { // given ArgumentCaptor> captor = ArgumentCaptor.forClass(Map.class); // when authorisationRequestManager.store("test-provider"); // then verify(configMap).setData(captor.capture()); Map value = captor.getValue(); assertEquals(value.get("skip-authorisation"), "[test-provider]"); } @Test public void shouldStoreAuthorisationRequestToNonEmptyConfigMap() { // given when(configMap.getData()).thenReturn(Map.of("skip-authorisation", "[existing-provider]")); ArgumentCaptor> captor = ArgumentCaptor.forClass(Map.class); // when authorisationRequestManager.store("test-provider"); // then verify(configMap).setData(captor.capture()); Map value = captor.getValue(); assertEquals(value.get("skip-authorisation"), "[test-provider, existing-provider]"); } @Test public void shouldNotStoreAuthorisationRequestIfAlreadyStored() { // given when(configMap.getData()).thenReturn(Map.of("skip-authorisation", "[test-provider]")); // when authorisationRequestManager.store("test-provider"); // then verify(configMap, never()).setData(anyMap()); } @Test public void shouldRemoveAuthorisationRequestFromNonEmptyConfigMap() { // given when(configMap.getData()) .thenReturn(Map.of("skip-authorisation", "[test-provider, existing-provider]")); ArgumentCaptor> captor = ArgumentCaptor.forClass(Map.class); // when authorisationRequestManager.remove("test-provider"); // then verify(configMap).setData(captor.capture()); Map value = captor.getValue(); assertEquals(value.get("skip-authorisation"), "[existing-provider]"); } @Test public void shouldRemoveAuthorisationRequestFromSingleValueConfigMap() { // given when(configMap.getData()).thenReturn(Map.of("skip-authorisation", "[test-provider]")); ArgumentCaptor> captor = ArgumentCaptor.forClass(Map.class); // when authorisationRequestManager.remove("test-provider"); // then verify(configMap).setData(captor.capture()); Map value = captor.getValue(); assertEquals(value.get("skip-authorisation"), "[]"); } @Test public void shouldNotRemoveAuthorisationRequestIfAlreadyRemoved() { // given when(configMap.getData()).thenReturn(Map.of("skip-authorisation", "[]")); // when authorisationRequestManager.remove("test-provider"); // then verify(configMap, never()).setData(anyMap()); } @Test public void shouldReturnFalseAuthorisationRequestStateFromEmptyConfigMap() { // when boolean stored = authorisationRequestManager.isStored("test-provider"); // then assertFalse(stored); } @Test public void shouldReturnFalseAuthorisationRequestStateFromNonEmptyConfigMap() { // given when(configMap.getData()).thenReturn(Map.of("skip-authorisation", "[existing-provider]")); // when boolean stored = authorisationRequestManager.isStored("test-provider"); // then assertFalse(stored); } @Test public void shouldReturnTrueAuthorisationRequestStateFromNonEmptyConfigMap() { // given when(configMap.getData()) .thenReturn(Map.of("skip-authorisation", "[existing-provider, test-provider]")); // when boolean stored = authorisationRequestManager.isStored("test-provider"); // then assertTrue(stored); } } ================================================ FILE: infrastructures/infrastructure-factory/src/test/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesGitCredentialManagerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.kubernetes; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.eclipse.che.api.factory.server.scm.kubernetes.KubernetesGitCredentialManager.ANNOTATION_CHE_USERID; import static org.eclipse.che.api.factory.server.scm.kubernetes.KubernetesGitCredentialManager.ANNOTATION_SCM_URL; import static org.eclipse.che.api.factory.server.scm.kubernetes.KubernetesGitCredentialManager.DEFAULT_SECRET_ANNOTATIONS; import static org.eclipse.che.api.factory.server.scm.kubernetes.KubernetesGitCredentialManager.NAME_PATTERN; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.api.model.SecretList; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class KubernetesGitCredentialManagerTest { @Mock private KubernetesNamespaceFactory namespaceFactory; @Mock private CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Mock private KubernetesClient kubeClient; @Mock private MixedOperation> secretsMixedOperation; @Mock NonNamespaceOperation> nonNamespaceOperation; @Mock private FilterWatchListDeletable> filterWatchDeletable; @Mock private SecretList secretList; KubernetesGitCredentialManager kubernetesGitCredentialManager; @BeforeMethod protected void init() { kubernetesGitCredentialManager = new KubernetesGitCredentialManager(namespaceFactory, cheServerKubernetesClientFactory); assertNotNull(this.kubernetesGitCredentialManager); } @Test public void testCreateAndSaveNewPATGitCredential() throws Exception { KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(Collections.singletonList(meta)); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); when(secretsMixedOperation.inNamespace(eq(meta.getName()))).thenReturn(nonNamespaceOperation); when(nonNamespaceOperation.withLabels(anyMap())).thenReturn(filterWatchDeletable); when(filterWatchDeletable.list()).thenReturn(secretList); when(secretList.getItems()).thenReturn(emptyList()); ArgumentCaptor captor = ArgumentCaptor.forClass(Secret.class); PersonalAccessToken token = new PersonalAccessToken( "https://bitbucket.com", "provider", "cheUser", "username", "token-name", "tid-23434", "token123"); // when kubernetesGitCredentialManager.createOrReplace(token); // then verify(nonNamespaceOperation).createOrReplace(captor.capture()); Secret createdSecret = captor.getValue(); assertNotNull(createdSecret); assertEquals( new String(Base64.getDecoder().decode(createdSecret.getData().get("credentials"))), "https://username:token123@bitbucket.com"); assertTrue(createdSecret.getMetadata().getName().startsWith(NAME_PATTERN)); assertFalse(createdSecret.getMetadata().getName().contains(token.getScmUserName())); } @Test public void shouldUseHardcodedUsernameIfScmOrganizationIsDefined() throws Exception { // given KubernetesNamespaceMeta namespaceMeta = new KubernetesNamespaceMetaImpl("test"); PersonalAccessToken token = new PersonalAccessToken( "https://bitbucket-server.com:5648", "cheUser", "cheOrganization", "username", "token-name", "tid-23434", "token123"); Map annotations = new HashMap<>(DEFAULT_SECRET_ANNOTATIONS); annotations.put(ANNOTATION_SCM_URL, token.getScmProviderUrl() + "/"); annotations.put(ANNOTATION_CHE_USERID, token.getCheUserId()); ObjectMeta objectMeta = new ObjectMetaBuilder() .withName(NameGenerator.generate(NAME_PATTERN, 5)) .withAnnotations(annotations) .build(); Secret existing = new SecretBuilder() .withMetadata(objectMeta) .withData(Map.of("credentials", "foo 123")) .build(); when(namespaceFactory.list()).thenReturn(Collections.singletonList(namespaceMeta)); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); when(secretsMixedOperation.inNamespace(eq(namespaceMeta.getName()))) .thenReturn(nonNamespaceOperation); when(nonNamespaceOperation.withLabels(anyMap())).thenReturn(filterWatchDeletable); when(filterWatchDeletable.list()).thenReturn(secretList); when(secretList.getItems()).thenReturn(singletonList(existing)); // when kubernetesGitCredentialManager.createOrReplace(token); // then ArgumentCaptor captor = ArgumentCaptor.forClass(Secret.class); verify(nonNamespaceOperation).createOrReplace(captor.capture()); Secret createdSecret = captor.getValue(); assertNotNull(createdSecret); assertEquals( new String(Base64.getDecoder().decode(createdSecret.getData().get("credentials"))), "https://username:token123@bitbucket-server.com:5648"); } @Test public void testCreateAndSaveNewOAuthGitCredential() throws Exception { KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(Collections.singletonList(meta)); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); when(secretsMixedOperation.inNamespace(eq(meta.getName()))).thenReturn(nonNamespaceOperation); when(nonNamespaceOperation.withLabels(anyMap())).thenReturn(filterWatchDeletable); when(filterWatchDeletable.list()).thenReturn(secretList); when(secretList.getItems()).thenReturn(emptyList()); ArgumentCaptor captor = ArgumentCaptor.forClass(Secret.class); PersonalAccessToken token = new PersonalAccessToken( "https://bitbucket.com", "provider", "cheUser", "username", "oauth2-token-name", "tid-23434", "token123"); // when kubernetesGitCredentialManager.createOrReplace(token); // then verify(nonNamespaceOperation).createOrReplace(captor.capture()); Secret createdSecret = captor.getValue(); assertNotNull(createdSecret); assertEquals( new String(Base64.getDecoder().decode(createdSecret.getData().get("credentials"))), "https://oauth2:token123@bitbucket.com"); assertTrue(createdSecret.getMetadata().getName().startsWith(NAME_PATTERN)); assertFalse(createdSecret.getMetadata().getName().contains(token.getScmUserName())); } @Test public void testCreateAndSaveNewBitbucketOAuthGitCredential() throws Exception { KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(Collections.singletonList(meta)); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); when(secretsMixedOperation.inNamespace(eq(meta.getName()))).thenReturn(nonNamespaceOperation); when(nonNamespaceOperation.withLabels(anyMap())).thenReturn(filterWatchDeletable); when(filterWatchDeletable.list()).thenReturn(secretList); when(secretList.getItems()).thenReturn(emptyList()); ArgumentCaptor captor = ArgumentCaptor.forClass(Secret.class); PersonalAccessToken token = new PersonalAccessToken( "https://bitbucket.com", "bitbucket", "cheUser", "username", "oauth2-token-name", "tid-23434", "token123"); // when kubernetesGitCredentialManager.createOrReplace(token); // then verify(nonNamespaceOperation).createOrReplace(captor.capture()); Secret createdSecret = captor.getValue(); assertNotNull(createdSecret); assertEquals( new String(Base64.getDecoder().decode(createdSecret.getData().get("credentials"))), "https://username:token123@bitbucket.com"); assertTrue(createdSecret.getMetadata().getName().startsWith(NAME_PATTERN)); assertFalse(createdSecret.getMetadata().getName().contains(token.getScmUserName())); } @Test public void testUpdateTokenInExistingCredential() throws Exception { KubernetesNamespaceMeta namespaceMeta = new KubernetesNamespaceMetaImpl("test"); PersonalAccessToken token = new PersonalAccessToken( "https://bitbucket.com:5648", "provider", "cheUser", "username", "token-name", "tid-23434", "token123"); Map annotations = new HashMap<>(DEFAULT_SECRET_ANNOTATIONS); annotations.put(ANNOTATION_SCM_URL, token.getScmProviderUrl() + "/"); annotations.put(ANNOTATION_CHE_USERID, token.getCheUserId()); ObjectMeta objectMeta = new ObjectMetaBuilder() .withName(NameGenerator.generate(NAME_PATTERN, 5)) .withAnnotations(annotations) .build(); Secret existing = new SecretBuilder() .withMetadata(objectMeta) .withData(Map.of("credentials", "foo 123")) .build(); when(namespaceFactory.list()).thenReturn(Collections.singletonList(namespaceMeta)); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); when(secretsMixedOperation.inNamespace(eq(namespaceMeta.getName()))) .thenReturn(nonNamespaceOperation); when(nonNamespaceOperation.withLabels(anyMap())).thenReturn(filterWatchDeletable); when(filterWatchDeletable.list()).thenReturn(secretList); when(secretList.getItems()).thenReturn(singletonList(existing)); // when kubernetesGitCredentialManager.createOrReplace(token); // then ArgumentCaptor captor = ArgumentCaptor.forClass(Secret.class); verify(nonNamespaceOperation).createOrReplace(captor.capture()); Secret createdSecret = captor.getValue(); assertNotNull(createdSecret); assertEquals( new String(Base64.getDecoder().decode(createdSecret.getData().get("credentials"))), "https://username:token123@bitbucket.com:5648"); assertEquals(createdSecret.getMetadata().getName(), objectMeta.getName()); } } ================================================ FILE: infrastructures/infrastructure-factory/src/test/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManagerTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.kubernetes; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.singletonList; import static org.eclipse.che.api.factory.server.scm.kubernetes.KubernetesPersonalAccessTokenManager.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.LabelSelector; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.api.model.SecretList; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.factory.server.scm.GitCredentialManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.ScmPersonalAccessTokenFetcher; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.stubbing.Answer; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class KubernetesPersonalAccessTokenManagerTest { @Mock private KubernetesNamespaceFactory namespaceFactory; @Mock private CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Mock private ScmPersonalAccessTokenFetcher scmPersonalAccessTokenFetcher; @Mock private KubernetesClient kubeClient; @Mock private GitCredentialManager gitCredentialManager; @Mock private MixedOperation> secretsMixedOperation; @Mock NonNamespaceOperation> nonNamespaceOperation; KubernetesPersonalAccessTokenManager personalAccessTokenManager; @BeforeMethod protected void init() { personalAccessTokenManager = new KubernetesPersonalAccessTokenManager( namespaceFactory, cheServerKubernetesClientFactory, scmPersonalAccessTokenFetcher, gitCredentialManager); assertNotNull(this.personalAccessTokenManager); } @Test public void shouldTrimBlankCharsInToken() throws Exception { KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); KubernetesNamespace kubernetesnamespace = Mockito.mock(KubernetesNamespace.class); KubernetesSecrets secrets = Mockito.mock(KubernetesSecrets.class); when(namespaceFactory.access(eq(null), eq(meta.getName()))).thenReturn(kubernetesnamespace); when(kubernetesnamespace.secrets()).thenReturn(secrets); when(scmPersonalAccessTokenFetcher.getScmUsername(any(PersonalAccessTokenParams.class))) .thenReturn(Optional.of("user")); Map data = Map.of("token", Base64.getEncoder().encodeToString(" token_value \n".getBytes(UTF_8))); ObjectMeta meta1 = new ObjectMetaBuilder() .withCreationTimestamp("2021-07-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user", ANNOTATION_SCM_URL, "http://host1")) .build(); Secret secret = new SecretBuilder().withMetadata(meta1).withData(data).build(); when(secrets.get(any(LabelSelector.class))).thenReturn(List.of(secret)); // when PersonalAccessToken token = personalAccessTokenManager .get( new SubjectImpl("user", Collections.emptyList(), "user", "t1", false), null, "http://host1", null) .get(); // then assertEquals(token.getToken(), "token_value"); } @Test public void testSavingOfPersonalAccessToken() throws Exception { KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); when(secretsMixedOperation.inNamespace(eq(meta.getName()))).thenReturn(nonNamespaceOperation); ArgumentCaptor captor = ArgumentCaptor.forClass(Secret.class); PersonalAccessToken token = new PersonalAccessToken( "https://bitbucket.com", "provider", "cheUser", "username", "token-name", "tid-24", "token123"); // when personalAccessTokenManager.store(token); // then verify(nonNamespaceOperation).createOrReplace(captor.capture()); Secret createdSecret = captor.getValue(); assertNotNull(createdSecret); assertTrue( createdSecret .getMetadata() .getName() .startsWith(KubernetesPersonalAccessTokenManager.NAME_PATTERN)); assertEquals( createdSecret.getData().get("token"), Base64.getEncoder().encodeToString(token.getToken().getBytes())); } @Test public void testGetTokenFromNamespace() throws Exception { KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); KubernetesNamespace kubernetesnamespace = Mockito.mock(KubernetesNamespace.class); KubernetesSecrets secrets = Mockito.mock(KubernetesSecrets.class); when(namespaceFactory.access(eq(null), eq(meta.getName()))).thenReturn(kubernetesnamespace); when(kubernetesnamespace.secrets()).thenReturn(secrets); when(scmPersonalAccessTokenFetcher.getScmUsername(any(PersonalAccessTokenParams.class))) .thenReturn(Optional.of("user")); Map data1 = Map.of("token", Base64.getEncoder().encodeToString("token1".getBytes(UTF_8))); Map data2 = Map.of("token", Base64.getEncoder().encodeToString("token2".getBytes(UTF_8))); Map data3 = Map.of("token", Base64.getEncoder().encodeToString("token3".getBytes(UTF_8))); ObjectMeta meta1 = new ObjectMetaBuilder() .withCreationTimestamp("2021-07-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1", ANNOTATION_SCM_URL, "http://host1")) .build(); ObjectMeta meta2 = new ObjectMetaBuilder() .withCreationTimestamp("2021-07-02T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1", ANNOTATION_SCM_URL, "http://host2")) .build(); ObjectMeta meta3 = new ObjectMetaBuilder() .withCreationTimestamp("2021-07-03T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user2", ANNOTATION_SCM_URL, "http://host3")) .build(); Secret secret1 = new SecretBuilder().withMetadata(meta1).withData(data1).build(); Secret secret2 = new SecretBuilder().withMetadata(meta2).withData(data2).build(); Secret secret3 = new SecretBuilder().withMetadata(meta3).withData(data3).build(); when(secrets.get(any(LabelSelector.class))) .thenReturn(Arrays.asList(secret1, secret2, secret3)); // when PersonalAccessToken token = personalAccessTokenManager .get( new SubjectImpl("user", Collections.emptyList(), "user1", "t1", false), null, "http://host1", null) .get(); // then assertEquals(token.getCheUserId(), "user1"); assertEquals(token.getScmProviderUrl(), "http://host1"); assertEquals(token.getToken(), "token1"); } @Test public void shouldGetTokenFromASecretWithSCMUsername() throws Exception { KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); KubernetesNamespace kubernetesnamespace = Mockito.mock(KubernetesNamespace.class); KubernetesSecrets secrets = Mockito.mock(KubernetesSecrets.class); when(namespaceFactory.access(eq(null), eq(meta.getName()))).thenReturn(kubernetesnamespace); when(kubernetesnamespace.secrets()).thenReturn(secrets); when(scmPersonalAccessTokenFetcher.getScmUsername(any(PersonalAccessTokenParams.class))) .thenReturn(Optional.of("user")); Map data = Map.of("token", Base64.getEncoder().encodeToString("token1".getBytes(UTF_8))); ObjectMeta metaData = new ObjectMetaBuilder() .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1", ANNOTATION_SCM_URL, "http://host1", "che.eclipse.org/scm-username", "scm-username")) .build(); Secret secret = new SecretBuilder().withMetadata(metaData).withData(data).build(); when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret)); // when Optional tokenOptional = personalAccessTokenManager.get( new SubjectImpl("user", Collections.emptyList(), "user1", "t1", false), null, "http://host1", null); // then assertTrue(tokenOptional.isPresent()); assertEquals(tokenOptional.get().getCheUserId(), "user1"); assertEquals(tokenOptional.get().getScmProviderUrl(), "http://host1"); assertEquals(tokenOptional.get().getToken(), "token1"); } @Test public void shouldGetTokenFromASecretWithoutSCMUsername() throws Exception { KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); KubernetesNamespace kubernetesnamespace = Mockito.mock(KubernetesNamespace.class); KubernetesSecrets secrets = Mockito.mock(KubernetesSecrets.class); when(namespaceFactory.access(eq(null), eq(meta.getName()))).thenReturn(kubernetesnamespace); when(kubernetesnamespace.secrets()).thenReturn(secrets); when(scmPersonalAccessTokenFetcher.getScmUsername(any(PersonalAccessTokenParams.class))) .thenReturn(Optional.of("user")); Map data = Map.of("token", Base64.getEncoder().encodeToString("token1".getBytes(UTF_8))); ObjectMeta metaData = new ObjectMetaBuilder() .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1", ANNOTATION_SCM_URL, "http://host1")) .build(); Secret secret = new SecretBuilder().withMetadata(metaData).withData(data).build(); when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret)); // when Optional tokenOptional = personalAccessTokenManager.get( new SubjectImpl("user", Collections.emptyList(), "user1", "t1", false), null, "http://host1", null); // then assertTrue(tokenOptional.isPresent()); assertEquals(tokenOptional.get().getCheUserId(), "user1"); assertEquals(tokenOptional.get().getScmProviderUrl(), "http://host1"); assertEquals(tokenOptional.get().getToken(), "token1"); } @Test public void testGetTokenFromNamespaceWithTrailingSlashMismatch() throws Exception { KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); KubernetesNamespace kubernetesnamespace = Mockito.mock(KubernetesNamespace.class); KubernetesSecrets secrets = Mockito.mock(KubernetesSecrets.class); when(namespaceFactory.access(eq(null), eq(meta.getName()))).thenReturn(kubernetesnamespace); when(kubernetesnamespace.secrets()).thenReturn(secrets); when(scmPersonalAccessTokenFetcher.getScmUsername(any(PersonalAccessTokenParams.class))) .thenReturn(Optional.of("user")); Map data1 = Map.of("token", Base64.getEncoder().encodeToString("token1".getBytes(UTF_8))); Map data2 = Map.of("token", Base64.getEncoder().encodeToString("token2".getBytes(UTF_8))); ObjectMeta meta1 = new ObjectMetaBuilder() .withCreationTimestamp("2021-07-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1", ANNOTATION_SCM_URL, "http://host1.com/")) .build(); ObjectMeta meta2 = new ObjectMetaBuilder() .withCreationTimestamp("2021-08-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1", ANNOTATION_SCM_URL, "http://host2.com")) .build(); Secret secret1 = new SecretBuilder().withMetadata(meta1).withData(data1).build(); Secret secret2 = new SecretBuilder().withMetadata(meta2).withData(data2).build(); when(secrets.get(any(LabelSelector.class))).thenReturn(Arrays.asList(secret1, secret2)); // when PersonalAccessToken token1 = personalAccessTokenManager .get( new SubjectImpl("user", Collections.emptyList(), "user1", "t1", false), null, "http://host1.com", null) .get(); PersonalAccessToken token2 = personalAccessTokenManager .get( new SubjectImpl("user", Collections.emptyList(), "user1", "t1", false), null, "http://host2.com/", null) .get(); // then assertNotNull(token1); assertNotNull(token2); } @Test public void shouldDeleteMisconfiguredTokensOnGet() throws Exception { // given KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); KubernetesNamespace kubernetesnamespace = Mockito.mock(KubernetesNamespace.class); KubernetesSecrets secrets = Mockito.mock(KubernetesSecrets.class); when(namespaceFactory.access(eq(null), eq(meta.getName()))).thenReturn(kubernetesnamespace); when(kubernetesnamespace.secrets()).thenReturn(secrets); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); when(secretsMixedOperation.inNamespace(eq(meta.getName()))).thenReturn(nonNamespaceOperation); Map data1 = Map.of("token", Base64.getEncoder().encodeToString("token1".getBytes(UTF_8))); ObjectMeta meta1 = new ObjectMetaBuilder() .withNamespace("test") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1")) .build(); Secret secret1 = new SecretBuilder().withMetadata(meta1).withData(data1).build(); when(secrets.get(any(LabelSelector.class))).thenReturn(Arrays.asList(secret1)); // when Optional token = personalAccessTokenManager.get( new SubjectImpl("user", Collections.emptyList(), "user1", "t1", false), null, "http://host1", null); // then assertFalse(token.isPresent()); verify(nonNamespaceOperation, times(1)).delete(eq(secret1)); } @Test public void shouldDeleteInvalidTokensOnGet() throws Exception { // given KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); KubernetesNamespace kubernetesnamespace = Mockito.mock(KubernetesNamespace.class); KubernetesSecrets secrets = Mockito.mock(KubernetesSecrets.class); when(namespaceFactory.access(eq(null), eq(meta.getName()))).thenReturn(kubernetesnamespace); when(kubernetesnamespace.secrets()).thenReturn(secrets); when(scmPersonalAccessTokenFetcher.getScmUsername(any(PersonalAccessTokenParams.class))) .thenReturn(Optional.empty()); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); when(secretsMixedOperation.inNamespace(eq(meta.getName()))).thenReturn(nonNamespaceOperation); Map data1 = Map.of("token", Base64.getEncoder().encodeToString("token1".getBytes(UTF_8))); ObjectMeta meta1 = new ObjectMetaBuilder() .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1", ANNOTATION_SCM_URL, "http://host1")) .build(); Secret secret1 = new SecretBuilder().withMetadata(meta1).withData(data1).build(); when(secrets.get(any(LabelSelector.class))).thenReturn(Arrays.asList(secret1)); // when Optional token = personalAccessTokenManager.get( new SubjectImpl("user", Collections.emptyList(), "user1", "t1", false), null, "http://host1", null); // then assertFalse(token.isPresent()); verify(nonNamespaceOperation, times(1)).delete(eq(secret1)); } @Test(dependsOnMethods = "shouldDeleteInvalidTokensOnGet") public void shouldReturnFirstValidTokenAndDeleteTheInvalidOne() throws Exception { // given KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); KubernetesNamespace kubernetesnamespace = Mockito.mock(KubernetesNamespace.class); KubernetesSecrets secrets = Mockito.mock(KubernetesSecrets.class); when(namespaceFactory.access(eq(null), eq(meta.getName()))).thenReturn(kubernetesnamespace); when(kubernetesnamespace.secrets()).thenReturn(secrets); when(scmPersonalAccessTokenFetcher.getScmUsername(any(PersonalAccessTokenParams.class))) .thenAnswer( (Answer>) invocation -> { PersonalAccessTokenParams params = invocation.getArgument(0); return "id2".equals(params.getScmTokenId()) ? Optional.of("user") : Optional.empty(); }); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); when(secretsMixedOperation.inNamespace(eq(meta.getName()))).thenReturn(nonNamespaceOperation); Map data1 = Map.of("token", Base64.getEncoder().encodeToString("token1".getBytes(UTF_8))); Map data2 = Map.of("token", Base64.getEncoder().encodeToString("token2".getBytes(UTF_8))); ObjectMeta meta1 = new ObjectMetaBuilder() .withCreationTimestamp("2021-07-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1", ANNOTATION_SCM_URL, "http://host1", ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID, "id1")) .build(); ObjectMeta meta2 = new ObjectMetaBuilder() .withCreationTimestamp("2021-07-02T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1", ANNOTATION_SCM_URL, "http://host1", ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID, "id2")) .build(); Secret secret1 = new SecretBuilder().withMetadata(meta1).withData(data1).build(); Secret secret2 = new SecretBuilder().withMetadata(meta2).withData(data2).build(); when(secrets.get(any(LabelSelector.class))).thenReturn(Arrays.asList(secret1, secret2)); // when Optional token = personalAccessTokenManager.get( new SubjectImpl("user", Collections.emptyList(), "user1", "t1", false), null, "http://host1", null); // then assertTrue(token.isPresent()); assertEquals(token.get().getScmTokenId(), "id2"); verify(nonNamespaceOperation, times(1)).delete(eq(secret1)); } @Test public void shouldReturnFirstValidTokenAndDeleteTheOlderOne() throws Exception { // given KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); KubernetesNamespace kubernetesnamespace = Mockito.mock(KubernetesNamespace.class); KubernetesSecrets secrets = Mockito.mock(KubernetesSecrets.class); when(kubernetesnamespace.secrets()).thenReturn(secrets); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); Map data1 = Map.of("token", Base64.getEncoder().encodeToString("token1".getBytes(UTF_8))); Map data2 = Map.of("token", Base64.getEncoder().encodeToString("token2".getBytes(UTF_8))); ObjectMeta meta1 = new ObjectMetaBuilder() .withCreationTimestamp("2021-07-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1", ANNOTATION_SCM_URL, "http://host1", ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID, "id1")) .build(); ObjectMeta meta2 = new ObjectMetaBuilder() .withCreationTimestamp("2021-07-02T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user1", ANNOTATION_SCM_URL, "http://host1", ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID, "id2")) .build(); Secret secret1 = new SecretBuilder().withMetadata(meta1).withData(data1).build(); Secret secret2 = new SecretBuilder().withMetadata(meta2).withData(data2).build(); when(secrets.get(any(LabelSelector.class))).thenReturn(Arrays.asList(secret1, secret2)); when(namespaceFactory.access(eq(null), eq(meta.getName()))).thenReturn(kubernetesnamespace); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); when(secretsMixedOperation.inNamespace(eq(meta.getName()))).thenReturn(nonNamespaceOperation); when(kubernetesnamespace.secrets()).thenReturn(secrets); PersonalAccessToken token = new PersonalAccessToken( "http://host1", "provider", "cheUser", "username", "token-name", "tid-24", "token123"); when(scmPersonalAccessTokenFetcher.refreshPersonalAccessToken( any(org.eclipse.che.commons.subject.Subject.class), eq("http://host1"))) .thenReturn(token); // when personalAccessTokenManager.forceRefreshPersonalAccessToken("http://host1"); // then verify(nonNamespaceOperation, times(1)).delete(eq(secret1)); } @Test public void shouldRemoveToken() throws Exception { // given Subject subject = mock(Subject.class); when(subject.getUserId()).thenReturn("user"); EnvironmentContext context = spy(EnvironmentContext.getCurrent()); EnvironmentContext.setCurrent(context); doReturn(subject).when(context).getSubject(); KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); KubernetesNamespace kubernetesnamespace = Mockito.mock(KubernetesNamespace.class); KubernetesSecrets secrets = Mockito.mock(KubernetesSecrets.class); when(kubernetesnamespace.secrets()).thenReturn(secrets); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); Map data1 = Map.of("token", Base64.getEncoder().encodeToString("token1".getBytes(UTF_8))); Map data2 = Map.of("token", Base64.getEncoder().encodeToString("token2".getBytes(UTF_8))); ObjectMeta meta1 = new ObjectMetaBuilder() .withCreationTimestamp("2021-07-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user", ANNOTATION_SCM_URL, "http://host1", ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID, "id1")) .build(); ObjectMeta meta2 = new ObjectMetaBuilder() .withCreationTimestamp("2021-07-02T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, "github", ANNOTATION_CHE_USERID, "user", ANNOTATION_SCM_URL, "http://host2", ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID, "id2")) .build(); Secret secret1 = new SecretBuilder().withMetadata(meta1).withData(data1).build(); Secret secret2 = new SecretBuilder().withMetadata(meta2).withData(data2).build(); when(secrets.get(any(LabelSelector.class))).thenReturn(Arrays.asList(secret1, secret2)); when(namespaceFactory.access(eq(null), eq(meta.getName()))).thenReturn(kubernetesnamespace); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); when(secretsMixedOperation.inNamespace(eq(meta.getName()))).thenReturn(nonNamespaceOperation); when(kubernetesnamespace.secrets()).thenReturn(secrets); // when personalAccessTokenManager.remove("http://host1"); // then verify(nonNamespaceOperation, times(1)).delete(eq(secret1)); } } ================================================ FILE: infrastructures/infrastructure-factory/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex ================================================ FILE: infrastructures/infrastructure-metrics/pom.xml ================================================ 4.0.0 che-infrastructures-parent org.eclipse.che.infrastructure 7.118.0-SNAPSHOT ../pom.xml infrastructure-metrics Infrastructure :: Metrics com.google.inject guice io.micrometer micrometer-core jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-core org.eclipse.che.infrastructure infrastructure-kubernetes org.slf4j slf4j-api org.testng testng test ================================================ FILE: infrastructures/infrastructure-metrics/src/main/java/org/eclipse/che/workspace/infrastructure/metrics/CurrentLogwatchersMeterBinder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.metrics; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import java.util.concurrent.atomic.AtomicLong; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.RuntimeLogEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.event.WatchLogStartedEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.event.WatchLogStoppedEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Counts sent messages and bytes to runtime log by listening to {@link RuntimeLogEvent}s. */ @Singleton public class CurrentLogwatchersMeterBinder implements MeterBinder { private static final Logger LOG = LoggerFactory.getLogger(CurrentLogwatchersMeterBinder.class); private final EventService eventService; private final AtomicLong currentWatchersCounter; @Inject CurrentLogwatchersMeterBinder(EventService eventService) { this.eventService = eventService; this.currentWatchersCounter = new AtomicLong(); } @Override public void bindTo(MeterRegistry registry) { Gauge.builder("log_watchers", this::current).register(registry); eventService.subscribe( (e) -> currentWatchersCounter.incrementAndGet(), WatchLogStartedEvent.class); eventService.subscribe( (e) -> { long counter = currentWatchersCounter.decrementAndGet(); if (counter < 0) { LOG.warn( "WatchLog current instances counter decremented below 0. Counter set explicitly to 0. This should not happen. Please report a bug if you see this message in the log."); currentWatchersCounter.set(0); } }, WatchLogStoppedEvent.class); } private long current() { return currentWatchersCounter.get(); } } ================================================ FILE: infrastructures/infrastructure-metrics/src/main/java/org/eclipse/che/workspace/infrastructure/metrics/InfrastructureMetricsModule.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.metrics; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import io.micrometer.core.instrument.binder.MeterBinder; /** * A Guice module to bind infrastructure specific metric binders to a single multi-binder. The set * of all metric binders is used to produce the Prometheus metrics on request. */ public class InfrastructureMetricsModule extends AbstractModule { @Override protected void configure() { Multibinder meterMultibinder = Multibinder.newSetBinder(binder(), MeterBinder.class); meterMultibinder.addBinding().to(CurrentLogwatchersMeterBinder.class); } } ================================================ FILE: infrastructures/infrastructure-metrics/src/test/java/org/eclipse/che/workspace/infrastructure/metrics/CurrentLogwatchersMeterBinderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.metrics; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.event.WatchLogStartedEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.event.WatchLogStoppedEvent; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class CurrentLogwatchersMeterBinderTest { private final String metricsKey = "log_watchers"; private MeterRegistry registry; private EventService eventService; private CurrentLogwatchersMeterBinder binder; @BeforeMethod public void setUp() { registry = new SimpleMeterRegistry(); eventService = new EventService(); binder = new CurrentLogwatchersMeterBinder(eventService); binder.bindTo(registry); } @Test public void testCurrentLogwatcherGaugeReactsOnEvents() { Assert.assertEquals(registry.get(metricsKey).gauge().value(), 0.0); eventService.publish(new WatchLogStartedEvent("container")); Assert.assertEquals(registry.get(metricsKey).gauge().value(), 1.0); eventService.publish(new WatchLogStartedEvent("container")); Assert.assertEquals(registry.get(metricsKey).gauge().value(), 2.0); eventService.publish(new WatchLogStoppedEvent("container")); Assert.assertEquals(registry.get(metricsKey).gauge().value(), 1.0); eventService.publish(new WatchLogStartedEvent("container")); Assert.assertEquals(registry.get(metricsKey).gauge().value(), 2.0); eventService.publish(new WatchLogStoppedEvent("container")); eventService.publish(new WatchLogStoppedEvent("container")); Assert.assertEquals(registry.get(metricsKey).gauge().value(), 0.0); } @Test public void testLogwatcherGaugeCantGoBelowZero() { Assert.assertEquals(registry.get(metricsKey).gauge().value(), 0.0); eventService.publish(new WatchLogStoppedEvent("container")); Assert.assertEquals(registry.get(metricsKey).gauge().value(), 0.0); eventService.publish(new WatchLogStartedEvent("container")); eventService.publish(new WatchLogStoppedEvent("container")); eventService.publish(new WatchLogStoppedEvent("container")); Assert.assertEquals(registry.get(metricsKey).gauge().value(), 0.0); } } ================================================ FILE: infrastructures/infrastructure-permission/pom.xml ================================================ 4.0.0 che-infrastructures-parent org.eclipse.che.infrastructure 7.118.0-SNAPSHOT infrastructure-permission Infrastructure :: Kubernetes Permissions jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.infrastructure infrastructure-kubernetes org.eclipse.che.multiuser che-multiuser-api-permission ch.qos.logback logback-classic test org.eclipse.che.core che-core-api-dto test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test ================================================ FILE: infrastructures/infrastructure-permission/src/main/java/org/eclipse/che/multiuser/permission/workspace/infra/kubernetes/BrokerServicePermissionFilter.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.permission.workspace.infra.kubernetes; import static org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events.BrokerService.BROKER_RESULT_METHOD; import static org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events.BrokerService.BROKER_STATUS_CHANGED_METHOD; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.api.workspace.shared.dto.event.BrokerStatusChangedEvent; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.multiuser.api.permission.server.jsonrpc.JsonRpcPermissionsFilterAdapter; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events.BrokerService; /** * Add permissions checking before {@link BrokerService} methods invocation. * * @author Sergii Leshchenko */ @Singleton public class BrokerServicePermissionFilter extends JsonRpcPermissionsFilterAdapter { @Inject public void register(RequestHandlerManager requestHandlerManager) { requestHandlerManager.registerMethodInvokerFilter( this, BROKER_STATUS_CHANGED_METHOD, BROKER_RESULT_METHOD); } @Override public void doAccept(String method, Object... params) throws ForbiddenException { String workspaceId; switch (method) { case BROKER_STATUS_CHANGED_METHOD: case BROKER_RESULT_METHOD: RuntimeIdentityDto runtimeId = ((BrokerStatusChangedEvent) params[0]).getRuntimeId(); if (runtimeId == null || runtimeId.getWorkspaceId() == null) { throw new ForbiddenException("Workspace id must be specified"); } workspaceId = runtimeId.getWorkspaceId(); break; default: throw new ForbiddenException("Unknown method is configured to be filtered."); } Subject currentSubject = EnvironmentContext.getCurrent().getSubject(); if (!currentSubject.hasPermission("workspace", workspaceId, "run")) { throw new ForbiddenException( "User doesn't have the required permissions to the specified workspace"); } } } ================================================ FILE: infrastructures/infrastructure-permission/src/test/java/org/eclipse/che/multiuser/permission/workspace/infra/kubernetes/BrokerServicePermissionFilterTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.permission.workspace.infra.kubernetes; import static org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events.BrokerService.BROKER_RESULT_METHOD; import static org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events.BrokerService.BROKER_STATUS_CHANGED_METHOD; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.api.workspace.shared.dto.event.BrokerStatusChangedEvent; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.dto.server.DtoFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link BrokerServicePermissionFilter} * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class BrokerServicePermissionFilterTest { @Mock private RequestHandlerManager requestHandlerManager; @Mock private Subject subject; private BrokerServicePermissionFilter permissionFilter; @BeforeMethod public void setUp() { EnvironmentContext.getCurrent().setSubject(subject); permissionFilter = new BrokerServicePermissionFilter(); } @AfterMethod public void tearDown() { EnvironmentContext.reset(); } @Test public void shouldRegisterItself() { // when permissionFilter.register(requestHandlerManager); // then requestHandlerManager.registerMethodInvokerFilter( permissionFilter, BROKER_STATUS_CHANGED_METHOD, BROKER_RESULT_METHOD); } @Test( dataProvider = "coveredMethods", expectedExceptions = ForbiddenException.class, expectedExceptionsMessageRegExp = "User doesn't have the required permissions to the specified workspace") public void shouldThrowExceptionIfUserDoesNotHaveRunPermission(String method) throws Exception { // given when(subject.hasPermission(eq("workspace"), eq("ws123"), eq("run"))).thenReturn(false); // when permissionFilter.doAccept( method, DtoFactory.newDto(BrokerStatusChangedEvent.class) .withRuntimeId(DtoFactory.newDto(RuntimeIdentityDto.class).withWorkspaceId("ws123"))); } @Test(dataProvider = "coveredMethods") public void shouldDoNothingIfUserHasRunPermissions(String method) throws Exception { // given when(subject.hasPermission("workspace", "ws123", "run")).thenReturn(true); // when permissionFilter.doAccept( method, DtoFactory.newDto(BrokerStatusChangedEvent.class) .withRuntimeId(DtoFactory.newDto(RuntimeIdentityDto.class).withWorkspaceId("ws123"))); } @Test( expectedExceptions = ForbiddenException.class, expectedExceptionsMessageRegExp = "Unknown method is configured to be filtered\\.") public void shouldThrowExceptionIfUnknownMethodIsInvoking() throws Exception { // when permissionFilter.doAccept( "unknown", DtoFactory.newDto(BrokerStatusChangedEvent.class) .withRuntimeId(DtoFactory.newDto(RuntimeIdentityDto.class).withWorkspaceId("ws123"))); } @DataProvider public Object[][] coveredMethods() { return new Object[][] {{BROKER_STATUS_CHANGED_METHOD}, {BROKER_RESULT_METHOD}}; } } ================================================ FILE: infrastructures/infrastructure-permission/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex target/log/test.log %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: infrastructures/kubernetes/pom.xml ================================================ 4.0.0 che-infrastructures-parent org.eclipse.che.infrastructure 7.118.0-SNAPSHOT infrastructure-kubernetes Infrastructure :: Kubernetes ${project.build.directory}/generated-sources/dto/ com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.core jackson-core com.fasterxml.jackson.core jackson-databind com.fasterxml.jackson.dataformat jackson-dataformat-yaml com.google.code.gson gson com.google.guava guava com.google.inject guice com.google.inject.extensions guice-assistedinject com.google.inject.extensions guice-persist io.fabric8 kubernetes-client-api io.fabric8 kubernetes-model-apps io.fabric8 kubernetes-model-batch io.fabric8 kubernetes-model-core io.fabric8 kubernetes-model-networking io.fabric8 kubernetes-model-rbac io.fabric8 openshift-model io.opentracing opentracing-api io.swagger.core.v3 swagger-annotations-jakarta jakarta.annotation jakarta.annotation-api jakarta.inject jakarta.inject-api jakarta.validation jakarta.validation-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-factory org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-api-ssh org.eclipse.che.core che-core-api-ssh-shared org.eclipse.che.core che-core-api-system org.eclipse.che.core che-core-api-user org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-inject org.eclipse.che.core che-core-commons-lang org.eclipse.che.core che-core-commons-observability org.eclipse.che.core che-core-commons-tracing org.eclipse.che.multiuser che-multiuser-machine-authentication org.eclipse.persistence jakarta.persistence org.slf4j slf4j-api ch.qos.logback logback-classic test ch.qos.logback logback-core test io.fabric8 kubernetes-client test netty-handler io.netty netty-handler-proxy io.netty netty-codec-http io.netty netty-resolver io.netty netty-buffer io.netty netty-transport io.netty netty-codec io.netty netty-common io.netty io.fabric8 kubernetes-server-mock test io.fabric8 mockwebserver test io.netty netty-handler test io.netty netty-handler-proxy test io.rest-assured rest-assured test org.eclipse.che.core che-core-commons-test test org.eclipse.che.core che-core-sql-schema test org.eclipse.persistence org.eclipse.persistence.core test org.eclipse.persistence org.eclipse.persistence.jpa test org.everrest everrest-assured test org.everrest everrest-core test org.flywaydb flyway-core test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} process-sources generate org.eclipse.che.infrastructure infrastructure-kubernetes ${project.version} org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto ${dto-generator-out-directory} org.eclipse.che.workspace.infrastructure.kubernetes.api.server.dto.DtoServerImpls server maven-compiler-plugin pre-compile generate-sources compile org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} org.apache.maven.plugins maven-jar-plugin test-jar **/tck/*.* com.mycila license-maven-plugin src/test/resources/jwtproxy-confg.yaml ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/Annotations.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import com.google.common.base.Strings; import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; /** * Helps to convert servers related entities (like {@link ServerConfig} and machine name) to * Kubernetes annotations and vise-versa. * * @author Sergii Leshchenko */ public class Annotations { public static final String ANNOTATION_PREFIX = "org.eclipse.che."; public static final String SERVER_PORT_ANNOTATION_FMT = ANNOTATION_PREFIX + "server.%s.port"; public static final String SERVER_PROTOCOL_ANNOTATION_FMT = ANNOTATION_PREFIX + "server.%s.protocol"; public static final String SERVER_PATH_ANNOTATION_FMT = ANNOTATION_PREFIX + "server.%s.path"; public static final String SERVER_ATTR_ANNOTATION_FMT = ANNOTATION_PREFIX + "server.%s.attributes"; public static final String MACHINE_NAME_ANNOTATION = ANNOTATION_PREFIX + "machine.name"; /** * Object annotated with this set to `true` should be created in Che installation namespace. It's * used only internally so it may be removed before actually creating k8s object, so it's not * exposed. */ public static final String CREATE_IN_CHE_INSTALLATION_NAMESPACE = ANNOTATION_PREFIX + "installation.namespace"; /** Pattern that matches server annotations e.g. "org.eclipse.che.server.exec-agent.port". */ private static final Pattern SERVER_ANNOTATION_PATTERN = Pattern.compile("org\\.eclipse\\.che\\.server\\.(?[\\w-/]+)\\..+"); private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); // used to avoid frequent creations of the object at runtime private static final java.lang.reflect.Type mapTypeToken = new TypeToken>() {}.getType(); /** Creates new annotations serializer. */ public static Serializer newSerializer() { return new Serializer(); } /** Creates new label deserializer from given annotations. */ public static Deserializer newDeserializer(Map annotations) { return new Deserializer(annotations); } /** Helps to serialize ServerConfig entities to Kubernetes annotations. */ public static class Serializer { private final Map annotations = new HashMap<>(); /** * Serializes server configuration as Kubernetes annotations. Appends serialization result to * this aggregate. * * @param ref server reference e.g. "exec-agent" * @param server server configuration * @return this serializer */ public Serializer server(String ref, ServerConfig server) { annotations.put(String.format(SERVER_PORT_ANNOTATION_FMT, ref), server.getPort()); annotations.put(String.format(SERVER_PROTOCOL_ANNOTATION_FMT, ref), server.getProtocol()); if (server.getPath() != null) { annotations.put(String.format(SERVER_PATH_ANNOTATION_FMT, ref), server.getPath()); } if (server.getAttributes() != null) { annotations.put( String.format(SERVER_ATTR_ANNOTATION_FMT, ref), GSON.toJson(server.getAttributes())); } return this; } public Serializer servers(Map servers) { servers.forEach(this::server); return this; } public Serializer machineName(String machineName) { annotations.put(MACHINE_NAME_ANNOTATION, machineName); return this; } public Map annotations() { return annotations; } } /** Helps to deserialize Kuberbetes annotations to known {@link ServerConfig} related entities. */ public static class Deserializer { private final Map annotations; public Deserializer(Map annotations) { this.annotations = annotations != null ? annotations : Collections.emptyMap(); } /** Retrieves server configuration from annotations and returns (ref -> server config) map. */ public Map servers() { Map servers = new HashMap<>(); for (Map.Entry entry : annotations.entrySet()) { Matcher refMatcher = SERVER_ANNOTATION_PATTERN.matcher(entry.getKey()); if (refMatcher.matches()) { String ref = refMatcher.group("ref"); if (!servers.containsKey(ref)) { // Null is serialized to empty string in annotations, but empty string as protocol // doesn't make any sense, so convert empty protocol to null which is respected // in other components String protocol = Strings.emptyToNull( annotations.get(String.format(SERVER_PROTOCOL_ANNOTATION_FMT, ref))); servers.put( ref, new ServerConfigImpl( annotations.get(String.format(SERVER_PORT_ANNOTATION_FMT, ref)), protocol, annotations.get(String.format(SERVER_PATH_ANNOTATION_FMT, ref)), GSON.fromJson( annotations.get(String.format(SERVER_ATTR_ANNOTATION_FMT, ref)), mapTypeToken))); } } } return servers; } public String machineName() { return annotations.get(MACHINE_NAME_ANNOTATION); } } private Annotations() {} } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/CheServerKubernetesClientFactory.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; /** * This {@link KubernetesClientFactory} ensures that we use `che` ServiceAccount and not related to * any workspace. It always provides client with default {@link Config}. It's useful for operations * that needs permissions of `che` SA, such as operations inside `che` namespace (like creating a * ConfigMaps for Gateway router) or some cluster-wide actions (like labeling the namespaces). */ @Singleton public class CheServerKubernetesClientFactory extends KubernetesClientFactory { @Inject public CheServerKubernetesClientFactory(KubernetesClientConfigFactory configBuilder) { super(configBuilder); } /** * @param workspaceId ignored */ @Override public KubernetesClient create(String workspaceId) throws InfrastructureException { return create(); } /** * creates an instance of {@link KubernetesClient} that is meant to be used on Che installation * namespace */ @Override public KubernetesClient create() throws InfrastructureException { return super.create(); } @Override protected Config buildConfig(Config config, String workspaceId) { return config; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/Constants.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; /** * Constants for Kubernetes implementation of spi. * * @author Sergii Leshchenko */ public final class Constants { /** * The label that contains a value with original object name. * *

Names of Kubernetes objects should be modified to avoid collision with objects of other * workspaces. * * @see org.eclipse.che.workspace.infrastructure.kubernetes.provision.UniqueNamesProvisioner */ public static final String CHE_ORIGINAL_NAME_LABEL = "che.original_name"; /** The label that contains a value with workspace id to which object belongs to. */ public static final String CHE_WORKSPACE_ID_LABEL = "che.workspace_id"; /** The label that contains a value with user id to which object belongs to. */ public static final String CHE_USER_ID_LABEL = "che.user_id"; /** The label that contains name of deployment responsible for Pod. */ public static final String CHE_DEPLOYMENT_NAME_LABEL = "che.deployment_name"; /** The label that contains a value with volume name. */ public static final String CHE_VOLUME_NAME_LABEL = "che.workspace.volume_name"; /** Kubernetes Pod status phase values */ public static final String POD_STATUS_PHASE_RUNNING = "Running"; public static final String POD_STATUS_PHASE_FAILED = "Failed"; public static final String POD_STATUS_PHASE_SUCCEEDED = "Succeeded"; /** DevWorkspace labels and annotations for mounting secrets and configmaps. */ public static final String DEV_WORKSPACE_MOUNT_LABEL = "controller.devfile.io/mount-to-devworkspace"; public static final String DEV_WORKSPACE_WATCH_SECRET_LABEL = "controller.devfile.io/watch-secret"; public static final String DEV_WORKSPACE_MOUNT_PATH_ANNOTATION = "controller.devfile.io/mount-path"; public static final String DEV_WORKSPACE_MOUNT_AS_ANNOTATION = "controller.devfile.io/mount-as"; private Constants() {} } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/K8sInfraNamespaceWsAttributeValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE; import java.util.Map; import javax.inject.Inject; import javax.inject.Provider; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.workspace.server.WorkspaceAttributeValidator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.NamespaceNameValidator; /** * Validates the values of {@link * org.eclipse.che.api.workspace.shared.Constants#WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE} * before workspace creation and updating. * * @author Sergii Leshchenko */ public class K8sInfraNamespaceWsAttributeValidator implements WorkspaceAttributeValidator { private final Provider namespaceFactoryProvider; @Inject public K8sInfraNamespaceWsAttributeValidator( Provider namespaceFactoryProvider) { this.namespaceFactoryProvider = namespaceFactoryProvider; } /** * Validates value of {@link * org.eclipse.che.api.workspace.shared.Constants#WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE}. * * @param attributes workspace attributes to validate * @throws ValidationException when infra namespace value has wrong format * @throws ValidationException when the current user is not permitted to use the specified infra * namespace */ @Override public void validate(Map attributes) throws ValidationException { String namespace = attributes.get(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE); if (!isNullOrEmpty(namespace)) { NamespaceNameValidator.validate(namespace); namespaceFactoryProvider.get().checkIfNamespaceIsAllowed(namespace); } } /** * Validates value of {@link * org.eclipse.che.api.workspace.shared.Constants#WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE} * before updating. * * @param existing actual workspace attributes * @param update new workspace attributes to validate * @throws ValidationException when new attributes removes or updates infra namespace value */ @Override public void validateUpdate(Map existing, Map update) throws ValidationException { String existingNamespace = existing.get(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE); String updateNamespace = update.get(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE); if (isNullOrEmpty(updateNamespace)) { if (isNullOrEmpty(existingNamespace)) { // this workspace was created before we start storing namespace info // namespace info will be stored during the next workspace start return; } throw new ValidationException( format( "The namespace information must not be updated or " + "deleted. You must provide \"%s\" attribute with \"%s\" as a value", WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, existingNamespace)); } if (isNullOrEmpty(existingNamespace)) { // this would mean that the user made an update to the workspace without it having the // namespace attribute stored. This is very, very unlikely, because the setting of attributes // happens during the creation process. But let's just cover this case anyway, just to be // sure. validate(update); // everything is fine. We allow to change infra namespace in such case. return; } if (!updateNamespace.equals(existingNamespace)) { throw new ValidationException( format( "The namespace from the provided object \"%s\" does " + "not match the actual namespace \"%s\"", updateNamespace, existingNamespace)); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesClientConfigFactory.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import static com.google.common.base.Strings.isNullOrEmpty; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.ConfigBuilder; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; /** * This class allows customizing the Kubernetes {@link Config} according to the current context * (workspace ID, current user). * * @author David Festal */ public class KubernetesClientConfigFactory { private final String masterUrl; private final Boolean doTrustCerts; public KubernetesClientConfigFactory(String masterUrl, Boolean doTrustCerts) { this.masterUrl = masterUrl; this.doTrustCerts = doTrustCerts; } /** * Builds the Kubernetes {@link Config} object based on a default {@link Config} object and an * optional workspace Id. */ public Config buildConfig(Config defaultConfig, @Nullable String workspaceId) throws InfrastructureException { return defaultConfig; } /** * Builds the default Kubernetes {@link Config} that will be the base configuration to create * per-workspace configurations. */ protected Config buildDefaultConfig() { ConfigBuilder configBuilder = new ConfigBuilder(); if (!isNullOrEmpty(masterUrl)) { configBuilder.withMasterUrl(masterUrl); } if (doTrustCerts != null) { configBuilder.withTrustCerts(doTrustCerts); } return configBuilder.build(); } /** * Returns true if implementation personalizes config to the current subject, otherwise returns * false if default config is always used. */ public boolean isPersonalized() { return false; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesClientFactory.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.http.HttpClient; import io.fabric8.kubernetes.client.utils.HttpClientUtils; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; /** * @author Sergii Leshchenko * @author Anton Korneta */ @Singleton public class KubernetesClientFactory { /** {@link HttpClient} instance shared by all Kubernetes clients. */ protected final HttpClient httpClient; protected final KubernetesClientConfigFactory configBuilder; @Inject public KubernetesClientFactory(KubernetesClientConfigFactory configBuilder) { this.httpClient = HttpClientUtils.createHttpClient(configBuilder.buildDefaultConfig()); this.configBuilder = configBuilder; } /** * Creates an instance of {@link KubernetesClient} that can be used to perform any operation * related to a given workspace.
For all operations performed in the context of a given * workspace (workspace start, workspace stop, etc ...), this method should be used to retrieve a * Kubernetes client. * * @param workspaceId Identifier of the workspace on which Kubernetes operations will be performed * @throws InfrastructureException if any error occurs on client instance creation. */ public KubernetesClient create(String workspaceId) throws InfrastructureException { Config configForWorkspace = buildConfig(getDefaultConfig(), workspaceId); return create(configForWorkspace); } /** * Creates an instance of {@link KubernetesClient} that can be used to perform any operation * that is not related to a given workspace.
For all operations performed * in the context of a given workspace (workspace start, workspace stop, etc ...), the {@code * create(String workspaceId)} method should be used to retrieve a Kubernetes client. * * @throws InfrastructureException if any error occurs on client instance creation. */ public KubernetesClient create() throws InfrastructureException { return create(buildConfig(getDefaultConfig(), null)); } /** * Shuts down the {@link KubernetesClient} by closing its connection pool. Typically should be * called on application tear down. */ public void shutdownClient() { httpClient.close(); } /** * Retrieves the default Kubernetes {@link Config} that will be the base configuration to create * per-workspace configurations. */ public Config getDefaultConfig() { return configBuilder.buildDefaultConfig(); } /** * Builds the Kubernetes {@link Config} object based on a provided {@link Config} object and an * optional workspace ID. */ protected Config buildConfig(Config config, @Nullable String workspaceId) throws InfrastructureException { return configBuilder.buildConfig(config, workspaceId); } /** * Creates instance of {@link KubernetesClient} that uses an {@link HttpClient} instance derived * from the shared {@code httpClient} instance in which interceptors are overridden to * authenticate with the credentials (user/password or Oauth token) contained in the {@code * config} parameter. */ protected KubernetesClient create(Config config) { return new UnclosableKubernetesClient(httpClient, config); } /** * Decorates the {@link DefaultKubernetesClient} so that it can not be closed from the outside. */ private static class UnclosableKubernetesClient extends DefaultKubernetesClient { public UnclosableKubernetesClient(HttpClient httpClient, Config config) { super(httpClient, config); } @Override public void close() {} } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesClientTermination.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import java.util.Collections; import java.util.Set; import javax.inject.Inject; import org.eclipse.che.api.system.server.ServiceTermination; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Termination for Kubernetes HTTP client. * * @author Max Shaposhnik (mshaposh@redhat.com) */ public class KubernetesClientTermination implements ServiceTermination { private static final Logger LOG = LoggerFactory.getLogger(KubernetesClientTermination.class); private KubernetesClientFactory kubernetesClientFactory; @Inject public KubernetesClientTermination(KubernetesClientFactory factory) { this.kubernetesClientFactory = factory; } @Override public void terminate() throws InterruptedException { suspend(); } @Override public void suspend() throws InterruptedException { try { kubernetesClientFactory.shutdownClient(); } catch (RuntimeException e) { LOG.error(e.getMessage()); } } @Override public String getServiceName() { return "KubernetesClient"; } @Override public Set getDependencies() { return Collections.emptySet(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesEnvironmentProvisioner.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GatewayRouterProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitConfigProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ImagePullSecretProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.PodTerminationGracePeriodProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecurityContextProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ServiceAccountProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SshKeysProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisionerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.UniqueNamesProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSslCertificateProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.env.EnvVarsConverter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.limits.ram.ContainerResourceProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter; import org.eclipse.che.workspace.infrastructure.kubernetes.server.PreviewUrlExposer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Applies the set of configurations to the Kubernetes environment and environment configuration * with the desired order, which corresponds to the needs of the Kubernetes infrastructure. * * @author Anton Korneta * @author Alexander Garagatyi */ public interface KubernetesEnvironmentProvisioner { void provision(T k8sEnv, RuntimeIdentity identity) throws InfrastructureException; @Singleton class KubernetesEnvironmentProvisionerImpl implements KubernetesEnvironmentProvisioner { private static final Logger LOG = LoggerFactory.getLogger(KubernetesEnvironmentProvisionerImpl.class); private final UniqueNamesProvisioner uniqueNamesProvisioner; private final ServersConverter serversConverter; private final EnvVarsConverter envVarsConverter; private final RestartPolicyRewriter restartPolicyRewriter; private final ContainerResourceProvisioner resourceLimitRequestProvisioner; private final SecurityContextProvisioner securityContextProvisioner; private final PodTerminationGracePeriodProvisioner podTerminationGracePeriodProvisioner; private final TlsProvisioner externalServerTlsProvisioner; private final ImagePullSecretProvisioner imagePullSecretProvisioner; private final ServiceAccountProvisioner serviceAccountProvisioner; private final CertificateProvisioner certificateProvisioner; private final SshKeysProvisioner sshKeysProvisioner; private final GitConfigProvisioner gitConfigProvisioner; private final PreviewUrlExposer previewUrlExposer; private final VcsSslCertificateProvisioner vcsSslCertificateProvisioner; private final GatewayRouterProvisioner gatewayRouterProvisioner; @Inject public KubernetesEnvironmentProvisionerImpl( UniqueNamesProvisioner uniqueNamesProvisioner, ServersConverter serversConverter, EnvVarsConverter envVarsConverter, RestartPolicyRewriter restartPolicyRewriter, ContainerResourceProvisioner resourceLimitRequestProvisioner, SecurityContextProvisioner securityContextProvisioner, PodTerminationGracePeriodProvisioner podTerminationGracePeriodProvisioner, TlsProvisionerProvider externalServerTlsProvisionerProvider, ImagePullSecretProvisioner imagePullSecretProvisioner, ServiceAccountProvisioner serviceAccountProvisioner, CertificateProvisioner certificateProvisioner, SshKeysProvisioner sshKeysProvisioner, GitConfigProvisioner gitConfigProvisioner, PreviewUrlExposer previewUrlExposer, VcsSslCertificateProvisioner vcsSslCertificateProvisioner, GatewayRouterProvisioner gatewayRouterProvisioner) { this.uniqueNamesProvisioner = uniqueNamesProvisioner; this.serversConverter = serversConverter; this.envVarsConverter = envVarsConverter; this.restartPolicyRewriter = restartPolicyRewriter; this.resourceLimitRequestProvisioner = resourceLimitRequestProvisioner; this.securityContextProvisioner = securityContextProvisioner; this.podTerminationGracePeriodProvisioner = podTerminationGracePeriodProvisioner; this.externalServerTlsProvisioner = externalServerTlsProvisionerProvider.get(); this.imagePullSecretProvisioner = imagePullSecretProvisioner; this.serviceAccountProvisioner = serviceAccountProvisioner; this.certificateProvisioner = certificateProvisioner; this.sshKeysProvisioner = sshKeysProvisioner; this.vcsSslCertificateProvisioner = vcsSslCertificateProvisioner; this.gitConfigProvisioner = gitConfigProvisioner; this.previewUrlExposer = previewUrlExposer; this.gatewayRouterProvisioner = gatewayRouterProvisioner; } @Traced public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { final String workspaceId = identity.getWorkspaceId(); TracingTags.WORKSPACE_ID.set(workspaceId); LOG.debug("Start provisioning Kubernetes environment for workspace '{}'", workspaceId); // 1st stage - converting Che model env to Kubernetes env LOG.debug("Provisioning servers & env vars converters for workspace '{}'", workspaceId); serversConverter.provision(k8sEnv, identity); previewUrlExposer.expose(k8sEnv); envVarsConverter.provision(k8sEnv, identity); // 2nd stage - add Kubernetes env items LOG.debug("Provisioning environment items for workspace '{}'", workspaceId); restartPolicyRewriter.provision(k8sEnv, identity); resourceLimitRequestProvisioner.provision(k8sEnv, identity); externalServerTlsProvisioner.provision(k8sEnv, identity); securityContextProvisioner.provision(k8sEnv, identity); podTerminationGracePeriodProvisioner.provision(k8sEnv, identity); imagePullSecretProvisioner.provision(k8sEnv, identity); serviceAccountProvisioner.provision(k8sEnv, identity); certificateProvisioner.provision(k8sEnv, identity); sshKeysProvisioner.provision(k8sEnv, identity); vcsSslCertificateProvisioner.provision(k8sEnv, identity); gitConfigProvisioner.provision(k8sEnv, identity); gatewayRouterProvisioner.provision(k8sEnv, identity); uniqueNamesProvisioner.provision(k8sEnv, identity); LOG.debug("Provisioning Kubernetes environment done for workspace '{}'", workspaceId); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import static com.google.inject.name.Names.named; import static org.eclipse.che.api.workspace.server.devfile.Constants.DOCKERIMAGE_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.DefaultHostExternalServiceExposureStrategy.DEFAULT_HOST_STRATEGY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY; import com.google.inject.AbstractModule; import com.google.inject.TypeLiteral; import com.google.inject.assistedinject.FactoryModuleBuilder; import com.google.inject.multibindings.MapBinder; import com.google.inject.multibindings.Multibinder; import java.util.Map; import org.eclipse.che.api.system.server.ServiceTermination; import org.eclipse.che.api.workspace.server.NoEnvironmentFactory; import org.eclipse.che.api.workspace.server.WorkspaceAttributeValidator; import org.eclipse.che.api.workspace.server.devfile.DevfileBindings; import org.eclipse.che.api.workspace.server.devfile.validator.ComponentIntegrityValidator.NoopComponentIntegrityValidator; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory; import org.eclipse.che.api.workspace.server.spi.provision.env.CheApiExternalEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.CheApiInternalEnvVarProvider; import org.eclipse.che.api.workspace.server.wsplugins.ChePluginsApplier; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.KubernetesNamespaceService; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner; import org.eclipse.che.workspace.infrastructure.kubernetes.cache.jpa.JpaKubernetesRuntimeCacheModule; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.DockerimageComponentToWorkspaceApplier; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesComponentToWorkspaceApplier; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesComponentValidator; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesDevfileBindings; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.RemoveNamespaceOnWorkspaceRemove; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.CredentialsSecretConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.GitconfigConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.OAuthTokenSecretsConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.PreferencesConfigMapConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.SshConfigConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserPermissionConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserPreferencesConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserProfileConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.WorkspaceServiceAccountConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GatewayTlsProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.IngressTlsProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.KubernetesCheApiExternalEnvVarProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.KubernetesCheApiInternalEnvVarProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.KubernetesPreviewUrlCommandProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.PreviewUrlCommandProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter; import org.eclipse.che.workspace.infrastructure.kubernetes.server.IngressAnnotationsProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.PreviewUrlExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.DefaultHostExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.GatewayServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.KubernetesExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultihostIngressServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ServiceExposureStrategyProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactoryProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.util.NonTlsDistributedClusterModeNotifier; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.KubernetesPluginsToolingApplier; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.PluginBrokerManager; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.BrokerEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.KubernetesBrokerEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events.BrokerService; /** * @author Sergii Leshchenko */ public class KubernetesInfraModule extends AbstractModule { @Override protected void configure() { Multibinder workspaceAttributeValidators = Multibinder.newSetBinder(binder(), WorkspaceAttributeValidator.class); workspaceAttributeValidators.addBinding().to(K8sInfraNamespaceWsAttributeValidator.class); // order matters here! // We first need to grant permissions to user, only then we can run other configurators with // user's client. Multibinder namespaceConfigurators = Multibinder.newSetBinder(binder(), NamespaceConfigurator.class); namespaceConfigurators.addBinding().to(UserPermissionConfigurator.class); namespaceConfigurators.addBinding().to(CredentialsSecretConfigurator.class); namespaceConfigurators.addBinding().to(OAuthTokenSecretsConfigurator.class); namespaceConfigurators.addBinding().to(PreferencesConfigMapConfigurator.class); namespaceConfigurators.addBinding().to(WorkspaceServiceAccountConfigurator.class); namespaceConfigurators.addBinding().to(UserProfileConfigurator.class); namespaceConfigurators.addBinding().to(UserPreferencesConfigurator.class); namespaceConfigurators.addBinding().to(SshConfigConfigurator.class); namespaceConfigurators.addBinding().to(GitconfigConfigurator.class); bind(PermissionsCleaner.class).asEagerSingleton(); bind(KubernetesNamespaceService.class); MapBinder factories = MapBinder.newMapBinder(binder(), String.class, InternalEnvironmentFactory.class); factories.addBinding(KubernetesEnvironment.TYPE).to(KubernetesEnvironmentFactory.class); factories.addBinding(Constants.NO_ENVIRONMENT_RECIPE_TYPE).to(NoEnvironmentFactory.class); MapBinder> tlsProvisioners = MapBinder.newMapBinder( binder(), new TypeLiteral() {}, new TypeLiteral>() {}); tlsProvisioners .addBinding(WorkspaceExposureType.GATEWAY) .to(new TypeLiteral>() {}); tlsProvisioners.addBinding(WorkspaceExposureType.NATIVE).to(IngressTlsProvisioner.class); bind(new TypeLiteral>() {}) .to(KubernetesEnvironmentProvisioner.KubernetesEnvironmentProvisionerImpl.class); install(new FactoryModuleBuilder().build(StartSynchronizerFactory.class)); bind(RemoveNamespaceOnWorkspaceRemove.class).asEagerSingleton(); bind(CheApiInternalEnvVarProvider.class).to(KubernetesCheApiInternalEnvVarProvider.class); bind(CheApiExternalEnvVarProvider.class).to(KubernetesCheApiExternalEnvVarProvider.class); Multibinder.newSetBinder(binder(), ServiceTermination.class) .addBinding() .to(KubernetesClientTermination.class); MapBinder ingressStrategies = MapBinder.newMapBinder(binder(), String.class, ExternalServiceExposureStrategy.class); ingressStrategies .addBinding(MULTI_HOST_STRATEGY) .to(MultiHostExternalServiceExposureStrategy.class); ingressStrategies .addBinding(SINGLE_HOST_STRATEGY) .to(SingleHostExternalServiceExposureStrategy.class); ingressStrategies .addBinding(DEFAULT_HOST_STRATEGY) .to(DefaultHostExternalServiceExposureStrategy.class); bind(ExternalServiceExposureStrategy.class).toProvider(ServiceExposureStrategyProvider.class); MapBinder> exposureStrategies = MapBinder.newMapBinder(binder(), new TypeLiteral<>() {}, new TypeLiteral<>() {}); exposureStrategies .addBinding(WorkspaceExposureType.NATIVE) .to(new TypeLiteral>() {}); exposureStrategies .addBinding(WorkspaceExposureType.GATEWAY) .to(new TypeLiteral>() {}); bind(new TypeLiteral>() {}) .annotatedWith(com.google.inject.name.Names.named("multihost-exposer")) .to(new TypeLiteral>() {}); bind(new TypeLiteral>() {}) .to(new TypeLiteral>() {}); bind(ServersConverter.class).to(new TypeLiteral>() {}); bind(PreviewUrlExposer.class) .to(new TypeLiteral>() {}); bind(PreviewUrlCommandProvisioner.class) .to(new TypeLiteral() {}); bind(new TypeLiteral>() {}) .annotatedWith(named("infra.kubernetes.ingress.annotations")) .toProvider(IngressAnnotationsProvider.class); install(new JpaKubernetesRuntimeCacheModule()); bind(SecureServerExposerFactoryProvider.class) .to(new TypeLiteral>() {}); MapBinder chePluginsAppliers = MapBinder.newMapBinder(binder(), String.class, ChePluginsApplier.class); chePluginsAppliers .addBinding(KubernetesEnvironment.TYPE) .to(KubernetesPluginsToolingApplier.class); bind(BrokerService.class); bind(new TypeLiteral>() {}) .to(KubernetesBrokerEnvironmentFactory.class); bind(PluginBrokerManager.class) .to(new TypeLiteral>() {}); DevfileBindings.onComponentIntegrityValidatorBinder( binder(), binder -> { binder.addBinding(KUBERNETES_COMPONENT_TYPE).to(KubernetesComponentValidator.class); binder.addBinding(DOCKERIMAGE_COMPONENT_TYPE).to(NoopComponentIntegrityValidator.class); }); DevfileBindings.onWorkspaceApplierBinder( binder(), binder -> { binder .addBinding(KUBERNETES_COMPONENT_TYPE) .to(KubernetesComponentToWorkspaceApplier.class); binder .addBinding(DOCKERIMAGE_COMPONENT_TYPE) .to(DockerimageComponentToWorkspaceApplier.class); }); KubernetesDevfileBindings.addKubernetesBasedEnvironmentTypeBindings( binder(), KubernetesEnvironment.TYPE); KubernetesDevfileBindings.addKubernetesBasedComponentTypeBindings( binder(), KUBERNETES_COMPONENT_TYPE); // We need to initialize the bindings somehow. Because no other environment type is upgradable // to kubernetes, we just call this in a way that initializes the binding with an empty map. KubernetesDevfileBindings.addAllowedEnvironmentTypeUpgradeBindings( binder(), KubernetesEnvironment.TYPE); bind(NonTlsDistributedClusterModeNotifier.class); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfrastructure.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import javax.inject.Singleton; /** * @author Sergii Leshchenko */ @Singleton public class KubernetesInfrastructure { public static final String NAME = "kubernetes"; } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfrastructureException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import io.fabric8.kubernetes.client.KubernetesClientException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; /** * An exception thrown by {@link KubernetesInfrastructure} and related components. Indicates that an * infrastructure operation can't be performed or an error occurred during operation execution. * * @author Sergii Leshchenko */ public class KubernetesInfrastructureException extends InfrastructureException { public KubernetesInfrastructureException(KubernetesClientException e) { super(extractMessage(e), e); } private static String extractMessage(KubernetesClientException e) { int code = e.getCode(); if (code == 401 || code == 403) { return e.getMessage() + " The error may be caused by an expired token or changed password. " + "Update Che server deployment with a new token or password."; } else { return e.getMessage(); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/MachineLogsPublisher.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import static org.slf4j.LoggerFactory.getLogger; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.cache.KubernetesMachineCache; import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesMachineImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEventHandler; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.slf4j.Logger; /** Listens pod events and publish them as machine logs. */ public class MachineLogsPublisher implements PodEventHandler { private static final Logger LOG = getLogger(MachineLogsPublisher.class); private final RuntimeEventsPublisher eventPublisher; private final KubernetesMachineCache machines; private final RuntimeIdentity runtimeIdentity; public MachineLogsPublisher( RuntimeEventsPublisher eventPublisher, KubernetesMachineCache machines, RuntimeIdentity runtimeIdentity) { this.eventPublisher = eventPublisher; this.machines = machines; this.runtimeIdentity = runtimeIdentity; } @Override public void handle(PodEvent event) { final String podName = event.getPodName(); try { for (KubernetesMachineImpl machine : machines.getMachines(runtimeIdentity).values()) { if (machine.getPodName().equals(podName)) { eventPublisher.sendMachineLogEvent( machine.getName(), event.getMessage(), event.getCreationTimeStamp(), runtimeIdentity); return; } } } catch (InfrastructureException e) { LOG.error("Error while machine fetching for logs publishing. Cause: {}", e.getMessage()); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/Names.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import static java.lang.String.format; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL; import com.google.common.collect.Maps; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ObjectMeta; import java.util.HashMap; import java.util.Map; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; /** * Helps to work with Kubernetes objects names. * * @author Sergii Leshchenko */ public class Names { public static final int MAX_CONTAINER_NAME_LENGTH = 63; // K8S container name limit private static final char WORKSPACE_ID_PREFIX_SEPARATOR = '.'; private static final int GENERATED_PART_SIZE = 8; private static final String CONTAINER_DISPLAY_NAMES_FMT = "org.eclipse.che.container.display-name/%s"; /** * Returns machine name for the specified container in the specified pod. * *

This is a convenience method for {@link #machineName(ObjectMeta, Container)} */ public static String machineName(PodData podData, Container container) { return machineName(podData.getMetadata(), container); } /** * Records the machine name used for given container in the annotations of the object metadata. * * @param objectMeta the object metadata * @param containerName the name of the container * @param machineName the name of the machine of the container */ public static void putMachineName( ObjectMeta objectMeta, String containerName, String machineName) { Map annotations = objectMeta.getAnnotations(); if (annotations == null) { objectMeta.setAnnotations(annotations = new HashMap<>()); } putMachineName(annotations, containerName, machineName); } /** * Transforms the provided mapping of container names to machine names into a map of annotations * to be put in some Kubernetes object's metadata. * * @param containerToMachineNames the mapping of container names to machine names * @return a map of annotations */ public static Map createMachineNameAnnotations( Map containerToMachineNames) { Map ret = new HashMap<>(); containerToMachineNames.forEach((c, m) -> putMachineName(ret, c, m)); return ret; } /** * Similar to {@link #createMachineNameAnnotations(Map)} but only creates annotations for a single * mapping. * * @param containerName the name of the container * @param machineName the name of the machine * @return a map of annotations for the container-machine mapping * @see #createMachineNameAnnotations(Map) */ public static Map createMachineNameAnnotations( String containerName, String machineName) { Map ret = Maps.newHashMapWithExpectedSize(2); putMachineName(ret, containerName, machineName); return ret; } private static void putMachineName( Map annotations, String containerName, String machineName) { annotations.put(format(CONTAINER_DISPLAY_NAMES_FMT, containerName), machineName); } /** * Returns machine name for the specified container in the specified pod. * *

Machine name is evaluated by the following algorithm:
* *

   * If an annotation with machine name for the corresponding container exists
   *   then return this value
   * If a label with original pod name exists
   *   then return originalPodName + '/' + containerName
   * otherwise return podName + '/' + containerName as machine name
   * 
*/ public static String machineName(ObjectMeta podMeta, Container container) { final Map annotations = podMeta.getAnnotations(); final String machineName; final String containerName = container.getName(); if (containerName != null && containerName.length() > MAX_CONTAINER_NAME_LENGTH) { throw new IllegalArgumentException( format( "The container name '%s' exceeds the allowed limit of %s characters.", containerName, MAX_CONTAINER_NAME_LENGTH)); } if (annotations != null && (machineName = annotations.get(format(CONTAINER_DISPLAY_NAMES_FMT, containerName))) != null) { return machineName; } final String originalPodName; final Map labels = podMeta.getLabels(); if (labels != null && (originalPodName = labels.get(CHE_ORIGINAL_NAME_LABEL)) != null) { return originalPodName + '/' + containerName; } return podMeta.getName() + '/' + containerName; } /** Return pod name that will be unique for a whole namespace. */ public static String uniqueResourceName(String originalResourceName, String workspaceId) { return workspaceId + WORKSPACE_ID_PREFIX_SEPARATOR + originalResourceName; } /** Returns randomly generated name with given prefix. */ public static String generateName(String prefix) { return NameGenerator.generate(prefix, GENERATED_PART_SIZE); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/RuntimeLogsPublisher.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import java.util.Set; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEventHandler; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; /** Listens pod events and publish them as runtime logs. */ public class RuntimeLogsPublisher implements PodEventHandler { private final RuntimeEventsPublisher eventPublisher; private final RuntimeIdentity runtimeIdentity; private final Set pods; public RuntimeLogsPublisher( RuntimeEventsPublisher eventPublisher, RuntimeIdentity runtimeIdentity, Set pods) { this.eventPublisher = eventPublisher; this.pods = pods; this.runtimeIdentity = runtimeIdentity; } @Override public void handle(PodEvent event) { final String podName = event.getPodName(); if (pods.contains(podName)) { eventPublisher.sendRuntimeLogEvent( event.getMessage(), event.getCreationTimeStamp(), runtimeIdentity); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/StartSynchronizer.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import com.google.inject.assistedinject.Assisted; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.server.spi.RuntimeStartInterruptedException; import org.eclipse.che.workspace.infrastructure.kubernetes.event.KubernetesRuntimeStoppedEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.event.KubernetesRuntimeStoppingEvent; /** * Controls the runtime start flow and helps to cancel it. * *

The runtime start with cancellation using the Start Synchronizer might look like: * *

 * ...
 *   public void startRuntime() {
 *     startSynchronizer.setStartThread();
 *     try {
 *       startMachines();
 *       startSynchronizer.checkFailure();
 *
 *       // ...
 *
 *       waitMachines()
 *         .then(startSynchronizer.checkFailure())
 *         .then(bootstrapAsync())
 *         .then(startSynchronizer.checkFailure())
 *         .then(checkServers)
 *         // ...
 *
 *       startSynchronizer.complete();
 *     } catch (Exception ex) {
 *       startSynchronizer.completeExceptionally(ex);
 *       throw ex;
 *     }
 *   }
 * ...
 * 
* *

At the same time stopping might look like: * *

 * ...
 *   public void stopRuntime() {
 *     if (startSynchronizer.interrupt()) {
 *       try {
 *         if (startSynchronizer.awaitInterruption(30)) {
 *           // runtime is interrupted correctly
 *         } else {
 *           // runtime is not interrupted correctly in time
 *           // need to forcibly stop it and clean up used resources
 *         }
 *       } catch (RuntimeStartInterruptedException ex) {
 *         // normal stop
 *       } catch (InterruptedException ex) {
 *         Thread.currentThread().interrupt();
 *         ...
 *       }
 *     }
 *   }
 * ...
 * 
* *

Note that class is designed to work in clustered mode. For this purpose it listens to {@link * KubernetesRuntimeStoppedEvent} and {@link KubernetesRuntimeStoppingEvent} events. So, if Che * Server is run in clustered mode then the described events must be published via {@link * EventService} otherwise start interruption won't work correctly. * * @author Sergii Leshchenko */ public class StartSynchronizer { private final RuntimeIdentity runtimeId; private final EventService eventService; // future that holds error that occurs during runtime start // failure must be completed with null value when start is finished without any exception private final CompletableFuture startFailure; // latch that indicates whether start is completed or not private CountDownLatch completionLatch; // thread which is performing start // it may be nullable when start is performing on another Che Server instance private Thread startThread; // flag that indicates whether start is in progress or not private boolean isStarting; private long startTimeMillis; private final RuntimeStartInterrupter runtimeStartInterrupter; private final RuntimeStopWatcher runtimeStopWatcher; @Inject public StartSynchronizer(EventService eventService, @Assisted RuntimeIdentity runtimeId) { this.eventService = eventService; this.startFailure = new CompletableFuture<>(); this.completionLatch = new CountDownLatch(0); this.runtimeId = runtimeId; this.runtimeStartInterrupter = new RuntimeStartInterrupter(); this.runtimeStopWatcher = new RuntimeStopWatcher(); this.isStarting = false; } /** Registers a runtime start. */ public synchronized void start() { if (!isStarting) { isStarting = true; startTimeMillis = System.currentTimeMillis(); completionLatch = new CountDownLatch(1); eventService.subscribe(runtimeStartInterrupter, KubernetesRuntimeStoppingEvent.class); eventService.subscribe(runtimeStopWatcher, KubernetesRuntimeStoppedEvent.class); } } /** * Sets {@link Thread#currentThread()} as a {@link #startThread}. * * @throws InternalInfrastructureException when {@link #startThread} is already set. */ public synchronized void setStartThread() throws InternalInfrastructureException { if (startThread != null) { throw new InternalInfrastructureException("Runtime is already started"); } startThread = Thread.currentThread(); } /** * Registers start completion. * *

It also releases {@link #awaitInterruption(long, TimeUnit)}. * * @throws RuntimeStartInterruptedException when start was interrupted * @throws InfrastructureException when any other exception occurs during start */ public synchronized void complete() throws InfrastructureException { completionLatch.countDown(); startThread = null; isStarting = false; eventService.unsubscribe(runtimeStartInterrupter); eventService.unsubscribe(runtimeStopWatcher); // try to complete start failure holder and // rethrow original exception if it is already completed exceptionally if (!startFailure.complete(null) && startFailure.isCompletedExceptionally()) { try { // future is already completed. startFailure.getNow(null); } catch (CompletionException e) { rethrowCause(e); } } } /** * Registers exception that occurs during runtime start. * *

Note that only first exception will be saved. * *

It also releases {@link #awaitInterruption(long, TimeUnit)}. */ public synchronized void completeExceptionally(Exception ex) { startFailure.completeExceptionally(ex); completionLatch.countDown(); startThread = null; isStarting = false; eventService.unsubscribe(runtimeStartInterrupter); eventService.unsubscribe(runtimeStopWatcher); } /** * Checks if start is failed. * *

If yes then original exception will be rethrown. * * @throws RuntimeStartInterruptedException when start was interrupted * @throws InfrastructureException when any other exception occurs during start */ public void checkFailure() throws InfrastructureException { try { startFailure.getNow(null); // no exception hasn't occurred yet } catch (CompletionException e) { rethrowCause(e); } } /** Returns future that holds error that occurs during runtime start */ public CompletableFuture getStartFailure() { return startFailure; } /** * Interrupts workspace start if it is in progress and is not interrupted yet * * @return true if workspace start is interrupted, false otherwise */ public synchronized boolean interrupt() { if (!isStarting) { return false; } startFailure.completeExceptionally(new RuntimeStartInterruptedException(runtimeId)); if (startThread != null) { startThread.interrupt(); // set to not to interrupt twice startThread = null; } return true; } /** * Awaits until workspace start process will be completed. * *

Returns true is interruption is completed. Or returns false when interruption wasn't * received or processed by start thread and workspace is STARTING or STOPPING. So it's needed to * stop a runtime and clean up used resources. * * @throws InterruptedException if the current thread is interrupted while waiting */ public boolean awaitInterruption(long timeout, TimeUnit unit) throws InterruptedException { boolean isCompleted = completionLatch.await(timeout, unit); if (isCompleted) { // check start failure try { startFailure.get(); } catch (ExecutionException e) { // start is interrupted or failed. Resources are freed return true; } } // runtime start is still in progress return false; } /** Returns true if start is completed, false otherwise. */ public boolean isCompleted() { return completionLatch.getCount() == 0; } /** Returns start failure exception if occurred otherwise null will be returned. */ public InfrastructureException getStartFailureNow() { try { startFailure.getNow(null); return null; } catch (CompletionException e) { Throwable cause = e.getCause(); if (cause instanceof InfrastructureException) { return (InfrastructureException) cause; } else { return new InternalInfrastructureException(cause.getMessage(), cause); } } } private void rethrowCause(Throwable e) throws InfrastructureException { try { throw e.getCause(); } catch (InfrastructureException ex) { throw ex; } catch (Throwable ex) { throw new InternalInfrastructureException(ex.getMessage(), ex); } } /** * Listens {@link KubernetesRuntimeStoppingEvent} and interrupts workspace start when workspace * become {@link WorkspaceStatus#STOPPING}. */ private class RuntimeStartInterrupter implements EventSubscriber { @Override public void onEvent(KubernetesRuntimeStoppingEvent event) { if (event.getWorkspaceId().equals(runtimeId.getWorkspaceId())) { interrupt(); } } } /** * Listens {@link KubernetesRuntimeStoppedEvent} and releases {@link #completionLatch} when * workspace become {@link WorkspaceStatus#STOPPED}. */ private class RuntimeStopWatcher implements EventSubscriber { @Override public void onEvent(KubernetesRuntimeStoppedEvent event) { if (event.getWorkspaceId().equals(runtimeId.getWorkspaceId())) { try { // try to complete start it if is not completed yet complete(); } catch (InfrastructureException ignored) { } } } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/StartSynchronizerFactory.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; /** * @author Sergii Leshchenko */ public interface StartSynchronizerFactory { StartSynchronizer create(RuntimeIdentity runtimeIdentity); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/Warnings.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; /** * Constants for Kubernetes infrastructure specific warnings. * * @author Sergii Leshchenko */ public final class Warnings { public static final int INGRESSES_IGNORED_WARNING_CODE = 4100; public static final String INGRESSES_IGNORED_WARNING_MESSAGE = "Ingresses specified in Kubernetes recipe are ignored. " + "To expose ports please define servers in machine configuration."; public static final int RESTART_POLICY_SET_TO_NEVER_WARNING_CODE = 4104; public static final String RESTART_POLICY_SET_TO_NEVER_WARNING_MESSAGE_FMT = "Restart policy '%s' for pod '%s' is rewritten with %s"; public static final int UNKNOWN_SECURE_SERVER_EXPOSER_CONFIGURED_IN_WS_WARNING_CODE = 4105; public static final int COMMAND_IS_CONFIGURED_IN_PLUGIN_WITHOUT_CONTAINERS_WARNING_CODE = 4106; public static final String COMMAND_IS_CONFIGURED_IN_PLUGIN_WITHOUT_CONTAINERS_WARNING_MESSAGE_FMT = "There are configured commands for plugin '%s' that doesn't have any containers"; public static final int COMMAND_IS_CONFIGURED_IN_PLUGIN_WITH_MULTIPLY_CONTAINERS_WARNING_CODE = 4107; public static final String COMMAND_IS_CONFIGURED_IN_PLUGIN_WITH_MULTIPLY_CONTAINERS_WARNING_MESSAGE_FMT = "There are configured commands for plugin '%s' that has multiply containers. Commands will be configured to be run in first container"; public static final int JSON_IS_NOT_A_VALID_REPRESENTATION_FOR_AN_OBJECT_OF_TYPE_WARNING_CODE = 4108; public static final String JSON_IS_NOT_A_VALID_REPRESENTATION_FOR_AN_OBJECT_OF_TYPE_MESSAGE_FMT = "Unable to provision git configuration into runtime. " + "Json object in user preferences is not a valid representation for an object of type map: '%s'"; public static final int EXCEPTION_IN_USER_MANAGEMENT_DURING_GIT_PROVISION_WARNING_CODE = 4109; public static final String EXCEPTION_IN_USER_MANAGEMENT_DURING_GIT_PROVISION_MESSAGE_FMT = "Unable to provision git configuration into runtime. " + "Internal server error occurred during operating with user management: '%s'"; public static final int NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL = 4110; public static final String NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL_MESSAGE = "Not able to provision objects for PreviewUrl. Message: '%s'"; public static final int NOT_ABLE_TO_PROVISION_SSH_KEYS = 4111; public static final String NOT_ABLE_TO_PROVISION_SSH_KEYS_MESSAGE = "Not able to provision SSH Keys. Message: '%s'"; public static final int NOT_ABLE_TO_PROVISION_WORKSPACE_DEPLOYMENT = 4200; public static final String NOT_ABLE_TO_PROVISION_WORKSPACE_DEPLOYMENT_MESSAGE = "Not able to find workspace attributes for %s. Reason %s"; public static final int NOT_ABLE_TO_PROVISION_WORKSPACE_DEPLOYMENT_LABELS_OR_ANNOTATIONS = 4250; public static final String NOT_ABLE_TO_PROVISION_WORKSPACE_DEPLOYMENT_LABELS_OR_ANNOTATIONS_MESSAGE = "Not able to provision workspace %s deployment labels or annotations because of invalid configuration. Reason: '%s'"; public static final int SSH_KEYS_WILL_NOT_BE_MOUNTED = 4300; public static final String SSH_KEYS_WILL_NOT_BE_MOUNTED_MESSAGE = "Ssh keys %s have invalid names and can't be mounted to workspace %s."; private Warnings() {} } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/api/server/KubernetesNamespaceService.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.api.server; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static org.eclipse.che.dto.server.DtoFactory.newDto; import com.google.common.annotations.Beta; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import java.util.List; import java.util.stream.Collectors; import javax.inject.Inject; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.core.rest.shared.dto.ExtendedError; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.KubernetesNamespaceMetaDto; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.NamespaceProvisioner; /** * @author Sergii Leshchenko */ @Tag(name = "kubernetes-namespace", description = "Kubernetes REST API for working with Namespaces") @Path("/kubernetes/namespace") @Beta public class KubernetesNamespaceService extends Service { private final KubernetesNamespaceFactory namespaceFactory; private final NamespaceProvisioner namespaceProvisioner; @Inject public KubernetesNamespaceService( KubernetesNamespaceFactory namespaceFactory, NamespaceProvisioner namespaceProvisioner) { this.namespaceFactory = namespaceFactory; this.namespaceProvisioner = namespaceProvisioner; } @GET @Produces(APPLICATION_JSON) @Operation( summary = "Get k8s namespaces where user is able to create workspaces. This operation can be performed only by authorized user." + "This is under beta and may be significant changed", responses = { @ApiResponse( responseCode = "200", description = "The namespaces successfully fetched", content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class)))), @ApiResponse( responseCode = "500", description = "Internal server error occurred during namespaces fetching") }) public List getNamespaces() throws InfrastructureException { return namespaceFactory.list().stream().map(this::asDto).collect(Collectors.toList()); } @POST @Path("provision") @Produces(APPLICATION_JSON) @Operation( summary = "Provision k8s namespace where user is able to create workspaces. This operation can be performed only by an authorized user." + " This is a beta feature that may be significantly changed.", responses = { @ApiResponse( responseCode = "200", description = "The namespace successfully provisioned", content = @Content(schema = @Schema(implementation = KubernetesNamespaceMetaDto.class))), @ApiResponse( responseCode = "500", description = "Internal server error occurred during namespace provisioning") }) public KubernetesNamespaceMetaDto provision() throws ApiException { try { return asDto( namespaceProvisioner.provision( new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject()))); } catch (InfrastructureException e) { throw new ServerException(newDto(ExtendedError.class).withMessage(e.getMessage())); } } private KubernetesNamespaceMetaDto asDto(KubernetesNamespaceMeta kubernetesNamespaceMeta) { return DtoFactory.newDto(KubernetesNamespaceMetaDto.class) .withName(kubernetesNamespaceMeta.getName()) .withAttributes(kubernetesNamespaceMeta.getAttributes()); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/api/server/impls/KubernetesNamespaceMetaImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls; import java.util.HashMap; import java.util.Map; import java.util.Objects; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; /** * @author Sergii Leshchenko */ public class KubernetesNamespaceMetaImpl implements KubernetesNamespaceMeta { private String name; private Map attributes; public KubernetesNamespaceMetaImpl(String name) { this.name = name; } public KubernetesNamespaceMetaImpl(String name, Map attributes) { this.name = name; if (attributes != null) { this.attributes = new HashMap<>(attributes); } } public KubernetesNamespaceMetaImpl(KubernetesNamespaceMeta namespaceMeta) { this(namespaceMeta.getName(), namespaceMeta.getAttributes()); } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof KubernetesNamespaceMetaImpl)) { return false; } KubernetesNamespaceMetaImpl that = (KubernetesNamespaceMetaImpl) o; return Objects.equals(getName(), that.getName()) && Objects.equals(getAttributes(), that.getAttributes()); } @Override public int hashCode() { return Objects.hash(getName(), getAttributes()); } @Override public String toString() { return "KubernetesNamespaceMetaImpl{" + "name='" + name + '\'' + ", attributes=" + attributes + '}'; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/api/shared/KubernetesNamespaceMeta.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.api.shared; import java.util.Map; /** * Describes meta information about kubernetes namespace. * * @author Sergii Leshchenko */ public interface KubernetesNamespaceMeta { /** * Attribute that shows if k8s namespace is configured as default. Possible values: true/false. * Absent value should be considered as false. */ String DEFAULT_ATTRIBUTE = "default"; /** * Attributes that contains information about current namespace status. Example values: Active, * Terminating. Absent value indicates that namespace is not created yet. */ String PHASE_ATTRIBUTE = "phase"; /** * Returns the name of namespace. * *

Value may be not a name of existing namespace, but predicted name with placeholders inside, * like . */ String getName(); /** Returns namespace attributes, which may contains additional info about it like description. */ Map getAttributes(); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/api/shared/dto/DockerAuthConfig.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto; import org.eclipse.che.dto.shared.DTO; /** * Implementation of docker AuthConfig object * * @author andrew00x * @see source */ @DTO public interface DockerAuthConfig { String getUsername(); void setUsername(String username); DockerAuthConfig withUsername(String username); String getPassword(); void setPassword(String password); DockerAuthConfig withPassword(String password); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/api/shared/dto/DockerAuthConfigs.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto; import java.util.Map; import org.eclipse.che.dto.shared.DTO; /** * Implementation of docker model ConfigFile object * * @author Max Shaposhnik * @see source */ @DTO public interface DockerAuthConfigs { Map getConfigs(); void setConfigs(Map configs); DockerAuthConfigs withConfigs(Map configs); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/api/shared/dto/KubernetesNamespaceMetaDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto; import java.util.Map; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; /** * @author Sergii Leshchenko */ @DTO public interface KubernetesNamespaceMetaDto extends KubernetesNamespaceMeta { @Override String getName(); void setName(String name); KubernetesNamespaceMetaDto withName(String name); @Override Map getAttributes(); void setAttributes(Map attributes); KubernetesNamespaceMetaDto withAttributes(Map attributes); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/authorization/AuthorizationChecker.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.authorization; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.subject.Subject; /** This {@link AuthorizationChecker} checks if user is allowed to use Che. */ public interface AuthorizationChecker { boolean isAuthorized(Subject subject) throws InfrastructureException; } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/authorization/AuthorizationException.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.authorization; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; /** * An exception thrown by RuntimeInfrastructure and related components. Indicates that a user is not * authorized to use Che. * * @author Anatolii Bazko */ public class AuthorizationException extends InfrastructureException { public AuthorizationException(String message) { super(message); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/authorization/KubernetesOIDCAuthorizationCheckerImpl.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.authorization; import static org.eclipse.che.commons.lang.StringUtils.strToSet; import java.util.Collections; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.subject.Subject; /** This {@link KubernetesOIDCAuthorizationCheckerImpl} checks if user is allowed to use Che. */ @Singleton public class KubernetesOIDCAuthorizationCheckerImpl implements AuthorizationChecker { private final Set allowUsers; private final Set allowGroups; private final Set denyUsers; private final Set denyGroups; @Inject public KubernetesOIDCAuthorizationCheckerImpl( @Nullable @Named("che.infra.kubernetes.advanced_authorization.allow_users") String allowUsers, @Nullable @Named("che.infra.kubernetes.advanced_authorization.allow_groups") String allowGroups, @Nullable @Named("che.infra.kubernetes.advanced_authorization.deny_users") String denyUsers, @Nullable @Named("che.infra.kubernetes.advanced_authorization.deny_groups") String denyGroups, @Named("che.infra.kubernetes.advanced_authorization.delimiter") String delimiter) { this.allowUsers = strToSet(allowUsers, delimiter); this.allowGroups = strToSet(allowGroups, delimiter); this.denyUsers = strToSet(denyUsers, delimiter); this.denyGroups = strToSet(denyGroups, delimiter); } public boolean isAuthorized(Subject subject) { return isAllowedUser(subject) && !isDeniedUser(subject); } private boolean isAllowedUser(Subject subject) { // All users from all groups are allowed by default if (allowUsers.isEmpty() && allowGroups.isEmpty()) { return true; } if (allowUsers.contains(subject.getUserName())) { return true; } return !Collections.disjoint(allowGroups, subject.getGroups()); } private boolean isDeniedUser(Subject subject) { // All users from all groups are allowed by default if (denyUsers.isEmpty() && denyGroups.isEmpty()) { return false; } if (denyUsers.contains(subject.getUserName())) { return true; } return !Collections.disjoint(denyGroups, subject.getGroups()); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/authorization/PermissionsCleaner.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.authorization; import static org.eclipse.che.commons.lang.StringUtils.strToSet; import io.fabric8.kubernetes.client.KubernetesClient; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; /** This {@link PermissionsCleaner} cleans up all user's permissions. */ @Singleton public class PermissionsCleaner { private final Set userClusterRoles; private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Inject public PermissionsCleaner( @Nullable @Named("che.infra.kubernetes.user_cluster_roles") String userClusterRoles, CheServerKubernetesClientFactory cheServerKubernetesClientFactory) { this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; this.userClusterRoles = strToSet(userClusterRoles); } public void cleanUp(String namespaceName) throws InfrastructureException { KubernetesClient client = cheServerKubernetesClientFactory.create(); for (String userClusterRole : userClusterRoles) { client.rbac().roleBindings().inNamespace(namespaceName).withName(userClusterRole).delete(); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/cache/KubernetesMachineCache.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.cache; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.MachineStatus; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesMachineImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesServerImpl; /** * Caches kubernetes machines. * * @author Sergii Leshchenko */ public interface KubernetesMachineCache { /** * Put machine state into cache. * *

Note that this method MUST NOT be used for machine state updating. * * @param runtimeIdentity runtime identifier * @param machine machine to cache * @throws InfrastructureException if machine with specified runtime id and name is already cached * @throws InfrastructureException if any exception occurs during machine caching */ void put(RuntimeIdentity runtimeIdentity, KubernetesMachineImpl machine) throws InfrastructureException; /** * Returns cached machine which belong to the specified runtime id. * * @param runtimeIdentity runtime identifier. * @throws InfrastructureException if any exception occurs during machines fetching */ Map getMachines(RuntimeIdentity runtimeIdentity) throws InfrastructureException; /** * Returns cached server. * * @param runtimeIdentity runtime identifier * @param machineName machine name to which the server belong to * @param serverName server name * @throws InfrastructureException if any exception occurs during server fetching */ KubernetesServerImpl getServer( RuntimeIdentity runtimeIdentity, String machineName, String serverName) throws InfrastructureException; /** * Updates machine status. * * @param runtimeIdentity runtime identifier * @param machineName machine name to which the server belong to * @param newStatus status to update * @throws InfrastructureException if any exception occurs during machine status updating */ void updateMachineStatus( RuntimeIdentity runtimeIdentity, String machineName, MachineStatus newStatus) throws InfrastructureException; /** * Updates server status. * * @param runtimeIdentity runtime identifier * @param machineName machine name to which the server belong to * @param serverName server name * @param newStatus status to update * @return true if status is update, false if the server already has the same status. * @throws InfrastructureException if any exception occurs during server status updating */ boolean updateServerStatus( RuntimeIdentity runtimeIdentity, String machineName, String serverName, ServerStatus newStatus) throws InfrastructureException; /** * Returns cached machine which belong to the specified runtime id. * * @param identity runtime identity * @throws InfrastructureException if any exception occurs during machines removing */ void remove(RuntimeIdentity identity) throws InfrastructureException; } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/cache/KubernetesRuntimeStateCache.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.cache; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesRuntimeState; /** * Caches runtime state. * * @author Sergii Leshchenko */ public interface KubernetesRuntimeStateCache { /** * Put runtime state into cache. * * @param state state to cache * @return true if state is put, false if state is already cached * @throws InfrastructureException if any exception occurs during entity putting */ boolean putIfAbsent(KubernetesRuntimeState state) throws InfrastructureException; /** * Returns runtime identities of cached runtimes. * * @throws InfrastructureException if any exception occurs during entities fetching */ Set getIdentities() throws InfrastructureException; /** * Returns optional with status of the runtime with specified identifier or empty optional if * there is not cached state. * * @param runtimeId runtime identifier * @throws InfrastructureException if any exception occurs during status fetching */ Optional getStatus(RuntimeIdentity runtimeId) throws InfrastructureException; /** * Returns commands of the runtime with specified identifier or empty list when state is not * cached. * * @param runtimeId runtime identifier * @throws InfrastructureException if any exception occurs during commands fetching */ List getCommands(RuntimeIdentity runtimeId) throws InfrastructureException; /** * Returns optional with state of the runtime with specified identifier. * * @param runtimeId runtime identifier * @throws InfrastructureException if any exception occurs during state fetching */ Optional get(RuntimeIdentity runtimeId) throws InfrastructureException; /** * Updates status of cached runtime. * * @param runtimeId runtime identifier * @param newStatus status to update * @throws InfrastructureException if any exception occurs during status updating */ void updateStatus(RuntimeIdentity runtimeId, WorkspaceStatus newStatus) throws InfrastructureException; /** * Updates status of cached runtime if previous value matches the specified predicate. * * @param identity runtime identifier * @param predicate predicate to test the previous status * @param newStatus status to update * @return true if status is updated, false if status is not specified because * @throws InfrastructureException if any exception occurs during status updating */ boolean updateStatus( RuntimeIdentity identity, Predicate predicate, WorkspaceStatus newStatus) throws InfrastructureException; /** * Updates commands of cached runtime state. * * @param identity runtime identifier * @param commands commands to store * @throws InfrastructureException if there is no cached state for specified runtime identifier * @throws InfrastructureException if any exception occurs during commands updating */ void updateCommands(RuntimeIdentity identity, List commands) throws InfrastructureException; /** * Removes state of the runtime with specified identifier. * * @param runtimeId runtime identifier * @throws InfrastructureException if any exception occurs during state removing */ void remove(RuntimeIdentity runtimeId) throws InfrastructureException; } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/cache/jpa/JpaKubernetesMachineCache.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.cache.jpa; import static java.lang.String.format; import static java.util.stream.Collectors.toMap; import com.google.inject.persist.Transactional; import java.util.Collection; import java.util.Map; import java.util.function.Function; import javax.inject.Inject; import javax.inject.Provider; import javax.persistence.EntityManager; import org.eclipse.che.api.core.model.workspace.runtime.MachineStatus; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.cache.KubernetesMachineCache; import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesMachineImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesMachineImpl.MachineId; import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesServerImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesServerImpl.ServerId; /** * JPA based implementation of {@link KubernetesMachineCache}. * * @author Sergii Leshchenko */ public class JpaKubernetesMachineCache implements KubernetesMachineCache { private final Provider managerProvider; @Inject public JpaKubernetesMachineCache(Provider managerProvider) { this.managerProvider = managerProvider; } @Override public void put(RuntimeIdentity runtimeIdentity, KubernetesMachineImpl machine) throws InfrastructureException { try { doPutMachine(machine); } catch (RuntimeException e) { throw new InfrastructureException(e.getMessage(), e); } } @Transactional(rollbackOn = InfrastructureException.class) @Override public Map getMachines(RuntimeIdentity runtimeIdentity) throws InfrastructureException { try { return managerProvider .get() .createNamedQuery("KubernetesMachine.getByWorkspaceId", KubernetesMachineImpl.class) .setParameter("workspaceId", runtimeIdentity.getWorkspaceId()) .getResultList() .stream() .collect(toMap(KubernetesMachineImpl::getName, Function.identity())); } catch (RuntimeException e) { throw new InfrastructureException(e.getMessage(), e); } } @Transactional(rollbackOn = InfrastructureException.class) @Override public KubernetesServerImpl getServer( RuntimeIdentity runtimeIdentity, String machineName, String serverName) throws InfrastructureException { try { String workspaceId = runtimeIdentity.getWorkspaceId(); KubernetesServerImpl server = managerProvider .get() .find(KubernetesServerImpl.class, new ServerId(workspaceId, machineName, serverName)); if (server == null) { throw new InfrastructureException( format("Server with name '%s' was not found", serverName)); } return server; } catch (RuntimeException e) { throw new InfrastructureException(e.getMessage(), e); } } @Override public void updateMachineStatus( RuntimeIdentity runtimeIdentity, String machineName, MachineStatus newStatus) throws InfrastructureException { try { doUpdateMachineStatus(runtimeIdentity.getWorkspaceId(), machineName, newStatus); } catch (RuntimeException e) { throw new InfrastructureException(e.getMessage(), e); } } @Override public boolean updateServerStatus( RuntimeIdentity runtimeIdentity, String machineName, String serverName, ServerStatus newStatus) throws InfrastructureException { try { return doUpdateServerStatus(runtimeIdentity, machineName, serverName, newStatus); } catch (RuntimeException e) { throw new InfrastructureException(e.getMessage(), e); } } @Override public void remove(RuntimeIdentity runtimeIdentity) throws InfrastructureException { try { doRemove(runtimeIdentity); } catch (RuntimeException e) { throw new InfrastructureException(e.getMessage(), e); } } @Transactional(rollbackOn = {RuntimeException.class, InfrastructureException.class}) protected void doRemove(RuntimeIdentity runtimeIdentity) throws InfrastructureException { EntityManager em = managerProvider.get(); Collection machines = getMachines(runtimeIdentity).values(); for (KubernetesMachineImpl machine : machines) { em.remove(machine); } em.flush(); } @Transactional protected void doPutMachine(KubernetesMachineImpl machine) { EntityManager em = managerProvider.get(); em.persist(machine); em.flush(); } @Transactional protected void doUpdateMachineStatus(String workspaceId, String machineName, MachineStatus status) throws InfrastructureException { EntityManager entityManager = managerProvider.get(); KubernetesMachineImpl machine = entityManager.find(KubernetesMachineImpl.class, new MachineId(workspaceId, machineName)); if (machine == null) { throw new InfrastructureException( format("Machine '%s:%s' was not found", workspaceId, machineName)); } machine.setStatus(status); entityManager.flush(); } @Transactional(rollbackOn = {RuntimeException.class, InfrastructureException.class}) protected boolean doUpdateServerStatus( RuntimeIdentity runtimeIdentity, String machineName, String serverName, ServerStatus status) throws InfrastructureException { EntityManager entityManager = managerProvider.get(); KubernetesServerImpl server = getServer(runtimeIdentity, machineName, serverName); if (server.getStatus() != status) { server.setStatus(status); entityManager.flush(); return true; } return false; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/cache/jpa/JpaKubernetesRuntimeCacheModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.cache.jpa; import com.google.inject.AbstractModule; import org.eclipse.che.workspace.infrastructure.kubernetes.cache.KubernetesMachineCache; import org.eclipse.che.workspace.infrastructure.kubernetes.cache.KubernetesRuntimeStateCache; /** * @author Sergii Leshchenko */ public class JpaKubernetesRuntimeCacheModule extends AbstractModule { @Override protected void configure() { bind(KubernetesRuntimeStateCache.class).to(JpaKubernetesRuntimeStateCache.class); bind(KubernetesMachineCache.class).to(JpaKubernetesMachineCache.class); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/cache/jpa/JpaKubernetesRuntimeStateCache.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.cache.jpa; import static java.util.Collections.emptyList; import com.google.inject.persist.Transactional; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; import javax.persistence.EntityManager; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.cache.KubernetesRuntimeStateCache; import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesRuntimeCommandImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesRuntimeState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * JPA based implementation of {@link KubernetesRuntimeStateCache}. * * @author Sergii Leshchenko */ public class JpaKubernetesRuntimeStateCache implements KubernetesRuntimeStateCache { private static final Logger LOG = LoggerFactory.getLogger(JpaKubernetesRuntimeStateCache.class); private final Provider managerProvider; private final EventService eventService; @Inject public JpaKubernetesRuntimeStateCache( Provider managerProvider, EventService eventService) { this.managerProvider = managerProvider; this.eventService = eventService; } @Override public boolean putIfAbsent(KubernetesRuntimeState runtimeState) throws InfrastructureException { try { doPutIfAbsent(runtimeState); return true; } catch (RuntimeException e) { throw new InfrastructureException(e.getMessage(), e); } } @Transactional(rollbackOn = InfrastructureException.class) @Override public Set getIdentities() throws InfrastructureException { try { return managerProvider .get() .createNamedQuery("KubernetesRuntime.getAll", KubernetesRuntimeState.class) .getResultList() .stream() .map(KubernetesRuntimeState::getRuntimeId) .collect(Collectors.toSet()); } catch (RuntimeException x) { throw new InfrastructureException(x.getMessage(), x); } } @Transactional(rollbackOn = InfrastructureException.class) @Override public Optional getStatus(RuntimeIdentity id) throws InfrastructureException { try { Optional runtimeStateOpt = get(id); return runtimeStateOpt.map(KubernetesRuntimeState::getStatus); } catch (RuntimeException x) { throw new InfrastructureException(x.getMessage(), x); } } @Override public List getCommands(RuntimeIdentity runtimeId) throws InfrastructureException { Optional k8sRuntimeState = get(runtimeId); if (k8sRuntimeState.isPresent()) { return k8sRuntimeState.get().getCommands(); } else { // runtime is not started yet return emptyList(); } } @Transactional(rollbackOn = InfrastructureException.class) @Override public Optional get(RuntimeIdentity runtimeId) throws InfrastructureException { try { return Optional.ofNullable( managerProvider.get().find(KubernetesRuntimeState.class, runtimeId.getWorkspaceId())); } catch (RuntimeException x) { throw new InfrastructureException(x.getMessage(), x); } } @Override public void updateStatus(RuntimeIdentity runtimeId, WorkspaceStatus newStatus) throws InfrastructureException { try { doUpdateStatus(runtimeId, newStatus); } catch (RuntimeException x) { throw new InfrastructureException(x.getMessage(), x); } } @Override public boolean updateStatus( RuntimeIdentity identity, Predicate predicate, WorkspaceStatus newStatus) throws InfrastructureException { try { doUpdateStatus(identity, predicate, newStatus); return true; } catch (IllegalStateException e) { return false; } catch (RuntimeException e) { throw new InfrastructureException(e.getMessage(), e); } } @Override public void updateCommands(RuntimeIdentity identity, List commands) throws InfrastructureException { try { doUpdateCommands( identity, commands.stream().map(KubernetesRuntimeCommandImpl::new).collect(Collectors.toList())); } catch (RuntimeException e) { throw new InfrastructureException(e.getMessage(), e); } } @Override public void remove(RuntimeIdentity runtimeId) throws InfrastructureException { try { doRemove(runtimeId); } catch (ServerException | RuntimeException x) { throw new InfrastructureException(x.getMessage(), x); } } @Transactional(rollbackOn = InfrastructureException.class) protected Optional find(String workspaceId) throws InfrastructureException { try { return Optional.ofNullable( managerProvider.get().find(KubernetesRuntimeState.class, workspaceId)); } catch (RuntimeException x) { throw new InfrastructureException(x.getMessage(), x); } } @Transactional(rollbackOn = {RuntimeException.class, ServerException.class}) protected void doRemove(RuntimeIdentity runtimeIdentity) throws ServerException { EntityManager em = managerProvider.get(); KubernetesRuntimeState runtime = em.find(KubernetesRuntimeState.class, runtimeIdentity.getWorkspaceId()); if (runtime != null) { em.remove(runtime); } } @Transactional(rollbackOn = {RuntimeException.class, InfrastructureException.class}) protected void doUpdateStatus(RuntimeIdentity id, WorkspaceStatus status) throws InfrastructureException { Optional runtimeStateOpt = get(id); if (!runtimeStateOpt.isPresent()) { throw new InfrastructureException( "Runtime state for workspace with id '" + id.getWorkspaceId() + "' was not found"); } runtimeStateOpt.get().setStatus(status); managerProvider.get().flush(); } @Transactional(rollbackOn = {RuntimeException.class, InfrastructureException.class}) protected void doUpdateStatus( RuntimeIdentity id, Predicate predicate, WorkspaceStatus newStatus) throws InfrastructureException { EntityManager entityManager = managerProvider.get(); Optional existingStateOpt = get(id); if (!existingStateOpt.isPresent()) { throw new InfrastructureException( "Runtime state for workspace with id '" + id.getWorkspaceId() + "' was not found"); } KubernetesRuntimeState existingState = existingStateOpt.get(); if (!predicate.test(existingState.getStatus())) { throw new IllegalStateException("Runtime status doesn't match to the specified predicate"); } existingState.setStatus(newStatus); entityManager.flush(); } @Transactional(rollbackOn = {RuntimeException.class, InfrastructureException.class}) protected void doUpdateCommands(RuntimeIdentity id, List commands) throws InfrastructureException { Optional runtimeStateOpt = get(id); if (!runtimeStateOpt.isPresent()) { throw new InfrastructureException( "Runtime state for workspace with id '" + id.getWorkspaceId() + "' was not found"); } runtimeStateOpt.get().setCommands(commands); managerProvider.get().flush(); } @Transactional protected void doPutIfAbsent(KubernetesRuntimeState runtimeState) { EntityManager em = managerProvider.get(); em.persist(runtimeState); em.flush(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/ContainerSearch.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import static java.util.stream.Collectors.toList; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodTemplate; import io.fabric8.kubernetes.api.model.ReplicationController; import io.fabric8.kubernetes.api.model.apps.DaemonSet; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.ReplicaSet; import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.fabric8.kubernetes.api.model.batch.v1.CronJob; import io.fabric8.kubernetes.api.model.batch.v1.Job; import io.fabric8.openshift.api.model.DeploymentConfig; import io.fabric8.openshift.api.model.Template; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Stream; import org.eclipse.che.commons.annotation.Nullable; /** * Container search goes through a list of Kubernetes resources and recursively looks for containers * that match the provided criteria. * *

The {@code parentName} constraint works both on the {@code name} and, if name is not set on * given k8s object, on the {@code generateName}. */ public class ContainerSearch { private final @Nullable String parentName; private final @Nullable Map parentSelector; private final @Nullable String containerName; /** * Constructs a new {@code ContainerSearch} instance in somewhat unsurprising manner. * * @param parentName the name of the parent object that should (indirectly) contain the containers * @param parentSelector the labels to match on the parent object, if any * @param containerName only search for containers with given name */ public ContainerSearch( @Nullable String parentName, @Nullable Map parentSelector, @Nullable String containerName) { this.parentName = parentName; this.parentSelector = parentSelector; this.containerName = containerName; } /** * Searches for containers in the provided list of Kubernetes objects. If any given item in the * list can contain a container (i.e. it is a pod, deployment, etc.) the item is searched for the * containers recursively. * * @param list the list of Kubernetes resources to sift through * @return a list of containers found in the provided object list */ public List search(Collection list) { return list.stream() .filter(this::matchMetadata) .flatMap(this::findContainers) .filter(this::matchContainer) .collect(toList()); } private Stream findContainers(HasMetadata o) { // hopefully, this covers all types of objects that can contain a container if (o instanceof Pod) { return ((Pod) o).getSpec().getContainers().stream(); } else if (o instanceof PodTemplate) { return ((PodTemplate) o).getTemplate().getSpec().getContainers().stream(); } else if (o instanceof DaemonSet) { return ((DaemonSet) o).getSpec().getTemplate().getSpec().getContainers().stream(); } else if (o instanceof Deployment) { return ((Deployment) o).getSpec().getTemplate().getSpec().getContainers().stream(); } else if (o instanceof Job) { return ((Job) o).getSpec().getTemplate().getSpec().getContainers().stream(); } else if (o instanceof ReplicaSet) { return ((ReplicaSet) o).getSpec().getTemplate().getSpec().getContainers().stream(); } else if (o instanceof ReplicationController) { return ((ReplicationController) o).getSpec().getTemplate().getSpec().getContainers().stream(); } else if (o instanceof StatefulSet) { return ((StatefulSet) o).getSpec().getTemplate().getSpec().getContainers().stream(); } else if (o instanceof CronJob) { return ((CronJob) o) .getSpec().getJobTemplate().getSpec().getTemplate().getSpec().getContainers().stream(); } else if (o instanceof DeploymentConfig) { return ((DeploymentConfig) o).getSpec().getTemplate().getSpec().getContainers().stream(); } else if (o instanceof Template) { return ((Template) o).getObjects().stream().flatMap(t -> findContainers((HasMetadata) t)); } else { return Stream.empty(); } } private boolean matchContainer(Container container) { return this.containerName == null || this.containerName.equals(container.getName()); } private boolean matchMetadata(HasMetadata object) { ObjectMeta metaData = object.getMetadata(); return matchesByName(metaData, parentName) && (parentSelector == null || SelectorFilter.test(metaData, parentSelector)); } private boolean matchesByName(@Nullable ObjectMeta metaData, @Nullable String name) { if (name == null) { return true; } String metaName = metaData == null ? null : metaData.getName(); String metaGenerateName = metaData == null ? null : metaData.getGenerateName(); // do not compare by the generateName if a name exists if (metaName != null) { return name.equals(metaName); } else { return name.equals(metaGenerateName); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/DockerimageComponentToWorkspaceApplier.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static org.eclipse.che.api.core.model.workspace.config.Command.MACHINE_NAME_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.DOCKERIMAGE_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier.convertEndpointsIntoServers; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.workspace.server.devfile.Constants; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSize; /** * Applies changes on workspace config according to the specified dockerimage component. * *

The {@code dockerimage} devfile components are handled as Kubernetes deployments internally. * * @author Sergii Leshchenko */ public class DockerimageComponentToWorkspaceApplier implements ComponentToWorkspaceApplier { /** * Label that contains component name to which object belongs to and it is provisioned for * generated deployments and its pod templates. */ static final String CHE_COMPONENT_NAME_LABEL = "che.component.name"; private final KubernetesEnvironmentProvisioner k8sEnvProvisioner; private final String devfileEndpointsExposure; @Inject public DockerimageComponentToWorkspaceApplier( @Named("che.infra.kubernetes.singlehost.workspace.devfile_endpoint_exposure") String devfileEndpointsExposure, KubernetesEnvironmentProvisioner k8sEnvProvisioner) { this.k8sEnvProvisioner = k8sEnvProvisioner; this.devfileEndpointsExposure = devfileEndpointsExposure; } /** * Applies changes on workspace config according to the specified dockerimage component. * *

Dockerimage component is provisioned as Deployment in Kubernetes recipe.
* Generated deployment contains container with environment variables, memory limit, docker image, * arguments and commands specified in component.
* Also, environment is provisioned with machine config with volumes and servers specified, then * Kubernetes infra will created needed PVC, Services, Ingresses, Routes according to specified * configuration. * * @param workspaceConfig workspace config on which changes should be applied * @param dockerimageComponent dockerimage component that should be applied * @param contentProvider optional content provider that may be used for external component * resource fetching * @throws DevfileException if specified workspace config already has default environment where * dockerimage component should be stored * @throws IllegalArgumentException if specified workspace config or plugin component is null * @throws IllegalArgumentException if specified component has type different from dockerimage */ @Override public void apply( WorkspaceConfigImpl workspaceConfig, ComponentImpl dockerimageComponent, FileContentProvider contentProvider) throws DevfileException { checkArgument(workspaceConfig != null, "Workspace config must not be null"); checkArgument(dockerimageComponent != null, "Component must not be null"); checkArgument( DOCKERIMAGE_COMPONENT_TYPE.equals(dockerimageComponent.getType()), format("Plugin must have `%s` type", DOCKERIMAGE_COMPONENT_TYPE)); String componentAlias = dockerimageComponent.getAlias(); String machineName = componentAlias == null ? toMachineName(dockerimageComponent.getImage()) : componentAlias; MachineConfigImpl machineConfig = createMachineConfig(dockerimageComponent, componentAlias); List componentObjects = createComponentObjects(dockerimageComponent, machineName); k8sEnvProvisioner.provision( workspaceConfig, KubernetesEnvironment.TYPE, componentObjects, ImmutableMap.of(machineName, machineConfig)); workspaceConfig.getCommands().stream() .filter( c -> componentAlias != null && componentAlias.equals( c.getAttributes().get(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE))) .forEach(c -> c.getAttributes().put(MACHINE_NAME_ATTRIBUTE, machineName)); } private MachineConfigImpl createMachineConfig( ComponentImpl dockerimageComponent, String componentAlias) { MachineConfigImpl machineConfig = new MachineConfigImpl(); machineConfig .getServers() .putAll( convertEndpointsIntoServers( dockerimageComponent.getEndpoints(), !SINGLE_HOST_STRATEGY.equals(devfileEndpointsExposure))); dockerimageComponent .getVolumes() .forEach( v -> machineConfig .getVolumes() .put(v.getName(), new VolumeImpl().withPath(v.getContainerPath()))); if (!isNullOrEmpty(componentAlias)) { machineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, componentAlias); } return machineConfig; } private List createComponentObjects( ComponentImpl dockerimageComponent, String machineName) { List componentObjects = new ArrayList<>(); Deployment deployment = buildDeployment( machineName, dockerimageComponent.getImage(), dockerimageComponent.getMemoryRequest(), dockerimageComponent.getMemoryLimit(), dockerimageComponent.getCpuRequest(), dockerimageComponent.getCpuLimit(), dockerimageComponent.getEnv().stream() .map(e -> new EnvVar(e.getName(), e.getValue(), null)) .collect(Collectors.toCollection(ArrayList::new)), dockerimageComponent.getCommand(), dockerimageComponent.getArgs()); componentObjects.add(deployment); return componentObjects; } private Deployment buildDeployment( String name, String image, String memoryRequest, String memoryLimit, String cpuRequest, String cpuLimit, List env, List command, List args) { Container container = new ContainerBuilder() .withImage(image) .withName(name) .withEnv(env) .withCommand(command) .withArgs(args) .build(); Containers.addRamLimit(container, memoryLimit); if (!isNullOrEmpty(memoryRequest)) { Containers.addRamRequest(container, memoryRequest); } if (!isNullOrEmpty(cpuRequest)) { Containers.addCpuRequest(container, KubernetesSize.toCores(cpuRequest)); } if (!isNullOrEmpty(cpuLimit)) { Containers.addCpuLimit(container, KubernetesSize.toCores(cpuLimit)); } return new DeploymentBuilder() .withNewMetadata() .addToLabels(CHE_COMPONENT_NAME_LABEL, name) .withName(name) .endMetadata() .withNewSpec() .withNewSelector() .addToMatchLabels(CHE_COMPONENT_NAME_LABEL, name) .endSelector() .withNewTemplate() .withNewMetadata() .withName(name) .addToLabels(CHE_COMPONENT_NAME_LABEL, name) .addToAnnotations(Names.createMachineNameAnnotations(name, name)) .endMetadata() .withNewSpec() .withContainers(container) .endSpec() .endTemplate() .endSpec() .build(); } @VisibleForTesting static String toMachineName(String imageName) throws DevfileException { if (imageName.isEmpty()) { return imageName; } if (imageName.length() > Names.MAX_CONTAINER_NAME_LENGTH) { throw new DevfileException( format( "The image name '%s' is longer than 63 characters and as such cannot be used as a container" + " name. Please provide an alias for the component with that image.", imageName)); } // the name needs to be both a valid k8s label and a valid machine name. String clean = imageName.replaceAll("[^-a-zA-Z0-9_]", "-"); if (isInvalidStartEndChar(clean.charAt(0)) || isInvalidStartEndChar(clean.charAt(clean.length() - 1))) { throw new DevfileException( format( "Cannot convert image %s to a valid component name." + " Please provide an alias that conforms to the Kubernetes label value format.", imageName)); } return clean; } /** * @return true if the character isn't an ASCII letter (of either case) or a number. */ private static boolean isInvalidStartEndChar(char ch) { return ch < '0' || ch > 'z'; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentToWorkspaceApplier.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.stream.Collectors.toList; import static org.eclipse.che.api.core.model.workspace.config.Command.MACHINE_NAME_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Components.getIdentifiableComponentName; import static org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier.convertEndpointsIntoServers; import static org.eclipse.che.workspace.infrastructure.kubernetes.Names.machineName; import static org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesDevfileBindings.KUBERNETES_BASED_COMPONENTS_KEY_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.VolumeMount; import io.fabric8.kubernetes.api.model.apps.Deployment; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.core.model.workspace.devfile.Entrypoint; import org.eclipse.che.api.workspace.server.devfile.Constants; import org.eclipse.che.api.workspace.server.devfile.DevfileRecipeFormatException; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser; import org.eclipse.che.workspace.infrastructure.kubernetes.util.EnvVars; /** * Applies changes on workspace config according to the specified kubernetes/openshift component. * * @author Sergii Leshchenko */ public class KubernetesComponentToWorkspaceApplier implements ComponentToWorkspaceApplier { private final KubernetesRecipeParser objectsParser; private final KubernetesEnvironmentProvisioner k8sEnvProvisioner; private final String environmentType; private final Set kubernetesBasedComponentTypes; private final EnvVars envVars; private final String devfileEndpointsExposure; @Inject public KubernetesComponentToWorkspaceApplier( KubernetesRecipeParser objectsParser, KubernetesEnvironmentProvisioner k8sEnvProvisioner, EnvVars envVars, @Named("che.infra.kubernetes.singlehost.workspace.devfile_endpoint_exposure") String devfileEndpointsExposure, @Named(KUBERNETES_BASED_COMPONENTS_KEY_NAME) Set kubernetesBasedComponentTypes) { this( objectsParser, k8sEnvProvisioner, envVars, KubernetesEnvironment.TYPE, devfileEndpointsExposure, kubernetesBasedComponentTypes); } protected KubernetesComponentToWorkspaceApplier( KubernetesRecipeParser objectsParser, KubernetesEnvironmentProvisioner k8sEnvProvisioner, EnvVars envVars, String environmentType, String devfileEndpointsExposure, Set kubernetesBasedComponentTypes) { this.objectsParser = objectsParser; this.k8sEnvProvisioner = k8sEnvProvisioner; this.environmentType = environmentType; this.kubernetesBasedComponentTypes = kubernetesBasedComponentTypes; this.envVars = envVars; this.devfileEndpointsExposure = devfileEndpointsExposure; } /** * Applies changes on workspace config according to the specified kubernetes/openshift component. * * @param workspaceConfig workspace config on which changes should be applied * @param k8sComponent kubernetes/openshift component that should be applied * @param contentProvider content provider that may be used for external component resource * fetching * @throws IllegalArgumentException if specified workspace config or plugin component is null * @throws IllegalArgumentException if specified component has type different from chePlugin * @throws DevfileException if specified content provider is null while kubernetes/openshift * component required external file content * @throws DevfileException if external file content is empty or any error occurred during content * retrieving */ @Override public void apply( WorkspaceConfigImpl workspaceConfig, ComponentImpl k8sComponent, FileContentProvider contentProvider) throws DevfileException { checkArgument(workspaceConfig != null, "Workspace config must not be null"); checkArgument(k8sComponent != null, "Component must not be null"); checkArgument( kubernetesBasedComponentTypes.contains(k8sComponent.getType()), format("Plugin must have %s type", String.join(" or ", kubernetesBasedComponentTypes))); String componentContent = retrieveContent(k8sComponent, contentProvider); final List componentObjects = prepareComponentObjects(k8sComponent, componentContent); List podsData = getPodDatas(componentObjects); if (!k8sComponent.getEnv().isEmpty()) { podsData.forEach(p -> envVars.apply(p, k8sComponent.getEnv())); } Map machineConfigs = prepareMachineConfigs(podsData, k8sComponent); linkCommandsToMachineName(workspaceConfig, k8sComponent, machineConfigs.keySet()); k8sEnvProvisioner.provision(workspaceConfig, environmentType, componentObjects, machineConfigs); } private List prepareComponentObjects(Component k8sComponent, String componentContent) throws DevfileRecipeFormatException { final List componentObjects; if (!k8sComponent.getSelector().isEmpty()) { componentObjects = SelectorFilter.filter( new ArrayList<>(unmarshalComponentObjects(k8sComponent, componentContent)), k8sComponent.getSelector()); } else { componentObjects = new ArrayList<>(unmarshalComponentObjects(k8sComponent, componentContent)); } applyEntrypoints(k8sComponent.getEntrypoints(), componentObjects); return componentObjects; } /** * Creates map of machine names and corresponding {@link MachineConfigImpl} with component alias * attribute set. */ private Map prepareMachineConfigs( List podsData, ComponentImpl component) throws DevfileException { Map machineConfigs = new HashMap<>(); for (PodData podData : podsData) { List containers = new ArrayList<>(); containers.addAll(podData.getSpec().getContainers()); containers.addAll(podData.getSpec().getInitContainers()); for (Container container : containers) { String machineName = machineName(podData, container); MachineConfigImpl config = new MachineConfigImpl(); if (!isNullOrEmpty(component.getAlias())) { config.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, component.getAlias()); } provisionVolumes(component, container, config); provisionEndpoints(component, config); machineConfigs.put(machineName, config); } } return machineConfigs; } private void provisionEndpoints(Component component, MachineConfigImpl config) { config .getServers() .putAll( convertEndpointsIntoServers( component.getEndpoints(), !SINGLE_HOST_STRATEGY.equals(devfileEndpointsExposure))); } private void provisionVolumes( ComponentImpl component, Container container, MachineConfigImpl config) throws DevfileException { for (org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl componentVolume : component.getVolumes()) { Optional sameNameMount = container.getVolumeMounts().stream() .filter(vm -> vm.getName().equals(componentVolume.getName())) .findFirst(); if (sameNameMount.isPresent() && sameNameMount.get().getMountPath().equals(componentVolume.getContainerPath())) { continue; } else if (sameNameMount.isPresent()) { throw new DevfileException( format( "Conflicting volume with same name ('%s') but different path ('%s') found for component '%s' and its container '%s'.", componentVolume.getName(), componentVolume.getContainerPath(), getIdentifiableComponentName(component), container.getName())); } if (container.getVolumeMounts().stream() .anyMatch(vm -> vm.getMountPath().equals(componentVolume.getContainerPath()))) { throw new DevfileException( format( "Conflicting volume with same path ('%s') but different name ('%s') found for component '%s' and its container '%s'.", componentVolume.getContainerPath(), componentVolume.getName(), getIdentifiableComponentName(component), container.getName())); } config .getVolumes() .put( componentVolume.getName(), new VolumeImpl().withPath(componentVolume.getContainerPath())); } } private String retrieveContent(Component recipeComponent, FileContentProvider fileContentProvider) throws DevfileException { checkArgument(fileContentProvider != null, "Content provider must not be null"); if (!isNullOrEmpty(recipeComponent.getReferenceContent())) { return recipeComponent.getReferenceContent(); } String recipeFileContent; try { recipeFileContent = fileContentProvider.fetchContent(recipeComponent.getReference()); } catch (DevfileException e) { throw new DevfileException( format( "Fetching content of file `%s` specified in `reference` field of component `%s` is not supported. " + "Please provide its content in `referenceContent` field. Cause: %s", recipeComponent.getReference(), getIdentifiableComponentName(recipeComponent), e.getMessage()), e); } catch (IOException e) { throw new DevfileException( format( "Error during recipe content retrieval for component '%s' with type '%s': %s", getIdentifiableComponentName(recipeComponent), recipeComponent.getType(), e.getMessage()), e); } if (isNullOrEmpty(recipeFileContent)) { throw new DevfileException( format( "The reference file '%s' defined in component '%s' is empty.", recipeComponent.getReference(), getIdentifiableComponentName(recipeComponent))); } return recipeFileContent; } /** * Set {@link Command#MACHINE_NAME_ATTRIBUTE} to commands which are configured in the specified * component. * *

Machine name will be set only if the specified recipe objects has the only one container. */ private void linkCommandsToMachineName( WorkspaceConfig workspaceConfig, Component component, Set machinesNames) { List componentCommands = workspaceConfig.getCommands().stream() .filter( c -> component.getAlias() != null && component .getAlias() .equals( c.getAttributes().get(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE))) .collect(toList()); if (componentCommands.isEmpty()) { return; } if (machinesNames.size() != 1) { // many or no pods - can't estimate the name because of ambiguity or lack of information return; } String machineName = machinesNames.iterator().next(); componentCommands.forEach(c -> c.getAttributes().put(MACHINE_NAME_ATTRIBUTE, machineName)); } private List getPodDatas(List componentsObjects) { List podsData = new ArrayList<>(); componentsObjects.stream() .filter(hasMetadata -> hasMetadata instanceof Pod) .map(hasMetadata -> (Pod) hasMetadata) .forEach(p -> podsData.add(new PodData(p))); componentsObjects.stream() .filter(hasMetadata -> hasMetadata instanceof Deployment) .map(hasMetadata -> (Deployment) hasMetadata) .forEach(d -> podsData.add(new PodData(d))); return podsData; } private List unmarshalComponentObjects( Component k8sComponent, String componentreferenceContent) throws DevfileRecipeFormatException { try { return unmarshal(componentreferenceContent); } catch (DevfileRecipeFormatException e) { throw new DevfileRecipeFormatException( format( "Error occurred during parsing list from file %s for component '%s': %s", k8sComponent.getReference(), getIdentifiableComponentName(k8sComponent), e.getMessage()), e); } } private List unmarshal(String recipeContent) throws DevfileRecipeFormatException { try { return objectsParser.parse(recipeContent); } catch (Exception e) { throw new DevfileRecipeFormatException(e.getMessage(), e); } } private void applyEntrypoints(List entrypoints, List list) { entrypoints.forEach(ep -> applyEntrypoint(ep, list)); } private void applyEntrypoint(Entrypoint entrypoint, List list) { ContainerSearch search = new ContainerSearch( entrypoint.getParentName(), entrypoint.getParentSelector(), entrypoint.getContainerName()); List cs = search.search(list); List command = entrypoint.getCommand(); List args = entrypoint.getArgs(); for (Container c : cs) { c.setCommand(command); c.setArgs(args); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import static java.lang.String.format; import static org.eclipse.che.api.workspace.server.devfile.Components.getIdentifiableComponentName; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.HasMetadata; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.core.model.workspace.devfile.Entrypoint; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.server.devfile.validator.ComponentIntegrityValidator; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser; public class KubernetesComponentValidator implements ComponentIntegrityValidator { private final KubernetesRecipeParser kubernetesRecipeParser; private final Set k8sBasedComponentTypes; @Inject public KubernetesComponentValidator( KubernetesRecipeParser kubernetesRecipeParser, @Named(KubernetesDevfileBindings.KUBERNETES_BASED_COMPONENTS_KEY_NAME) Set k8sBasedComponentTypes) { this.kubernetesRecipeParser = kubernetesRecipeParser; this.k8sBasedComponentTypes = k8sBasedComponentTypes; } @Override public void validateComponent(Component component, FileContentProvider contentProvider) throws DevfileFormatException { try { List selectedObjects = validateSelector(component, contentProvider); validateEntrypointSelector(component, selectedObjects); } catch (Exception e) { throw new DevfileFormatException( format( "Failed to validate content reference of component '%s' of type '%s': %s", getIdentifiableComponentName(component), component.getType(), e.getMessage()), e); } } /** * Validates that the selector, if any, selects some objects from the component's referenced * content. Only does anything for kubernetes and openshift components. * * @param component the component to check * @param contentProvider the content provider to use when fetching content * @return the list of referenced objects matching the selector or empty list * @throws ValidationException on failure to validate the referenced content * @throws InfrastructureException on failure to parse the referenced content * @throws IOException on failure to retrieve the referenced content * @throws DevfileException if the selector filters out all referenced objects */ private List validateSelector( Component component, FileContentProvider contentProvider) throws ValidationException, InfrastructureException, IOException, DevfileException { if (!k8sBasedComponentTypes.contains(component.getType())) { return Collections.emptyList(); } List content = getReferencedKubernetesList(component, contentProvider); Map selector = component.getSelector(); if (selector == null || selector.isEmpty()) { return content; } content = SelectorFilter.filter(content, selector); if (content.isEmpty()) { throw new DevfileException( format( "The selector of the component '%s' of type '%s' filters out all objects from" + " the list.", getIdentifiableComponentName(component), component.getType())); } return content; } private void validateEntrypointSelector(Component component, List filteredObjects) throws DevfileException { if (component.getEntrypoints() == null || component.getEntrypoints().isEmpty()) { return; } for (Entrypoint ep : component.getEntrypoints()) { ContainerSearch search = new ContainerSearch(ep.getParentName(), ep.getParentSelector(), ep.getContainerName()); List cs = search.search(filteredObjects); if (cs.isEmpty()) { throw new DevfileFormatException( format( "Component '%s' of type '%s' contains an entry point that doesn't match any" + " container.", getIdentifiableComponentName(component), component.getType())); } } } private List getReferencedKubernetesList( Component component, FileContentProvider contentProvider) throws ValidationException, InfrastructureException, IOException, DevfileException { List content; if (component.getReferenceContent() != null) { content = kubernetesRecipeParser.parse(component.getReferenceContent()); } else if (component.getReference() != null) { String data = contentProvider.fetchContent(component.getReference()); content = kubernetesRecipeParser.parse(data); } else { content = Collections.emptyList(); } return content; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesDevfileBindings.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import com.google.inject.Binder; import com.google.inject.multibindings.MapBinder; import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; /** * A utility class to ease the binding of Kubernetes-related devfile bindings in a Guice module. * *

Consult the individual methods to see if you need to use them. */ public class KubernetesDevfileBindings { public static final String ALLOWED_ENVIRONMENT_TYPE_UPGRADES_KEY_NAME = "allowedEnvironmentTypeUpgrades"; public static final String KUBERNETES_BASED_ENVIRONMENTS_KEY_NAME = "kubernetesBasedEnvironments"; public static final String KUBERNETES_BASED_COMPONENTS_KEY_NAME = "kubernetesBasedComponents"; /** * Any workspace environments based on Kubernetes recipes need to register the binding using this * method so that the {@link KubernetesComponentProvisioner} and {@link * KubernetesEnvironmentProvisioner} can work properly with these environments. * * @param baseBinder the binder available in the Guice module calling this method. * @param environmentTypes the environment types to be registered as handled by Kubernetes recipes */ public static void addKubernetesBasedEnvironmentTypeBindings( Binder baseBinder, String... environmentTypes) { Multibinder binder = Multibinder.newSetBinder( baseBinder, String.class, Names.named(KUBERNETES_BASED_ENVIRONMENTS_KEY_NAME)); for (String envType : environmentTypes) { binder.addBinding().toInstance(envType); } } /** * Any devfile components based on Kubernetes recipes need to register the binding using this * method so that the {@link KubernetesComponentProvisioner} and {@link * KubernetesComponentToWorkspaceApplier} can work properly with these components. * * @param baseBinder the binder available in the Guice module calling this method. * @param componentTypes the component types to be registered as handled by Kubernetes recipes */ public static void addKubernetesBasedComponentTypeBindings( Binder baseBinder, String... componentTypes) { Multibinder binder = Multibinder.newSetBinder( baseBinder, String.class, Names.named(KUBERNETES_BASED_COMPONENTS_KEY_NAME)); for (String envType : componentTypes) { binder.addBinding().toInstance(envType); } } /** * It is possible "upgrade" a kubernetes-based environment to a more specific type (e.g. a * Kubernetes can be upgraded to Openshift environment, because Openshift is compatible with * Kubernetes, but an Openshift environment cannot be "upgraded" Kubernetes environment, because * Kubernetes is not itself compatible with Openshift). * * @param baseBinder the binder available in the Guice module calling this method * @param targetEnvironmentType the environment type to upgrade to, if possible * @param baseEnvironmentTypes the environments from which it is possible to upgrade to the target * environment type. */ public static void addAllowedEnvironmentTypeUpgradeBindings( Binder baseBinder, String targetEnvironmentType, String... baseEnvironmentTypes) { MapBinder binder = MapBinder.newMapBinder( baseBinder, String.class, String.class, Names.named(ALLOWED_ENVIRONMENT_TYPE_UPGRADES_KEY_NAME)) .permitDuplicates(); for (String baseType : baseEnvironmentTypes) { binder.addBinding(targetEnvironmentType).toInstance(baseType); } } private KubernetesDevfileBindings() {} } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesEnvironmentProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import static java.lang.String.format; import static java.util.Collections.emptyMap; import static org.eclipse.che.api.workspace.shared.Constants.PROJECTS_VOLUME_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesDevfileBindings.ALLOWED_ENVIRONMENT_TYPE_UPGRADES_KEY_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesDevfileBindings.KUBERNETES_BASED_ENVIRONMENTS_KEY_NAME; import com.google.common.annotations.VisibleForTesting; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesListBuilder; import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.utils.Serialization; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.workspace.server.devfile.DevfileRecipeFormatException; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser; /** * Provisions default K8s/OS environment with specified objects (K8s/OS objects, machines) into * {@link WorkspaceConfigImpl}. * * @author Sergii Leshchenko */ public class KubernetesEnvironmentProvisioner { @VisibleForTesting static final String YAML_CONTENT_TYPE = "application/x-yaml"; private final KubernetesRecipeParser objectsParser; private final Map> allowedEnvironmentTypeUpgrades; private final Set k8sBasedEnvTypes; @Inject public KubernetesEnvironmentProvisioner( KubernetesRecipeParser objectsParser, @Named(ALLOWED_ENVIRONMENT_TYPE_UPGRADES_KEY_NAME) Map> allowedEnvironmentTypeUpgrades, @Named(KUBERNETES_BASED_ENVIRONMENTS_KEY_NAME) Set k8sBasedEnvTypes) { this.objectsParser = objectsParser; this.allowedEnvironmentTypeUpgrades = allowedEnvironmentTypeUpgrades; this.k8sBasedEnvTypes = k8sBasedEnvTypes; } /** * Provisions default K8s/OS environment with specified objects (K8s/OS objects, machines) into * {@link WorkspaceConfigImpl}. * *

If there is already a default environment with kubernetes/openshift recipe then content will * be updated with result or merging existing objects and specified ones. * * @param workspaceConfig workspace where recipe should be provisioned * @param environmentType type of environment that should be provisioned. Should be one of the * Kubernetes-based environments. * @param componentObjects objects that should be provisioned into the workspace config * @param machines machines that should be provisioned into the workspace config * @throws DevfileRecipeFormatException if exception occurred during existing environment parsing * @throws DevfileRecipeFormatException if exception occurred during kubernetes object * serialization * @throws DevfileException if any other exception occurred */ public void provision( WorkspaceConfigImpl workspaceConfig, String environmentType, List componentObjects, Map machines) throws DevfileException, DevfileRecipeFormatException { String defaultEnv = workspaceConfig.getDefaultEnv(); EnvironmentImpl environment = workspaceConfig.getEnvironments().get(defaultEnv); if (environment == null) { checkItemsHasUniqueKindToName(componentObjects); RecipeImpl recipe = new RecipeImpl(environmentType, YAML_CONTENT_TYPE, asYaml(componentObjects), null); String envName = "default"; EnvironmentImpl env = new EnvironmentImpl(recipe, emptyMap()); env.getMachines().putAll(machines); workspaceConfig.getEnvironments().put(envName, env); workspaceConfig.setDefaultEnv(envName); } else { RecipeImpl envRecipe = environment.getRecipe(); for (Entry machineEntry : machines.entrySet()) { if (environment.getMachines().put(machineEntry.getKey(), machineEntry.getValue()) != null) { throw new DevfileException( format("Environment already contains machine '%s'", machineEntry.getKey())); } } environment.getMachines().putAll(machines); // check if it is needed to update recipe type since // kubernetes component is compatible with openshift but not vice versa Set allowedEnvTypeBases = allowedEnvironmentTypeUpgrades.get(environmentType); if (allowedEnvTypeBases != null) { envRecipe.setType(environmentType); } // workspace already has k8s/OS recipe // it is needed to merge existing recipe objects with component's ones List envObjects = unmarshalObjects(envRecipe); mergeProjectsPVC(envObjects, componentObjects); envObjects.addAll(componentObjects); checkItemsHasUniqueKindToName(envObjects); envRecipe.setContent(asYaml(envObjects)); } } private void mergeProjectsPVC(List envObjects, List componentObjects) { componentObjects.removeIf( co -> co instanceof PersistentVolumeClaim && co.getMetadata().getName().equals(PROJECTS_VOLUME_NAME) && envObjects.stream() .filter(envObject -> envObject instanceof PersistentVolumeClaim) .anyMatch(pvc -> pvc.equals(co))); } private List unmarshalObjects(RecipeImpl k8sRecipe) throws DevfileException { if (!k8sBasedEnvTypes.contains(k8sRecipe.getType())) { String allowedEnvTypes = String.join(" or ", k8sBasedEnvTypes); throw new DevfileException( format( "Kubernetes component can only be applied to a workspace with any of %s recipe type" + " but workspace has a recipe of type '%s'", allowedEnvTypes, k8sRecipe.getType())); } return unmarshal(k8sRecipe.getContent()); } /** * Makes sure that all items of the specified list have unique names per kind. * * @param list the list to check * @throws DevfileFormatException if objects list contains item with no unique combination of kind * and name */ private void checkItemsHasUniqueKindToName(List list) throws DevfileFormatException { Set> uniqueKindToName = new HashSet<>(); for (HasMetadata hasMeta : list) { if (!uniqueKindToName.add(new Pair<>(hasMeta.getKind(), hasMeta.getMetadata().getName()))) { throw new DevfileFormatException( format( "Components can not have objects with the same name and kind but there are multiple objects with kind '%s' and name '%s'", hasMeta.getKind(), hasMeta.getMetadata().getName())); } } } private String asYaml(List list) throws DevfileRecipeFormatException { try { return Serialization.asYaml(new KubernetesListBuilder().withItems(list).build()); } catch (KubernetesClientException e) { throw new DevfileRecipeFormatException( format( "Unable to deserialize objects to store them in workspace config. Error: %s", e.getMessage()), e); } } private List unmarshal(String recipeContent) throws DevfileRecipeFormatException { try { return objectsParser.parse(recipeContent); } catch (Exception e) { throw new DevfileRecipeFormatException(e.getMessage(), e); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/SelectorFilter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import static java.util.stream.Collectors.toCollection; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.ObjectMeta; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.eclipse.che.commons.annotation.Nullable; /** Helper class to filter Kubernetes objects by a selector. */ public class SelectorFilter { /** * Uses the {@link #test(ObjectMeta, Map)} method to filter the provided list. * * @param list the list to filter * @param selector the selector to match the metadata of the objects in the list with * @return the filtered list */ public static List filter(List list, Map selector) { return list.stream() .filter(o -> test(o.getMetadata(), selector)) .collect(toCollection(ArrayList::new)); } /** * Returns true is specified object is matched by specified selector, false otherwise. * *

An empty selector is considered to match anything. * * @param metadata object metadata to check matching * @param selector the selector to match the metadata with */ public static boolean test(@Nullable ObjectMeta metadata, Map selector) { if (selector.isEmpty()) { // anything matches if we have nothing to select with return true; } if (metadata == null) { return false; } Map labels = metadata.getLabels(); if (labels == null) { return false; } return labels.entrySet().containsAll(selector.entrySet()); } private SelectorFilter() { throw new AssertionError("Thou shall not instantiate me!"); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/docker/auth/UserSpecificDockerRegistryCredentialsProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.docker.auth; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.Base64; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.DockerAuthConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.DockerAuthConfigs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class for reading credentials for docker registries from user preferences. * * @author Mykola Morhun */ @Singleton public class UserSpecificDockerRegistryCredentialsProvider { private static final String DOCKER_REGISTRY_CREDENTIALS_KEY = "dockerCredentials"; private static final Logger LOG = LoggerFactory.getLogger(UserSpecificDockerRegistryCredentialsProvider.class); private PreferenceManager preferenceManager; @Inject public UserSpecificDockerRegistryCredentialsProvider(PreferenceManager preferenceManager) { this.preferenceManager = preferenceManager; } /** * Gets and decode credentials for docker registries from user preferences. If it hasn't saved * yet, {@code AuthConfigs} with empty map will be returned. * * @return docker registry credentials from user preferences or null when preferences can't be * retrieved or parsed */ @Nullable public DockerAuthConfigs getCredentials() { try { String encodedCredentials = preferenceManager .find( EnvironmentContext.getCurrent().getSubject().getUserId(), DOCKER_REGISTRY_CREDENTIALS_KEY) .get(DOCKER_REGISTRY_CREDENTIALS_KEY); String credentials = encodedCredentials != null ? new String(Base64.getDecoder().decode(encodedCredentials), "UTF-8") : "{}"; return DtoFactory.newDto(DockerAuthConfigs.class) .withConfigs( DtoFactory.getInstance().createMapDtoFromJson(credentials, DockerAuthConfig.class)); } catch (Exception e) { LOG.warn(e.getLocalizedMessage()); return null; } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/CheInstallationLocation.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import com.google.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; /** * This class checks the KUBERNETES_NAMESPACE and POD_NAMESPACE environment variables to determine * what namespace Che is installed in. Users should use this class to retrieve the installation * namespace name. * * @author Tom George */ @Singleton public class CheInstallationLocation { @Inject(optional = true) @Named("env.KUBERNETES_NAMESPACE") String kubernetesNamespace; @Inject(optional = true) @Named("env.POD_NAMESPACE") String podNamespace; /** * @return The name of the namespace where Che is installed or null if both {@code * KUBERNETES_NAMESPACE} and {@code POD_NAMESPACE} environment variables are not set * @throws InfrastructureException when both {@code KUBERNETES_NAMESPACE} and {@code * POD_NAMESPACE} are null */ public String getInstallationLocationNamespace() throws InfrastructureException { if (kubernetesNamespace == null && podNamespace == null) { throw new InfrastructureException( "Neither KUBERNETES_NAMESPACE nor POD_NAMESPACE is defined. Unable to determine Che installation location"); } return kubernetesNamespace == null ? podNamespace : kubernetesNamespace; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironment.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.PodTemplateSpec; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe; /** * Holds objects of Kubernetes environment. * * @author Sergii Leshchenko */ public class KubernetesEnvironment extends InternalEnvironment { public static final String TYPE = "kubernetes"; private final Map pods; private final Map deployments; /** * Stores abstracted spec and meta from either a deployment or pod. * *

{@link PodData} */ private final Map podData; private final Map services; private final Map ingresses; private final Map persistentVolumeClaims; private final Map secrets; private final Map configMaps; private final Map> injectablePods; /** Returns builder for creating environment from blank {@link KubernetesEnvironment}. */ public static Builder builder() { return new Builder(); } /** * Returns builder for creating environment based on specified {@link InternalEnvironment}. * *

It means that {@link InternalEnvironment} specific fields like machines, warnings will be * preconfigured in Builder. */ public static Builder builder(InternalEnvironment internalEnvironment) { return new Builder(internalEnvironment); } public KubernetesEnvironment(KubernetesEnvironment k8sEnv) { this( k8sEnv, k8sEnv.getPodsCopy(), k8sEnv.getDeploymentsCopy(), k8sEnv.getServices(), k8sEnv.getIngresses(), k8sEnv.getPersistentVolumeClaims(), k8sEnv.getSecrets(), k8sEnv.getConfigMaps()); } protected KubernetesEnvironment( InternalEnvironment internalEnvironment, Map pods, Map deployments, Map services, Map ingresses, Map persistentVolumeClaims, Map secrets, Map configMaps) { super(internalEnvironment); setType(TYPE); this.pods = new HashMap<>(pods); this.deployments = new HashMap<>(deployments); this.services = services; this.ingresses = ingresses; this.persistentVolumeClaims = persistentVolumeClaims; this.secrets = secrets; this.configMaps = configMaps; this.podData = new HashMap<>(); this.injectablePods = new HashMap<>(); pods.forEach((name, pod) -> podData.put(name, new PodData(pod))); deployments.forEach((name, deployment) -> podData.put(name, new PodData(deployment))); } protected KubernetesEnvironment( InternalRecipe internalRecipe, Map machines, List warnings, Map pods, Map deployments, Map services, Map ingresses, Map persistentVolumeClaims, Map secrets, Map configMaps) { super(internalRecipe, machines, warnings); setType(TYPE); this.pods = pods; this.deployments = deployments; this.services = services; this.ingresses = ingresses; this.persistentVolumeClaims = persistentVolumeClaims; this.secrets = secrets; this.configMaps = configMaps; this.podData = new HashMap<>(); this.injectablePods = new HashMap<>(); pods.forEach((name, pod) -> podData.put(name, new PodData(pod))); deployments.forEach((name, deployment) -> podData.put(name, new PodData(deployment))); } @Override public KubernetesEnvironment setType(String type) { return (KubernetesEnvironment) super.setType(type); } /** * Returns pods that should be created when environment starts. * *

Note: This map should not be changed, as it will only return pods and not * deployments. If objects in the map need to be changed, see {@link #getPodsData()} * *

If pods need to be added to the environment, then {@link #addPod(Pod)} should be used * instead. */ public Map getPodsCopy() { return ImmutableMap.copyOf(pods); } /** * Returns deployments that should be created when environment starts. * *

Note: This map should not be changed. If objects in the map need to be changed, see * {@link #getPodsData()} * *

If pods need to be added to the environment, then {@link #addPod(Pod)} should be used * instead. */ public Map getDeploymentsCopy() { return ImmutableMap.copyOf(deployments); } /** * Returns {@link PodData} representing the metadata and pod spec of objects (pods or deployments) * that should be created when environment starts. The data returned by this method represents all * deployment and pod objects that form the workspace, and should be used when provisioning or * performing any action that needs to see every object in the environment. * *

If pods need to be added to the environment, then {@link #addPod(Pod)} should be used * instead. */ public Map getPodsData() { return ImmutableMap.copyOf(podData); } /** * Add a pod to the current environment. This method is necessary as the map returned by {@link * #getPodsCopy()} is a copy. This method also adds the relevant data to {@link #getPodsData()}. */ public void addPod(Pod pod) { String podName = pod.getMetadata().getName(); pods.put(podName, pod); podData.put(podName, new PodData(pod.getSpec(), pod.getMetadata())); } /** * Get the pods that are meant to be injected into other deployments, like JWT proxy. * *

The keys in the returned map are machine names of machines that require the pods to be * injected into their deployments. The values are maps of the injectable pods keyed by their * names. */ public Map> getInjectablePodsCopy() { return ImmutableMap.copyOf(injectablePods); } /** * An injectable pod is a pod that is intended to be merged into all deployments that require it. * This is established by tracking the names of the machines that require this additional pod. * * @param requiringMachine the name of the machine that has been determined to require this * additional pod * @param injectablePodMachineName the name of the injectable pod * @param pod the pod to merge into the deployment containing the machine */ public void addInjectablePod(String requiringMachine, String injectablePodMachineName, Pod pod) { this.injectablePods .computeIfAbsent(requiringMachine, __ -> new HashMap<>()) .put(injectablePodMachineName, pod); this.podData.put(injectablePodMachineName, new PodData(pod, PodRole.INJECTABLE)); } /** Returns services that should be created when environment starts. */ public Map getServices() { return services; } /** Returns ingresses that should be created when environment starts. */ public Map getIngresses() { return ingresses; } /** Returns PVCs that should be created when environment starts. */ public Map getPersistentVolumeClaims() { return persistentVolumeClaims; } /** Returns secrets that should be created when environment starts. */ public Map getSecrets() { return secrets; } /** Returns config maps that should be created when environment starts. */ public Map getConfigMaps() { return configMaps; } public static class Builder { protected final InternalEnvironment internalEnvironment; protected final Map pods = new HashMap<>(); protected final Map deployments = new HashMap<>(); protected final Map podData = new HashMap<>(); protected final Map services = new HashMap<>(); protected final Map ingresses = new HashMap<>(); protected final Map pvcs = new HashMap<>(); protected final Map secrets = new HashMap<>(); protected final Map configMaps = new HashMap<>(); protected final Map attributes = new HashMap<>(); protected Builder() { this.internalEnvironment = new InternalEnvironment() {}; } public Builder(InternalEnvironment internalEnvironment) { this.internalEnvironment = internalEnvironment; } public Builder setInternalRecipe(InternalRecipe internalRecipe) { internalEnvironment.setRecipe(internalRecipe); return this; } public Builder setMachines(Map machines) { internalEnvironment.setMachines(new HashMap<>(machines)); return this; } public Builder setWarnings(List warnings) { internalEnvironment.setWarnings(new ArrayList<>(warnings)); return this; } public Builder setCommands(List commands) { internalEnvironment.setCommands(new ArrayList<>(commands)); return this; } public Builder setPods(Map pods) { this.pods.putAll(pods); return this; } public Builder setDeployments(Map deployments) { this.deployments.putAll(deployments); return this; } public Builder setServices(Map services) { this.services.putAll(services); return this; } public Builder setIngresses(Map ingresses) { this.ingresses.putAll(ingresses); return this; } public Builder setPersistentVolumeClaims(Map pvcs) { this.pvcs.putAll(pvcs); return this; } public Builder setSecrets(Map secrets) { this.secrets.putAll(secrets); return this; } public Builder setConfigMaps(Map configMaps) { this.configMaps.putAll(configMaps); return this; } public Builder setAttributes(Map attributes) { this.attributes.putAll(attributes); return this; } public KubernetesEnvironment build() { return new KubernetesEnvironment( internalEnvironment, pods, deployments, services, ingresses, pvcs, secrets, configMaps); } } public enum PodRole { DEPLOYMENT, INJECTABLE } /** * Abstraction of pod, since deployments store pod spec and meta within a PodSpecTemplate instead * of a pod object. This class allows us to use one class to support passing of the relevant parts * of a pod or deployment when it comes to provisioning. * *

The methods for accessing metadata and spec are identical to that of the Pod class (i.e. * {@code getSpec()} and {@code getMetadata()}. * *

This class additionally specifies the role of the pod in the final workspace which the * provisioners can use to specialize their behavior for. */ public static class PodData { private PodSpec podSpec; private ObjectMeta podMeta; private PodRole role; public PodData(PodSpec podSpec, ObjectMeta podMeta, PodRole role) { this.podSpec = podSpec; this.podMeta = podMeta; this.role = role; } public PodData(PodSpec podSpec, ObjectMeta podMeta) { this(podSpec, podMeta, PodRole.DEPLOYMENT); } public PodData(Pod pod) { this(pod.getSpec(), pod.getMetadata()); } public PodData(Pod pod, PodRole role) { this(pod.getSpec(), pod.getMetadata(), role); } public PodData(Deployment deployment) { PodTemplateSpec podTemplate = deployment.getSpec().getTemplate(); // it is not required for PodTemplate to have name specified // but many of Che Server components rely that PodData has name // so, provision name from deployment if it is missing ObjectMeta podTemplateMeta = podTemplate.getMetadata(); if (podTemplateMeta == null) { podTemplate.setMetadata( new ObjectMetaBuilder().withName(deployment.getMetadata().getName()).build()); } else { if (podTemplateMeta.getName() == null) { podTemplateMeta.setName(deployment.getMetadata().getName()); } } this.podSpec = podTemplate.getSpec(); this.podMeta = podTemplate.getMetadata(); this.role = PodRole.DEPLOYMENT; } public PodSpec getSpec() { return podSpec; } public void setSpec(PodSpec podSpec) { this.podSpec = podSpec; } public ObjectMeta getMetadata() { return podMeta; } public void setMetadata(ObjectMeta podMeta) { this.podMeta = podMeta; } public PodRole getRole() { return role; } public void setRole(PodRole role) { this.role = role; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof PodData)) { return false; } final PodData that = (PodData) obj; return Objects.equals(podSpec, that.podSpec) && Objects.equals(podMeta, that.podMeta) && Objects.equals(role, that.role); } @Override public int hashCode() { return Objects.hash(podSpec, podMeta, role); } @Override public String toString() { return "PodData{" + "podSpec=" + podSpec + ", podMeta=" + podMeta + ", role=" + role + '}'; } } @Override public String toString() { return "KubernetesEnvironment{" + "pods=" + pods + ", deployments=" + deployments + ", podData=" + podData + ", services=" + services + ", ingresses=" + ingresses + ", persistentVolumeClaims=" + persistentVolumeClaims + ", secrets=" + secrets + ", configMaps=" + configMaps + ", injectablePods=" + injectablePods + "} " + super.toString(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import static java.lang.String.format; import static org.eclipse.che.workspace.infrastructure.kubernetes.environment.PodMerger.DEPLOYMENT_NAME_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabel; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.setSelector; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.inject.Inject; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.workspace.server.model.impl.WarningImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe; import org.eclipse.che.api.workspace.server.spi.environment.MachineConfigsValidator; import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; /** * Parses {@link InternalEnvironment} into {@link KubernetesEnvironment}. * * @author Sergii Leshchenko */ public class KubernetesEnvironmentFactory extends InternalEnvironmentFactory { private final KubernetesRecipeParser recipeParser; private final KubernetesEnvironmentValidator envValidator; private final PodMerger podMerger; @Inject public KubernetesEnvironmentFactory( RecipeRetriever recipeRetriever, MachineConfigsValidator machinesValidator, KubernetesRecipeParser recipeParser, KubernetesEnvironmentValidator envValidator, PodMerger podMerger) { super(recipeRetriever, machinesValidator); this.recipeParser = recipeParser; this.envValidator = envValidator; this.podMerger = podMerger; } @Override protected KubernetesEnvironment doCreate( @Nullable InternalRecipe recipe, Map machines, List sourceWarnings) throws InfrastructureException, ValidationException { checkNotNull(recipe, "Null recipe is not supported by kubernetes environment factory"); List warnings = new ArrayList<>(); if (sourceWarnings != null) { warnings.addAll(sourceWarnings); } final List recipeObjects = recipeParser.parse(recipe); Map pods = new HashMap<>(); Map deployments = new HashMap<>(); Map services = new HashMap<>(); Map configMaps = new HashMap<>(); Map pvcs = new HashMap<>(); Map secrets = new HashMap<>(); boolean isAnyIngressPresent = false; for (HasMetadata object : recipeObjects) { checkNotNull(object.getKind(), "Environment contains object without specified kind field"); checkNotNull(object.getMetadata(), "%s metadata must not be null", object.getKind()); checkNotNull(object.getMetadata().getName(), "%s name must not be null", object.getKind()); if (object instanceof Pod) { putInto(pods, object.getMetadata().getName(), (Pod) object); } else if (object instanceof Deployment) { putInto(deployments, object.getMetadata().getName(), (Deployment) object); } else if (object instanceof Service) { putInto(services, object.getMetadata().getName(), (Service) object); } else if (object instanceof Ingress) { isAnyIngressPresent = true; } else if (object instanceof PersistentVolumeClaim) { putInto(pvcs, object.getMetadata().getName(), (PersistentVolumeClaim) object); } else if (object instanceof Secret) { putInto(secrets, object.getMetadata().getName(), (Secret) object); } else if (object instanceof ConfigMap) { putInto(configMaps, object.getMetadata().getName(), (ConfigMap) object); } else { throw new ValidationException( format( "Found unknown object type in recipe -- name: '%s', kind: '%s'", object.getMetadata().getName(), object.getKind())); } } if (deployments.size() + pods.size() > 1) { mergePods(pods, deployments, services); } if (isAnyIngressPresent) { warnings.add( new WarningImpl( Warnings.INGRESSES_IGNORED_WARNING_CODE, Warnings.INGRESSES_IGNORED_WARNING_MESSAGE)); } KubernetesEnvironment k8sEnv = KubernetesEnvironment.builder() .setInternalRecipe(recipe) .setMachines(machines) .setWarnings(warnings) .setPods(pods) .setDeployments(deployments) .setServices(services) .setPersistentVolumeClaims(pvcs) .setIngresses(new HashMap<>()) .setSecrets(secrets) .setConfigMaps(configMaps) .build(); envValidator.validate(k8sEnv); return k8sEnv; } /** * Merges the specified pods and deployments to a single Deployment. * *

Note that method will modify the specified collections and put work result there. * * @param pods pods to merge * @param deployments deployments to merge * @param services services to reconfigure to point new deployment * @throws ValidationException if the specified lists has pods with conflicting configuration */ private void mergePods( Map pods, Map deployments, Map services) throws ValidationException { List podsData = Stream.concat( pods.values().stream().map(PodData::new), deployments.values().stream().map(PodData::new)) .collect(Collectors.toList()); Deployment deployment = podMerger.merge(podsData); String deploymentName = deployment.getMetadata().getName(); // provision merged deployment instead of recipe pods/deployments pods.clear(); deployments.clear(); deployments.put(deploymentName, deployment); // multiple pods/deployments are merged to one deployment // to avoid issues because of overriding labels // provision const label and selector to match all services to merged Deployment putLabel( deployment.getSpec().getTemplate().getMetadata(), DEPLOYMENT_NAME_LABEL, deploymentName); services.values().forEach(s -> setSelector(s, DEPLOYMENT_NAME_LABEL, deploymentName)); } /** * Puts the specified key/value pair into the specified map or throw an exception if map already * contains such key. * * @param map the map to put key/value pair * @param key key that should be put * @param value value that should be put * @param type of object to put * @throws ValidationException if the specified map already contains the specified key */ private void putInto(Map map, String key, T value) throws ValidationException { if (map.put(key, value) != null) { String kind = value.getKind(); String name = value.getMetadata().getName(); throw new ValidationException( format( "Environment can not contain two '%s' objects with the same name '%s'", kind, name)); } } private void checkNotNull(Object object, String errorMessage) throws ValidationException { if (object == null) { throw new ValidationException(errorMessage); } } private void checkNotNull(Object object, String messageFmt, Object... messageArguments) throws ValidationException { if (object == null) { throw new ValidationException(format(messageFmt, messageArguments)); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentPodsValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import static org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentValidator.checkArgument; import com.google.common.base.Joiner; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSource; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeMount; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; /** * Validates {@link KubernetesEnvironment#getPodsData()}. * * @author Sergii Leshchenko */ public class KubernetesEnvironmentPodsValidator { public void validate(KubernetesEnvironment env) throws ValidationException { checkArgument( !env.getPodsData().isEmpty(), "Environment should contain at least 1 pod or deployment"); ensureHasMetaAndSpec(env.getPodsData()); ensureConfiguredMachinesHaveContainers(env); validatePodVolumes(env); } private void ensureHasMetaAndSpec(Map podsData) throws ValidationException { for (PodData podData : podsData.values()) { if (podData.getMetadata() == null) { throw new ValidationException("Environment contains pod with missing metadata"); } if (podData.getSpec() == null) { throw new ValidationException( String.format("Pod '%s' with missing metadata", podData.getMetadata().getName())); } } } private void validatePodVolumes(KubernetesEnvironment env) throws ValidationException { Set pvcsNames = env.getPersistentVolumeClaims().keySet(); for (PodData pod : env.getPodsData().values()) { Set volumesNames = new HashSet<>(); for (Volume volume : pod.getSpec().getVolumes()) { volumesNames.add(volume.getName()); PersistentVolumeClaimVolumeSource pvcSource = volume.getPersistentVolumeClaim(); if (pvcSource != null && !pvcsNames.contains(pvcSource.getClaimName())) { throw new ValidationException( String.format( "Pod '%s' contains volume '%s' with PVC sources that references missing PVC '%s'", pod.getMetadata().getName(), volume.getName(), pvcSource.getClaimName())); } } List containers = new ArrayList<>(); containers.addAll(pod.getSpec().getContainers()); containers.addAll(pod.getSpec().getInitContainers()); for (Container container : containers) { for (VolumeMount volumeMount : container.getVolumeMounts()) { if (!volumesNames.contains(volumeMount.getName())) { throw new ValidationException( String.format( "Container '%s' in pod '%s' contains volume mount that references missing volume '%s'", container.getName(), pod.getMetadata().getName(), volumeMount.getName())); } } } } } private void ensureConfiguredMachinesHaveContainers(KubernetesEnvironment env) throws ValidationException { Set missingMachines = new HashSet<>(env.getMachines().keySet()); for (PodData pod : env.getPodsData().values()) { if (pod.getSpec() != null && pod.getSpec().getContainers() != null) { for (Container container : pod.getSpec().getContainers()) { missingMachines.remove(Names.machineName(pod, container)); } for (Container container : pod.getSpec().getInitContainers()) { missingMachines.remove(Names.machineName(pod, container)); } } } checkArgument( missingMachines.isEmpty(), "Environment contains machines that are missing in recipe: %s", Joiner.on(", ").join(missingMachines)); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import static java.lang.String.format; import javax.inject.Inject; import org.eclipse.che.api.core.ValidationException; /** * Validates {@link KubernetesEnvironment}. * * @author Sergii Leshchenko */ public class KubernetesEnvironmentValidator { private final KubernetesEnvironmentPodsValidator podsValidator; @Inject public KubernetesEnvironmentValidator(KubernetesEnvironmentPodsValidator podsValidator) { this.podsValidator = podsValidator; } /** * Validates {@link KubernetesEnvironment}. * * @param env environment to perform validation * @throws ValidationException if the specified {@link KubernetesEnvironment} is invalid */ public void validate(KubernetesEnvironment env) throws ValidationException { podsValidator.validate(env); // TODO Implement validation for other Kubernetes objects // https://github.com/eclipse/che/issues/7381 } static void checkArgument(boolean expression, String error) throws ValidationException { if (!expression) { throw new ValidationException(error); } } static void checkArgument( boolean expression, String errorMessageTemplate, Object... errorMessageParams) throws ValidationException { if (!expression) { throw new ValidationException(format(errorMessageTemplate, errorMessageParams)); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesRecipeParser.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.String.format; import com.google.common.collect.ImmutableSet; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClientException; import java.io.ByteArrayInputStream; import java.util.List; import java.util.Set; import javax.inject.Inject; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; /** * Parses Kubernetes objects from recipe. * *

Note that this class can also parse OpenShift specific objects. * * @author Sergii Leshchenko */ public class KubernetesRecipeParser { private static final Set SUPPORTED_CONTENT_TYPES = ImmutableSet.of("application/x-yaml", "text/yaml", "text/x-yaml"); private final KubernetesClientFactory clientFactory; @Inject public KubernetesRecipeParser(KubernetesClientFactory clientFactory) { this.clientFactory = clientFactory; } /** * Parses Kubernetes objects from recipe. * * @param recipe that contains objects to parse * @return parsed objects * @throws IllegalArgumentException if recipe content is null * @throws IllegalArgumentException if recipe content type is null * @throws ValidationException if recipe content has broken format * @throws ValidationException if recipe content has unrecognized objects * @throws InfrastructureException when exception occurred during kubernetes client creation */ public List parse(InternalRecipe recipe) throws ValidationException, InfrastructureException { String content = recipe.getContent(); String contentType = recipe.getContentType(); checkNotNull(contentType, "Recipe content type must not be null"); if (!SUPPORTED_CONTENT_TYPES.contains(contentType)) { throw new ValidationException( format( "Provided environment recipe content type '%s' is unsupported. Supported values are: %s", contentType, String.join(", ", SUPPORTED_CONTENT_TYPES))); } return parse(content); } /** * Parses Kubernetes objects from recipe content. * * @param recipeContent recipe content that should be parsed * @return parsed objects * @throws IllegalArgumentException if recipe content is null * @throws ValidationException if recipe content has broken format * @throws ValidationException if recipe content has unrecognized objects * @throws InfrastructureException when exception occurred during kubernetes client creation */ public List parse(String recipeContent) throws ValidationException, InfrastructureException { checkNotNull(recipeContent, "Recipe content type must not be null"); try { // Behavior: // - If `content` is a single object like Deployment, load().get() will get the object in that // list // - If `content` is a Kubernetes List, load().get() will get the objects in that list // - If `content` is an OpenShift template, load().get() will get the objects in the template // with parameters substituted (e.g. with default values). List parsed = clientFactory.create().load(new ByteArrayInputStream(recipeContent.getBytes())).get(); // needed because Che master namespace is set by K8s API during list loading parsed.stream() .filter(o -> o.getMetadata() != null) .forEach(o -> o.getMetadata().setNamespace(null)); return parsed; } catch (KubernetesClientException e) { // KubernetesClient wraps the error when a JsonMappingException occurs so we need the cause String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage(); if (message.contains("\n")) { // Clean up message if it comes from JsonMappingException. Format is e.g. // `No resource type found for:v1#Route1\n at [...]` message = message.split("\\n", 2)[0]; } throw new ValidationException(format("Could not parse Kubernetes recipe: %s", message)); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/PodMerger.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import static java.lang.String.format; import static org.eclipse.che.api.workspace.shared.Constants.PROJECTS_VOLUME_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putAnnotations; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabels; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.LocalObjectReference; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.PodSecurityContext; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.PodTemplateSpec; import io.fabric8.kubernetes.api.model.Toleration; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; /** * Helps to merge multiple pods to a single deployment. * * @author Sergii Leshchenko */ public class PodMerger { /** Key of pod template metadata that contains deployment name. */ public static final String DEPLOYMENT_NAME_LABEL = "deployment"; private static final String DEFAULT_DEPLOYMENT_NAME = "workspace"; /** * Creates a new deployments that contains all pods from the specified pods data lists. * *

If multiple pods have labels, annotations, additionalProperties with the same key then * override will happen and result pod template will have only the last value. You may need to * reconfigure objects that rely on these value, like selector field in Service. You can use all * of pod template labels for reconfiguring or single {@link #DEFAULT_DEPLOYMENT_NAME} label that * contains deployment name; * * @param podsData the pods data to be merged * @return a new Deployment that have pod template that is result of merging the specified lists * @throws ValidationException if pods can not be merged because of critical names collisions like * volumes names */ public Deployment merge(List podsData) throws ValidationException { Deployment baseDeployment = createEmptyDeployment(DEFAULT_DEPLOYMENT_NAME); PodTemplateSpec basePodTemplate = baseDeployment.getSpec().getTemplate(); ObjectMeta basePodMeta = basePodTemplate.getMetadata(); PodSpec baseSpec = basePodTemplate.getSpec(); Set containerNames = new HashSet<>(); Set initContainerNames = new HashSet<>(); Set volumes = new HashSet<>(); Set pullSecrets = new HashSet<>(); for (PodData podData : podsData) { // if there are entries with such keys then values will be overridden ObjectMeta podMeta = podData.getMetadata(); putLabels(basePodMeta, podMeta.getLabels()); putAnnotations(basePodMeta, podMeta.getAnnotations()); basePodMeta.getAdditionalProperties().putAll(podMeta.getAdditionalProperties()); for (Container container : podData.getSpec().getContainers()) { String containerName = container.getName(); // generate container name to avoid collisions while (!containerNames.add(container.getName())) { containerName = NameGenerator.generate(container.getName(), 4); container.setName(containerName); } // store original recipe machine name Names.putMachineName(basePodMeta, containerName, Names.machineName(podMeta, container)); baseSpec.getContainers().add(container); } for (Container initContainer : podData.getSpec().getInitContainers()) { // generate container name to avoid collisions while (!initContainerNames.add(initContainer.getName())) { initContainer.setName(NameGenerator.generate(initContainer.getName(), 4)); } // store original recipe machine name Names.putMachineName( basePodMeta, initContainer.getName(), Names.machineName(podMeta, initContainer)); baseSpec.getInitContainers().add(initContainer); } for (Volume volume : podData.getSpec().getVolumes()) { if (!volumes.add(volume.getName())) { if (volume.getName().equals(PROJECTS_VOLUME_NAME)) { // project volume already added, can be skipped continue; } throw new ValidationException( format( "Pods have to have volumes with unique names but there are multiple `%s` volumes", volume.getName())); } baseSpec.getVolumes().add(volume); } for (LocalObjectReference pullSecret : podData.getSpec().getImagePullSecrets()) { if (pullSecrets.add(pullSecret.getName())) { // add pull secret only if it is not present yet baseSpec.getImagePullSecrets().add(pullSecret); } } if (podData.getSpec().getTerminationGracePeriodSeconds() != null) { if (baseSpec.getTerminationGracePeriodSeconds() != null) { baseSpec.setTerminationGracePeriodSeconds( Long.max( baseSpec.getTerminationGracePeriodSeconds(), podData.getSpec().getTerminationGracePeriodSeconds())); } else { baseSpec.setTerminationGracePeriodSeconds( podData.getSpec().getTerminationGracePeriodSeconds()); } } baseSpec.setSecurityContext( mergeSecurityContexts( baseSpec.getSecurityContext(), podData.getSpec().getSecurityContext())); baseSpec.setServiceAccount( mergeServiceAccount(baseSpec.getServiceAccount(), podData.getSpec().getServiceAccount())); baseSpec.setServiceAccountName( mergeServiceAccountName( baseSpec.getServiceAccountName(), podData.getSpec().getServiceAccountName())); baseSpec.setNodeSelector( mergeNodeSelector(baseSpec.getNodeSelector(), podData.getSpec().getNodeSelector())); // if there are entries with such keys then values will be overridden baseSpec.getAdditionalProperties().putAll(podData.getSpec().getAdditionalProperties()); // add tolerations to baseSpec if any for (Toleration toleration : podData.getSpec().getTolerations()) { if (!baseSpec.getTolerations().contains(toleration)) { baseSpec.getTolerations().add(toleration); } } } Map matchLabels = new HashMap<>(); matchLabels.put(DEPLOYMENT_NAME_LABEL, baseDeployment.getMetadata().getName()); putLabels(basePodMeta, matchLabels); baseDeployment.getSpec().getSelector().setMatchLabels(matchLabels); return baseDeployment; } private Deployment createEmptyDeployment(String name) { return new DeploymentBuilder() .withMetadata(new ObjectMetaBuilder().withName(name).build()) .withNewSpec() .withNewSelector() .endSelector() .withReplicas(1) .withNewTemplate() .withNewMetadata() .endMetadata() .withSpec(new PodSpec()) .endTemplate() .endSpec() .build(); } private PodSecurityContext mergeSecurityContexts( @Nullable PodSecurityContext a, @Nullable PodSecurityContext b) throws ValidationException { return nonNullOrEqual(a, b, "Cannot merge pods with different security contexts: %s, %s"); } private String mergeServiceAccount(@Nullable String a, @Nullable String b) throws ValidationException { return nonNullOrEqual(a, b, "Cannot merge pods with different service accounts: %s, %s"); } private String mergeServiceAccountName(@Nullable String a, @Nullable String b) throws ValidationException { return nonNullOrEqual(a, b, "Cannot merge pods with different service account names: %s, %s"); } @Nullable private static T nonNullOrEqual(@Nullable T a, @Nullable T b, String errorMessageTemplate) throws ValidationException { if (a == null) { return b; } else if (b == null || a.equals(b)) { return a; } else { throw new ValidationException(String.format(errorMessageTemplate, a, b)); } } private Map mergeNodeSelector( @Nullable Map base, @Nullable Map nodeSelector) { Map mergedMap = base; if (nodeSelector != null && !nodeSelector.isEmpty()) { if (mergedMap == null) { mergedMap = new HashMap<>(); } mergedMap.putAll(nodeSelector); } return mergedMap; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/util/EntryPoint.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment.util; import com.google.common.collect.ImmutableList; import java.util.List; import java.util.Objects; /** Represents an entry-point definition parsed from a string using the {@link EntryPointParser}. */ public final class EntryPoint { private final List command; private final List arguments; public EntryPoint(List command, List arguments) { this.command = ImmutableList.copyOf(command); this.arguments = ImmutableList.copyOf(arguments); } /** * @return unmodifiable list representing the command of the entrypoint */ public List getCommand() { return command; } /** * @return unmodifiable list representing the arguments of the entrypoint */ public List getArguments() { return arguments; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } EntryPoint that = (EntryPoint) o; return Objects.equals(command, that.command) && Objects.equals(arguments, that.arguments); } @Override public int hashCode() { return Objects.hash(command, arguments); } @Override public String toString() { return "EntryPoint{" + "command=" + command + ", arguments=" + arguments + '}'; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/util/EntryPointParser.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment.util; import static java.lang.String.format; import static java.util.Collections.emptyList; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import java.io.IOException; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; /** Can be used to parse container entry-point definition specified as a YAML list of strings. */ public class EntryPointParser { private final YAMLMapper mapper = new YAMLMapper(); /** * Parses the attributes contained in the provided machine config and produces an entry point * definition. * *

This method looks for the values of the {@link MachineConfig#CONTAINER_COMMAND_ATTRIBUTE} * and {@link MachineConfig#CONTAINER_ARGS_ATTRIBUTE} attributews in the provided map and * constructs an Entrypoint instance parsed out of the contents of those attributes. * * @param machineAttributes the attributes of a machine to extract the entry point info from * @return an entry point definition, never null * @throws InfrastructureException on failure to parse the command or arguments */ public EntryPoint parse(Map machineAttributes) throws InfrastructureException { String command = machineAttributes.get(MachineConfig.CONTAINER_COMMAND_ATTRIBUTE); String args = machineAttributes.get(MachineConfig.CONTAINER_ARGS_ATTRIBUTE); List commandList = command == null ? emptyList() : parseAsList(command, MachineConfig.CONTAINER_COMMAND_ATTRIBUTE); List argList = args == null ? emptyList() : parseAsList(args, MachineConfig.CONTAINER_ARGS_ATTRIBUTE); return new EntryPoint(commandList, argList); } /** * Serializes an entry (that might have been produced from {@link #parse(Map)}) back to a string * representation. * * @param entry the command or args entry * @return a serialized representation of the entry */ public String serializeEntry(List entry) { try { return mapper.writer().writeValueAsString(entry); } catch (JsonProcessingException e) { throw new IllegalStateException( format("Failed to serialize list of strings %s to YAML", entry), e); } } private List parseAsList(String data, String attributeName) throws InfrastructureException { try { return mapper.readValue(data, new TypeReference>() {}); } catch (IOException e) { throw new InfrastructureException( format( "Failed to parse the attribute %s as a YAML list. The value was %s", attributeName, data), e.getCause()); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/event/KubernetesRuntimeStoppedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.event; import java.util.Objects; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.workspace.infrastructure.kubernetes.StartSynchronizer; /** * Should published via {@link EventService} when workspace becomes {@link WorkspaceStatus#STOPPED}. * *

Note that is not published by default. It is needed for {@link StartSynchronizer} to * synchronize state in cluster mode. * * @author Sergii Leshchenko */ public class KubernetesRuntimeStoppedEvent { private final String workspaceId; public KubernetesRuntimeStoppedEvent(String workspaceId) { this.workspaceId = workspaceId; } public String getWorkspaceId() { return workspaceId; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof KubernetesRuntimeStoppedEvent)) { return false; } final KubernetesRuntimeStoppedEvent that = (KubernetesRuntimeStoppedEvent) obj; return Objects.equals(workspaceId, that.workspaceId); } @Override public int hashCode() { return Objects.hashCode(workspaceId); } @Override public String toString() { return "KubernetesRuntimeStoppedEvent{" + "workspaceId='" + workspaceId + '\'' + '}'; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/event/KubernetesRuntimeStoppingEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.event; import java.util.Objects; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.workspace.infrastructure.kubernetes.StartSynchronizer; /** * Should published via {@link EventService} when workspace becomes {@link * WorkspaceStatus#STOPPING}. * *

Note that is not published by default. It is needed for {@link StartSynchronizer} to * synchronize state in cluster mode. * * @author Sergii Leshchenko */ public class KubernetesRuntimeStoppingEvent { private final String workspaceId; public KubernetesRuntimeStoppingEvent(String workspaceId) { this.workspaceId = workspaceId; } public String getWorkspaceId() { return workspaceId; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof KubernetesRuntimeStoppingEvent)) { return false; } final KubernetesRuntimeStoppingEvent that = (KubernetesRuntimeStoppingEvent) obj; return Objects.equals(workspaceId, that.workspaceId); } @Override public int hashCode() { return Objects.hashCode(workspaceId); } @Override public String toString() { return "KubernetesRuntimeStoppingEvent{" + "workspaceId='" + workspaceId + '\'' + '}'; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/model/KubernetesMachineImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.model; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; import javax.persistence.MapKeyColumn; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.runtime.Machine; import org.eclipse.che.api.core.model.workspace.runtime.MachineStatus; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; /** * @author Sergii Leshchenko */ @Entity(name = "KubernetesMachine") @Table(name = "che_k8s_machine") @NamedQueries({ @NamedQuery( name = "KubernetesMachine.getByWorkspaceId", query = "SELECT m FROM KubernetesMachine m WHERE m.machineId.workspaceId = :workspaceId") }) public class KubernetesMachineImpl implements Machine { @EmbeddedId private MachineId machineId; @Column(name = "pod_name") private String podName; @Column(name = "container_name") private String containerName; @Column(name = "status") @Enumerated(EnumType.STRING) private MachineStatus status; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "che_k8s_machine_attributes", joinColumns = { @JoinColumn(name = "workspace_id", referencedColumnName = "workspace_id"), @JoinColumn(name = "machine_name", referencedColumnName = "machine_name") }) @MapKeyColumn(name = "attribute_key") @Column(name = "attribute") private Map attributes; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumns({ @JoinColumn(name = "workspace_id", referencedColumnName = "workspace_id"), @JoinColumn(name = "machine_name", referencedColumnName = "machine_name") }) @MapKeyColumn(name = "server_name", insertable = false, updatable = false) private Map servers; public KubernetesMachineImpl() {} public KubernetesMachineImpl( String workspaceId, String machineName, String podName, String containerName, MachineStatus status, Map attributes, Map servers) { this.machineId = new MachineId(workspaceId, machineName); this.podName = podName; this.containerName = containerName; this.status = status; this.attributes = attributes; this.servers = servers.entrySet().stream() .collect( Collectors.toMap( Map.Entry::getKey, e -> new KubernetesServerImpl( workspaceId, machineName, e.getKey(), e.getValue()))); } public MachineStatus getStatus() { return status; } public void setStatus(MachineStatus status) { this.status = status; } public MachineId getMachineId() { return machineId; } public String getPodName() { return podName; } public String getContainerName() { return containerName; } public Map getAttributes() { return attributes; } public Map getServers() { return servers; } public String getName() { return machineId.machineName; } public String getWorkspaceId() { return machineId.workspaceId; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof KubernetesMachineImpl)) { return false; } final KubernetesMachineImpl that = (KubernetesMachineImpl) obj; return Objects.equals(machineId, that.machineId) && Objects.equals(podName, that.podName) && Objects.equals(containerName, that.containerName) && Objects.equals(status, that.status) && getAttributes().equals(that.getAttributes()) && getServers().equals(that.getServers()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(machineId); hash = 31 * hash + Objects.hashCode(podName); hash = 31 * hash + Objects.hashCode(containerName); hash = 31 * hash + Objects.hashCode(status); hash = 31 * hash + getAttributes().hashCode(); hash = 31 * hash + getServers().hashCode(); return hash; } @Override public String toString() { return "KubernetesMachineImpl{" + "machineId=" + machineId + ", podName='" + podName + '\'' + ", containerName='" + containerName + '\'' + ", status=" + status + ", attributes=" + attributes + ", servers=" + servers + '}'; } @Embeddable public static class MachineId { @Column(name = "workspace_id") private String workspaceId; @Column(name = "machine_name") private String machineName; public MachineId() {} public MachineId(String workspaceId, String machineName) { this.workspaceId = workspaceId; this.machineName = machineName; } public String getWorkspaceId() { return workspaceId; } public String getMachineName() { return machineName; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof MachineId)) { return false; } final MachineId that = (MachineId) obj; return Objects.equals(workspaceId, that.workspaceId) && Objects.equals(machineName, that.machineName); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(workspaceId); hash = 31 * hash + Objects.hashCode(machineName); return hash; } @Override public String toString() { return "MachineId{" + "workspaceId='" + workspaceId + '\'' + ", machineName='" + machineName + '\'' + '}'; } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/model/KubernetesRuntimeCommandImpl.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.model; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.StringJoiner; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.devfile.PreviewUrl; import org.eclipse.che.api.workspace.server.model.impl.devfile.PreviewUrlImpl; /** * Data object for {@link Command}. * * @author Serhii Leshchenko */ @Entity(name = "KubernetesRuntimeCommand") @Table(name = "k8s_runtime_command") public class KubernetesRuntimeCommandImpl implements Command { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "name", nullable = false) private String name; @Column(name = "commandline", nullable = false, columnDefinition = "TEXT") private String commandLine; @Column(name = "type", nullable = false) private String type; @Embedded private PreviewUrlImpl previewUrl; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "k8s_runtime_command_attributes", joinColumns = @JoinColumn(name = "command_id")) @MapKeyColumn(name = "name") @Column(name = "value_param", columnDefinition = "TEXT") private Map attributes; public KubernetesRuntimeCommandImpl() {} public KubernetesRuntimeCommandImpl(String name, String commandLine, String type) { this.name = name; this.commandLine = commandLine; this.type = type; } public KubernetesRuntimeCommandImpl(Command command) { this.name = command.getName(); this.commandLine = command.getCommandLine(); this.type = command.getType(); if (command.getPreviewUrl() != null) { this.previewUrl = new PreviewUrlImpl(command.getPreviewUrl()); } this.attributes = command.getAttributes(); } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String getCommandLine() { return commandLine; } public void setCommandLine(String commandLine) { this.commandLine = commandLine; } @Override public PreviewUrl getPreviewUrl() { return previewUrl; } public void setPreviewUrl(PreviewUrlImpl previewUrl) { this.previewUrl = previewUrl; } @Override public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } KubernetesRuntimeCommandImpl that = (KubernetesRuntimeCommandImpl) o; return Objects.equals(id, that.id) && Objects.equals(name, that.name) && Objects.equals(commandLine, that.commandLine) && Objects.equals(type, that.type) && Objects.equals(previewUrl, that.previewUrl) && Objects.equals(attributes, that.attributes); } @Override public int hashCode() { return Objects.hash(id, name, commandLine, type, previewUrl, attributes); } @Override public String toString() { return new StringJoiner(", ", KubernetesRuntimeCommandImpl.class.getSimpleName() + "[", "]") .add("id=" + id) .add("name='" + name + "'") .add("commandLine='" + commandLine + "'") .add("type='" + type + "'") .add("previewUrl=" + previewUrl) .add("attributes=" + attributes) .toString(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/model/KubernetesRuntimeState.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.model; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; /** * @author Sergii Leshchenko */ @Entity(name = "KubernetesRuntime") @Table(name = "che_k8s_runtime") @NamedQueries({ @NamedQuery(name = "KubernetesRuntime.getAll", query = "SELECT r FROM KubernetesRuntime r") }) public class KubernetesRuntimeState { @Id @Column(name = "workspace_id") private String workspaceId; @Column(name = "env_name") private String envName; @Column(name = "owner_id") private String ownerId; @Column(name = "namespace") private String namespace; @Column(name = "status") @Enumerated(EnumType.STRING) private WorkspaceStatus status; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "workspace_id", referencedColumnName = "workspace_id") private List commands; public KubernetesRuntimeState() {} public KubernetesRuntimeState( RuntimeIdentity runtimeIdentity, WorkspaceStatus status, List commands) { this.envName = runtimeIdentity.getEnvName(); this.workspaceId = runtimeIdentity.getWorkspaceId(); this.ownerId = runtimeIdentity.getOwnerId(); this.namespace = runtimeIdentity.getInfrastructureNamespace(); this.status = status; if (commands != null) { this.commands = commands.stream().map(KubernetesRuntimeCommandImpl::new).collect(Collectors.toList()); } } public KubernetesRuntimeState(KubernetesRuntimeState entity) { this(entity.getRuntimeId(), entity.getStatus(), entity.getCommands()); } public String getNamespace() { return namespace; } public RuntimeIdentity getRuntimeId() { return new RuntimeIdentityImpl(workspaceId, envName, ownerId, namespace); } public WorkspaceStatus getStatus() { return status; } public void setStatus(WorkspaceStatus status) { this.status = status; } public KubernetesRuntimeState withStatus(WorkspaceStatus status) { this.status = status; return this; } public List getCommands() { return commands; } public void setCommands(List commands) { this.commands = commands; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof KubernetesRuntimeState)) { return false; } KubernetesRuntimeState that = (KubernetesRuntimeState) o; return Objects.equals(workspaceId, that.workspaceId) && Objects.equals(envName, that.envName) && Objects.equals(ownerId, that.ownerId) && Objects.equals(namespace, that.namespace) && getStatus() == that.getStatus(); } @Override public int hashCode() { return Objects.hash(workspaceId, envName, ownerId, namespace, getStatus()); } @Override public String toString() { return "KubernetesRuntimeState{" + "workspaceId='" + workspaceId + '\'' + ", envName='" + envName + '\'' + ", ownerId='" + ownerId + '\'' + ", namespace='" + namespace + '\'' + ", status=" + status + '}'; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/model/KubernetesServerImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.model; import java.util.Map; import java.util.Objects; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; /** * @author Sergii Leshchenko */ @Entity(name = "KubernetesServer") @Table(name = "che_k8s_server") public class KubernetesServerImpl implements Server { @EmbeddedId private ServerId serverId; @Column(name = "url") private String url; @Column(name = "status") @Enumerated(EnumType.STRING) private ServerStatus status; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "che_k8s_server_attributes", joinColumns = { @JoinColumn(name = "workspace_id", referencedColumnName = "workspace_id"), @JoinColumn(name = "machine_name", referencedColumnName = "machine_name"), @JoinColumn(name = "server_name", referencedColumnName = "server_name") }) @MapKeyColumn(name = "attribute_key") @Column(name = "attribute") private Map attributes; public KubernetesServerImpl() {} public KubernetesServerImpl( String workspaceId, String machineName, String serverName, Server server) { this.serverId = new ServerId(workspaceId, machineName, serverName); this.url = server.getUrl(); this.status = server.getStatus(); this.attributes = server.getAttributes(); } @Override public String getUrl() { return url; } @Override public ServerStatus getStatus() { return status; } public void setStatus(ServerStatus status) { this.status = status; } @Override public Map getAttributes() { return attributes; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof KubernetesServerImpl)) { return false; } final KubernetesServerImpl that = (KubernetesServerImpl) obj; return Objects.equals(serverId, that.serverId) && Objects.equals(url, that.url) && Objects.equals(status, that.status) && getAttributes().equals(that.getAttributes()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(serverId); hash = 31 * hash + Objects.hashCode(url); hash = 31 * hash + Objects.hashCode(status); hash = 31 * hash + getAttributes().hashCode(); return hash; } @Override public String toString() { return "KubernetesServerImpl{" + "serverId=" + serverId + ", url='" + url + '\'' + ", status=" + status + ", attributes=" + attributes + '}'; } @Embeddable public static class ServerId { @Column(name = "workspace_id") private String workspaceId; @Column(name = "machine_name") private String machineName; @Column(name = "server_name") private String serverName; public ServerId() {} public ServerId(String workspaceId, String machineName, String serverName) { this.workspaceId = workspaceId; this.machineName = machineName; this.serverName = serverName; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof ServerId)) { return false; } final ServerId that = (ServerId) obj; return Objects.equals(workspaceId, that.workspaceId) && Objects.equals(machineName, that.machineName) && Objects.equals(serverName, that.serverName); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(workspaceId); hash = 31 * hash + Objects.hashCode(machineName); hash = 31 * hash + Objects.hashCode(serverName); return hash; } @Override public String toString() { return "ServerId{" + "workspaceId='" + workspaceId + '\'' + ", machineName='" + machineName + '\'' + ", serverName='" + serverName + '\'' + '}'; } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/multiuser/oauth/KubernetesOidcProviderConfigFactory.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.multiuser.oauth; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.ConfigBuilder; import java.util.Optional; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientConfigFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class creates {@link Config} from Kubernetes OIDC token. It does not guarantee that token is * valid. */ @Singleton public class KubernetesOidcProviderConfigFactory extends KubernetesClientConfigFactory { private static final Logger LOG = LoggerFactory.getLogger(KubernetesOidcProviderConfigFactory.class); @Inject public KubernetesOidcProviderConfigFactory( @Nullable @Named("che.infra.kubernetes.master_url") String masterUrl, @Nullable @Named("che.infra.kubernetes.trust_certs") Boolean doTrustCerts) { super(masterUrl, doTrustCerts); } @Override public boolean isPersonalized() { return getToken().isPresent(); } /** * Builds the OpenShift {@link Config} object based on a default {@link Config} object and token * stored in {@link EnvironmentContext}. It ignores 'workspaceId'. */ public Config buildConfig(Config defaultConfig, @Nullable String workspaceId) { return getToken() .map((token) -> new ConfigBuilder(defaultConfig).withOauthToken(token).build()) .orElseGet( () -> { LOG.debug("NO TOKEN FOUND. Getting default client config."); return defaultConfig; }); } private Optional getToken() { return Optional.ofNullable(EnvironmentContext.getCurrent().getSubject().getToken()); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/AbstractWorkspaceServiceAccount.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesResourceList; import io.fabric8.kubernetes.api.model.ServiceAccountBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.Resource; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.function.Function; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class implements the logic for creating the roles and role bindings for the workspace * service account. Because of the differences between Kubernetes and OpenShift we need to use a lot * of generic params. * * @param the type of the client to use * @param the Role type * @param the RoleBinding type */ public abstract class AbstractWorkspaceServiceAccount< Client extends KubernetesClient, R extends HasMetadata, B extends HasMetadata> { private static final Logger LOG = LoggerFactory.getLogger(AbstractWorkspaceServiceAccount.class); public static final String EXEC_ROLE_NAME = "exec"; public static final String VIEW_ROLE_NAME = "workspace-view"; public static final String METRICS_ROLE_NAME = "workspace-metrics"; public static final String SECRETS_ROLE_NAME = "workspace-secrets"; public static final String CONFIGMAPS_ROLE_NAME = "workspace-configmaps"; public static final String CREDENTIALS_SECRET_NAME = "workspace-credentials-secret"; public static final String PREFERENCES_CONFIGMAP_NAME = "workspace-preferences-configmap"; public static final String GIT_USERDATA_CONFIGMAP_NAME = "workspace-userdata-gitconfig-configmap"; protected final String namespace; protected final String serviceAccountName; private final ClientFactory clientFactory; private final String workspaceId; private final Set clusterRoleNames; private final Function< Client, MixedOperation, ? extends Resource>> roles; private final Function< Client, MixedOperation, ? extends Resource>> roleBindings; protected AbstractWorkspaceServiceAccount( String workspaceId, String namespace, String serviceAccountName, Set clusterRoleNames, ClientFactory clientFactory, Function< Client, MixedOperation, ? extends Resource>> roles, Function< Client, MixedOperation, ? extends Resource>> roleBindings) { this.workspaceId = workspaceId; this.namespace = namespace; this.serviceAccountName = serviceAccountName; this.clusterRoleNames = clusterRoleNames; this.clientFactory = clientFactory; this.roles = roles; this.roleBindings = roleBindings; } /** * Make sure that workspace service account exists and has `view` and `exec` role bindings, as * well as create workspace-view and exec roles in namespace scope * * @throws InfrastructureException when any exception occurred */ public void prepare() throws InfrastructureException { Client k8sClient = clientFactory.create(workspaceId); if (k8sClient.serviceAccounts().inNamespace(namespace).withName(serviceAccountName).get() == null) { createWorkspaceServiceAccount(k8sClient); } ensureImplicitRolesWithBindings(k8sClient); ensureExplicitClusterRoleBindings(k8sClient); } /** * Creates implicit Roles and RoleBindings for workspace ServiceAccount that we need to have fully * working workspaces with this SA. * *

creates {@code -exec} and {@code -view} */ private void ensureImplicitRolesWithBindings(Client k8sClient) { // exec role ensureRoleWithBinding( k8sClient, buildRole( EXEC_ROLE_NAME, singletonList("pods/exec"), emptyList(), singletonList(""), singletonList("create")), serviceAccountName + "-exec"); // view role ensureRoleWithBinding( k8sClient, buildRole( VIEW_ROLE_NAME, Arrays.asList("pods", "services"), emptyList(), singletonList(""), singletonList("list")), serviceAccountName + "-view"); // metrics role try { if (k8sClient.supportsApiPath("/apis/metrics.k8s.io")) { ensureRoleWithBinding( k8sClient, buildRole( METRICS_ROLE_NAME, Arrays.asList("pods", "nodes"), emptyList(), singletonList("metrics.k8s.io"), Arrays.asList("list", "get", "watch")), serviceAccountName + "-metrics"); } } catch (KubernetesClientException e) { // workaround to unblock workspace start if no permissions for metrics if (e.getCode() == 403) { LOG.warn( "Unable to add metrics roles due to insufficient permissions. Workspace metrics will be disabled."); } else { throw e; } } // credentials-secret role ensureRoleWithBinding( k8sClient, buildRole( SECRETS_ROLE_NAME, singletonList("secrets"), singletonList(CREDENTIALS_SECRET_NAME), singletonList(""), Arrays.asList("get", "patch")), serviceAccountName + "-secrets"); // preferences-configmap role ensureRoleWithBinding( k8sClient, buildRole( CONFIGMAPS_ROLE_NAME, singletonList("configmaps"), singletonList(PREFERENCES_CONFIGMAP_NAME), singletonList(""), Arrays.asList("get", "patch")), serviceAccountName + "-configmaps"); } private void ensureRoleWithBinding(Client k8sClient, R role, String bindingName) { ensureRole(k8sClient, role); //noinspection unchecked roleBindings .apply(k8sClient) .inNamespace(namespace) .createOrReplace(createRoleBinding(role.getMetadata().getName(), bindingName, false)); } /** * Creates workspace ServiceAccount ClusterRoleBindings that are defined in * 'che.infra.kubernetes.workspace_sa_cluster_roles' property. * * @see KubernetesNamespaceFactory#KubernetesNamespaceFactory(String, String, String, boolean, * boolean, String, String, KubernetesClientFactory, CheServerKubernetesClientFactory, * UserManager, PreferenceManager, KubernetesSharedPool) */ private void ensureExplicitClusterRoleBindings(Client k8sClient) { // If the user specified an additional cluster roles for the workspace, // create a role binding for them too int idx = 0; for (String clusterRoleName : this.clusterRoleNames) { if (k8sClient.rbac().clusterRoles().withName(clusterRoleName).get() != null) { //noinspection unchecked roleBindings .apply(k8sClient) .inNamespace(namespace) .createOrReplace( createRoleBinding(clusterRoleName, serviceAccountName + "-cluster" + idx++, true)); } else { LOG.warn( "Unable to find the cluster role {}. Skip creating custom role binding.", clusterRoleName); } } } /** * Builds a new role in the configured namespace but does not persist it. * * @param name the name of the role * @param resources the resources the role grants access to * @param resourceNames specific resource names witch the role grants access to. * @param verbs the verbs the role allows * @return the role object for the given type of Client */ protected abstract R buildRole( String name, List resources, List resourceNames, List apiGroups, List verbs); /** * Builds a new role binding but does not persist it. * * @param roleName the name of the role to bind to * @param bindingName the name of the binding * @param clusterRole whether the binding is for a cluster role or to a role in the namespace * @return */ protected abstract B createRoleBinding(String roleName, String bindingName, boolean clusterRole); private void createWorkspaceServiceAccount(Client k8sClient) { k8sClient .serviceAccounts() .inNamespace(namespace) .createOrReplace( new ServiceAccountBuilder() .withAutomountServiceAccountToken(true) .withNewMetadata() .withName(serviceAccountName) .endMetadata() .build()); } private void ensureRole(Client k8sClient, R role) { //noinspection unchecked roles.apply(k8sClient).inNamespace(namespace).createOrReplace(role); } public interface ClientFactory { C create(String workspaceId) throws InfrastructureException; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/K8sVersion.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import io.fabric8.kubernetes.client.VersionInfo; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Gets version of k8s cluster from given {@link KubernetesClientFactory#create()} and provides * functions over it. It lazy-loads {@link VersionInfo} from k8s and use the stored data after. * Future methods implementations must ensure that {@link VersionInfo} is properly lazy-loaded. * *

In case of issue like infrastructure or parsing failures, this implementation assumes that * we're on newer version. * *

Parsing of version strings is very naive, we just strip all non-digit characters and parse as * integer. This should be enough for standard version strings and where it fails, we assume we're * on newer version. These are version formats from various platforms: * *

 * minikube v1.11.0 with k8s 1.17.6:
 * {
 *  major=1,
 *  minor=17,
 *  ...
 * }
 *
 * minishift v1.34.2+83ebaab:
 * {
 *   major=1,
 *   minor=11+,
 *   ...
 * }
 *
 * crc 1.12.0+6710aff with OpenShift version 4.4.8
 * {
 *  major=1,
 *  minor=17+,
 *  ...
 * }
 * 
*/ @Singleton public class K8sVersion { private static final Logger LOG = LoggerFactory.getLogger(K8sVersion.class); private final KubernetesClientFactory clientFactory; private VersionInfo versionInfo; private int major; private int minor; @Inject public K8sVersion(KubernetesClientFactory clientFactory) { this.clientFactory = clientFactory; } /** * Returns 'true' if k8s version is newer or equal than given {@code major.minor}. 'false' if k8s * version is older. * *

In case of any issue like infrastructure or parse failures, assume we're on newer version * and return 'true'. * * @param major major version to compare * @param minor minor version to compare * @return true if k8s version is newer or equal than given {@code major.minor} */ public boolean newerOrEqualThan(int major, int minor) { try { initVersionInfo(); } catch (InfrastructureException ie) { LOG.warn("Unable to obtain k8s VersionInfo.", ie); return true; } if (this.major > major) { return true; } else if (this.major == major) { return this.minor >= minor; } else { return false; } } /** * Returns 'true' if k8s version is older than given {@code major.minor}. 'false' if k8s version * is newer or equal. * *

In case of any issue like infrastructure or parse failures, assume we're on newer version * and return 'false'. * * @param major major version to compare * @param minor minor version to compare * @return true if given {@code major.minor} version is older than k8s version */ public boolean olderThan(int major, int minor) { return !newerOrEqualThan(major, minor); } private void initVersionInfo() throws InfrastructureException { if (versionInfo == null) { synchronized (this) { if (versionInfo == null) { versionInfo = clientFactory.create().getVersion(); LOG.debug("Obtained k8s version {} {}", versionInfo.getMajor(), versionInfo.getMinor()); parseVersions(); } } } } /** * Try parse versions into integers. This naive implementation removes all non-digits and try to * parse what's left into integer. */ private void parseVersions() { try { this.major = parseVersionNumber(versionInfo.getMajor()); this.minor = parseVersionNumber(versionInfo.getMinor()); } catch (NumberFormatException nfe) { LOG.warn( "Unable to parse k8s version [major: {}, minor: {}].", versionInfo.getMajor(), versionInfo.getMinor(), nfe); this.major = Integer.MAX_VALUE; this.minor = Integer.MAX_VALUE; } } private int parseVersionNumber(String versionString) { versionString = versionString.replaceAll("\\D+", ""); return Integer.parseInt(versionString); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesConfigsMaps.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static io.fabric8.kubernetes.api.model.DeletionPropagation.BACKGROUND; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_WORKSPACE_ID_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabel; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.client.KubernetesClientException; import java.util.Optional; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; /** * Defines an internal API for managing {@link ConfigMap} instances in {@link * KubernetesConfigsMaps#namespace predefined namespace}. * * @author Sergii Leshchenko */ public class KubernetesConfigsMaps { private final String namespace; private final String workspaceId; private final KubernetesClientFactory clientFactory; KubernetesConfigsMaps( String namespace, String workspaceId, KubernetesClientFactory clientFactory) { this.namespace = namespace; this.workspaceId = workspaceId; this.clientFactory = clientFactory; } /** * Retrieves config map by name. * * @param configMapName name of config map to get * @return config map optional * @throws InfrastructureException when any exception occurs */ public Optional get(String configMapName) throws InfrastructureException { return Optional.ofNullable( clientFactory .create(workspaceId) .configMaps() .inNamespace(namespace) .withName(configMapName) .get()); } /** * Creates specified config map. * * @param configMap config map to create * @throws InfrastructureException when any exception occurs * @return created {@link ConfigMap} */ public ConfigMap create(ConfigMap configMap) throws InfrastructureException { putLabel(configMap, CHE_WORKSPACE_ID_LABEL, workspaceId); try { return clientFactory .create(workspaceId) .configMaps() .inNamespace(namespace) .create(configMap); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Deletes all existing config maps. * * @throws InfrastructureException when any exception occurs */ public void delete() throws InfrastructureException { try { clientFactory .create(workspaceId) .configMaps() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .withPropagationPolicy(BACKGROUND) .delete(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesDeployments.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static io.fabric8.kubernetes.api.model.DeletionPropagation.BACKGROUND; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.CompletableFuture.allOf; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_DEPLOYMENT_NAME_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_WORKSPACE_ID_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.POD_STATUS_PHASE_FAILED; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.POD_STATUS_PHASE_RUNNING; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.POD_STATUS_PHASE_SUCCEEDED; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabel; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.setSelector; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import io.fabric8.kubernetes.api.model.ContainerStateTerminated; import io.fabric8.kubernetes.api.model.ContainerStateWaiting; import io.fabric8.kubernetes.api.model.ContainerStatus; import io.fabric8.kubernetes.api.model.Event; import io.fabric8.kubernetes.api.model.LocalObjectReference; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectReference; import io.fabric8.kubernetes.api.model.OwnerReference; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.PodStatus; import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.StatusDetails; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.WatcherException; import io.fabric8.kubernetes.client.dsl.ExecListener; import io.fabric8.kubernetes.client.dsl.ExecWatch; import io.fabric8.kubernetes.client.dsl.PodResource; import io.fabric8.kubernetes.client.dsl.ScalableResource; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodActionHandler; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEventHandler; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.LogWatchTimeouts; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.LogWatcher; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.PodLogHandler; import org.eclipse.che.workspace.infrastructure.kubernetes.util.PodEvents; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Defines an internal API for managing {@link Pod} and {@link Deployment} instances in {@link * KubernetesDeployments#namespace predefined namespace}. * * @author Sergii Leshchenko * @author Anton Korneta * @author Angel Misevski */ public class KubernetesDeployments { private static final Logger LOG = LoggerFactory.getLogger(KubernetesDeployments.class); private static final String CONTAINER_NAME_GROUP = "name"; // when event is related to container `fieldPath` field contains // information in the following format: `spec.container{web}`, where `web` is container name private static final Pattern CONTAINER_FIELD_PATH_PATTERN = Pattern.compile("spec.containers\\{(?<" + CONTAINER_NAME_GROUP + ">.*)}"); // TODO https://github.com/eclipse/che/issues/7656 public static final int POD_REMOVAL_TIMEOUT_MIN = 5; public static final int POD_CREATION_TIMEOUT_MIN = 1; private static final String POD_OBJECT_KIND = "Pod"; private static final String REPLICASET_OBJECT_KIND = "ReplicaSet"; private static final String DEPLOYMENT_OBJECT_KIND = "Deployment"; // error stream data initial capacity public static final int ERROR_BUFF_INITIAL_CAP = 2048; public static final String STDOUT = "stdout"; public static final String STDERR = "stderr"; protected final String namespace; protected final String workspaceId; private final KubernetesClientFactory clientFactory; private final ConcurrentLinkedQueue podActionHandlers; private final ConcurrentLinkedQueue containerEventsHandlers; private final Executor executor; private Watch podWatch; private Watch containerWatch; private Date watcherInitializationDate; private LogWatcher logWatcher; protected KubernetesDeployments( String namespace, String workspaceId, KubernetesClientFactory clientFactory, Executor executor) { this.namespace = namespace; this.workspaceId = workspaceId; this.clientFactory = clientFactory; this.containerEventsHandlers = new ConcurrentLinkedQueue<>(); this.podActionHandlers = new ConcurrentLinkedQueue<>(); this.executor = executor; } /** * Starts the specified Pod via a Deployment. * * @param pod pod to deploy * @return created pod * @throws InfrastructureException when any exception occurs */ public Pod deploy(Pod pod) throws InfrastructureException { putLabel(pod, CHE_WORKSPACE_ID_LABEL, workspaceId); // Since we use the pod's metadata as the deployment's metadata // This is used to identify the pod in CreateWatcher. String originalName = pod.getMetadata().getName(); putLabel(pod, CHE_DEPLOYMENT_NAME_LABEL, originalName); ObjectMeta metadata = pod.getMetadata(); PodSpec podSpec = pod.getSpec(); podSpec.setRestartPolicy("Always"); // Only allowable value Deployment deployment = new DeploymentBuilder() .withMetadata(metadata) .withNewSpec() .withNewSelector() .withMatchLabels(metadata.getLabels()) .endSelector() .withReplicas(1) .withNewTemplate() .withMetadata(metadata) .withSpec(podSpec) .endTemplate() .endSpec() .build(); return createDeployment(deployment, workspaceId); } public Pod deploy(Deployment deployment) throws InfrastructureException { ObjectMeta podMeta = deployment.getSpec().getTemplate().getMetadata(); putLabel(podMeta, CHE_WORKSPACE_ID_LABEL, workspaceId); putLabel(podMeta, CHE_DEPLOYMENT_NAME_LABEL, deployment.getMetadata().getName()); putLabel(deployment.getMetadata(), CHE_WORKSPACE_ID_LABEL, workspaceId); // Match condition for a deployment is an AND of all labels setSelector(deployment, podMeta.getLabels()); // Avoid accidental setting of multiple replicas deployment.getSpec().setReplicas(1); PodSpec podSpec = deployment.getSpec().getTemplate().getSpec(); podSpec.setRestartPolicy("Always"); // Only allowable value return createDeployment(deployment, workspaceId); } private Pod createDeployment(Deployment deployment, String workspaceId) throws InfrastructureException { addPullSecretsOfSA(deployment); final String deploymentName = deployment.getMetadata().getName(); final CompletableFuture createFuture = new CompletableFuture<>(); final Watch createWatch = clientFactory .create(workspaceId) .pods() .inNamespace(namespace) .withLabels( Map.of( CHE_WORKSPACE_ID_LABEL, workspaceId, CHE_DEPLOYMENT_NAME_LABEL, deploymentName)) .watch(new CreateWatcher(createFuture, workspaceId, deploymentName)); try { clientFactory .create(workspaceId) .apps() .deployments() .inNamespace(namespace) .create(deployment); return createFuture.get(POD_CREATION_TIMEOUT_MIN, TimeUnit.MINUTES); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InfrastructureException( format( "Interrupted while waiting for Pod creation. -id: %s -message: %s", deploymentName, e.getMessage())); } catch (ExecutionException e) { throw new InfrastructureException( format( "Error occured while waiting for Pod creation. -id: %s -message: %s", deploymentName, e.getCause().getMessage())); } catch (TimeoutException e) { throw new InfrastructureException( format( "Pod creation timeout exceeded. -id: %s -message: %s", deploymentName, e.getMessage())); } finally { createWatch.close(); } } /** * Create a terminating pod that is not part of a Deployment. * * @param pod the Pod to create * @return the created pod * @throws InfrastructureException when any error occurs */ public Pod create(Pod pod) throws InfrastructureException { putLabel(pod, CHE_WORKSPACE_ID_LABEL, workspaceId); try { return clientFactory.create(workspaceId).pods().inNamespace(namespace).create(pod); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Returns all existing pods. * * @throws InfrastructureException when any exception occurs */ public List get() throws InfrastructureException { try { return clientFactory .create(workspaceId) .pods() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .list() .getItems(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Returns optional with pod that either has specified name or is controlled by Deployment with * specified name. * * @param name name of the Pod or Deployment * @throws InfrastructureException when any exception occurs */ public Optional get(String name) throws InfrastructureException { return findPod(name); } /** * Returns a future which completes when pod state satisfies the specified predicate. * *

Note that for resource cleanup, the resulting future must be explicitly cancelled when its * completion no longer important. * * @param name name of pod or deployment containing pod that should be watched * @param predicate predicate to perform state check * @return pod that satisfies the specified predicate * @throws InfrastructureException when specified pod or deployment does not exist * @throws InfrastructureException when any other exception occurs */ public CompletableFuture waitAsync(String name, Predicate predicate) throws InfrastructureException { String podName = getPodName(name); CompletableFuture future = new CompletableFuture<>(); try { PodResource podResource = clientFactory.create(workspaceId).pods().inNamespace(namespace).withName(podName); Watch watch = podResource.watch( new Watcher<>() { @Override public void eventReceived(Action action, Pod pod) { if (predicate.test(pod)) { future.complete(pod); } } @Override public void onClose(WatcherException cause) { future.completeExceptionally( new InfrastructureException( "Waiting for pod '" + podName + "' was interrupted")); } }); Pod actualPod = podResource.get(); if (actualPod == null) { if (name.equals(podName)) { // `name` refers to a bare pod throw new InfrastructureException("Specified pod " + podName + " doesn't exist"); } else { // `name` refers to a deployment throw new InfrastructureException("No pod in deployment " + name + " found."); } } if (predicate.test(actualPod)) { return CompletableFuture.completedFuture(actualPod); } future.whenComplete((ok, ex) -> watch.close()); return future; } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Subscribes to pod events and returns the resulting future, which completes when pod becomes * running. * *

Note that the resulting future must be explicitly cancelled when its completion no longer * important because of finalization allocated resources. * * @param name the pod or deployment (that contains pod) name that should be watched * @return completable future that is completed when one of the following conditions is met: *

    *
  • complete successfully in case of "Running" pod state. *
  • complete exceptionally in case of "Failed" pod state. Exception will contain pod * status reason value, or if absent, it will attempt to retrieve pod logs. *
  • complete exceptionally in case of "Succeeded" pod state. (workspace container has * been terminated). *
  • complete exceptionally when exception while getting pod resource occurred. *
  • complete exceptionally when connection problem occurred. *
* otherwise, it must be explicitly closed */ public CompletableFuture waitRunningAsync(String name) { final CompletableFuture podRunningFuture = new CompletableFuture<>(); try { final String podName = getPodName(name); final PodResource podResource = clientFactory.create(workspaceId).pods().inNamespace(namespace).withName(podName); final Watch watch = podResource.watch( new Watcher<>() { @Override public void eventReceived(Action action, Pod pod) { handleStartingPodStatus(podRunningFuture, pod); } @Override public void onClose(WatcherException cause) { podRunningFuture.completeExceptionally( new InfrastructureException( "Waiting for pod '" + podName + "' was interrupted")); } }); podRunningFuture.whenComplete((ok, ex) -> watch.close()); final Pod pod = podResource.get(); if (pod == null) { InfrastructureException ex; if (name.equals(podName)) { // `name` refers to bare pod ex = new InfrastructureException("Specified pod " + podName + " doesn't exist"); } else { ex = new InfrastructureException("No pod in deployment " + name + " found."); } podRunningFuture.completeExceptionally(ex); } else { handleStartingPodStatus(podRunningFuture, pod); } } catch (KubernetesClientException | InfrastructureException ex) { podRunningFuture.completeExceptionally(ex); } return podRunningFuture; } private void handleStartingPodStatus(CompletableFuture podRunningFuture, Pod pod) { PodStatus status = pod.getStatus(); String podPhase = status.getPhase(); if (POD_STATUS_PHASE_RUNNING.equals(podPhase)) { // check that all the containers are ready... Map terminatedContainers = new HashMap<>(); List restartingContainers = new ArrayList<>(); for (ContainerStatus cs : status.getContainerStatuses()) { ContainerStateTerminated terminated = cs.getState().getTerminated(); if (terminated == null) { ContainerStateWaiting waiting = cs.getState().getWaiting(); // we've caught the container waiting for a restart after a failure if (waiting != null) { terminated = cs.getLastState().getTerminated(); } } if (terminated != null) { terminatedContainers.put( cs.getName(), format( "reason = '%s', exit code = %d, message = '%s'", terminated.getReason(), terminated.getExitCode(), terminated.getMessage())); } if (cs.getRestartCount() != null && cs.getRestartCount() > 0) { restartingContainers.add(cs.getName()); } } if (terminatedContainers.isEmpty() && restartingContainers.isEmpty()) { podRunningFuture.complete(null); } else { StringBuilder errorMessage = new StringBuilder(); if (!restartingContainers.isEmpty()) { errorMessage.append("The following containers have restarted during the startup:\n"); errorMessage.append(String.join(", ", restartingContainers)); } if (!terminatedContainers.isEmpty()) { if (errorMessage.length() > 0) { errorMessage.append("\n"); } errorMessage.append("The following containers have terminated:\n"); errorMessage.append( terminatedContainers.entrySet().stream() .map(e -> e.getKey() + ": " + e.getValue()) .collect(Collectors.joining("" + "\n"))); } podRunningFuture.completeExceptionally( new InfrastructureException(errorMessage.toString())); } return; } if (POD_STATUS_PHASE_SUCCEEDED.equals(podPhase)) { podRunningFuture.completeExceptionally( new InfrastructureException( "Pod container has been terminated. Container must be configured to use a non-terminating command.")); return; } if (POD_STATUS_PHASE_FAILED.equals(podPhase)) { String exceptionMessage = "Pod '" + pod.getMetadata().getName() + "' failed to start."; String reason = pod.getStatus().getReason(); if (Strings.isNullOrEmpty(reason)) { try { String podLog = clientFactory .create(workspaceId) .pods() .inNamespace(namespace) .withName(pod.getMetadata().getName()) .getLog(); exceptionMessage = exceptionMessage.concat(" Pod logs: ").concat(podLog); } catch (InfrastructureException | KubernetesClientException e) { exceptionMessage = exceptionMessage.concat(" Error occurred while fetching pod logs: " + e.getMessage()); } } else { exceptionMessage = exceptionMessage.concat(" Reason: ").concat(reason); } podRunningFuture.completeExceptionally(new InfrastructureException(exceptionMessage)); LOG.warn(exceptionMessage); } } /** * Starts watching the pods inside Kubernetes namespace and registers a specified handler for such * events. Note that watcher can be started only once so two times invocation of this method will * not produce new watcher and just register the event handlers. * * @param handler pod action events handler * @throws InfrastructureException if any error occurs while watcher starting */ public void watch(PodActionHandler handler) throws InfrastructureException { if (podWatch == null) { final Watcher watcher = new Watcher<>() { @Override public void eventReceived(Action action, Pod pod) { podActionHandlers.forEach(h -> h.handle(action, pod)); } @Override public void onClose(WatcherException cause) {} }; try { podWatch = clientFactory .create(workspaceId) .pods() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .watch(watcher); } catch (KubernetesClientException ex) { throw new KubernetesInfrastructureException(ex); } } podActionHandlers.add(handler); } /** * Registers a specified handler for handling events about changes in pods containers. Registering * several handlers doesn't create multiple websocket connections, so it is efficient to call this * method several times instead of using composite handler to combine other handlers. * * @param handler pod container events handler * @throws InfrastructureException if any error occurs while watcher starting */ public void watchEvents(PodEventHandler handler) throws InfrastructureException { if (containerWatch == null) { final Watcher watcher = new Watcher<>() { @Override public void eventReceived(Action action, Event event) { ObjectReference involvedObject = event.getInvolvedObject(); if (POD_OBJECT_KIND.equals(involvedObject.getKind()) || REPLICASET_OBJECT_KIND.equals(involvedObject.getKind()) || DEPLOYMENT_OBJECT_KIND.equals(involvedObject.getKind())) { String podName = involvedObject.getName(); String lastTimestamp = event.getLastTimestamp(); if (lastTimestamp == null) { String firstTimestamp = event.getFirstTimestamp(); if (firstTimestamp != null) { // Done in the same way like it made in // https://github.com/kubernetes/kubernetes/pull/86557 lastTimestamp = firstTimestamp; } else { LOG.debug( "lastTimestamp and firstTimestamp are undefined. Event: {}. Fallback to the current time.", event); lastTimestamp = PodEvents.convertDateToEventTimestamp(new Date()); } } PodEvent podEvent = new PodEvent( podName, getContainerName(involvedObject.getFieldPath()), event.getReason(), event.getMessage(), event.getMetadata().getCreationTimestamp(), lastTimestamp); try { if (happenedAfterWatcherInitialization(podEvent)) { containerEventsHandlers.forEach(h -> h.handle(podEvent)); } } catch (ParseException e) { LOG.error( "Failed to parse last timestamp of the event. Cause: {}. Event: {}", e.getMessage(), podEvent, e); } } } @Override public void onClose(WatcherException ignored) {} /** * Returns the container name if the event is related to container. When the event is * related to container `fieldPath` field contain information in the following format: * `spec.container{web}`, where `web` is container name */ private String getContainerName(String fieldPath) { String containerName = null; if (fieldPath != null) { Matcher containerFieldMatcher = CONTAINER_FIELD_PATH_PATTERN.matcher(fieldPath); if (containerFieldMatcher.matches()) { containerName = containerFieldMatcher.group(CONTAINER_NAME_GROUP); } } return containerName; } /** * Returns true if 'lastTimestamp' of the event is *after* the time of the watcher * initialization */ private boolean happenedAfterWatcherInitialization(PodEvent event) throws ParseException { String eventLastTimestamp = event.getLastTimestamp(); Date eventLastTimestampDate = PodEvents.convertEventTimestampToDate(eventLastTimestamp); return eventLastTimestampDate.after(watcherInitializationDate); } }; try { watcherInitializationDate = new Date(); containerWatch = clientFactory.create(workspaceId).v1().events().inNamespace(namespace).watch(watcher); } catch (KubernetesClientException ex) { throw new KubernetesInfrastructureException(ex); } } containerEventsHandlers.add(handler); } /** * Start watching the logs of this deployment. * * @param handler is processing log messages * @param podNames pods of interest for watching the logs */ public synchronized void watchLogs( PodLogHandler handler, RuntimeEventsPublisher eventsPublisher, LogWatchTimeouts timeouts, Set podNames, long limitInputStreamBytes) throws InfrastructureException { if (logWatcher == null) { LOG.debug("start watching logs of pods '{}' of workspace '{}'", podNames, workspaceId); logWatcher = new LogWatcher( clientFactory, eventsPublisher, workspaceId, namespace, podNames, executor, timeouts, limitInputStreamBytes); logWatcher.addLogHandler(handler); watchEvents(logWatcher); } else { LOG.debug("Already watching logs of workspace [{}], just adding log handler", workspaceId); logWatcher.addLogHandler(handler); } } public void stopWatch() { stopWatch(false); } /** * Stops watching the pods inside Kubernetes namespace. * * @param failed true if workspace startup ended in failure. */ public void stopWatch(boolean failed) { try { if (podWatch != null) { podWatch.close(); } } catch (KubernetesClientException ex) { LOG.error( "Failed to stop pod watcher for namespace '{}' cause '{}'", namespace, ex.getMessage()); } podActionHandlers.clear(); try { if (containerWatch != null) { containerWatch.close(); } } catch (KubernetesClientException ex) { LOG.error( "Failed to stop pods containers watcher for namespace '{}' cause '{}'", namespace, ex.getMessage()); } containerEventsHandlers.clear(); if (logWatcher != null) { logWatcher.close(failed); logWatcher = null; } } /** * Executes command in specified container. * * @param name pod name (or name of deployment containing pod) where command will be executed * @param containerName container name where command will be executed * @param timeoutMin timeout to wait until process will be done * @param command command to execute * @param outputConsumer command output biconsumer, that is accepts stream type and message * @throws InfrastructureException when specified timeout is reached * @throws InfrastructureException when {@link Thread} is interrupted while command executing * @throws InfrastructureException when command error stream is not empty * @throws InfrastructureException when any other exception occurs */ public void exec( String name, String containerName, int timeoutMin, String[] command, BiConsumer outputConsumer) throws InfrastructureException { final String podName = getPodName(name); final ExecWatchdog watchdog = new ExecWatchdog(); final ByteArrayOutputStream errStream = new ByteArrayOutputStream(ERROR_BUFF_INITIAL_CAP); try (ExecWatch watch = clientFactory .create(workspaceId) .pods() .inNamespace(namespace) .withName(podName) .inContainer(containerName) .writingError(errStream) .usingListener(watchdog) .exec(encode(command))) { try { watchdog.wait(timeoutMin, TimeUnit.MINUTES); final byte[] error = errStream.toByteArray(); if (error.length > 0) { final String cmd = Arrays.stream(command).collect(Collectors.joining(" ", "", "\n")); final String err = new String(error, UTF_8); outputConsumer.accept(STDOUT, cmd); outputConsumer.accept(STDERR, err); throw new InfrastructureException(err); } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new InfrastructureException(ex.getMessage(), ex); } } catch (KubernetesClientException ex) { throw new KubernetesInfrastructureException(ex); } } /** * Executes command in specified container. * * @param name pod name (or name of deployment containing pod) where command will be executed * @param containerName container name where command will be executed * @param timeoutMin timeout to wait until process will be done * @param command command to execute * @throws InfrastructureException when specified timeout is reached * @throws InfrastructureException when {@link Thread} is interrupted while command executing * @throws InfrastructureException when any other exception occurs */ public void exec(String name, String containerName, int timeoutMin, String[] command) throws InfrastructureException { final String podName = getPodName(name); final ExecWatchdog watchdog = new ExecWatchdog(); try (ExecWatch watch = clientFactory .create(workspaceId) .pods() .inNamespace(namespace) .withName(podName) .inContainer(containerName) // redirecting error output to exec watch out stream .redirectingError() .usingListener(watchdog) .exec(encode(command))) { try { watchdog.wait(timeoutMin, TimeUnit.MINUTES); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InfrastructureException(e.getMessage(), e); } } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Get logs from pod with specified name. The name can refer to either a deployment or pod * directly. In case of a deployment being provided, logs are returned from pods in that * deployment. * * @return the logs from the pod or null if pod cannot be found. */ @Nullable public String getPodLogs(String name) { String podName; try { podName = getPodName(name); return clientFactory .create(workspaceId) .pods() .inNamespace(namespace) .withName(podName) .getLog(); } catch (InfrastructureException e) { LOG.error("Could not get logs from pod {}. Pod not found", name); return null; } } /** * Deletes pod or deployment with given name. If a Pod controlled by a Deployment is specified, * the owning Deployment will be deleted instead. * *

Note that this method will mark Kubernetes pod as interrupted and then will wait until pod * will be killed. * * @param name name of pod or deployment to remove * @throws InfrastructureException when {@link Thread} is interrupted while command executing * @throws InfrastructureException when pod removal timeout is reached * @throws InfrastructureException when any other exception occurs */ public void delete(String name) throws InfrastructureException { try { Pod pod = clientFactory.create(workspaceId).pods().inNamespace(namespace).withName(name).get(); if (pod != null) { doDeletePod(name).get(POD_REMOVAL_TIMEOUT_MIN, TimeUnit.MINUTES); } else { doDeleteDeployment(name).get(POD_REMOVAL_TIMEOUT_MIN, TimeUnit.MINUTES); } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new InfrastructureException( "Interrupted while waiting for pod removal. " + ex.getMessage()); } catch (ExecutionException ex) { throw new InfrastructureException( "Error occurred while waiting for pod removal. " + ex.getMessage()); } catch (TimeoutException ex) { throw new InfrastructureException("Pod removal timeout reached " + ex.getMessage()); } } /** * Deletes all existing pods and the Deployments that control them. * *

Note that this method will mark Kubernetes pods as interrupted and then will wait until all * pods will be killed. * * @throws InfrastructureException when {@link Thread} is interrupted while command executing * @throws InfrastructureException when pods removal timeout is reached * @throws InfrastructureException when any other exception occurs */ public void delete() throws InfrastructureException { try { final List> deleteFutures = new ArrayList<>(); // We first delete all deployments, then clean up any bare Pods. List deployments = clientFactory .create(workspaceId) .apps() .deployments() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .list() .getItems(); for (Deployment deployment : deployments) { deleteFutures.add(doDeleteDeployment(deployment.getMetadata().getName())); } // We have to be careful to not include pods that are controlled by a deployment List pods = clientFactory .create(workspaceId) .pods() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .withoutLabel(CHE_DEPLOYMENT_NAME_LABEL) .list() .getItems(); for (Pod pod : pods) { List ownerReferences = pod.getMetadata().getOwnerReferences(); if (ownerReferences == null || ownerReferences.isEmpty()) { deleteFutures.add(doDeletePod(pod.getMetadata().getName())); } } final CompletableFuture removed = allOf(deleteFutures.toArray(new CompletableFuture[deleteFutures.size()])); try { removed.get(POD_REMOVAL_TIMEOUT_MIN, TimeUnit.MINUTES); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InfrastructureException( "Interrupted while waiting for pod removal. " + e.getMessage()); } catch (ExecutionException e) { throw new InfrastructureException( "Error occurred while waiting for pod removing. " + e.getMessage()); } catch (TimeoutException ex) { throw new InfrastructureException("Pods removal timeout reached " + ex.getMessage()); } } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } protected CompletableFuture doDeleteDeployment(String deploymentName) throws InfrastructureException { // Try to get pod name if it exists (it may not, if e.g. workspace config refers to // nonexistent service account). String podName; try { podName = getPodName(deploymentName); } catch (InfrastructureException e) { // Not an error, just means the Deployment has failed to create a pod. podName = null; } Watch toCloseOnException = null; try { ScalableResource deploymentResource = clientFactory .create(workspaceId) .apps() .deployments() .inNamespace(namespace) .withName(deploymentName); if (deploymentResource.get() == null) { throw new InfrastructureException( format("No deployment found to delete for name %s", deploymentName)); } final CompletableFuture deleteFuture = new CompletableFuture<>(); final Watch watch; // If we have a Pod, we have to watch to make sure it is deleted, otherwise, we watch the // Deployment we are deleting. if (!Strings.isNullOrEmpty(podName)) { PodResource podResource = clientFactory.create(workspaceId).pods().inNamespace(namespace).withName(podName); watch = podResource.watch(new DeleteWatcher<>(deleteFuture)); toCloseOnException = watch; } else { watch = deploymentResource.watch(new DeleteWatcher<>(deleteFuture)); toCloseOnException = watch; } List deleted = deploymentResource.withPropagationPolicy(BACKGROUND).delete(); if (deleted == null || deleted.isEmpty()) { deleteFuture.complete(null); } return deleteFuture.whenComplete( (v, e) -> { if (e != null) { LOG.warn("Failed to remove deployment {} cause {}", deploymentName, e.getMessage()); } watch.close(); }); } catch (KubernetesClientException e) { if (toCloseOnException != null) { toCloseOnException.close(); } throw new KubernetesInfrastructureException(e); } catch (Exception e) { if (toCloseOnException != null) { toCloseOnException.close(); } throw e; } } protected CompletableFuture doDeletePod(String podName) throws InfrastructureException { Watch toCloseOnException = null; try { PodResource podResource = clientFactory.create(workspaceId).pods().inNamespace(namespace).withName(podName); if (podResource.get() == null) { throw new InfrastructureException(format("No pod found to delete for name %s", podName)); } final CompletableFuture deleteFuture = new CompletableFuture<>(); final Watch watch = podResource.watch(new DeleteWatcher<>(deleteFuture)); toCloseOnException = watch; List deleted = podResource.withPropagationPolicy(BACKGROUND).delete(); if (deleted == null || deleted.isEmpty()) { deleteFuture.complete(null); } return deleteFuture.whenComplete( (v, e) -> { if (e != null) { LOG.warn("Failed to remove pod {} cause {}", podName, e.getMessage()); } watch.close(); }); } catch (KubernetesClientException e) { if (toCloseOnException != null) { toCloseOnException.close(); } throw new KubernetesInfrastructureException(e); } catch (Exception e) { if (toCloseOnException != null) { toCloseOnException.close(); } throw e; } } private String[] encode(String[] toEncode) throws InfrastructureException { String[] encoded = new String[toEncode.length]; for (int i = 0; i < toEncode.length; i++) { try { encoded[i] = URLEncoder.encode(toEncode[i], "UTF-8"); } catch (UnsupportedEncodingException e) { throw new InfrastructureException(e.getMessage(), e); } } return encoded; } private Optional findPod(String name) throws InfrastructureException { Pod pod = clientFactory.create(workspaceId).pods().inNamespace(namespace).withName(name).get(); if (pod != null) { return Optional.of(pod); } Deployment deployment = clientFactory .create(workspaceId) .apps() .deployments() .inNamespace(namespace) .withName(name) .get(); if (deployment == null) { return Optional.empty(); } Map selector = deployment.getSpec().getSelector().getMatchLabels(); List pods = clientFactory .create(workspaceId) .pods() .inNamespace(namespace) .withLabels(selector) .list() .getItems(); if (pods.isEmpty()) { return Optional.empty(); } else if (pods.size() > 1) { throw new InfrastructureException(format("Found multiple pods in Deployment '%s'", name)); } return Optional.of(pods.get(0)); } /** * Returns the name of a specified Pod given either the actual Pod name or the name of the * Deployment that controls it.
* This is necessary because we are trying to transparently wrap Pods in Deployment; attempting to * create a Pod named {@code testPod} will result in a Deployment with name {@code testPod}, which * will in turn create a Pod named e.g {@code testPod-1-xxxxx}. * * @param name Pod or Deployment name * @return the name of the intended pod. */ private String getPodName(String name) throws InfrastructureException { Optional pod = findPod(name); if (pod.isPresent()) { return pod.get().getMetadata().getName(); } else { throw new InfrastructureException(format("Failed to find pod with name %s", name)); } } private static class CreateWatcher implements Watcher { private final CompletableFuture future; private final String workspaceId; private final String deploymentName; private CreateWatcher( CompletableFuture future, String workspaceId, String deploymentName) { this.future = future; this.workspaceId = workspaceId; this.deploymentName = deploymentName; } @Override public void eventReceived(Action action, Pod resource) { if (action == Action.ADDED) { future.complete(resource); } } @Override public void onClose(WatcherException cause) { future.completeExceptionally( new RuntimeException("Websocket connection closed before Pod creation event received")); } } private static class DeleteWatcher implements Watcher { private final CompletableFuture future; private DeleteWatcher(CompletableFuture future) { this.future = future; } @Override public void eventReceived(Action action, T hasMetadata) { if (action == Action.DELETED) { future.complete(null); } } @Override public void onClose(WatcherException e) { // if event about removing is received then this completion has no effect future.completeExceptionally( new RuntimeException( "Websocket connection is closed. But event about removing is not received.", e)); } } private class ExecWatchdog implements ExecListener { private final CompletableFuture completionFuture; private ExecWatchdog() { this.completionFuture = new CompletableFuture<>(); } @Override public void onFailure(Throwable t, Response response) { completionFuture.completeExceptionally(t); } @Override public void onClose(int code, String reason) { completionFuture.complete(null); } public void wait(long timeout, TimeUnit timeUnit) throws InterruptedException, InfrastructureException { try { completionFuture.get(timeout, timeUnit); } catch (ExecutionException e) { throw new InfrastructureException( "Error occured while executing command in pod: " + e.getMessage(), e); } catch (TimeoutException e) { throw new InfrastructureException("Timeout reached while execution of command"); } } } @VisibleForTesting void addPullSecretsOfSA(Deployment deployment) throws InfrastructureException { final PodSpec podSpec = deployment.getSpec().getTemplate().getSpec(); final String podServiceAccountName = podSpec.getServiceAccountName(); if (podServiceAccountName == null) { return; } ServiceAccount podServiceAccount = clientFactory .create(workspaceId) .serviceAccounts() .inNamespace(namespace) .withName(podServiceAccountName) .get(); if (podServiceAccount == null) { return; } Set uniquePullSecrets = new HashSet<>(); if (podSpec.getImagePullSecrets() != null) { uniquePullSecrets.addAll(podSpec.getImagePullSecrets()); } if (podServiceAccount.getImagePullSecrets() != null) { uniquePullSecrets.addAll(podServiceAccount.getImagePullSecrets()); } podSpec.setImagePullSecrets(new ArrayList<>(uniquePullSecrets)); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesIngresses.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static io.fabric8.kubernetes.api.model.DeletionPropagation.BACKGROUND; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_WORKSPACE_ID_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabel; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.WatcherException; import io.fabric8.kubernetes.client.dsl.Resource; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Predicate; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; /** * Defines an internal API for managing {@link Ingress} instances in {@link * KubernetesIngresses#namespace predefined namespace}. * * @author Sergii Leshchenko * @author Guy Daich */ public class KubernetesIngresses { private final String namespace; private final String workspaceId; private final KubernetesClientFactory clientFactory; KubernetesIngresses(String namespace, String workspaceId, KubernetesClientFactory clientFactory) { this.namespace = namespace; this.workspaceId = workspaceId; this.clientFactory = clientFactory; } public Ingress create(Ingress ingress) throws InfrastructureException { putLabel(ingress, CHE_WORKSPACE_ID_LABEL, workspaceId); try { return clientFactory .create(workspaceId) .network() .v1() .ingresses() .inNamespace(namespace) .withName(ingress.getMetadata().getName()) .create(ingress); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } public List get() throws InfrastructureException { try { return clientFactory .create(workspaceId) .network() .v1() .ingresses() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .list() .getItems(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } public Ingress wait(String name, long timeout, TimeUnit timeoutUnit, Predicate predicate) throws InfrastructureException { CompletableFuture future = new CompletableFuture<>(); Watch watch = null; try { Resource ingressResource = clientFactory .create(workspaceId) .network() .v1() .ingresses() .inNamespace(namespace) .withName(name); watch = ingressResource.watch( new Watcher<>() { @Override public void eventReceived(Action action, Ingress ingress) { if (predicate.test(ingress)) { future.complete(ingress); } } @Override public void onClose(WatcherException cause) { future.completeExceptionally( new InfrastructureException( "Waiting for ingress '" + name + "' was interrupted")); } }); Ingress actualIngress = ingressResource.get(); if (actualIngress == null) { throw new InfrastructureException("Specified ingress " + name + " doesn't exist"); } if (predicate.test(actualIngress)) { return actualIngress; } try { return future.get(timeout, timeoutUnit); } catch (ExecutionException e) { throw new InfrastructureException(e.getCause().getMessage(), e); } catch (TimeoutException e) { throw new InfrastructureException("Waiting for ingress '" + name + "' reached timeout"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InfrastructureException("Waiting for ingress '" + name + "' was interrupted"); } } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } finally { if (watch != null) { watch.close(); } } } public void delete() throws InfrastructureException { try { clientFactory .create(workspaceId) .network() .v1() .ingresses() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .withPropagationPolicy(BACKGROUND) .delete(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespace.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static io.fabric8.kubernetes.api.model.DeletionPropagation.BACKGROUND; import static java.lang.String.format; import com.google.common.annotations.VisibleForTesting; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.NamespaceBuilder; import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.WatcherException; import io.fabric8.kubernetes.client.dsl.Resource; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Predicate; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Defines an internal API for managing subset of objects inside {@link Namespace} instance. * * @author Sergii Leshchenko */ public class KubernetesNamespace { private static final Logger LOG = LoggerFactory.getLogger(KubernetesNamespace.class); /** An experimental value used to determine how long to wait for the 'default' service account. */ private static final int SERVICE_ACCOUNT_READINESS_TIMEOUT_SEC = 3; /** * Default service account is used to get access to the API server so we need to be sure that it * is accessible, more detailed information about service accounts by default you can find here: * https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ */ private static final String DEFAULT_SERVICE_ACCOUNT_NAME = "default"; protected static final String MANAGED_NAMESPACE_LABEL = "che-managed"; private final String workspaceId; private final String name; private final KubernetesDeployments deployments; private final KubernetesServices services; private final KubernetesPersistentVolumeClaims pvcs; private final KubernetesIngresses ingresses; /** Factory for cluster related operations clients (like labeling the namespaces) */ private final CheServerKubernetesClientFactory cheSAClientFactory; private final KubernetesSecrets secrets; private final KubernetesConfigsMaps configMaps; @VisibleForTesting protected KubernetesNamespace( CheServerKubernetesClientFactory cheSAClientFactory, String workspaceId, String name, KubernetesDeployments deployments, KubernetesServices services, KubernetesPersistentVolumeClaims pvcs, KubernetesIngresses kubernetesIngresses, KubernetesSecrets secrets, KubernetesConfigsMaps configMaps) { this.cheSAClientFactory = cheSAClientFactory; this.workspaceId = workspaceId; this.name = name; this.deployments = deployments; this.services = services; this.pvcs = pvcs; this.ingresses = kubernetesIngresses; this.secrets = secrets; this.configMaps = configMaps; } public KubernetesNamespace( CheServerKubernetesClientFactory cheSAClientFactory, Executor executor, String name, String workspaceId) { this.cheSAClientFactory = cheSAClientFactory; this.workspaceId = workspaceId; this.name = name; this.deployments = new KubernetesDeployments(name, workspaceId, cheSAClientFactory, executor); this.services = new KubernetesServices(name, workspaceId, cheSAClientFactory); this.pvcs = new KubernetesPersistentVolumeClaims(name, workspaceId, cheSAClientFactory); this.ingresses = new KubernetesIngresses(name, workspaceId, cheSAClientFactory); this.secrets = new KubernetesSecrets(name, workspaceId, cheSAClientFactory); this.configMaps = new KubernetesConfigsMaps(name, workspaceId, cheSAClientFactory); } /** * Prepare namespace for using. * *

Preparing includes creating if needed and waiting for default service account. * *

The method will try to label the namespace with provided `labels`. It does not matter if the * namespace already exists or we create new one. If update labels operation fail due to lack of * permission, we do not fail completely. * *

The method will try to annotate the namespace with provided `annotations`. It does not * matter if the namespace already exists or we create new one. If update annotations operation * fail due to lack of permission, we do not fail completely. * * @param canCreate defines what to do when the namespace is not found. The namespace is created * when {@code true}, otherwise an exception is thrown. * @param labels labels that should be set to the namespace * @param annotations annotations that should be set to the namespace * @throws InfrastructureException if any exception occurs during namespace preparation or if the * namespace doesn't exist and {@code canCreate} is {@code false}. */ void prepare(boolean canCreate, Map labels, Map annotations) throws InfrastructureException { KubernetesClient client = cheSAClientFactory.create(workspaceId); Namespace namespace = get(name, client); if (namespace == null) { if (!canCreate) { throw new InfrastructureException( format("Creating the namespace '%s' is not allowed, yet it was not found.", name)); } create(name, client); } label(client.namespaces().withName(name).get(), labels); annotate(client.namespaces().withName(name).get(), annotations); } /** * Applies given `ensureLabels` into given `namespace` and update the `namespace` in the * Kubernetes. * *

If we do not have permissions to do so (code=403), this method does not throw any exception. * * @param namespace namespace to label * @param ensureLabels these labels should be applied on given `namespace` * @throws InfrastructureException if something goes wrong with update, except lack of permissions */ protected void label(Namespace namespace, Map ensureLabels) throws InfrastructureException { if (ensureLabels.isEmpty()) { return; } Map currentLabels = namespace.getMetadata().getLabels(); Map newLabels = currentLabels != null ? new HashMap<>(currentLabels) : new HashMap<>(); if (newLabels.entrySet().containsAll(ensureLabels.entrySet())) { LOG.debug( "Nothing to do, namespace [{}] already has all required labels.", namespace.getMetadata().getName()); return; } try { // update the namespace with new labels cheSAClientFactory .create() .namespaces() .createOrReplace( new NamespaceBuilder(namespace) .editMetadata() .addToLabels(ensureLabels) .endMetadata() .build()); } catch (KubernetesClientException kce) { if (kce.getCode() == 403) { LOG.warn( "Can't label the namespace due to lack of permissions. Grant cluster-wide permissions " + "to `get` and `update` the `namespaces` to the `che` service account " + "(Che operator might have already prepared a cluster role called " + "`che-namespace-editor` for this, depending on its configuration). " + "Alternatively, consider disabling the feature by setting " + "`che.infra.kubernetes.namepsace.label` to `false`."); return; } throw new InfrastructureException(kce); } } /** * Applies given `ensureAnnotations` into given `namespace` and update the `namespace` in the * Kubernetes. * *

If we do not have permissions to do so (code=403), this method does not throw any exception. * * @param namespace namespace to annotate * @param ensureAnnotations these annotations should be applied on given `namespace` * @throws InfrastructureException if something goes wrong with update, except lack of permissions */ protected void annotate(Namespace namespace, Map ensureAnnotations) throws InfrastructureException { if (ensureAnnotations.isEmpty()) { return; } Map currentAnnotations = namespace.getMetadata().getAnnotations(); Map newAnnotations = currentAnnotations != null ? new HashMap<>(currentAnnotations) : new HashMap<>(); if (newAnnotations.entrySet().containsAll(ensureAnnotations.entrySet())) { LOG.debug( "Nothing to do, namespace [{}] already has all required annotations.", namespace.getMetadata().getName()); return; } try { // update the namespace with new annotations cheSAClientFactory .create() .namespaces() .createOrReplace( new NamespaceBuilder(namespace) .editMetadata() .addToAnnotations(ensureAnnotations) .endMetadata() .build()); } catch (KubernetesClientException kce) { if (kce.getCode() == 403) { LOG.warn( "Can't annotate the namespace due to lack of permissions. Grant cluster-wide permissions " + "to `get` and `update` the `namespaces` to the `che` service account " + "(Che operator might have already prepared a cluster role called " + "`che-namespace-editor` for this, depending on its configuration). " + "Alternatively, consider disabling the feature by setting " + "`che.infra.kubernetes.namepsace.annotate` to `false`."); return; } throw new InfrastructureException(kce); } } /** * Deletes the namespace. Deleting a non-existent namespace is not an error as is not an attempt * to delete a namespace that is already being deleted. * * @throws InfrastructureException if any unexpected exception occurs during namespace deletion */ void delete() throws InfrastructureException { KubernetesClient client = cheSAClientFactory.create(workspaceId); try { delete(name, client); } catch (KubernetesClientException e) { if (e.getCode() == 403) { throw new InfrastructureException( format( "Could not access the namespace %s when deleting it for workspace %s", name, workspaceId), e); } throw new KubernetesInfrastructureException(e); } } /** Returns namespace name */ public String getName() { return name; } /** Returns identifier of the workspace for which current namespace is used */ public String getWorkspaceId() { return workspaceId; } /** Returns object for managing {@link Pod} instances inside namespace. */ public KubernetesDeployments deployments() { return deployments; } /** Returns object for managing {@link Service} instances inside namespace. */ public KubernetesServices services() { return services; } /** Returns object for managing {@link PersistentVolumeClaim} instances inside namespace. */ public KubernetesPersistentVolumeClaims persistentVolumeClaims() { return pvcs; } /** Returns object for managing {@link Ingress} instances inside namespace. */ public KubernetesIngresses ingresses() { return ingresses; } /** Returns object for managing {@link Secret} instances inside namespace. */ public KubernetesSecrets secrets() { return secrets; } /** Returns object for managing {@link ConfigMap} instances inside namespace. */ public KubernetesConfigsMaps configMaps() { return configMaps; } /** Removes all object except persistent volume claims inside namespace. */ public void cleanUp() throws InfrastructureException { doRemove( ingresses::delete, services::delete, deployments::delete, secrets::delete, configMaps::delete); } /** * Performs all the specified operations and throw exception with composite message if errors * occurred while any operation execution */ protected void doRemove(RemoveOperation... operations) throws InfrastructureException { StringBuilder errors = new StringBuilder(); for (RemoveOperation operation : operations) { try { operation.perform(); } catch (InternalInfrastructureException e) { LOG.warn( "Internal infra error occurred while cleaning up the namespace for workspace with id " + workspaceId, e); errors.append(" ").append(e.getMessage()); } catch (InfrastructureException e) { errors.append(" ").append(e.getMessage()); } } if (errors.length() > 0) { throw new InfrastructureException( "Error(s) occurs while cleaning up the namespace." + errors.toString()); } } private Namespace create(String namespaceName, KubernetesClient client) throws InfrastructureException { try { Namespace namespace = client .namespaces() .create( new NamespaceBuilder() .withNewMetadata() .withName(namespaceName) .endMetadata() .build()); waitDefaultServiceAccount(namespaceName, client); return namespace; } catch (KubernetesClientException e) { if (e.getCode() == 403) { LOG.error( "Unable to create new Kubernetes project due to lack of permissions." + "When using workspace namespace placeholders, service account with lenient permissions (cluster-admin) must be used."); } throw new KubernetesInfrastructureException(e); } } private void delete(String namespaceName, KubernetesClient client) throws InfrastructureException { try { client.namespaces().withName(namespaceName).withPropagationPolicy(BACKGROUND).delete(); } catch (KubernetesClientException e) { if (e.getCode() == 404) { LOG.warn( format( "Tried to delete namespace '%s' but it doesn't exist in the cluster.", namespaceName), e); } else if (e.getCode() == 409) { LOG.info(format("The namespace '%s' is currently being deleted.", namespaceName), e); } else { throw new KubernetesInfrastructureException(e); } } } /** * Waits few seconds until 'default' service account become available otherwise an infrastructure * exception will be thrown. */ protected void waitDefaultServiceAccount(String namespaceName, KubernetesClient client) throws InfrastructureException { final Predicate predicate = Objects::nonNull; final CompletableFuture future = new CompletableFuture<>(); Watch watch = null; try { final Resource saResource = client .serviceAccounts() .inNamespace(namespaceName) .withName(DEFAULT_SERVICE_ACCOUNT_NAME); watch = saResource.watch( new Watcher<>() { @Override public void eventReceived(Action action, ServiceAccount serviceAccount) { if (predicate.test(serviceAccount)) { future.complete(serviceAccount); } } @Override public void onClose(WatcherException cause) { future.completeExceptionally( new InfrastructureException( "Waiting for service account '" + DEFAULT_SERVICE_ACCOUNT_NAME + "' was interrupted")); } }); if (predicate.test(saResource.get())) { return; } try { future.get(SERVICE_ACCOUNT_READINESS_TIMEOUT_SEC, TimeUnit.SECONDS); } catch (ExecutionException ex) { throw new InfrastructureException(ex.getCause().getMessage(), ex); } catch (TimeoutException ex) { throw new InfrastructureException( "Waiting for service account '" + DEFAULT_SERVICE_ACCOUNT_NAME + "' reached timeout"); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new InfrastructureException( "Waiting for service account '" + DEFAULT_SERVICE_ACCOUNT_NAME + "' was interrupted"); } } catch (KubernetesClientException ex) { throw new KubernetesInfrastructureException(ex); } finally { if (watch != null) { watch.close(); } } } private Namespace get(String namespaceName, KubernetesClient client) throws InfrastructureException { try { return client.namespaces().withName(namespaceName).get(); } catch (KubernetesClientException e) { if (e.getCode() == 403) { // namespace is foreign or doesn't exist LOG.warn( "Trying to get namespace '{}', but failed because the lack of permissions.", namespaceName); return null; } else { throw new KubernetesInfrastructureException(e); } } } protected interface RemoveOperation { void perform() throws InfrastructureException; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceFactory.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.DEFAULT_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.NamespaceNameValidator.METADATA_NAME_MAX_LENGTH; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import com.google.inject.Singleton; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.client.KubernetesClientException; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Named; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationChecker; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationException; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Helps to create {@link KubernetesNamespace} instances. * * @author Anton Korneta */ @Singleton public class KubernetesNamespaceFactory { private static final Logger LOG = LoggerFactory.getLogger(KubernetesNamespaceFactory.class); private static final Map> NAMESPACE_NAME_PLACEHOLDERS = new HashMap<>(); private static final Set REQUIRED_NAMESPACE_NAME_PLACEHOLDERS = new HashSet<>(); private static final String USERNAME_PLACEHOLDER = ""; private static final String USERID_PLACEHOLDER = ""; static final String NAMESPACE_TEMPLATE_ATTRIBUTE = "infrastructureNamespaceTemplate"; static { NAMESPACE_NAME_PLACEHOLDERS.put(USERNAME_PLACEHOLDER, NamespaceResolutionContext::getUserName); NAMESPACE_NAME_PLACEHOLDERS.put(USERID_PLACEHOLDER, NamespaceResolutionContext::getUserId); REQUIRED_NAMESPACE_NAME_PLACEHOLDERS.add(USERNAME_PLACEHOLDER); REQUIRED_NAMESPACE_NAME_PLACEHOLDERS.add(USERID_PLACEHOLDER); } private final String defaultNamespaceName; protected final boolean labelNamespaces; protected final boolean annotateNamespaces; protected final Map namespaceLabels; protected final Map namespaceAnnotations; private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private final boolean namespaceCreationAllowed; private final PreferenceManager preferenceManager; protected final Set namespaceConfigurators; protected final KubernetesSharedPool sharedPool; protected final AuthorizationChecker authorizationChecker; protected final PermissionsCleaner permissionsCleaner; @Inject public KubernetesNamespaceFactory( @Nullable @Named("che.infra.kubernetes.namespace.default") String defaultNamespaceName, @Named("che.infra.kubernetes.namespace.creation_allowed") boolean namespaceCreationAllowed, @Named("che.infra.kubernetes.namespace.label") boolean labelNamespaces, @Named("che.infra.kubernetes.namespace.annotate") boolean annotateNamespaces, @Named("che.infra.kubernetes.namespace.labels") String namespaceLabels, @Named("che.infra.kubernetes.namespace.annotations") String namespaceAnnotations, Set namespaceConfigurators, CheServerKubernetesClientFactory cheServerKubernetesClientFactory, PreferenceManager preferenceManager, KubernetesSharedPool sharedPool, AuthorizationChecker authorizationChecker, PermissionsCleaner permissionsCleaner) throws ConfigurationException { this.namespaceCreationAllowed = namespaceCreationAllowed; this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; this.defaultNamespaceName = defaultNamespaceName; this.preferenceManager = preferenceManager; this.sharedPool = sharedPool; this.labelNamespaces = labelNamespaces; this.annotateNamespaces = annotateNamespaces; this.namespaceConfigurators = ImmutableSet.copyOf(namespaceConfigurators); this.authorizationChecker = authorizationChecker; this.permissionsCleaner = permissionsCleaner; //noinspection UnstableApiUsage Splitter.MapSplitter csvMapSplitter = Splitter.on(",").withKeyValueSeparator("="); //noinspection UnstableApiUsage this.namespaceLabels = isNullOrEmpty(namespaceLabels) ? emptyMap() : csvMapSplitter.split(namespaceLabels); //noinspection UnstableApiUsage this.namespaceAnnotations = isNullOrEmpty(namespaceAnnotations) ? emptyMap() : csvMapSplitter.split(namespaceAnnotations); if (isNullOrEmpty(defaultNamespaceName)) { throw new ConfigurationException("che.infra.kubernetes.namespace.default must be configured"); } else if (REQUIRED_NAMESPACE_NAME_PLACEHOLDERS.stream() .noneMatch(defaultNamespaceName::contains)) { throw new ConfigurationException( format( "Only 'per user' is allowed." + "Using the %s placeholder is required in the 'che.infra.kubernetes.namespace.default' parameter." + " The current value is: `%s`.", Joiner.on(" or ").join(REQUIRED_NAMESPACE_NAME_PLACEHOLDERS), defaultNamespaceName)); } } /** * Creates a Kubernetes namespace for the specified workspace. * *

Namespace won't be prepared. This method should be used only in case workspace recovering. * * @param workspaceId identifier of the workspace * @return created namespace */ public KubernetesNamespace access(String workspaceId, String namespace) { return doCreateNamespaceAccess(workspaceId, namespace); } @VisibleForTesting KubernetesNamespace doCreateNamespaceAccess(String workspaceId, String name) { return new KubernetesNamespace( cheServerKubernetesClientFactory, sharedPool.getExecutor(), name, workspaceId); } /** * Checks if the current user is able to use the specified namespace for their new workspaces. * * @param namespaceName namespace name to check * @throws ValidationException if the specified namespace is not permitted for the current user */ public void checkIfNamespaceIsAllowed(String namespaceName) throws ValidationException { NamespaceResolutionContext context = new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject()); final String defaultNamespace = findStoredNamespace(context).orElse(evalPlaceholders(defaultNamespaceName, context)); if (!namespaceName.equals(defaultNamespace)) { try { List labeledNamespaces = findPreparedNamespaces(context); if (labeledNamespaces.stream().noneMatch(n -> n.getName().equals(namespaceName))) { throw new ValidationException( format( "User defined namespaces are not allowed. Only the default namespace '%s' is available.", defaultNamespace)); } } catch (InfrastructureException e) { throw new ValidationException("Some infrastructure failure caused failed validation.", e); } } } /** Returns list of k8s namespaces names where a user is able to run workspaces. */ public List list() throws InfrastructureException { NamespaceResolutionContext resolutionCtx = new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject()); List labeledNamespaces = findPreparedNamespaces(resolutionCtx); if (!labeledNamespaces.isEmpty()) { return labeledNamespaces; } else { return singletonList(getDefaultNamespace(resolutionCtx)); } } /** * Returns default namespace, it's based on existing namespace if there is such or just object * holder if there is no such namespace on cluster. */ private KubernetesNamespaceMeta getDefaultNamespace(NamespaceResolutionContext resolutionCtx) throws InfrastructureException { String evaluatedName = evaluateNamespaceName(resolutionCtx); Optional defaultNamespaceOpt = fetchNamespace(evaluatedName); KubernetesNamespaceMeta defaultNamespace = defaultNamespaceOpt // if the predefined namespace does not exist - return dummy info and it will be created // during the first workspace start .orElseGet(() -> new KubernetesNamespaceMetaImpl(evaluatedName)); defaultNamespace.getAttributes().put(DEFAULT_ATTRIBUTE, "true"); return defaultNamespace; } /** * Fetches the specified namespace from a cluster. * * @param name name of namespace that should be fetched. * @return optional with kubernetes namespace meta * @throws InfrastructureException when any error occurs during namespace fetching */ public Optional fetchNamespace(String name) throws InfrastructureException { try { Namespace namespace = cheServerKubernetesClientFactory.create().namespaces().withName(name).get(); if (namespace == null) { return Optional.empty(); } else { return Optional.of(asNamespaceMeta(namespace)); } } catch (KubernetesClientException e) { throw new InfrastructureException( "Error occurred when tried to fetch default namespace. Cause: " + e.getMessage(), e); } } private KubernetesNamespaceMeta asNamespaceMeta(Namespace namespace) { Map attributes = new HashMap<>(2); if (namespace.getStatus() != null && namespace.getStatus().getPhase() != null) { attributes.put(PHASE_ATTRIBUTE, namespace.getStatus().getPhase()); } return new KubernetesNamespaceMetaImpl(namespace.getMetadata().getName(), attributes); } /** * Tells the caller whether the namespace that is being prepared for the provided workspace * runtime identity can be created or is expected to already be present. * * @return true if the namespace can be created, false if the namespace is expected to already * exist */ protected boolean canCreateNamespace() { return namespaceCreationAllowed; } /** * A managed namespace of a workspace is a namespace that is fully controlled by Che. Such * namespaces are deleted when the workspace is deleted. * * @param namespaceName the name of the namespace the workspace is stored in * @param workspace the workspace */ protected boolean isWorkspaceNamespaceManaged(String namespaceName, Workspace workspace) { return namespaceName != null && namespaceName.contains(workspace.getId()); } public KubernetesNamespace getOrCreate(RuntimeIdentity identity) throws InfrastructureException { KubernetesNamespace namespace = get(identity); var subject = EnvironmentContext.getCurrent().getSubject(); var userName = subject.getUserName(); validateAuthorization(namespace.getName(), subject); NamespaceResolutionContext resolutionCtx = new NamespaceResolutionContext(identity.getWorkspaceId(), subject.getUserId(), userName); Map namespaceAnnotationsEvaluated = evaluateAnnotationPlaceholders(resolutionCtx); namespace.prepare( canCreateNamespace(), labelNamespaces ? namespaceLabels : emptyMap(), annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap()); configureNamespace(resolutionCtx, namespace.getName()); return namespace; } public KubernetesNamespace get(RuntimeIdentity identity) throws InfrastructureException { String workspaceId = identity.getWorkspaceId(); String namespaceName = identity.getInfrastructureNamespace(); return doCreateNamespaceAccess(workspaceId, namespaceName); } /** Gets a namespace the workspace is deployed to. */ public KubernetesNamespace get(Workspace workspace) throws InfrastructureException { return doCreateNamespaceAccess(workspace.getId(), getNamespaceName(workspace)); } /** Returns a namespace name where workspace is assigned to. */ protected String getNamespaceName(Workspace workspace) throws InfrastructureException { String namespace = workspace.getAttributes().get(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE); if (namespace == null) { // it seems to be legacy workspace since the namespace is not stored in workspace attributes // it's needed to evaluate that with current user and workspace id NamespaceResolutionContext resolutionCtx = new NamespaceResolutionContext( workspace.getId(), EnvironmentContext.getCurrent().getSubject().getUserId(), EnvironmentContext.getCurrent().getSubject().getUserName()); namespace = evaluateNamespaceName(resolutionCtx); LOG.warn( "Workspace '{}' doesn't have an explicit namespace assigned." + " The legacy namespace resolution resolved it to '{}'.", workspace.getId(), namespace); } if (!NamespaceNameValidator.isValid(namespace)) { // At a certain unfortunate past version of Che, we stored invalid namespace names. // At this point in time, we're trying to work with an existing workspace that never could // started OR has been running since before that unfortunate version. In both cases, going // back to the default namespace name is the most safe bet we can make. // but of course, our attempt will be futile if we're running in a context that doesn't know // the current user. Subject subj = EnvironmentContext.getCurrent().getSubject(); if (!subj.isAnonymous()) { NamespaceResolutionContext resolutionCtx = new NamespaceResolutionContext(workspace.getId(), subj.getUserId(), subj.getUserName()); String defaultNamespace = evaluateNamespaceName(resolutionCtx); LOG.warn( "The namespace '{}' of the workspace '{}' is not valid. Trying to recover" + " from this situation using a default namespace which resolved to '{}'.", namespace, workspace.getId(), defaultNamespace); namespace = defaultNamespace; } else { // log a warning including a stacktrace to be able to figure out from where we got here... LOG.warn( "The namespace '{}' of the workspace '{}' is not valid but we currently don't have" + " an active user to try an recover from this situation. We're letting the parent" + " workflow continue, but it may fail at some later point in time because of" + " the incorrect namespace name in use.", namespace, workspace.getId(), new Throwable()); } // ok, we tried to recover the namespace but nothing helped. } return namespace; } /** * Evaluates namespace according to the specified context. * *

First we try to find namespace with labels set in `che.infra.kubernetes.namespace.labels` * property. If any found, we take the first one and use it. See: {@link * KubernetesNamespaceFactory#findFirstLabeledNamespace(NamespaceResolutionContext)} * *

Then we try to find namespace stored in persisted user's preferences and use it if found. * See: {@link KubernetesNamespaceFactory#findStoredNamespace(NamespaceResolutionContext)} * *

As a last option, we construct namespace name from `che.infra.kubernetes.namespace.default` * property. See: {@link * KubernetesNamespaceFactory#evalDefaultNamespace(NamespaceResolutionContext)} * * @param resolutionCtx context for namespace evaluation * @return evaluated namespace name * @throws InfrastructureException when there legacy namespace doesn't exist and default namespace * is not configured * @throws InfrastructureException when any exception occurs during evaluation */ public String evaluateNamespaceName(NamespaceResolutionContext resolutionCtx) throws InfrastructureException { Optional namespace = findFirstLabeledNamespace(resolutionCtx).or(() -> findStoredNamespace(resolutionCtx)); if (namespace.isPresent()) { return namespace.get(); } else { return evalDefaultNamespace(resolutionCtx); } } /** * Finds first namespace matches labels set by `che.infra.kubernetes.namespace.labels` property. * * @return first found labeled namespace if such namespace exists */ private Optional findFirstLabeledNamespace(NamespaceResolutionContext resolutionCtx) throws InfrastructureException { List labeledNamespaces = findPreparedNamespaces(resolutionCtx); if (!labeledNamespaces.isEmpty()) { String foundNamespace = labeledNamespaces.stream() .findFirst() .map(KubernetesNamespaceMeta::getName) .orElseThrow( () -> new InfrastructureException( "Failed when fetching labeled namespaces. It should not happen. Please report a bug if you see this!")); if (labeledNamespaces.size() > 1) { LOG.warn( "found '{}' matching labeled namespaces {}. Using '{}'.", labeledNamespaces.size(), labeledNamespaces.toString(), foundNamespace); } return Optional.of(foundNamespace); } else { return Optional.empty(); } } /** * Finds namespace name stored in User's preferences and ensures it is still valid. * * @return user's stored namespace if exists */ private Optional findStoredNamespace(NamespaceResolutionContext resolutionCtx) { Optional> storedNamespace = getPreferencesNamespaceName(resolutionCtx); if (storedNamespace.isPresent() && storedNamespace.get().second.equals(defaultNamespaceName)) { return Optional.of(storedNamespace.get().first); } else { return Optional.empty(); } } /** * Constructs the namespace name from `che.infra.kubernetes.namespace.default` property. Ensures * that all placeholders are evaluated and final namespace name is in valid format. * * @return ready-to-use namespace name */ private String evalDefaultNamespace(NamespaceResolutionContext resolutionCtx) throws InfrastructureException { String namespace = evalPlaceholders(defaultNamespaceName, resolutionCtx); if (!NamespaceNameValidator.isValid(namespace)) { Optional namespaceMetaOptional; String normalizedNamespace = normalizeNamespaceName(namespace); if (normalizedNamespace.isEmpty()) { throw new InfrastructureException( format( "Evaluated empty namespace name for workspace %s", resolutionCtx.getWorkspaceId())); } do { normalizedNamespace = normalizedNamespace .substring(0, Math.min(55, normalizedNamespace.length())) .concat(NameGenerator.generate("-", 6)); namespaceMetaOptional = fetchNamespace(normalizedNamespace); } while (namespaceMetaOptional.isPresent()); namespace = normalizedNamespace; } LOG.debug( "Evaluated the namespace for workspace {} using the namespace default to {}", resolutionCtx.getWorkspaceId(), namespace); return namespace; } /** * Finds all namespaces that matches the labels configured in * `che.infra.kubernetes.namespace.labels` and annotations in * `che.infra.kubernetes.namespace.annotations` properties. Makes sure that placeholder in the * annotations property are correctly evaluated. * *

If used ServiceAccount does not have permissions to list the namespaces, returns the empty * list. * * @return namespaces that matches the configured labels and annotations * @throws InfrastructureException in case of any Kubernetes request failure */ protected List findPreparedNamespaces( NamespaceResolutionContext namespaceCtx) throws InfrastructureException { try { List workspaceNamespaces = cheServerKubernetesClientFactory .create() .namespaces() .withLabels(namespaceLabels) .list() .getItems(); if (!workspaceNamespaces.isEmpty()) { Map evaluatedAnnotations = evaluateAnnotationPlaceholders(namespaceCtx); return workspaceNamespaces.stream() .filter(p -> matchesAnnotations(p, evaluatedAnnotations)) .map(this::asNamespaceMeta) .collect(Collectors.toList()); } else { return emptyList(); } } catch (KubernetesClientException kce) { if (kce.getCode() == 403) { LOG.warn( "Trying to fetch namespaces with labels '{}', but failed for lack of permissions. Cause: '{}'", namespaceLabels, kce.getMessage()); return emptyList(); } else { throw new InfrastructureException( "Error occurred when tried to list all available namespaces. Cause: " + kce.getMessage(), kce); } } } protected void configureNamespace( NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException { for (NamespaceConfigurator configurator : namespaceConfigurators) { configurator.configure(namespaceResolutionContext, namespaceName); } } /** * Evaluate placeholder in `che.infra.kubernetes.namespace.annotations` property with given {@link * NamespaceResolutionContext}. * * @return evaluated labels */ protected Map evaluateAnnotationPlaceholders( NamespaceResolutionContext namespaceCtx) { Map evaluatedAnnotations = new HashMap<>(); for (String annotationName : namespaceAnnotations.keySet()) { String evaluatedAnnotationValue = namespaceAnnotations .get(annotationName) .replace(USERNAME_PLACEHOLDER, namespaceCtx.getUserName()); evaluatedAnnotations.put(annotationName, evaluatedAnnotationValue); } return evaluatedAnnotations; } /** * Checks if given `object` contains all given `annotations` with exact values. * * @param object to check * @param annotations that given `object` has to contain * @return true if `object` contains all `annotations`. False otherwise. */ protected boolean matchesAnnotations(HasMetadata object, Map annotations) { if (object.getMetadata().getAnnotations() == null) { return false; } return object.getMetadata().getAnnotations().entrySet().containsAll(annotations.entrySet()); } public void deleteIfManaged(Workspace workspace) throws InfrastructureException { KubernetesNamespace namespace = get(workspace); if (isWorkspaceNamespaceManaged(namespace.getName(), workspace)) { namespace.delete(); } } protected void validateAuthorization(String namespaceName, Subject subject) throws InfrastructureException { if (!authorizationChecker.isAuthorized(subject)) { try { permissionsCleaner.cleanUp(namespaceName); } catch (InfrastructureException | KubernetesClientException e) { LOG.error( "Failed to clean up permissions for user '{}' in namespace '{}'. Cause: {}", subject.getUserName(), namespaceName, e.getMessage(), e); } throw new AuthorizationException( format( "User '%s' is not authorized to create a project. Please contact your system administrator.", subject.getUserName())); } } protected String evalPlaceholders(String namespace, NamespaceResolutionContext ctx) { checkArgument(!isNullOrEmpty(namespace)); String evaluated = namespace; for (Entry> placeHolder : NAMESPACE_NAME_PLACEHOLDERS.entrySet()) { String key = placeHolder.getKey(); String value = placeHolder.getValue().apply(ctx); if (value != null) { evaluated = evaluated.replaceAll(key, value); } } return evaluated; } /** * Stores computed namespace name and it's template into user preferences. Template is required to * track its changes and re-generate namespace in case it didn't matches. */ private void recordEvaluatedNamespaceName(String namespace, NamespaceResolutionContext context) { try { final String owner = context.getUserId(); Map preferences = preferenceManager.find(owner); preferences.put(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, namespace); preferences.put(NAMESPACE_TEMPLATE_ATTRIBUTE, defaultNamespaceName); preferenceManager.update(owner, preferences); } catch (ServerException e) { LOG.error("Failed storing namespace name in user properties.", e); } } /** Returns stored namespace if any, and its default template. */ private Optional> getPreferencesNamespaceName( NamespaceResolutionContext context) { try { String owner = context.getUserId(); Map preferences = preferenceManager.find(owner); if (preferences.containsKey(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE) && preferences.containsKey(NAMESPACE_TEMPLATE_ATTRIBUTE)) { return Optional.of( Pair.of( preferences.get(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE), preferences.get(NAMESPACE_TEMPLATE_ATTRIBUTE))); } } catch (ServerException e) { LOG.error(e.getMessage(), e); } return Optional.empty(); } /** * Normalizes input namespace name to K8S accepted format * * @param namespaceName input namespace name * @return normalized namespace name */ @VisibleForTesting String normalizeNamespaceName(String namespaceName) { namespaceName = namespaceName .toLowerCase() .replaceAll("[^-a-zA-Z0-9]", "-") // replace invalid chars with '-' .replaceAll("-+", "-") // replace multiple '-' with single ones .replaceAll("^-|-$", ""); // trim dashes at beginning/end of the string if (namespaceName.startsWith("kube-")) { namespaceName = "che-" + namespaceName; } return namespaceName.substring( 0, Math.min( namespaceName.length(), METADATA_NAME_MAX_LENGTH)); // limit length to METADATA_NAME_MAX_LENGTH } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesObjectUtil.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import static org.eclipse.che.workspace.infrastructure.kubernetes.Annotations.CREATE_IN_CHE_INSTALLATION_NAMESPACE; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.LabelSelector; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder; import io.fabric8.kubernetes.api.model.PersistentVolumeClaimFluent; import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSource; import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSourceBuilder; import io.fabric8.kubernetes.api.model.Quantity; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceSpec; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.VolumeMount; import io.fabric8.kubernetes.api.model.VolumeMountBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentSpec; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; /** * Helps to work with Kubernetes objects. * * @author Anton Korneta */ public class KubernetesObjectUtil { private static final String STORAGE_PARAM = "storage"; private static final String VALID_CONFIG_MAP_KEY_NAME = "[-._a-zA-Z0-9]+"; private static final Pattern VALID_CONFIG_MAP_KEY_NAME_PATTERN = Pattern.compile(VALID_CONFIG_MAP_KEY_NAME); /** Checks if the specified object has the specified label. */ public static boolean isLabeled(HasMetadata source, String key, String value) { ObjectMeta metadata = source.getMetadata(); if (metadata == null) { return false; } Map labels = metadata.getLabels(); if (labels == null) { return false; } return value.equals(labels.get(key)); } /** Adds label to target Kubernetes object. */ public static void putLabel(HasMetadata target, String key, String value) { ObjectMeta metadata = target.getMetadata(); if (metadata == null) { target.setMetadata(metadata = new ObjectMeta()); } putLabel(target.getMetadata(), key, value); } /** Adds labels to target Kubernetes object. */ public static void putLabels(ObjectMeta metadata, Map labels) { if (labels == null || labels.isEmpty()) { return; } Map metaLabels = metadata.getLabels(); if (metaLabels == null) { metadata.setLabels(new HashMap<>(labels)); } else { metaLabels.putAll(labels); } } /** Adds label to target Kubernetes object. */ public static void putLabel(ObjectMeta metadata, String key, String value) { Map labels = metadata.getLabels(); if (labels == null) { metadata.setLabels(labels = new HashMap<>()); } labels.put(key, value); } /** Adds annotation to target Kubernetes object. */ public static void putAnnotation(HasMetadata target, String key, String value) { ObjectMeta metadata = target.getMetadata(); if (metadata == null) { target.setMetadata(metadata = new ObjectMeta()); } putAnnotation(metadata, key, value); } /** Adds annotation to target ObjectMeta object. */ public static void putAnnotation(ObjectMeta metadata, String key, String value) { Map annotations = metadata.getAnnotations(); if (annotations == null) { metadata.setAnnotations(annotations = new HashMap<>()); } annotations.put(key, value); } /** Adds annotations to target ObjectMeta object. */ public static void putAnnotations(ObjectMeta metadata, Map annotations) { if (annotations == null || annotations.isEmpty()) { return; } Map metaAnnotations = metadata.getAnnotations(); if (metaAnnotations == null) { metadata.setAnnotations(new HashMap<>(annotations)); } else { metaAnnotations.putAll(annotations); } } /** Adds selector into target Kubernetes service. */ public static void putSelector(Service target, String key, String value) { ServiceSpec spec = target.getSpec(); if (spec == null) { target.setSpec(spec = new ServiceSpec()); } Map selector = spec.getSelector(); if (selector == null) { spec.setSelector(selector = new HashMap<>()); } selector.put(key, value); } /** Sets the specified key/value par of a selector into target Kubernetes service. */ public static void setSelector(Service target, String key, String value) { ServiceSpec spec = target.getSpec(); if (spec == null) { spec = new ServiceSpec(); target.setSpec(spec); } HashMap selector = new HashMap<>(); selector.put(key, value); spec.setSelector(selector); } /** Sets the specified match labels for a selector into target Kubernetes Deployment. */ public static void setSelector(Deployment target, Map matchLabels) { DeploymentSpec spec = target.getSpec(); if (spec == null) { spec = new DeploymentSpec(); target.setSpec(spec); } LabelSelector selector = spec.getSelector(); if (selector == null) { selector = new LabelSelector(); spec.setSelector(selector); } selector.setMatchLabels(new HashMap<>(matchLabels)); } /** * Returns new instance of {@link PersistentVolumeClaim} with specified name, accessMode and * quantity. */ public static PersistentVolumeClaim newPVC(String name, String accessMode, String quantity) { return newPVC(name, accessMode, quantity, null); } /** * Returns new instance of {@link PersistentVolumeClaim} with specified name, accessMode, quantity * and storageClassName. */ public static PersistentVolumeClaim newPVC( String name, String accessMode, String quantity, String storageClassName) { PersistentVolumeClaimFluent.SpecNested< PersistentVolumeClaimBuilder> specs = new PersistentVolumeClaimBuilder() .withNewMetadata() .withName(name) .endMetadata() .withNewSpec() .withAccessModes(accessMode); if (!isNullOrEmpty(storageClassName)) { specs.withStorageClassName(storageClassName); } return specs .withNewResources() .withRequests(ImmutableMap.of(STORAGE_PARAM, new Quantity(quantity))) .endResources() .endSpec() .build(); } /** Returns new instance of {@link VolumeMount} with specified name, mountPath and subPath. */ public static VolumeMount newVolumeMount(String name, String mountPath, String subPath) { return new VolumeMountBuilder() .withMountPath(mountPath) .withName(name) .withSubPath(subPath) .build(); } /** Returns new instance of {@link Volume} with specified name and name of claim related to. */ public static Volume newVolume(String name, String pvcName) { final PersistentVolumeClaimVolumeSource pvcs = new PersistentVolumeClaimVolumeSourceBuilder().withClaimName(pvcName).build(); return new VolumeBuilder().withPersistentVolumeClaim(pvcs).withName(name).build(); } /** * Checks the object if it is propetly annotated to be created in Che installation namespace. * *

Create in Che installation namespace only if there is {@link * Annotations#CREATE_IN_CHE_INSTALLATION_NAMESPACE} annotation set exactly to `true`. In all * other cases we create the object in Workspace's namespace. * * @param k8sObject object to check * @return `true` if {@link Annotations#CREATE_IN_CHE_INSTALLATION_NAMESPACE} is set to `true`. * `false` otherwise. */ public static boolean shouldCreateInCheNamespace(HasMetadata k8sObject) { if (k8sObject.getMetadata() == null) { return false; } Map annotations = k8sObject.getMetadata().getAnnotations(); if (annotations == null || annotations.isEmpty()) { return false; } return annotations .getOrDefault(CREATE_IN_CHE_INSTALLATION_NAMESPACE, FALSE.toString()) .equals(TRUE.toString()); } /** Checks the provided name is a valid kubernetes config map key name */ public static boolean isValidConfigMapKeyName(String configMapKeyName) { return VALID_CONFIG_MAP_KEY_NAME_PATTERN.matcher(configMapKeyName).matches(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesPersistentVolumeClaims.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static io.fabric8.kubernetes.api.model.DeletionPropagation.BACKGROUND; import static java.util.stream.Collectors.toSet; import io.fabric8.kubernetes.api.model.Event; import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.WatcherException; import io.fabric8.kubernetes.client.dsl.Resource; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Defines an internal API for managing {@link PersistentVolumeClaim} instances in {@link * KubernetesPersistentVolumeClaims#namespace predefined namespace}. * * @author Sergii Leshchenko */ public class KubernetesPersistentVolumeClaims { private static final Logger LOG = LoggerFactory.getLogger(KubernetesPersistentVolumeClaims.class); private static final String PVC_BOUND_PHASE = "Bound"; private static final String PVC_EVENT_REASON_FIELD_KEY = "reason"; private static final String PVC_EVENT_WAIT_CONSUMER_REASON = "WaitForFirstConsumer"; private static final String PVC_EVENT_UID_FIELD_KEY = "involvedObject.uid"; private final String namespace; private final String workspaceId; private final KubernetesClientFactory clientFactory; KubernetesPersistentVolumeClaims( String namespace, String workspaceId, KubernetesClientFactory clientFactory) { this.namespace = namespace; this.clientFactory = clientFactory; this.workspaceId = workspaceId; } /** * Creates specified persistent volume claim. * * @param pvc persistent volume claim to create * @return created persistent volume claim * @throws InfrastructureException when any exception occurs */ public PersistentVolumeClaim create(PersistentVolumeClaim pvc) throws InfrastructureException { try { return clientFactory .create(workspaceId) .persistentVolumeClaims() .inNamespace(namespace) .create(pvc); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Returns all existing persistent volume claims. * * @throws InfrastructureException when any exception occurs */ public List get() throws InfrastructureException { try { return clientFactory .create(workspaceId) .persistentVolumeClaims() .inNamespace(namespace) .list() .getItems(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Returns all existing persistent volume claims with given label. * * @param labelName name of provided label * @param labelValue value of provided label * @return list of matched PVCs * @throws InfrastructureException when any exception occurs while fetching the pvcs */ public List getByLabel(String labelName, String labelValue) throws InfrastructureException { try { return clientFactory .create(workspaceId) .persistentVolumeClaims() .inNamespace(namespace) .withLabel(labelName, labelValue) .list() .getItems(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Creates all PVCs which are not present in current Kubernetes namespace. * * @param toCreate collection of PVCs to create * @throws InfrastructureException when any error occurs while creation */ public void createIfNotExist(Collection toCreate) throws InfrastructureException { final Set existing = get().stream().map(p -> p.getMetadata().getName()).collect(toSet()); for (PersistentVolumeClaim pvc : toCreate) { if (!existing.contains(pvc.getMetadata().getName())) { create(pvc); } } } /** * Removes all PVCs which have the specified labels. * * @param labels labels to filter PVCs * @throws InfrastructureException when any error occurs while removing */ public void delete(Map labels) throws InfrastructureException { try { clientFactory .create(workspaceId) .persistentVolumeClaims() .inNamespace(namespace) .withLabels(labels) .withPropagationPolicy(BACKGROUND) .delete(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Removes PVC by name. * * @param name the name of the PVC * @throws InfrastructureException when any error occurs while removing */ public void delete(final String name) throws InfrastructureException { try { clientFactory .create(workspaceId) .persistentVolumeClaims() .inNamespace(namespace) .withName(name) .withPropagationPolicy(BACKGROUND) .delete(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Waits until persistent volume claim state is bound. If used k8s Storage Class has * 'volumeBindingMode: WaitForFirstConsumer', we don't wait to avoid deadlock. * * @param name name of persistent volume claim that should be watched * @param timeoutMillis waiting timeout in milliseconds * @return persistent volume claim that is bound or in waiting for consumer state * @throws InfrastructureException when specified timeout is reached * @throws InfrastructureException when {@link Thread} is interrupted while waiting * @throws InfrastructureException when any other exception occurs */ public PersistentVolumeClaim waitBound(String name, long timeoutMillis) throws InfrastructureException { try { Resource pvcResource = clientFactory .create(workspaceId) .persistentVolumeClaims() .inNamespace(namespace) .withName(name); PersistentVolumeClaim actualPvc = pvcResource.get(); if (actualPvc.getStatus().getPhase().equals(PVC_BOUND_PHASE)) { return actualPvc; } CompletableFuture future = new CompletableFuture<>(); // any of these watchers can finish the operation resolving the future try (Watch boundWatcher = pvcIsBoundWatcher(future, pvcResource); Watch waitingWatcher = pvcIsWaitingForConsumerWatcher(future, actualPvc)) { return future.get(timeoutMillis, TimeUnit.MILLISECONDS); } catch (ExecutionException e) { // May happen only if WebSocket Connection is closed before needed event received. // Throw internal exception because there may be some cluster/network issues that admin // should take a look. throw new InternalInfrastructureException(e.getCause().getMessage(), e); } catch (TimeoutException e) { // May happen when PVC is not bound in the time. // Throw internal exception because there may be some cluster configuration/performance // issues that admin should take a look. throw new InternalInfrastructureException( "Waiting for persistent volume claim '" + name + "' reached timeout"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InfrastructureException( "Waiting for persistent volume claim '" + name + "' was interrupted"); } } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } private Watch pvcIsBoundWatcher( CompletableFuture future, Resource pvcResource) { return pvcResource.watch( new Watcher() { @Override public void eventReceived(Action action, PersistentVolumeClaim pvc) { if (pvc.getStatus().getPhase().equals(PVC_BOUND_PHASE)) { LOG.debug("pvc '" + pvc.getMetadata().getName() + "' is bound"); future.complete(pvc); } } @Override public void onClose(WatcherException cause) { safelyFinishFutureOnClose(cause, future, pvcResource.get().getMetadata().getName()); } }); } /** * Creates and returns {@link Watch} that watches for 'WaitForFirstConsumer' events on given PVC. */ private Watch pvcIsWaitingForConsumerWatcher( CompletableFuture future, PersistentVolumeClaim actualPvc) throws InfrastructureException { return clientFactory .create(workspaceId) .v1() .events() .inNamespace(namespace) .withField(PVC_EVENT_REASON_FIELD_KEY, PVC_EVENT_WAIT_CONSUMER_REASON) .withField(PVC_EVENT_UID_FIELD_KEY, actualPvc.getMetadata().getUid()) .watch( new Watcher() { @Override public void eventReceived(Action action, Event resource) { LOG.debug( "PVC '" + actualPvc.getMetadata().getName() + "' is waiting for first consumer. Don't wait to bound to avoid deadlock."); future.complete(actualPvc); } @Override public void onClose(WatcherException cause) { safelyFinishFutureOnClose(cause, future, actualPvc.getMetadata().getName()); } }); } private void safelyFinishFutureOnClose( WatcherException cause, CompletableFuture future, String pvcName) { if (cause != null) { future.completeExceptionally( new InfrastructureException( "Waiting for persistent volume claim '" + pvcName + "' was interrupted")); } else if (!future.isDone()) { future.cancel(true); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesSecrets.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static io.fabric8.kubernetes.api.model.DeletionPropagation.BACKGROUND; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_WORKSPACE_ID_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabel; import io.fabric8.kubernetes.api.model.LabelSelector; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.client.KubernetesClientException; import java.util.List; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; /** * Defines an internal API for managing {@link Secret} instances in {@link * KubernetesSecrets#namespace predefined namespace}. * * @author Sergii Leshchenko */ public class KubernetesSecrets { private final String namespace; private final String workspaceId; private final KubernetesClientFactory clientFactory; KubernetesSecrets(String namespace, String workspaceId, KubernetesClientFactory clientFactory) { this.namespace = namespace; this.workspaceId = workspaceId; this.clientFactory = clientFactory; } /** * Creates specified secret. * * @param secret secret to create * @throws InfrastructureException when any exception occurs */ public void create(Secret secret) throws InfrastructureException { putLabel(secret, CHE_WORKSPACE_ID_LABEL, workspaceId); try { clientFactory.create(workspaceId).secrets().inNamespace(namespace).create(secret); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Finds secrets matching specified label selector. * * @param labelSelector selector to filter secrets * @return matched secrets list * @throws InfrastructureException when any exception occurs */ public List get(LabelSelector labelSelector) throws InfrastructureException { try { return clientFactory .create(workspaceId) .secrets() .inNamespace(namespace) .withLabelSelector(labelSelector) .list() .getItems(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Get all secrets. * * @return namespace secrets list * @throws InfrastructureException when any exception occurs */ public List get() throws InfrastructureException { try { return clientFactory.create(workspaceId).secrets().inNamespace(namespace).list().getItems(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Deletes all existing secrets. * * @throws InfrastructureException when any exception occurs */ public void delete() throws InfrastructureException { try { clientFactory .create(workspaceId) .secrets() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .withPropagationPolicy(BACKGROUND) .delete(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesServices.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static io.fabric8.kubernetes.api.model.DeletionPropagation.BACKGROUND; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_WORKSPACE_ID_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabel; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putSelector; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.client.KubernetesClientException; import java.util.List; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; /** * Defines an internal API for managing {@link Service} instances in {@link * KubernetesServices#namespace predefined namespace}. * * @author Sergii Leshchenko */ public class KubernetesServices { private final String namespace; private final String workspaceId; private final KubernetesClientFactory clientFactory; KubernetesServices(String namespace, String workspaceId, KubernetesClientFactory clientFactory) { this.namespace = namespace; this.workspaceId = workspaceId; this.clientFactory = clientFactory; } /** * Creates specified service. * * @param service service to create * @return created service * @throws InfrastructureException when any exception occurs */ public Service create(Service service) throws InfrastructureException { putLabel(service, CHE_WORKSPACE_ID_LABEL, workspaceId); putSelector(service, CHE_WORKSPACE_ID_LABEL, workspaceId); try { return clientFactory.create(workspaceId).services().inNamespace(namespace).create(service); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Returns all existing services. * * @throws InfrastructureException when any exception occurs */ public List get() throws InfrastructureException { try { return clientFactory .create(workspaceId) .services() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .list() .getItems(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Deletes all existing services. * * @throws InfrastructureException when any exception occurs */ public void delete() throws InfrastructureException { try { clientFactory .create(workspaceId) .services() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .withPropagationPolicy(BACKGROUND) .delete(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesWorkspaceServiceAccount.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import io.fabric8.kubernetes.api.model.rbac.PolicyRuleBuilder; import io.fabric8.kubernetes.api.model.rbac.Role; import io.fabric8.kubernetes.api.model.rbac.RoleBinding; import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder; import io.fabric8.kubernetes.api.model.rbac.RoleBuilder; import io.fabric8.kubernetes.api.model.rbac.SubjectBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import java.util.List; import java.util.Set; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; /** * Holds logic for preparing workspace service account. * *

It checks that required service account, roles and role bindings exist and creates if needed. * * @author Sergii Leshchenko */ public class KubernetesWorkspaceServiceAccount extends AbstractWorkspaceServiceAccount { public KubernetesWorkspaceServiceAccount( String workspaceId, String namespace, String serviceAccountName, Set clusterRoleNames, KubernetesClientFactory clientFactory) { super( workspaceId, namespace, serviceAccountName, clusterRoleNames, clientFactory::create, c -> c.rbac().roles(), c -> c.rbac().roleBindings()); } @Override protected Role buildRole( String name, List resources, List resourceNames, List apiGroups, List verbs) { return new RoleBuilder() .withNewMetadata() .withName(name) .endMetadata() .withRules( new PolicyRuleBuilder() .withResources(resources) .withResourceNames(resourceNames) .withApiGroups(apiGroups) .withVerbs(verbs) .build()) .build(); } @Override protected RoleBinding createRoleBinding( String roleName, String bindingName, boolean clusterRole) { return new RoleBindingBuilder() .withNewMetadata() .withName(bindingName) .withNamespace(namespace) .endMetadata() .withNewRoleRef() .withKind(clusterRole ? "ClusterRole" : "Role") .withName(roleName) .endRoleRef() .withSubjects( new SubjectBuilder() .withKind("ServiceAccount") .withName(serviceAccountName) .withNamespace(namespace) .build()) .build(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/NamespaceNameValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import com.google.common.annotations.VisibleForTesting; import java.util.regex.Pattern; import org.eclipse.che.api.core.ValidationException; public final class NamespaceNameValidator { static final int METADATA_NAME_MAX_LENGTH = 63; private static final String METADATA_NAME_REGEX = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"; private static final Pattern METADATA_NAME_PATTERN = Pattern.compile(METADATA_NAME_REGEX); private NamespaceNameValidator() { throw new AssertionError(); } /** * Checks whether the provided name is a valid namespace name and throws an exception detailing * the validation result if it is not. * * @throws ValidationException if the provided name is not a valid namespace name */ public static void validate(String namespaceName) throws ValidationException { ValidationResult res = validateInternal(namespaceName); if (!res.isOk()) { throw res.toException(namespaceName); } } /** * Similar to {@link #validate(String)} but doesn't throw an exception. The caller cannot * distinguish why the name is not valid. * * @return true if the namespace is valid, false otherwise */ public static boolean isValid(String name) { return validateInternal(name).isOk(); } @VisibleForTesting static ValidationResult validateInternal(String namespaceName) { if (isNullOrEmpty(namespaceName)) { return ValidationResult.NULL_OR_EMPTY; } if (namespaceName.length() > METADATA_NAME_MAX_LENGTH) { return ValidationResult.TOO_LONG; } if (!METADATA_NAME_PATTERN.matcher(namespaceName).matches()) { return ValidationResult.INVALID; } return ValidationResult.OK; } @VisibleForTesting enum ValidationResult { OK, NULL_OR_EMPTY, TOO_LONG, INVALID; boolean isOk() { return this == OK; } ValidationException toException(String namespaceName) { switch (this) { case OK: throw new IllegalStateException( "Tried to convert a successful validation into" + " an exception. This is a bug."); case NULL_OR_EMPTY: return new ValidationException("Namespace name cannot be undefined."); case TOO_LONG: return new ValidationException( "The specified namespace " + namespaceName + " is invalid: must be no more than 63 characters"); case INVALID: return new ValidationException( "The specified namespace " + namespaceName + " is invalid: a DNS-1123 label must consist of lower case alphanumeric" + " characters or '-', and must start and end with an" + " alphanumeric character (e.g. 'my-name', or '123-abc', regex used for" + " validation is '" + METADATA_NAME_REGEX + "')"); default: throw new IllegalStateException( format( "Could not convert namespace validation failure '%s' to a validation exception." + " This is a bug.", this)); } } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/RemoveNamespaceOnWorkspaceRemove.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import com.google.inject.Inject; import com.google.inject.Singleton; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.shared.event.WorkspaceRemovedEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Listener for removing Kubernetes namespace on {@code WorkspaceRemovedEvent}. * * @author Sergii Leshchenko */ @Singleton public class RemoveNamespaceOnWorkspaceRemove implements EventSubscriber { private static final Logger LOG = LoggerFactory.getLogger(RemoveNamespaceOnWorkspaceRemove.class); private final KubernetesNamespaceFactory namespaceFactory; @Inject public RemoveNamespaceOnWorkspaceRemove(KubernetesNamespaceFactory namespaceFactory) { this.namespaceFactory = namespaceFactory; } @Inject public void subscribe(EventService eventService) { eventService.subscribe(this); } @Override public void onEvent(WorkspaceRemovedEvent event) { String workspaceId = event.getWorkspace().getId(); try { namespaceFactory.deleteIfManaged(event.getWorkspace()); } catch (InfrastructureException e) { LOG.warn( "Fail to remove Kubernetes namespace for workspace with id {}. Cause: {}", workspaceId, e.getMessage()); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/CredentialsSecretConfigurator.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Secret; import java.util.Base64; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This {@link NamespaceConfigurator} ensures that Secret {@link * org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount#CREDENTIALS_SECRET_NAME} * is present in the Workspace namespace. */ @Singleton public class CredentialsSecretConfigurator implements NamespaceConfigurator { private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private final PersonalAccessTokenManager personalAccessTokenManager; private static final int SSL_ERROR_CODE = 495; private static final Map SEARCH_LABELS = ImmutableMap.of( "app.kubernetes.io/part-of", "che.eclipse.org", "app.kubernetes.io/component", "scm-personal-access-token"); private static final String ANNOTATION_SCM_URL = "che.eclipse.org/scm-url"; private static final String MERGED_GIT_CREDENTIALS_SECRET_NAME = "devworkspace-merged-git-credentials"; private static final Logger LOG = LoggerFactory.getLogger(CredentialsSecretConfigurator.class); @Inject public CredentialsSecretConfigurator( CheServerKubernetesClientFactory cheServerKubernetesClientFactory, PersonalAccessTokenManager personalAccessTokenManager) { this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; this.personalAccessTokenManager = personalAccessTokenManager; } @Override public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException { var client = cheServerKubernetesClientFactory.create(); Secret mergedCredentialsSecret = client .secrets() .inNamespace(namespaceName) .withName(MERGED_GIT_CREDENTIALS_SECRET_NAME) .get(); for (Secret s : client.secrets().inNamespace(namespaceName).withLabels(SEARCH_LABELS).list().getItems()) { if (mergedCredentialsSecret == null || !getSecretData(mergedCredentialsSecret, "credentials") .contains(getSecretData(s, "token"))) { String scmServerUrl = s.getMetadata().getAnnotations().get(ANNOTATION_SCM_URL); try { personalAccessTokenManager.storeGitCredentials(scmServerUrl); } catch (ScmCommunicationException | ScmConfigurationPersistenceException | UnsatisfiedScmPreconditionException | ScmUnauthorizedException e) { if (e instanceof ScmCommunicationException && ((ScmCommunicationException) e).getStatusCode() == SSL_ERROR_CODE) { // need to remove the PAT secret as invalid personalAccessTokenManager.remove(scmServerUrl); throw new InfrastructureException(e.getMessage(), e); } LOG.error(e.getMessage(), e); } } } } private String getSecretData(Secret secret, String key) { return new String(Base64.getDecoder().decode(secret.getData().get(key).getBytes())); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigConfigurator.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.StringJoiner; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; import org.eclipse.che.api.factory.server.scm.GitUserData; import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class GitconfigConfigurator implements NamespaceConfigurator { private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private final Set gitUserDataFetchers; private static final Logger LOG = LoggerFactory.getLogger(GitconfigConfigurator.class); private static final String CONFIGMAP_DATA_KEY = "gitconfig"; // TODO: rename to a more generic name, since it is not only for user data private static final String GITCONFIG_CONFIGMAP_NAME = "workspace-userdata-gitconfig-configmap"; private static final Map GITCONFIG_CONFIGMAP_LABELS = ImmutableMap.of( "controller.devfile.io/mount-to-devworkspace", "true", "controller.devfile.io/watch-configmap", "true"); private static final Map GITCONFIG_CONFIGMAP_ANNOTATIONS = ImmutableMap.of( "controller.devfile.io/mount-as", "subpath", "controller.devfile.io/mount-path", "/etc"); private final Pattern usernmaePattern = Pattern.compile("\\[user](.|\\s)*name\\s*=\\s*(?.*)"); private final Pattern emailPattern = Pattern.compile("\\[user](.|\\s)*email\\s*=\\s*(?.*)"); private final Pattern emptyStringPattern = Pattern.compile("[\"']\\s*[\"']"); private final Pattern gitconfigSectionPattern = Pattern.compile("\\[(?[a-zA-Z0-9]+)](\\n\\s*\\S*\\s*=.*)*"); @Inject public GitconfigConfigurator( CheServerKubernetesClientFactory cheServerKubernetesClientFactory, Set gitUserDataFetchers) { this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; this.gitUserDataFetchers = gitUserDataFetchers; } @Override public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException { KubernetesClient client = cheServerKubernetesClientFactory.create(); Optional gitconfigOptional = getGitconfig(client, namespaceName); // Try to get username and email from the gitconfig configmap if present. Optional> usernameAndEmailFromGitconfigOptional = gitconfigOptional.isPresent() ? getUsernameAndEmailFromGitconfig(gitconfigOptional.get()) : Optional.empty(); // If username and email are not present in the gitconfig configmap, or the values are empty, or // the gitconfig configmap is empty, try to get them from the fetcher. if (usernameAndEmailFromGitconfigOptional.isEmpty()) { Optional> usernameAndEmailFromFetcher = // Fetch username and email from a token secret if it is present. getUsernameAndEmailFromFetcher(namespaceName); // If username and email are present, create or update the gitconfig configmap. if (usernameAndEmailFromFetcher.isPresent()) { ConfigMap gitconfigConfigmap = buildGitconfigConfigmap(); Optional gitconfigSectionsOptional = generateGitconfigSections(gitconfigOptional, usernameAndEmailFromFetcher); gitconfigConfigmap.setData( ImmutableMap.of(CONFIGMAP_DATA_KEY, gitconfigSectionsOptional.orElse(""))); client.configMaps().inNamespace(namespaceName).createOrReplace(gitconfigConfigmap); } } } private ConfigMap buildGitconfigConfigmap() { return new ConfigMapBuilder() .withNewMetadata() .withName(GITCONFIG_CONFIGMAP_NAME) .withLabels(GITCONFIG_CONFIGMAP_LABELS) .withAnnotations(GITCONFIG_CONFIGMAP_ANNOTATIONS) .endMetadata() .build(); } private Optional generateGitconfigSections( Optional gitconfigOptional, Optional> usernameAndEmailOptional) { Optional userSectionOptional = usernameAndEmailOptional.map(p -> generateUserSection(p.first, p.second)); StringJoiner joiner = new StringJoiner("\n"); userSectionOptional.ifPresent(joiner::add); gitconfigOptional.flatMap(this::getOtherStoredSections).ifPresent(joiner::add); return joiner.length() > 0 ? Optional.of(joiner.toString()) : Optional.empty(); } Optional getOtherStoredSections(String gitconfig) { StringJoiner joiner = new StringJoiner("\n"); Matcher matcher = gitconfigSectionPattern.matcher(gitconfig); while (matcher.find()) { String sectionName = matcher.group("sectionName"); if (!sectionName.equals("user") && !sectionName.equals("http")) { joiner.add(matcher.group()); } } return joiner.length() > 0 ? Optional.of(joiner.toString()) : Optional.empty(); } private Optional> getUsernameAndEmailFromGitconfig(String gitconfig) { if (gitconfig.contains("[user]")) { Matcher usernameMatcher = usernmaePattern.matcher(gitconfig); Matcher emailMatcher = emailPattern.matcher(gitconfig); if (usernameMatcher.find() && emailMatcher.find()) { String username = usernameMatcher.group("username"); String email = emailMatcher.group("email"); if (!emptyStringPattern.matcher(username).matches() && !emptyStringPattern.matcher(email).matches()) { return Optional.of(new Pair<>(username, email)); } } } return Optional.empty(); } private Optional> getUsernameAndEmailFromFetcher(String namespaceName) { GitUserData gitUserData; for (GitUserDataFetcher fetcher : gitUserDataFetchers) { try { gitUserData = fetcher.fetchGitUserData(namespaceName); if (!isNullOrEmpty(gitUserData.getScmUsername()) && !isNullOrEmpty(gitUserData.getScmUserEmail())) { return Optional.of( new Pair<>(gitUserData.getScmUsername(), gitUserData.getScmUserEmail())); } } catch (ScmUnauthorizedException | ScmCommunicationException | ScmConfigurationPersistenceException | ScmItemNotFoundException | ScmBadRequestException e) { LOG.debug("No GitUserDataFetcher is configured. " + e.getMessage()); } } return Optional.empty(); } private String generateUserSection(String username, String email) { return String.format("[user]\n\tname = %1$s\n\temail = %2$s", username, email); } private Optional getGitconfig(KubernetesClient client, String namespaceName) { ConfigMap gitconfigConfigmap = client.configMaps().inNamespace(namespaceName).withName(GITCONFIG_CONFIGMAP_NAME).get(); if (gitconfigConfigmap != null) { String gitconfig = gitconfigConfigmap.getData().get(CONFIGMAP_DATA_KEY); if (!isNullOrEmpty(gitconfig)) { return Optional.of(gitconfig); } } return Optional.empty(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/NamespaceConfigurator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import io.fabric8.kubernetes.api.model.Secret; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.NamespaceProvisioner; /** * Configures user's namespace after provisioning in {@link NamespaceProvisioner} with whatever is * needed. Such as creating user profile and preferences {@link Secret} in user namespace. * * @author Pavol Baran */ public interface NamespaceConfigurator { /** * Configures user's namespace after provisioning. * * @param namespaceResolutionContext users namespace context * @throws InfrastructureException when any error occurs */ void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException; } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/OAuthTokenSecretsConfigurator.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import com.google.common.collect.ImmutableMap; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Ensures that OAuth token that are represented by Kubernetes Secrets are valid. * * @author Anatolii Bazko */ @Singleton public class OAuthTokenSecretsConfigurator implements NamespaceConfigurator { private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private final PersonalAccessTokenManager personalAccessTokenManager; private static final String ANNOTATION_SCM_URL = "che.eclipse.org/scm-url"; private static final String ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME = "che.eclipse.org/scm-personal-access-token-name"; private static final Map SEARCH_LABELS = ImmutableMap.of( "app.kubernetes.io/part-of", "che.eclipse.org", "app.kubernetes.io/component", "scm-personal-access-token"); private static final Logger LOG = LoggerFactory.getLogger(OAuthTokenSecretsConfigurator.class); @Inject public OAuthTokenSecretsConfigurator( CheServerKubernetesClientFactory cheServerKubernetesClientFactory, PersonalAccessTokenManager personalAccessTokenManager) { this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; this.personalAccessTokenManager = personalAccessTokenManager; } @Override public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException { var client = cheServerKubernetesClientFactory.create(); client.secrets().inNamespace(namespaceName).withLabels(SEARCH_LABELS).list().getItems().stream() .filter( s -> s.getMetadata().getAnnotations() != null && s.getMetadata().getAnnotations().containsKey(ANNOTATION_SCM_URL) && s.getMetadata() .getAnnotations() .containsKey(ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME) && s.getMetadata() .getAnnotations() .get(ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME) .startsWith(PersonalAccessTokenFetcher.OAUTH_2_PREFIX)) .forEach( s -> { try { Subject cheSubject = EnvironmentContext.getCurrent().getSubject(); personalAccessTokenManager.get( cheSubject, null, s.getMetadata().getAnnotations().get(ANNOTATION_SCM_URL), namespaceName); } catch (ScmConfigurationPersistenceException | ScmCommunicationException e) { LOG.error(e.getMessage(), e); } }); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/PreferencesConfigMapConfigurator.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import javax.inject.Inject; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; /** * This {@link NamespaceConfigurator} ensures that ConfigMap {@link * org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount#PREFERENCES_CONFIGMAP_NAME} * is present in the Workspace namespace. */ public class PreferencesConfigMapConfigurator implements NamespaceConfigurator { private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Inject public PreferencesConfigMapConfigurator( CheServerKubernetesClientFactory cheServerKubernetesClientFactory) { this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; } @Override public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException { var client = cheServerKubernetesClientFactory.create(); if (client.configMaps().inNamespace(namespaceName).withName(PREFERENCES_CONFIGMAP_NAME).get() == null) { ConfigMap configMap = new ConfigMapBuilder() .withNewMetadata() .withName(PREFERENCES_CONFIGMAP_NAME) .endMetadata() .build(); client.configMaps().inNamespace(namespaceName).create(configMap); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/SshConfigConfigurator.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.client.KubernetesClient; import java.util.Base64; import javax.inject.Inject; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; /** * This {@link NamespaceConfigurator} ensures that the SSH key Secret from the Workspace namespace * does not contain the `ssh_config` key in its data. If the key is present in the Secret data, * remove it in order to avoid mount collisions from the `git-ssh-config` ConfigMap and Create a * dedicated ConfigMap to mount SSH Config. See * https://github.com/eclipse-che/che-dashboard/pull/1443 */ public class SshConfigConfigurator implements NamespaceConfigurator { private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Inject public SshConfigConfigurator(CheServerKubernetesClientFactory cheServerKubernetesClientFactory) { this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; } @Override public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException { String configKeyName = "ssh_config"; String config = "host *\n" + " IdentityFile /etc/ssh/dwo_ssh_key\n" + " StrictHostKeyChecking = no\n" + "\n" + "Include /etc/ssh/ssh_config.d/*.conf"; KubernetesClient client = cheServerKubernetesClientFactory.create(); Secret secret = client.secrets().inNamespace(namespaceName).withName("git-ssh-key").get(); if (secret != null && secret.getData().containsKey(configKeyName)) { if (trimEnd(new String(Base64.getDecoder().decode(secret.getData().get(configKeyName))), '\n') .equals(config)) { return; } secret.getData().put(configKeyName, Base64.getEncoder().encodeToString(config.getBytes())); client.secrets().inNamespace(namespaceName).resource(secret).patch(); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/SshKeysConfigurator.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.stream.Collectors.toList; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_PATH_ANNOTATION; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_WATCH_SECRET_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.isValidConfigMapKeyName; import com.google.common.annotations.VisibleForTesting; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import jakarta.validation.constraints.NotNull; import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import javax.inject.Inject; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.ssh.server.SshManager; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; import org.eclipse.che.api.ssh.shared.model.SshPair; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class mounts existing user SSH Keys into a special Kubernetes Secret on user-s namespace. */ public class SshKeysConfigurator implements NamespaceConfigurator { public static final String SSH_KEY_SECRET_NAME = "che-git-ssh-key"; private static final String SSH_KEYS_WILL_NOT_BE_MOUNTED_MESSAGE = "Ssh keys %s have invalid names and can't be mounted to namespace %s."; private static final String SSH_BASE_CONFIG_PATH = "/etc/ssh/"; private final SshManager sshManager; private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private static final Logger LOG = LoggerFactory.getLogger(SshKeysConfigurator.class); public static final Pattern VALID_DOMAIN_PATTERN = Pattern.compile("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"); @Inject public SshKeysConfigurator( SshManager sshManager, CheServerKubernetesClientFactory cheServerKubernetesClientFactory) { this.sshManager = sshManager; this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; } @Override public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException { var client = cheServerKubernetesClientFactory.create(); List vcsSshPairs = getVcsSshPairs(namespaceResolutionContext); List invalidSshKeyNames = vcsSshPairs.stream() .filter(keyPair -> !isValidSshKeyPair(keyPair)) .map(SshPairImpl::getName) .collect(toList()); if (!invalidSshKeyNames.isEmpty()) { String message = format( SSH_KEYS_WILL_NOT_BE_MOUNTED_MESSAGE, invalidSshKeyNames.toString(), namespaceName); LOG.warn(message); // filter vcsSshPairs = vcsSshPairs.stream().filter(this::isValidSshKeyPair).collect(toList()); } if (vcsSshPairs.size() == 0) { // nothing to provision return; } doProvisionSshKeys(vcsSshPairs, client, namespaceName); } /** * Return list of keys related to the VCS (Version Control Systems), Git, SVN and etc. Usually * managed by user * * @param context NamespaceResolutionContext * @return list of ssh pairs */ private List getVcsSshPairs(NamespaceResolutionContext context) throws InfrastructureException { List sshPairs; try { sshPairs = sshManager.getPairs(context.getUserId(), "vcs"); } catch (ServerException e) { String message = format("Unable to get SSH Keys. Cause: %s", e.getMessage()); LOG.warn(message); throw new InfrastructureException(e); } return sshPairs; } @VisibleForTesting boolean isValidSshKeyPair(SshPairImpl keyPair) { return isValidConfigMapKeyName(keyPair.getName()) && VALID_DOMAIN_PATTERN.matcher(keyPair.getName()).matches(); } private void doProvisionSshKeys( List sshPairs, KubernetesClient client, String namespaceName) { StringBuilder sshConfigData = new StringBuilder(); Map data = new HashMap<>(); for (SshPair sshPair : sshPairs) { sshConfigData.append(buildConfig(sshPair.getName())); if (!isNullOrEmpty(sshPair.getPrivateKey()) && !isNullOrEmpty(sshPair.getPublicKey())) { data.put( sshPair.getName(), Base64.getEncoder().encodeToString(sshPair.getPrivateKey().getBytes())); data.put( sshPair.getName() + ".pub", Base64.getEncoder().encodeToString(sshPair.getPublicKey().getBytes())); } } data.put("ssh_config", Base64.getEncoder().encodeToString(sshConfigData.toString().getBytes())); Secret secret = new SecretBuilder() .addToData(data) .withType("generic") .withMetadata(buildMetadata()) .build(); client.secrets().inNamespace(namespaceName).createOrReplace(secret); } private ObjectMeta buildMetadata() { return new ObjectMetaBuilder() .withName(SSH_KEY_SECRET_NAME) .withLabels( Map.of( DEV_WORKSPACE_MOUNT_LABEL, "true", DEV_WORKSPACE_WATCH_SECRET_LABEL, "true")) .withAnnotations(Map.of(DEV_WORKSPACE_MOUNT_PATH_ANNOTATION, SSH_BASE_CONFIG_PATH)) .build(); } /** * Returns the ssh configuration entry which includes host, identity file location and Host Key * checking policy * *

Example of provided configuration: * *

   * host github.com
   * IdentityFile /.ssh/private/github-com/private
   * StrictHostKeyChecking = no
   * 
* * or * *
   * host *
   * IdentityFile /.ssh/private/default-123456/private
   * StrictHostKeyChecking = no
   * 
* * @param name the of key given during generate for vcs service we will consider it as host of * version control service (e.g. github.com, gitlab.com and etc) if name starts from * "default-{anyString}" it will be replaced on wildcard "*" host name. Name with format * "default-{anyString}" will be generated on client side by Theia SSH Plugin, if user doesn't * provide own name. Details see here: * https://github.com/eclipse/che/issues/13494#issuecomment-512761661. Note: behavior can be * improved in 7.x releases after 7.0.0 * @return the ssh configuration which include host, identity file location and Host Key checking * policy */ private String buildConfig(@NotNull String name) { String host = name.startsWith("default-") ? "*" : name; return "host " + host + "\nIdentityFile " + SSH_BASE_CONFIG_PATH + name + "\nStrictHostKeyChecking = no" + "\n\n"; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/UserPermissionConfigurator.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.base.Splitter; import com.google.common.collect.Sets; import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import java.util.Collections; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; /** * This {@link NamespaceConfigurator} ensures that User has assigned configured ClusterRoles from * `che.infra.kubernetes.user_cluster_roles` property. These are assigned with RoleBindings in * user's namespace. */ @Singleton public class UserPermissionConfigurator implements NamespaceConfigurator { private final Set userClusterRoles; private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Inject public UserPermissionConfigurator( @Nullable @Named("che.infra.kubernetes.user_cluster_roles") String userClusterRoles, CheServerKubernetesClientFactory cheServerKubernetesClientFactory) { this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; if (!isNullOrEmpty(userClusterRoles)) { this.userClusterRoles = Sets.newHashSet( Splitter.on(",").trimResults().omitEmptyStrings().split(userClusterRoles)); } else { this.userClusterRoles = Collections.emptySet(); } } @Override public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException { if (!userClusterRoles.isEmpty()) { bindRoles( cheServerKubernetesClientFactory.create(), namespaceName, namespaceResolutionContext.getUserName(), userClusterRoles); } } private void bindRoles( KubernetesClient client, String namespaceName, String username, Set clusterRoles) { for (String clusterRole : clusterRoles) { client .rbac() .roleBindings() .inNamespace(namespaceName) .createOrReplace( new RoleBindingBuilder() .withNewMetadata() .withName(clusterRole) .endMetadata() .addToSubjects( new io.fabric8.kubernetes.api.model.rbac.Subject( "rbac.authorization.k8s.io", "User", username, namespaceName)) .withNewRoleRef() .withApiGroup("rbac.authorization.k8s.io") .withKind("ClusterRole") .withName(clusterRole) .endRoleRef() .build()); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/UserPreferencesConfigurator.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import com.google.common.annotations.VisibleForTesting; import io.fabric8.kubernetes.api.model.Secret; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Creates {@link Secret} with user preferences. This serves as a way for DevWorkspaces to acquire * information about the user. * * @author Pavol Baran */ @Deprecated @Singleton public class UserPreferencesConfigurator implements NamespaceConfigurator { private static final Logger LOG = LoggerFactory.getLogger(UserProfileConfigurator.class); private static final String USER_PREFERENCES_SECRET_NAME = "user-preferences"; private static final String USER_PREFERENCES_SECRET_MOUNT_PATH = "/config/user/preferences"; private static final int PREFERENCE_NAME_MAX_LENGTH = 253; @Inject public UserPreferencesConfigurator() {} @Override public void configure( NamespaceResolutionContext namespaceResolutionContext, String namespaceName) { LOG.debug("'user-preferences' secret is obsolete and not configured anymore for DevWorkspaces"); } /** * Some preferences names are not compatible with k8s restrictions on key field in secret. The * keys of data must consist of alphanumeric characters, -, _ or . This method replaces illegal * characters with - * * @param name original preference name * @return k8s compatible preference name used as a key field in Secret */ @VisibleForTesting String normalizePreferenceName(String name) { name = name.replaceAll("[^-._a-zA-Z0-9]+", "-").replaceAll("-+", "-"); return name.substring(0, Math.min(name.length(), PREFERENCE_NAME_MAX_LENGTH)); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/UserProfileConfigurator.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_AS_ANNOTATION; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_PATH_ANNOTATION; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_WATCH_SECRET_LABEL; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.KubernetesClientException; import java.util.Base64; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; /** * Creates {@link Secret} with user profile information such as his id, name and email. This serves * as a way for DevWorkspaces to acquire information about the user. * * @author Pavol Baran */ @Singleton public class UserProfileConfigurator implements NamespaceConfigurator { private static final String USER_PROFILE_SECRET_NAME = "user-profile"; private static final String USER_PROFILE_SECRET_MOUNT_PATH = "/config/user/profile"; private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Inject public UserProfileConfigurator( CheServerKubernetesClientFactory cheServerKubernetesClientFactory) { this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; } @Override public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException { Secret userProfileSecret = prepareProfileSecret(namespaceResolutionContext); try { cheServerKubernetesClientFactory .create() .secrets() .inNamespace(namespaceName) .createOrReplace(userProfileSecret); } catch (KubernetesClientException e) { throw new InfrastructureException( "Error occurred while trying to create user profile secret.", e); } } private Secret prepareProfileSecret(NamespaceResolutionContext namespaceResolutionContext) throws InfrastructureException { var userId = namespaceResolutionContext.getUserId(); var userName = namespaceResolutionContext.getUserName(); var userEmail = userName + "@che"; Base64.Encoder enc = Base64.getEncoder(); final Map userProfileData = new HashMap<>(); userProfileData.put("id", enc.encodeToString(userId.getBytes())); userProfileData.put("name", enc.encodeToString(userName.getBytes())); userProfileData.put("email", enc.encodeToString(userEmail.getBytes())); return new SecretBuilder() .addToData(userProfileData) .withNewMetadata() .withName(USER_PROFILE_SECRET_NAME) .addToLabels(DEV_WORKSPACE_MOUNT_LABEL, "true") .addToLabels(DEV_WORKSPACE_WATCH_SECRET_LABEL, "true") .addToLabels("app.kubernetes.io/part-of", "che.eclipse.org") .addToAnnotations(DEV_WORKSPACE_MOUNT_AS_ANNOTATION, "file") .addToAnnotations(DEV_WORKSPACE_MOUNT_PATH_ANNOTATION, USER_PROFILE_SECRET_MOUNT_PATH) .endMetadata() .build(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/WorkspaceServiceAccountConfigurator.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import com.google.common.collect.Sets; import java.util.Collections; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesWorkspaceServiceAccount; /** * This {@link NamespaceConfigurator} ensures that workspace ServiceAccount with proper ClusterRole * is set in Workspace namespace. */ @Singleton public class WorkspaceServiceAccountConfigurator implements NamespaceConfigurator { private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private final String serviceAccountName; private final Set clusterRoleNames; @Inject public WorkspaceServiceAccountConfigurator( @Nullable @Named("che.infra.kubernetes.service_account_name") String serviceAccountName, @Nullable @Named("che.infra.kubernetes.workspace_sa_cluster_roles") String clusterRoleNames, CheServerKubernetesClientFactory cheServerKubernetesClientFactory) { this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; this.serviceAccountName = serviceAccountName; if (!isNullOrEmpty(clusterRoleNames)) { this.clusterRoleNames = Sets.newHashSet( Splitter.on(",").trimResults().omitEmptyStrings().split(clusterRoleNames)); } else { this.clusterRoleNames = Collections.emptySet(); } } @Override public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException { if (!isNullOrEmpty(serviceAccountName)) { KubernetesWorkspaceServiceAccount workspaceServiceAccount = doCreateServiceAccount(namespaceResolutionContext.getWorkspaceId(), namespaceName); workspaceServiceAccount.prepare(); } } @VisibleForTesting public KubernetesWorkspaceServiceAccount doCreateServiceAccount( String workspaceId, String namespaceName) { return new KubernetesWorkspaceServiceAccount( workspaceId, namespaceName, serviceAccountName, clusterRoleNames, cheServerKubernetesClientFactory); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/event/PodActionHandler.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.client.Watcher.Action; /** * Defines the handling mechanism for Kubernetes pod action events. * * @author Anton Korneta */ public interface PodActionHandler { /** Handles the pod action events. */ void handle(Action action, Pod pod); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/event/PodEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event; import java.util.Objects; import org.eclipse.che.commons.annotation.Nullable; /** * The event that should be published when the pod event occurs, e.g. pulling image, created * container * * @author Sergii Leshchenko */ public class PodEvent { private final String podName; @Nullable private final String containerName; private final String reason; private final String message; private final String creationTimestamp; private final String lastTimestamp; public PodEvent( String podName, String containerName, String reason, String message, String creationTimestamp, String lastTimestamp) { this.podName = podName; this.containerName = containerName; this.reason = reason; this.message = message; this.creationTimestamp = creationTimestamp; this.lastTimestamp = lastTimestamp; } /** Returns name of pod related to the event. */ public String getPodName() { return podName; } /** * Returns container name produced by the event. Could be null if the event is related to the pod * but not any particular 'container' */ @Nullable public String getContainerName() { return containerName; } /** Returns the reason of the event. */ public String getReason() { return reason; } /** Returns the contents of the event. */ public String getMessage() { return message; } /** Returns creation timestamp in format '2018-05-15T16:17:54Z' */ public String getCreationTimeStamp() { return creationTimestamp; } /** Returns last timestamp in format '2018-05-15T16:17:54Z' */ public String getLastTimestamp() { return lastTimestamp; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } PodEvent that = (PodEvent) o; return Objects.equals(podName, that.podName) && Objects.equals(containerName, that.containerName) && Objects.equals(reason, that.reason) && Objects.equals(message, that.message) && Objects.equals(creationTimestamp, that.creationTimestamp) && Objects.equals(lastTimestamp, that.lastTimestamp); } @Override public int hashCode() { return Objects.hash(podName, containerName, reason, message, creationTimestamp, lastTimestamp); } @Override public String toString() { return "PodEvent{" + "podName='" + podName + '\'' + ", containerName='" + containerName + '\'' + ", reason='" + reason + '\'' + ", message='" + message + '\'' + ", creationTimestamp='" + creationTimestamp + '\'' + ", lastTimestamp='" + lastTimestamp + '\'' + '}'; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/event/PodEventHandler.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event; /** * Defines the handling mechanism for Kubernetes events. * * @author Sergii Leshchenko */ public interface PodEventHandler { /** Handles the container event. */ void handle(PodEvent event); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/log/ContainerLogWatch.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log; import com.google.common.base.Stopwatch; import com.google.common.io.ByteStreams; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.LogWatch; import java.io.BufferedReader; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Objects; import java.util.StringJoiner; import java.util.concurrent.TimeUnit; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is responsible for watching logs in a single container instance. Messages are * provided, unbuffered, to a provided {@link PodLogHandler} * *

Reading logs is a blocking operation. */ class ContainerLogWatch implements Runnable, Closeable { private static final Logger LOG = LoggerFactory.getLogger(ContainerLogWatch.class); private final KubernetesClient client; private final RuntimeEventsPublisher eventPublisher; private final PodLogHandler logHandler; private final LogWatchTimeouts timeouts; private final long inputStreamLimit; private final String namespace; private final String podName; private final String containerName; // current LogWatch instance. We need it so we can close it from outside in close() method. private LogWatch currentLogWatch; // json parser used to parse log messages to check for errorness private final JsonParser jsonParser = new JsonParser(); // flag whether we should still try to get the logs private boolean closed = false; ContainerLogWatch( KubernetesClient client, RuntimeEventsPublisher eventPublisher, String namespace, String podName, String containerName, PodLogHandler logHandler, LogWatchTimeouts timeouts, long inputStreamLimit) { this.client = client; this.eventPublisher = eventPublisher; this.namespace = namespace; this.podName = podName; this.containerName = containerName; this.logHandler = logHandler; this.timeouts = timeouts; this.inputStreamLimit = inputStreamLimit; } /** * Do the best effort to get the logs from the container. The method is trying for {@link * LogWatchTimeouts#getWatchTimeoutMs()} to get the logs from the container. If response on log * request from the k8s is 40x, it possibly means that container is not ready to get the logs and * we'll try again after {@link LogWatchTimeouts#getWaitBeforeNextTry()} milliseconds. */ @Override public void run() { Stopwatch stopwatch = Stopwatch.createStarted(); // we try to get logs for `timeouts.getWatchTimeoutMs()` while (stopwatch.elapsed(TimeUnit.MILLISECONDS) < timeouts.getWatchTimeoutMs()) { // request k8s to get the logs from the container try (LogWatch logWatch = client .pods() .inNamespace(namespace) .withName(podName) .inContainer(containerName) .watchLog()) { eventPublisher.sendWatchLogStartedEvent(containerKey()); // we need to synchroinze here to avoid adding new `currentLogWatch` after we close it synchronized (this) { if (closed) { return; } currentLogWatch = logWatch; } if (currentLogWatch.getOutput() == null || !readAndHandle( ByteStreams.limit(currentLogWatch.getOutput(), inputStreamLimit), logHandler)) { // failed to get the logs this time, so removing this watcher LOG.trace( "failed to get the logs for '{} : {} : {}'. Container probably still starting after [{}]ms.", namespace, podName, containerName, stopwatch.elapsed(TimeUnit.MILLISECONDS)); // wait before next try Thread.sleep(timeouts.getWaitBeforeNextTry()); } else { LOG.debug( "finished watching the logs of '{} : {} : {}'", namespace, podName, containerName); return; } } catch (InterruptedException e) { LOG.error( "Failed watch the logs '{} : {} : {}', nothing better to do here.", namespace, podName, containerName, e); return; } finally { eventPublisher.sendWatchLogStoppedEvent(containerKey()); } } } /** * Reads given inputStream. If we receive error message about pod is initializing from k8s (see: * {@link ContainerLogWatch#isErrorMessage(String)}, returns false immediately so we can try again * later. Otherwise keeps reading the messages from the stream and gives them to given handler. Be * aware that it is blocking and potentially long operation! * * @param inputStream to read log messages from * @param handler we delegate log messages to this handler. * @return false if error message received from k8s, true at the end of the stream or if * interrupted */ private boolean readAndHandle(InputStream inputStream, PodLogHandler handler) { try (BufferedReader in = new BufferedReader(new InputStreamReader(inputStream))) { String logMessage; while ((logMessage = in.readLine()) != null) { if (!this.isErrorMessage(logMessage)) { handler.handle(logMessage, containerName); } else { LOG.debug("error message [{}]", logMessage); LOG.debug( "failed to get the logs for [{} : {}], should try again if enough time.", podName, containerName); return false; } } } catch (IOException e) { // TODO: can we somehow recognize if it is failure or intended close()? LOG.debug( "End of watching log of [{} : {} : {}]. It could be either intended or some connection failure.", namespace, podName, containerName); LOG.trace("End of watching log of [{} : {} : {}]", namespace, podName, containerName, e); } return true; } /** * Tells whether given `message` is error message so we should try to watch again. * *

error message should look like this: * *

   *   {
   *    "kind":"Status",
   *    "apiVersion":"v1",
   *    "metadata":{},
   *    "status":"Failure",
   *    "message":"container \"che-plugin-metadata-broker-v3-1-1\" in pod
   *      \"workspace1a7u0mmfknhsgzqc.che-plugin-broker\" is waiting to start: ContainerCreating",
   *    "reason":"BadRequest",
   *    "code":400}
   * 
* *

Regular message is usually not a json, so we first try to find pod name. That should * eliminate close to 100% regular messages to being checked for error, because container app * should not know where it runs. If this initial check fails, we try to parse the message as a * json and match it for more details. * * @param message to check * @return true if message is an json error message, false otherwise */ private boolean isErrorMessage(String message) { if (!message.contains(podName)) { return false; } try { JsonObject json = jsonParser.parse(message).getAsJsonObject(); return !(!"Status".equals(json.get("kind").getAsString()) || !"Failure".equals(json.get("status").getAsString()) || !json.has("code") || !json.get("code").getAsString().contains("40")); } catch (JsonParseException jpe) { LOG.debug("Cannot parse the message as JSON.", jpe); return false; } } @Override public void close() { synchronized (this) { closed = true; if (currentLogWatch != null) { currentLogWatch.close(); } } } private String containerKey() { return namespace + ":" + podName + ":" + containerName; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ContainerLogWatch that = (ContainerLogWatch) o; return Objects.equals(namespace, that.namespace) && Objects.equals(podName, that.podName) && Objects.equals(containerName, that.containerName); } @Override public int hashCode() { return Objects.hash(namespace, podName, containerName); } @Override public String toString() { return new StringJoiner(", ", ContainerLogWatch.class.getSimpleName() + "[", "]") .add("namespace='" + namespace + "'") .add("podName='" + podName + "'") .add("containerName='" + containerName + "'") .toString(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/log/LogWatchTimeouts.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log; /** Various timeouts used in watching workspace logs logic. All values are in milliseconds. */ public class LogWatchTimeouts { /** Standard timeouts gentle to io resources. Use this unless you need something more eager. */ public static final LogWatchTimeouts DEFAULT = new LogWatchTimeouts(30_000, 2_000, 5_000); /** Aggressive timeouts for more short-lived tasks. */ public static final LogWatchTimeouts AGGRESSIVE = new LogWatchTimeouts(5_000, 100, 2_500); private final long watchTimeoutMs; private final long waitBetweenTriesMs; private final long waitBeforeCleanupMs; public LogWatchTimeouts(long watchTimeoutMs, long waitBetweenTriesMs, long waitBeforeCleanupMs) { this.watchTimeoutMs = watchTimeoutMs; this.waitBetweenTriesMs = waitBetweenTriesMs; this.waitBeforeCleanupMs = waitBeforeCleanupMs; } /** * How long we should try watch the logs. * * @return timeout in ms */ public long getWatchTimeoutMs() { return watchTimeoutMs; } /** * How long to block cleanup to get all container logs. * * @return timeout in ms */ public long getWaitBeforeCleanupMs() { return waitBeforeCleanupMs; } /** * How long to wait between individual tries to get container logs. * * @return timeout in ms */ public long getWaitBeforeNextTry() { return waitBetweenTriesMs; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/log/LogWatcher.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log; import static org.eclipse.che.api.workspace.shared.Constants.DEBUG_WORKSPACE_START; import static org.eclipse.che.api.workspace.shared.Constants.DEBUG_WORKSPACE_START_LOG_LIMIT_BYTES; import io.fabric8.kubernetes.client.KubernetesClient; import java.io.Closeable; import java.util.Map; import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEventHandler; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class watches workspace's pod events and tries to read the logs of all its containers. * *

Current implementation uses provided thread-pool and each container log watch session runs in * separate thread from this thread-pool. * *

Watching logs of individual containers is delegated to instances of {@link ContainerLogWatch}. */ public class LogWatcher implements PodEventHandler, Closeable { private static final Logger LOG = LoggerFactory.getLogger(LogWatcher.class); // 10MB public static final Long DEFAULT_LOG_LIMIT_BYTES = 10L * 1024 * 1024; private static final String STARTED_EVENT_REASON = "Started"; private final KubernetesClient client; private final RuntimeEventsPublisher eventsPublisher; private final Set logHandlers = ConcurrentHashMap.newKeySet(); private final Executor containerWatchersThreadPool; private final LogWatchTimeouts timeouts; private final long inputStreamLimit; private final String namespace; private final String workspaceId; private final Set podsOfInterest; private boolean closed = false; /** * Map of current watchers where key is name of the container and value is {@link * ContainerLogWatch} instance. */ private final Map currentContainerWatchers = new ConcurrentHashMap<>(); public LogWatcher( KubernetesClientFactory clientFactory, RuntimeEventsPublisher eventsPublisher, String workspaceId, String namespace, Set podsOfInterest, Executor executor, LogWatchTimeouts timeouts, long inputStreamLimit) throws InfrastructureException { this.client = clientFactory.create(workspaceId); this.eventsPublisher = eventsPublisher; this.workspaceId = workspaceId; this.namespace = namespace; this.containerWatchersThreadPool = executor; this.timeouts = timeouts; this.podsOfInterest = podsOfInterest; this.inputStreamLimit = inputStreamLimit; } public void addLogHandler(PodLogHandler handler) { logHandlers.add(handler); } @Override public void handle(PodEvent event) { final String podName = event.getPodName(); final String containerName = event.getContainerName(); if (containerName != null && event.getReason().equals(STARTED_EVENT_REASON)) { for (PodLogHandler logHandler : logHandlers) { // we need to synchronize here so we won't add new watcher while we're cleaning them synchronized (this) { if (closed) { return; } if (podsOfInterest.contains(podName) && !currentContainerWatchers.containsKey(podContainerKey(podName, containerName))) { ContainerLogWatch logWatch = new ContainerLogWatch( client, eventsPublisher, namespace, podName, containerName, logHandler, timeouts, inputStreamLimit); currentContainerWatchers.put(podContainerKey(podName, containerName), logWatch); LOG.trace( "adding [{}] to watching containers now watching [{}]", containerName, currentContainerWatchers.keySet()); containerWatchersThreadPool.execute(logWatch); } else { LOG.debug( "Not for this handler or already watching '{} : {} : {}'", namespace, podName, containerName); } } } } } private String podContainerKey(String podName, String containerName) { return podName + ":" + containerName; } public void close() { this.close(false); } /** * Closes all opened log watchers. In case of failed workspace, we want to block the pod for some * time before removing it so we have better chance to get all the logs from it. If that's the * case, use {@code needWait=false}. Otherwise watchers will be cleaned immediately, which does * not ensure that we get all the logs. * * @param needWait true if we need to pause before cleanup */ public void close(boolean needWait) { try { if (needWait) { LOG.debug( "Waiting '{}ms' before closing all log watchers for workspace '{}'.", timeouts.getWaitBeforeCleanupMs(), workspaceId); Thread.sleep(timeouts.getWaitBeforeCleanupMs()); } } catch (InterruptedException e) { LOG.error( "Interrupted while waiting before closing the log watch for workspace '{}'.", workspaceId, e); } finally { LOG.debug("Closing all log watchers for '{}'", workspaceId); synchronized (this) { closed = true; currentContainerWatchers.values().forEach(ContainerLogWatch::close); currentContainerWatchers.clear(); } } } @Override public String toString() { return new StringJoiner(", ", LogWatcher.class.getSimpleName() + "[", "]") .add("namespace='" + namespace + "'") .add("workspaceId='" + workspaceId + "'") .toString(); } /** * Gets log limit bytes from given `startOptions` if it's set there under {@link * org.eclipse.che.api.workspace.shared.Constants#DEBUG_WORKSPACE_START_LOG_LIMIT_BYTES} key. * Otherwise returns default {@link LogWatcher#DEFAULT_LOG_LIMIT_BYTES}. * * @param startOptions options where we'll try to find log limit param * @return valid log limit bytes */ public static long getLogLimitBytes(Map startOptions) { if (startOptions == null || startOptions.isEmpty() || !startOptions.containsKey(DEBUG_WORKSPACE_START_LOG_LIMIT_BYTES)) { return DEFAULT_LOG_LIMIT_BYTES; } else { try { return Long.parseLong(startOptions.get(DEBUG_WORKSPACE_START_LOG_LIMIT_BYTES)); } catch (NumberFormatException nfe) { LOG.debug( "failed to parse log limit bytes value from startOptions. Value '{}'", startOptions.get(DEBUG_WORKSPACE_START_LOG_LIMIT_BYTES), nfe); return DEFAULT_LOG_LIMIT_BYTES; } } } /** * Takes `startOptions` map and tells whether it's set so we should watch the logs. To return * true, flag must be stored under {@link * org.eclipse.che.api.workspace.shared.Constants#DEBUG_WORKSPACE_START} key. * * @param startOptions options where we'll try to find log watch flag * @return true if we should watch the logs, false otherwise */ public static boolean shouldWatchLogs(Map startOptions) { if (startOptions == null || startOptions.isEmpty()) { return false; } return Boolean.parseBoolean( startOptions.getOrDefault(DEBUG_WORKSPACE_START, Boolean.FALSE.toString())); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/log/PodLogHandler.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log; /** * Handle log message of the pod's container. Implementors must be also aware of pods that are * interest of this handler. */ public interface PodLogHandler { /** * Receives single log message and do something with it. It receives also containerName so we can * better format the message for the end-user. * * @param message single log message * @param containerName source container of this log message */ void handle(String message, String containerName); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/log/PodLogToEventPublisher.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log; import java.time.ZonedDateTime; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** This class is responsible for reading the logs. It is aware of machines it should follow. */ public class PodLogToEventPublisher implements PodLogHandler { private final Logger LOG = LoggerFactory.getLogger(this.getClass()); private final String LOG_MESSAGE_FORMAT = "[%s] -> %s"; private final RuntimeEventsPublisher eventsPublisher; private final RuntimeIdentity identity; public PodLogToEventPublisher(RuntimeEventsPublisher eventsPublisher, RuntimeIdentity identity) { this.eventsPublisher = eventsPublisher; this.identity = identity; } /** * Receives the message, formats it and send it to {@link PodLogToEventPublisher#eventsPublisher} * * @param message to handle * @param containerName source container of the log message */ @Override public void handle(String message, String containerName) { LOG.trace("forwarding message '{}' from the container '{}'", message, containerName); eventsPublisher.sendRuntimeLogEvent( String.format(LOG_MESSAGE_FORMAT, containerName, message), ZonedDateTime.now().toString(), identity); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/log/event/WatchLogStartedEvent.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.event; import java.util.Objects; import java.util.StringJoiner; /** * This event should be fired when WatchLog instance started and there is active connection to k8s * API watching logs of particular container. */ public class WatchLogStartedEvent { private final String container; public WatchLogStartedEvent(String container) { this.container = container; } public String getContainer() { return container; } @Override public String toString() { return new StringJoiner(", ", WatchLogStartedEvent.class.getSimpleName() + "[", "]") .add("container='" + container + "'") .toString(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } WatchLogStartedEvent that = (WatchLogStartedEvent) o; return Objects.equals(container, that.container); } @Override public int hashCode() { return Objects.hash(container); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/log/event/WatchLogStoppedEvent.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.event; import java.util.Objects; import java.util.StringJoiner; /** * This event should be fired when WatchLog instance stopped and particular container's logs are no * longer watched. */ public class WatchLogStoppedEvent { private final String container; public WatchLogStoppedEvent(String container) { this.container = container; } public String getContainer() { return container; } @Override public String toString() { return new StringJoiner(", ", WatchLogStoppedEvent.class.getSimpleName() + "[", "]") .add("container='" + container + "'") .toString(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } WatchLogStoppedEvent that = (WatchLogStoppedEvent) o; return Objects.equals(container, that.container); } @Override public int hashCode() { return Objects.hash(container); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/CertificateProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.api.model.SecretVolumeSourceBuilder; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.VolumeMount; import io.fabric8.kubernetes.api.model.VolumeMountBuilder; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole; /** * Mount configured self-signed certificate as file in each workspace machines if configured. * * @author Sergii Leshchenko */ @Singleton public class CertificateProvisioner implements ConfigurationProvisioner { public static final String CHE_SELF_SIGNED_CERT_SECRET_SUFFIX = "-che-self-signed-cert"; public static final String CHE_SELF_SIGNED_CERT_VOLUME = "che-self-signed-cert"; public static final String CERT_MOUNT_PATH = "/tmp/che/secret/"; public static final String CA_CERT_FILE = "ca.crt"; @Inject(optional = true) @Named("che.self_signed_cert") private String certificate; public CertificateProvisioner() {} @VisibleForTesting CertificateProvisioner(String certificate) { this.certificate = certificate; } public boolean isConfigured() { return !isNullOrEmpty(certificate); } public String getCertPath() { return CERT_MOUNT_PATH + CA_CERT_FILE; } @Override public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { if (!isConfigured()) { return; } String selfSignedCertSecretName = identity.getWorkspaceId() + CHE_SELF_SIGNED_CERT_SECRET_SUFFIX; k8sEnv .getSecrets() .put( selfSignedCertSecretName, new SecretBuilder() .withNewMetadata() .withName(selfSignedCertSecretName) .endMetadata() .withStringData(ImmutableMap.of(CA_CERT_FILE, certificate)) .build()); for (PodData pod : k8sEnv.getPodsData().values()) { if (pod.getRole() == PodRole.DEPLOYMENT) { if (pod.getSpec().getVolumes().stream() .noneMatch(v -> v.getName().equals(CHE_SELF_SIGNED_CERT_VOLUME))) { pod.getSpec().getVolumes().add(buildCertSecretVolume(selfSignedCertSecretName)); } } for (Container container : pod.getSpec().getInitContainers()) { provisionCertVolumeMountIfNeeded(container); } for (Container container : pod.getSpec().getContainers()) { provisionCertVolumeMountIfNeeded(container); } } } private void provisionCertVolumeMountIfNeeded(Container container) { if (container.getVolumeMounts().stream() .noneMatch(vm -> vm.getName().equals(CHE_SELF_SIGNED_CERT_VOLUME))) { container.getVolumeMounts().add(buildCertVolumeMount()); } } private VolumeMount buildCertVolumeMount() { return new VolumeMountBuilder() .withName(CHE_SELF_SIGNED_CERT_VOLUME) .withReadOnly(true) .withMountPath(CERT_MOUNT_PATH) .build(); } private Volume buildCertSecretVolume(String secretName) { return new VolumeBuilder() .withName(CHE_SELF_SIGNED_CERT_VOLUME) .withSecret(new SecretVolumeSourceBuilder().withSecretName(secretName).build()) .build(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/ConfigurationProvisioner.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Modifies workspace environment configuration and Kubernetes environment with everything needed. * * @author Anton Korneta */ public interface ConfigurationProvisioner { /** * Configures the Kubernetes environment and workspace environment with infrastructure needs. * * @param k8sEnv Kubernetes environment * @param identity runtime identity * @throws InfrastructureException when any error occurs */ void provision(T k8sEnv, RuntimeIdentity identity) throws InfrastructureException; } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/GatewayRouterProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVICE_NAME_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVICE_PORT_ATTRIBUTE; import io.fabric8.kubernetes.api.model.ConfigMap; import java.util.Map; import java.util.Map.Entry; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.GatewayRouteConfigGenerator; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.GatewayRouteConfigGeneratorFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.util.GatewayConfigmapLabels; /** * This provisioner finds {@link ConfigMap}s, that configures the single-host Gateway, generates * Gateway configuration and puts it into their data. * *

It uses {@link GatewayRouteConfigGenerator} to generate the gateway configuration. */ public class GatewayRouterProvisioner implements ConfigurationProvisioner { private final GatewayRouteConfigGeneratorFactory configGeneratorFactory; private final GatewayConfigmapLabels configmapLabels; @Inject public GatewayRouterProvisioner( GatewayRouteConfigGeneratorFactory configGeneratorFactory, GatewayConfigmapLabels configmapLabels) { this.configGeneratorFactory = configGeneratorFactory; this.configmapLabels = configmapLabels; } @Override public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { for (Entry configMapEntry : k8sEnv.getConfigMaps().entrySet()) { if (configmapLabels.isGatewayConfig(configMapEntry.getValue())) { ConfigMap gatewayConfigMap = configMapEntry.getValue(); Map servers = new Annotations.Deserializer(gatewayConfigMap.getMetadata().getAnnotations()).servers(); if (servers.size() != 1) { throw new InfrastructureException( "Expected exactly 1 server in gateway config ConfigMap's '" + gatewayConfigMap.getMetadata().getName() + "' annotations. This is a bug, please report."); } Entry serverConfigEntry = servers.entrySet().iterator().next(); ServerConfigImpl server = serverConfigEntry.getValue(); if (!server.getAttributes().containsKey(SERVICE_NAME_ATTRIBUTE) || !server.getAttributes().containsKey(SERVICE_PORT_ATTRIBUTE)) { throw new InfrastructureException( "Expected `serviceName` and `servicePort` in gateway config ServerConfig attributes for gateway config Configmap '" + gatewayConfigMap.getMetadata().getName() + "'. This is a bug, please report."); } // We're now creating only 1 gateway route configuration per ConfigMap, so we need to create // generator in each loop iteration. GatewayRouteConfigGenerator gatewayRouteConfigGenerator = configGeneratorFactory.create(); gatewayRouteConfigGenerator.addRouteConfig(configMapEntry.getKey(), gatewayConfigMap); Map gatewayConfiguration = gatewayRouteConfigGenerator.generate(identity.getInfrastructureNamespace()); gatewayConfigMap.setData(gatewayConfiguration); // Configuration is now generated, so remove these internal attributes server.getAttributes().remove(SERVICE_NAME_ATTRIBUTE); server.getAttributes().remove(SERVICE_PORT_ATTRIBUTE); gatewayConfigMap .getMetadata() .getAnnotations() .putAll( new Annotations.Serializer() .server(serverConfigEntry.getKey(), server) .annotations()); } } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/GatewayTlsProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisioner.getSecureProtocol; import io.fabric8.kubernetes.api.model.ConfigMap; import java.util.Map; import java.util.Map.Entry; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType; import org.eclipse.che.workspace.infrastructure.kubernetes.util.GatewayConfigmapLabels; /** * Enables Transport Layer Security (TLS) for external server implemented with gateway ConfigMaps. */ @Singleton public class GatewayTlsProvisioner implements ConfigurationProvisioner, TlsProvisioner { private final boolean isTlsEnabled; private final GatewayConfigmapLabels configmapLabels; private final TlsProvisioner nativeProvisioner; @Inject public GatewayTlsProvisioner( @Named("che.infra.kubernetes.tls_enabled") boolean isTlsEnabled, GatewayConfigmapLabels configmapLabels, TlsProvisionerProvider provisionerProvider) { this.isTlsEnabled = isTlsEnabled; this.configmapLabels = configmapLabels; this.nativeProvisioner = provisionerProvider.get(WorkspaceExposureType.NATIVE); } @Override public void provision(T k8sEnv, RuntimeIdentity identity) throws InfrastructureException { if (!isTlsEnabled) { return; } for (ConfigMap configMap : k8sEnv.getConfigMaps().values()) { if (configmapLabels.isGatewayConfig(configMap)) { useSecureProtocolForGatewayConfigMap(configMap); } } nativeProvisioner.provision(k8sEnv, identity); } private void useSecureProtocolForGatewayConfigMap(ConfigMap configMap) throws InfrastructureException { Map servers = Annotations.newDeserializer(configMap.getMetadata().getAnnotations()).servers(); if (servers.isEmpty()) { return; } if (servers.size() != 1) { throw new InfrastructureException( "Expected exactly 1 server in Gateway configuration ConfigMap '" + configMap.getMetadata().getName() + "'. This is a bug, please report."); } Entry serverConfigEntry = servers.entrySet().iterator().next(); ServerConfigImpl serverConfig = serverConfigEntry.getValue(); serverConfig.setProtocol(getSecureProtocol(serverConfig.getProtocol())); configMap .getMetadata() .getAnnotations() .putAll( Annotations.newSerializer() .server(serverConfigEntry.getKey(), serverConfig) .annotations()); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/GitConfigProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static java.util.Optional.empty; import static java.util.Optional.of; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.VolumeMount; import io.fabric8.kubernetes.api.model.VolumeMountBuilder; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import java.util.Optional; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.api.workspace.server.model.impl.WarningImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole; @Singleton public class GitConfigProvisioner implements ConfigurationProvisioner { public static final String GIT_CONFIG_MAP_NAME = "gitconfig"; private static final String GIT_BASE_CONFIG_PATH = "/etc/"; public static final String GIT_CONFIG = "gitconfig"; private static final String GIT_CONFIG_PATH = GIT_BASE_CONFIG_PATH + GIT_CONFIG; private static final String PREFERENCES_KEY_FILTER = "theia-user-preferences"; private static final String GIT_USER_NAME_PROPERTY = "git.user.name"; private static final String GIT_USER_EMAIL_PROPERTY = "git.user.email"; private static final String CONFIG_MAP_VOLUME_NAME = "gitconfigvolume"; private static final String HTTPS = "https://"; private PreferenceManager preferenceManager; private UserManager userManager; private VcsSslCertificateProvisioner vcsSslCertificateProvisioner; @Inject public GitConfigProvisioner( PreferenceManager preferenceManager, UserManager userManager, VcsSslCertificateProvisioner vcsSslCertificateProvisioner) { this.preferenceManager = preferenceManager; this.userManager = userManager; this.vcsSslCertificateProvisioner = vcsSslCertificateProvisioner; } @Override public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { try { Pair userAndEmail = getUserFromPreferences(); if (userAndEmail == null) { userAndEmail = getUserFromUserManager(); } prepareAndProvisionGitConfiguration( userAndEmail.first, userAndEmail.second, k8sEnv, identity); } catch (ServerException | NotFoundException e) { reportWarning( k8sEnv, Warnings.EXCEPTION_IN_USER_MANAGEMENT_DURING_GIT_PROVISION_WARNING_CODE, format( Warnings.EXCEPTION_IN_USER_MANAGEMENT_DURING_GIT_PROVISION_MESSAGE_FMT, e.getMessage())); } catch (JsonSyntaxException e) { reportWarning( k8sEnv, Warnings.JSON_IS_NOT_A_VALID_REPRESENTATION_FOR_AN_OBJECT_OF_TYPE_WARNING_CODE, format( Warnings.JSON_IS_NOT_A_VALID_REPRESENTATION_FOR_AN_OBJECT_OF_TYPE_MESSAGE_FMT, e.getMessage())); } } private void reportWarning(KubernetesEnvironment k8sEnv, int code, String message) { k8sEnv.getWarnings().add(new WarningImpl(code, message)); } private Pair getUserFromPreferences() throws ServerException, JsonSyntaxException { String preferenceJson = getPreferenceJson(PREFERENCES_KEY_FILTER); Map preferences = getMapFromJsonObject(preferenceJson); String name = getStringValueOrNull(preferences, GIT_USER_NAME_PROPERTY); String email = getStringValueOrNull(preferences, GIT_USER_EMAIL_PROPERTY); return isNullOrEmpty(name) && isNullOrEmpty(email) ? null : Pair.of(name, email); } private Pair getUserFromUserManager() throws NotFoundException, ServerException { String userId = EnvironmentContext.getCurrent().getSubject().getUserId(); User user = userManager.getById(userId); return Pair.of(user.getName(), user.getEmail()); } private void prepareAndProvisionGitConfiguration( String name, String email, KubernetesEnvironment k8sEnv, RuntimeIdentity identity) { prepareGitConfigurationContent(name, email) .ifPresent(content -> doProvisionGitConfiguration(GIT_CONFIG_MAP_NAME, content, k8sEnv)); } private String getStringValueOrNull(Map map, String key) { Object value = map.get(key); return value instanceof String ? (String) value : null; } private Map getMapFromJsonObject(String json) throws JsonSyntaxException { if (isNullOrEmpty(json)) { return emptyMap(); } Type typeToken = new TypeToken>() {}.getType(); /* The main idea to implicit cast map from string:object to string:string is that, we're looking for a two specific properties in the end, that have 100% string values, so we don't care about what we have in other properties. */ return new Gson().fromJson(json, typeToken); } private String getPreferenceJson(String keyFilter) throws ServerException { String userId = EnvironmentContext.getCurrent().getSubject().getUserId(); Map preferencesMap = preferenceManager.find(userId, keyFilter); return preferencesMap.get(keyFilter); } private Optional prepareGitConfigurationContent(String userName, String userEmail) { if (isNullOrEmpty(userName) && isNullOrEmpty(userEmail)) { return empty(); } StringBuilder config = new StringBuilder(); config.append("[user]").append('\n'); if (userName != null) { config.append('\t').append("name = ").append(userName).append('\n'); } if (userEmail != null) { config.append('\t').append("email = ").append(userEmail).append('\n'); } if (vcsSslCertificateProvisioner.isConfigured()) { String host = vcsSslCertificateProvisioner.getGitServerHost(); // Will add leading scheme (https://) if it not provide in configuration. // If host not configured wil return empty string, it will means that // provided certificate will used for all https connections. StringBuilder gitServerHosts = new StringBuilder(); if (!isNullOrEmpty(host)) { gitServerHosts.append(" \""); if (!host.startsWith(HTTPS)) { gitServerHosts.append(HTTPS); } gitServerHosts.append(host); gitServerHosts.append("\""); } config .append("[http") .append(gitServerHosts.toString()) .append("]") .append('\n') .append('\t') .append("sslCAInfo = ") .append(vcsSslCertificateProvisioner.getCertPath()); } return of(config.toString()); } private void doProvisionGitConfiguration( String gitConfigMapName, String gitConfig, KubernetesEnvironment k8sEnv) { Map gitConfigData = singletonMap(GIT_CONFIG, gitConfig); ConfigMap configMap = new ConfigMapBuilder() .withNewMetadata() .withName(gitConfigMapName) .endMetadata() .withData(gitConfigData) .build(); k8sEnv.getConfigMaps().put(configMap.getMetadata().getName(), configMap); k8sEnv .getPodsData() .values() .forEach( p -> mountConfigFile(p.getSpec(), gitConfigMapName, p.getRole() != PodRole.INJECTABLE)); } private void mountConfigFile(PodSpec podSpec, String gitConfigMapName, boolean addVolume) { if (addVolume) { podSpec .getVolumes() .add( new VolumeBuilder() .withName(CONFIG_MAP_VOLUME_NAME) .withConfigMap( new ConfigMapVolumeSourceBuilder().withName(gitConfigMapName).build()) .build()); } List containers = podSpec.getContainers(); containers.forEach( container -> { VolumeMount volumeMount = new VolumeMountBuilder() .withName(CONFIG_MAP_VOLUME_NAME) .withMountPath(GIT_CONFIG_PATH) .withSubPath(GIT_CONFIG) .withReadOnly(false) .build(); container.getVolumeMounts().add(volumeMount); }); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/ImagePullSecretProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.stream.JsonWriter; import io.fabric8.kubernetes.api.model.LocalObjectReference; import io.fabric8.kubernetes.api.model.LocalObjectReferenceBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import java.io.IOException; import java.io.StringWriter; import java.util.Base64; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.DockerAuthConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.DockerAuthConfigs; import org.eclipse.che.workspace.infrastructure.kubernetes.docker.auth.UserSpecificDockerRegistryCredentialsProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * This class allows workspace-related pods the pull images from the private docker registries * defined in the Dashboard 'Administration' page, even with the Kubernetes and OpenShift * infrastructures.
*
* How it works
* When starting a workspace, this provisioner adds an {@code * imagePullSecret} into environment that reflects the user private registries settings. * *

Then a reference to the created {@code imagePullSecret} is added in each workspace POD * specification. * * @author David Festal */ public class ImagePullSecretProvisioner implements ConfigurationProvisioner { static final String SECRET_NAME_SUFFIX = "-private-registries"; private final UserSpecificDockerRegistryCredentialsProvider credentialsProvider; @Inject public ImagePullSecretProvisioner( UserSpecificDockerRegistryCredentialsProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; } @Override @Traced public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { TracingTags.WORKSPACE_ID.set(identity::getWorkspaceId); DockerAuthConfigs credentials = credentialsProvider.getCredentials(); if (credentials == null) { return; } Map authConfigs = credentials.getConfigs(); if (authConfigs == null || authConfigs.isEmpty()) { return; } String encodedConfig = Base64.getEncoder().encodeToString(generateDockerCfg(authConfigs).getBytes()); Secret secret = new SecretBuilder() .addToData(".dockercfg", encodedConfig) .withType("kubernetes.io/dockercfg") .withNewMetadata() .withName(identity.getWorkspaceId() + SECRET_NAME_SUFFIX) .endMetadata() .build(); k8sEnv.getSecrets().put(secret.getMetadata().getName(), secret); k8sEnv .getPodsData() .values() .forEach(p -> addImagePullSecret(secret.getMetadata().getName(), p.getSpec())); } /** * Generates a dockercfg file with the authentication information contained in the given {@code * authConfigs} parameter. * *

The syntax of the produced dockercfg file is as follow: * *


   *   {
   *     "private.repo.1" : {
   *        "username": "theUsername1",
   *        "password": "thePassword1",
   *        "email": "theEmail1",
   *        "auth": "",
   *      },
   *     "private.repo.2" : {
   *        "username": "theUsername2",
   *        "password": "thePassword2",
   *        "email": "theEmail2",
   *        "auth": "",
   *      }
   *   }
   * 
*/ private String generateDockerCfg(Map authConfigs) throws InfrastructureException { try (StringWriter strWriter = new StringWriter(); JsonWriter jsonWriter = new Gson().newJsonWriter(strWriter)) { Base64.Encoder encoder = Base64.getEncoder(); jsonWriter.beginObject(); for (Map.Entry entry : authConfigs.entrySet()) { String name = entry.getKey(); DockerAuthConfig dockerAuthConfig = entry.getValue(); try { if (!name.startsWith("https://") && !name.startsWith("http://")) { name = "https://" + name; } jsonWriter.name(name); jsonWriter.beginObject(); jsonWriter.name("username"); jsonWriter.value(dockerAuthConfig.getUsername()); jsonWriter.name("password"); jsonWriter.value(dockerAuthConfig.getPassword()); jsonWriter.name("email"); jsonWriter.value("email@email"); String auth = dockerAuthConfig.getUsername() + ':' + dockerAuthConfig.getPassword(); jsonWriter.name("auth"); jsonWriter.value(encoder.encodeToString(auth.getBytes())); jsonWriter.endObject(); } catch (IOException e) { throw new InfrastructureException( "Unexpected exception occurred while building the 'ImagePullSecret' from private docker registry user preferences", e); } } jsonWriter.endObject(); jsonWriter.flush(); return strWriter.toString(); } catch (IOException e) { throw new InfrastructureException(e); } } private void addImagePullSecret(String secretName, PodSpec podSpec) { List imagePullSecrets = podSpec.getImagePullSecrets(); podSpec.setImagePullSecrets( ImmutableList.builder() .add(new LocalObjectReferenceBuilder().withName(secretName).build()) .addAll(imagePullSecrets) .build()); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/IngressTlsProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisioner.getSecureProtocol; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressTLS; import io.fabric8.kubernetes.api.model.networking.v1.IngressTLSBuilder; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Enables Transport Layer Security (TLS) for external server ingresses * * @author Guy Daich */ @Singleton public class IngressTlsProvisioner implements ConfigurationProvisioner, TlsProvisioner { static final String TLS_SECRET_TYPE = "kubernetes.io/tls"; private final boolean isTlsEnabled; private final String tlsSecretName; private final String tlsCert; private final String tlsKey; @Inject public IngressTlsProvisioner( @Named("che.infra.kubernetes.tls_enabled") boolean isTlsEnabled, @Named("che.infra.kubernetes.tls_secret") String tlsSecretName, @Nullable @Named("che.infra.kubernetes.tls_cert") String tlsCert, @Nullable @Named("che.infra.kubernetes.tls_key") String tlsKey) { this.isTlsEnabled = isTlsEnabled; this.tlsSecretName = tlsSecretName; if (isNullOrEmpty(tlsCert) != isNullOrEmpty(tlsKey)) { throw new ConfigurationException( "None or both of `che.infra.kubernetes.tls_cert` and " + "`che.infra.kubernetes.tls_key` must be configured with non-null value."); } this.tlsCert = tlsCert; this.tlsKey = tlsKey; } @Override public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws KubernetesInfrastructureException { if (!isTlsEnabled) { return; } String wsTlsSecretName = tlsSecretName; if (!isNullOrEmpty(tlsCert) && !isNullOrEmpty(tlsKey)) { wsTlsSecretName = identity.getWorkspaceId() + '-' + tlsSecretName; provisionTlsSecret(k8sEnv, wsTlsSecretName); } for (Ingress ingress : k8sEnv.getIngresses().values()) { useSecureProtocolForIngressServers(ingress); enableTLS(ingress, wsTlsSecretName); } } private void provisionTlsSecret(KubernetesEnvironment k8sEnv, String wsTlsSecretName) { Map data = new HashMap<>(); data.put("tls.crt", tlsCert); data.put("tls.key", tlsKey); Secret tlsSecret = new SecretBuilder() .withNewMetadata() .withName(wsTlsSecretName) .endMetadata() .withStringData(data) .withType(TLS_SECRET_TYPE) .build(); k8sEnv.getSecrets().put(wsTlsSecretName, tlsSecret); } private void enableTLS(Ingress ingress, String wsTlsSecretName) { String host = ingress.getSpec().getRules().get(0).getHost(); IngressTLSBuilder ingressTLSBuilder = new IngressTLSBuilder().withHosts(host); // according to ingress tls spec, secret name is optional // when working in single-host mode, nginx controller wil reuse the che-master secret // https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/api/extensions/v1beta1/types.go if (!isNullOrEmpty(wsTlsSecretName)) { ingressTLSBuilder.withSecretName(wsTlsSecretName); } IngressTLS ingressTLS = ingressTLSBuilder.build(); List ingressTLSList = new ArrayList<>(Collections.singletonList(ingressTLS)); ingress.getSpec().setTls(ingressTLSList); } private void useSecureProtocolForIngressServers(final Ingress ingress) { Map servers = Annotations.newDeserializer(ingress.getMetadata().getAnnotations()).servers(); if (servers.isEmpty()) { return; } servers.values().forEach(s -> s.setProtocol(getSecureProtocol(s.getProtocol()))); Map annotations = Annotations.newSerializer().servers(servers).annotations(); ingress.getMetadata().getAnnotations().putAll(annotations); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/KubernetesCheApiExternalEnvVarProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.provision.env.CheApiExternalEnvVarProvider; import org.eclipse.che.commons.lang.Pair; /** * Provides env variable to Kubernetes machine with url of Che API. * * @author Mykhailo Kuznietsov */ public class KubernetesCheApiExternalEnvVarProvider implements CheApiExternalEnvVarProvider { private final String cheServerEndpoint; @Inject public KubernetesCheApiExternalEnvVarProvider(@Named("che.api") String cheServerEndpoint) { this.cheServerEndpoint = cheServerEndpoint; } @Override public Pair get(RuntimeIdentity runtimeIdentity) throws InfrastructureException { return Pair.of(CHE_API_EXTERNAL_VARIABLE, cheServerEndpoint); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/KubernetesCheApiInternalEnvVarProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static com.google.common.base.Strings.isNullOrEmpty; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.provision.env.CheApiInternalEnvVarProvider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.Pair; /** * Provides env variable to Kubernetes machine with url of Che API. * * @author Mykhailo Kuznietsov */ public class KubernetesCheApiInternalEnvVarProvider implements CheApiInternalEnvVarProvider { private final String cheServerEndpoint; @Inject public KubernetesCheApiInternalEnvVarProvider( @Nullable @Named("che.api.internal") String cheServerEndpoint) { this.cheServerEndpoint = cheServerEndpoint; } @Override public Pair get(RuntimeIdentity runtimeIdentity) throws InfrastructureException { if (isNullOrEmpty(this.cheServerEndpoint)) { return null; } return Pair.of(CHE_API_INTERNAL_VARIABLE, cheServerEndpoint); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/KubernetesPreviewUrlCommandProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; import java.util.List; import java.util.Optional; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Ingresses; /** * Extends {@link PreviewUrlCommandProvisioner} where needed. For Kubernetes, we work with {@link * Ingress}es and {@link KubernetesNamespace}. */ @Singleton public class KubernetesPreviewUrlCommandProvisioner extends PreviewUrlCommandProvisioner { @Override protected List loadExposureObjects(KubernetesNamespace namespace) throws InfrastructureException { return namespace.ingresses().get(); } @Override protected Optional findHostForServicePort( List ingresses, Service service, int port) { return Ingresses.findIngressRuleForServicePort(ingresses, service, port) .map(IngressRule::getHost); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/NamespaceProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import io.fabric8.kubernetes.api.model.Namespace; import javax.inject.Inject; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; /** * Provisions the k8s {@link Namespace}. After provisioning, configures the namespace through {@link * NamespaceConfigurator}. * * @author Pavol Baran */ public class NamespaceProvisioner { private final KubernetesNamespaceFactory namespaceFactory; @Inject public NamespaceProvisioner(KubernetesNamespaceFactory namespaceFactory) { this.namespaceFactory = namespaceFactory; } /** Tests for this method are in KubernetesNamespaceFactoryTest. */ public KubernetesNamespaceMeta provision(NamespaceResolutionContext namespaceResolutionContext) throws InfrastructureException { KubernetesNamespace namespace = namespaceFactory.getOrCreate( new RuntimeIdentityImpl( null, null, namespaceResolutionContext.getUserId(), namespaceFactory.evaluateNamespaceName(namespaceResolutionContext))); return namespaceFactory .fetchNamespace(namespace.getName()) .orElseThrow( () -> new InfrastructureException("Not able to find namespace " + namespace.getName())); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/PodTerminationGracePeriodProvisioner.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import io.fabric8.kubernetes.api.model.PodSpec; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; /** * Adds grace termination period to workspace pods. * *

Note: if `terminationGracePeriodSeconds` have been explicitly set in Kubernetes / OpenShift * recipe it will not be overridden * * @author Ilya Buziuk (ibuziuk@redhat.com) */ public class PodTerminationGracePeriodProvisioner implements ConfigurationProvisioner { private final long graceTerminationPeriodSec; @Inject public PodTerminationGracePeriodProvisioner( @Named("che.infra.kubernetes.pod.termination_grace_period_sec") long graceTerminationPeriodSec) { this.graceTerminationPeriodSec = graceTerminationPeriodSec; } @Override @Traced public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { TracingTags.WORKSPACE_ID.set(identity::getWorkspaceId); for (PodData pod : k8sEnv.getPodsData().values()) { if (!isTerminationGracePeriodSet(pod.getSpec())) { pod.getSpec().setTerminationGracePeriodSeconds(getGraceTerminationPeriodSec(k8sEnv)); } } } /** * Returns true if 'terminationGracePeriodSeconds' have been explicitly set in Kubernetes / * OpenShift recipe, false otherwise */ private boolean isTerminationGracePeriodSet(final PodSpec podSpec) { return podSpec.getTerminationGracePeriodSeconds() != null; } private long getGraceTerminationPeriodSec(KubernetesEnvironment k8sEnv) { return graceTerminationPeriodSec; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/PreviewUrlCommandProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static org.eclipse.che.api.core.model.workspace.config.Command.PREVIEW_URL_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.Warnings.NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL; import static org.eclipse.che.workspace.infrastructure.kubernetes.Warnings.NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL_MESSAGE; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.WarningImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Services; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Updates commands with proper Preview URL attribute. * *

Goes through all {@link CommandImpl}s in given {@link KubernetesEnvironment} and for ones that * have defined Preview URL tries to find matching {@link Service} and {@link Ingress} from given * {@link KubernetesNamespace}. When found, it composes full URL and set it as attribute of a * command with key {@link * org.eclipse.che.api.core.model.workspace.config.Command#PREVIEW_URL_ATTRIBUTE}. * * @param type of the environment */ public abstract class PreviewUrlCommandProvisioner< E extends KubernetesEnvironment, T extends HasMetadata> { private static final Logger LOG = LoggerFactory.getLogger(PreviewUrlCommandProvisioner.class); public void provision(E env, KubernetesNamespace namespace) throws InfrastructureException { injectsPreviewUrlToCommands(env, namespace); } /** * Go through all commands, find matching service and exposed host. Then construct full preview * url from this data and set it as Command's parameter under `previewUrl` key. * * @param env environment to get commands * @param namespace current kubernetes namespace where we're looking for services and ingresses */ private void injectsPreviewUrlToCommands(E env, KubernetesNamespace namespace) throws InfrastructureException { if (env.getCommands() == null) { return; } List exposureObjects = loadExposureObjects(namespace); List services = namespace.services().get(); for (CommandImpl command : env.getCommands().stream() .filter(c -> c.getPreviewUrl() != null) .collect(Collectors.toList())) { Optional foundService = Services.findServiceWithPort(services, command.getPreviewUrl().getPort()); if (!foundService.isPresent()) { String message = String.format( "unable to find service for port '%s' for command '%s'", command.getPreviewUrl().getPort(), command.getName()); LOG.warn(message); env.addWarning( new WarningImpl( NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL, String.format(NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL_MESSAGE, message))); continue; } Optional foundHost = findHostForServicePort( exposureObjects, foundService.get(), command.getPreviewUrl().getPort()); if (foundHost.isPresent()) { command.getAttributes().put(PREVIEW_URL_ATTRIBUTE, foundHost.get()); } else { String message = String.format( "unable to find ingress for service '%s' and port '%s'", foundService.get(), command.getPreviewUrl().getPort()); LOG.warn(message); env.addWarning( new WarningImpl( NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL, String.format(NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL_MESSAGE, message))); } } } /** * Loads exposure objects of running infrastructure. Kubernetes implementation should return * `List`, OpenShift implementation `List`. */ protected abstract List loadExposureObjects(KubernetesNamespace namespace) throws InfrastructureException; protected abstract Optional findHostForServicePort( List ingressList, Service service, int port) throws InternalInfrastructureException; } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/SecurityContextProvisioner.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import io.fabric8.kubernetes.api.model.PodSecurityContextBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * @author Sergii Leshchenko */ public class SecurityContextProvisioner implements ConfigurationProvisioner { private final Long runAsUser; private final Long fsGroup; @Inject public SecurityContextProvisioner( @Nullable @Named("che.infra.kubernetes.pod.security_context.run_as_user") String runAsUser, @Nullable @Named("che.infra.kubernetes.pod.security_context.fs_group") String fsGroup) { this.runAsUser = runAsUser == null ? null : Long.parseLong(runAsUser); this.fsGroup = fsGroup == null ? null : Long.parseLong(fsGroup); } @Override public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { if (runAsUser != null) { k8sEnv.getPodsData().values().forEach(p -> provision(p.getSpec())); } } public void provision(PodSpec podSpec) { podSpec.setSecurityContext( new PodSecurityContextBuilder().withRunAsUser(runAsUser).withFsGroup(fsGroup).build()); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/ServiceAccountProvisioner.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static com.google.common.base.Strings.isNullOrEmpty; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; /** * Sets the service account to workspace pods if configured. * *

Service account won't be set to pods if property value is `NULL` and then Kubernetes * infrastructure will set default one. * * @author Sergii Leshchenko */ @Singleton public class ServiceAccountProvisioner implements ConfigurationProvisioner { private final String serviceAccount; @Inject public ServiceAccountProvisioner( @Nullable @Named("che.infra.kubernetes.service_account_name") String serviceAccount) { this.serviceAccount = serviceAccount; } @Override @Traced public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { TracingTags.WORKSPACE_ID.set(identity::getWorkspaceId); if (!isNullOrEmpty(serviceAccount)) { for (PodData pod : k8sEnv.getPodsData().values()) { pod.getSpec().setServiceAccountName(serviceAccount); pod.getSpec().setAutomountServiceAccountToken(true); } } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/SshKeysProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.Warnings.NOT_ABLE_TO_PROVISION_SSH_KEYS; import static org.eclipse.che.workspace.infrastructure.kubernetes.Warnings.NOT_ABLE_TO_PROVISION_SSH_KEYS_MESSAGE; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.isValidConfigMapKeyName; import com.google.common.annotations.VisibleForTesting; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.api.model.SecretVolumeSourceBuilder; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.VolumeMount; import io.fabric8.kubernetes.api.model.VolumeMountBuilder; import jakarta.validation.constraints.NotNull; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import javax.inject.Inject; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.ssh.server.SshManager; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; import org.eclipse.che.api.ssh.shared.model.SshPair; import org.eclipse.che.api.workspace.server.model.impl.WarningImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class allows mount user SSH Keys as Kubernetes Secrets on each * workspace container.
* *

How it works: * *

    *
  • all registered SSH Keys are fetched; *
  • create single K8s Secret with opaque type for storing SSH keys; *
  • in following secret key represents host name and value contains private SSH key; *
  • each key and value in secret is represented on file system by the following structure: * '/etc/ssh/private/{hostName}/ssh-privatekey', where hostName is a key taken * from secret and ssh-privatekey is a file that contains SSH private key taking * by the following key name; *
  • for VCS keys, ConfigMap with SSH settings is created and mounted to container by path * '/etc/ssh/ssh_config'. *
* * @author Vitalii Parfonov * @author Vlad Zhukovskyi */ public class SshKeysProvisioner implements ConfigurationProvisioner { private static String SSH_BASE_CONFIG_PATH = "/etc/ssh/"; private static final String SSH_PRIVATE_KEYS = "private"; private static final String SSH_PRIVATE_KEYS_PATH = SSH_BASE_CONFIG_PATH + SSH_PRIVATE_KEYS; private static final String SSH_CONFIG = "ssh_config"; private static final String SSH_CONFIG_PATH = SSH_BASE_CONFIG_PATH + SSH_CONFIG; private static final String SSH_CONFIG_MAP_NAME = "sshconfigmap"; private static final String SSH_SECRET_NAME_SUFFIX = "-sshprivatekeys"; private static final String SSH_SECRET_TYPE = "opaque"; public static final Pattern VALID_DOMAIN_PATTERN = Pattern.compile("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"); private static final Logger LOG = LoggerFactory.getLogger(SshKeysProvisioner.class); private final SshManager sshManager; private final RuntimeEventsPublisher runtimeEventsPublisher; @Inject public SshKeysProvisioner(SshManager sshManager, RuntimeEventsPublisher runtimeEventsPublisher) { this.sshManager = sshManager; this.runtimeEventsPublisher = runtimeEventsPublisher; } @Override @Traced public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { String workspaceId = identity.getWorkspaceId(); TracingTags.WORKSPACE_ID.set(workspaceId); List vcsSshPairs = getVcsSshPairs(k8sEnv, identity); List systemSshPairs = getSystemSshPairs(k8sEnv, identity); List allSshPairs = new ArrayList<>(vcsSshPairs); allSshPairs.addAll(systemSshPairs); List invalidSshKeyNames = allSshPairs.stream() .filter(keyPair -> !isValidSshKeyPair(keyPair)) .map(SshPairImpl::getName) .collect(toList()); if (!invalidSshKeyNames.isEmpty()) { String message = format( Warnings.SSH_KEYS_WILL_NOT_BE_MOUNTED_MESSAGE, invalidSshKeyNames.toString(), identity.getWorkspaceId()); LOG.warn(message); k8sEnv.addWarning(new WarningImpl(Warnings.SSH_KEYS_WILL_NOT_BE_MOUNTED, message)); runtimeEventsPublisher.sendRuntimeLogEvent(message, ZonedDateTime.now().toString(), identity); } doProvisionSshKeys( allSshPairs.stream().filter(this::isValidSshKeyPair).collect(toList()), k8sEnv, workspaceId); doProvisionVcsSshConfig( vcsSshPairs.stream().filter(this::isValidSshKeyPair).collect(toList()), k8sEnv, workspaceId); } /** * Return list of keys related to the VCS (Version Control Systems), Git, SVN and etc. Usually * managed by user * * @param identity * @return list of ssh pairs */ private List getVcsSshPairs(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { List sshPairs; try { sshPairs = sshManager.getPairs(identity.getOwnerId(), "vcs"); } catch (ServerException e) { String message = format("Unable to get SSH Keys. Cause: %s", e.getMessage()); LOG.warn(message); k8sEnv.addWarning( new WarningImpl( NOT_ABLE_TO_PROVISION_SSH_KEYS, format(NOT_ABLE_TO_PROVISION_SSH_KEYS_MESSAGE, message))); throw new InfrastructureException(e); } if (sshPairs.isEmpty()) { try { sshPairs = singletonList( sshManager.generatePair( identity.getOwnerId(), "vcs", "default-" + new Date().getTime())); } catch (ServerException | ConflictException e) { String message = format("Unable to generate the initial SSH key. Cause: %s", e.getMessage()); LOG.warn(message); k8sEnv.addWarning( new WarningImpl( NOT_ABLE_TO_PROVISION_SSH_KEYS, format(NOT_ABLE_TO_PROVISION_SSH_KEYS_MESSAGE, message))); throw new InfrastructureException(e); } } return sshPairs; } /** * Return system (aka 'internal') ssh keys, this key used for internal services like rsync via ssh * and etc. Not generated or managed by a user * * @param identity * @return list of keys pair */ private List getSystemSshPairs(KubernetesEnvironment k8sEn, RuntimeIdentity identity) throws InfrastructureException { List sshPairs; try { sshPairs = sshManager.getPairs(identity.getOwnerId(), "internal"); } catch (ServerException e) { String message = format("Unable to get SSH Keys. Cause: %s", e.getMessage()); LOG.warn(message); k8sEn.addWarning( new WarningImpl( NOT_ABLE_TO_PROVISION_SSH_KEYS, format(NOT_ABLE_TO_PROVISION_SSH_KEYS_MESSAGE, message))); throw new InfrastructureException(e); } return sshPairs; } private void doProvisionSshKeys( List sshPairs, KubernetesEnvironment k8sEnv, String wsId) { Map data = sshPairs.stream() .filter(sshPair -> !isNullOrEmpty(sshPair.getPrivateKey())) .collect( toMap( SshPairImpl::getName, sshPair -> Base64.getEncoder().encodeToString(sshPair.getPrivateKey().getBytes()))); Secret secret = new SecretBuilder() .addToData(data) .withType(SSH_SECRET_TYPE) .withNewMetadata() .withName(wsId + SSH_SECRET_NAME_SUFFIX) .endMetadata() .build(); k8sEnv.getSecrets().put(secret.getMetadata().getName(), secret); k8sEnv .getPodsData() .values() .forEach( p -> { mountSshKeySecret( secret.getMetadata().getName(), p.getSpec(), p.getRole() != PodRole.INJECTABLE); }); } private void mountSshKeySecret(String secretName, PodSpec podSpec, boolean addVolume) { if (addVolume) { podSpec .getVolumes() .add( new VolumeBuilder() .withName(secretName) .withSecret( new SecretVolumeSourceBuilder() .withSecretName(secretName) .withDefaultMode(0600) .build()) .build()); } List containers = podSpec.getContainers(); containers.forEach( container -> { VolumeMount volumeMount = new VolumeMountBuilder() .withName(secretName) .withReadOnly(true) .withMountPath(SSH_PRIVATE_KEYS_PATH) .build(); container.getVolumeMounts().add(volumeMount); }); } private void doProvisionVcsSshConfig( List vcsSshPairs, KubernetesEnvironment k8sEnv, String wsId) { StringBuilder sshConfigData = new StringBuilder(); for (SshPair sshPair : vcsSshPairs) { sshConfigData.append(buildConfig(sshPair.getName())); } Map sshConfig = new HashMap<>(); sshConfig.put(SSH_CONFIG, sshConfigData.toString()); ConfigMap configMap = new ConfigMapBuilder() .withNewMetadata() .withName(SSH_CONFIG_MAP_NAME) .endMetadata() .withData(sshConfig) .build(); k8sEnv.getConfigMaps().put(configMap.getMetadata().getName(), configMap); k8sEnv .getPodsData() .values() .forEach( p -> mountConfigFile( p.getSpec(), SSH_CONFIG_MAP_NAME, p.getRole() != PodRole.INJECTABLE)); } private void mountConfigFile(PodSpec podSpec, String sshConfigMapName, boolean addVolume) { String configMapVolumeName = "ssshkeyconfigvolume"; if (addVolume) { podSpec .getVolumes() .add( new VolumeBuilder() .withName(configMapVolumeName) .withConfigMap( new ConfigMapVolumeSourceBuilder().withName(sshConfigMapName).build()) .build()); } List containers = podSpec.getContainers(); containers.forEach( container -> { VolumeMount volumeMount = new VolumeMountBuilder() .withName(configMapVolumeName) .withMountPath(SSH_CONFIG_PATH) .withSubPath(SSH_CONFIG) .withReadOnly(true) .build(); container.getVolumeMounts().add(volumeMount); }); } @VisibleForTesting boolean isValidSshKeyPair(SshPairImpl keyPair) { return isValidConfigMapKeyName(keyPair.getName()) && VALID_DOMAIN_PATTERN.matcher(keyPair.getName()).matches(); } /** * Returns the ssh configuration entry which includes host, identity file location and Host Key * checking policy * *

Example of provided configuration: * *

   * host github.com
   * IdentityFile /etc/ssh/private/github-com/ssh-privatekey
   * StrictHostKeyChecking = no
   * 
* * or * *
   * host *
   * IdentityFile /etc/ssh/private/default-123456/ssh-privatekey
   * StrictHostKeyChecking = no
   * 
* * @param name the of key given during generate for vcs service we will consider it as host of * version control service (e.g. github.com, gitlab.com and etc) if name starts from * "default-{anyString}" it will be replaced on wildcard "*" host name. Name with format * "default-{anyString}" will be generated on client side by Theia SSH Plugin, if user doesn't * provide own name. Details see here: * https://github.com/eclipse/che/issues/13494#issuecomment-512761661. Note: behavior can be * improved in 7.x releases after 7.0.0 * @return the ssh configuration which include host, identity file location and Host Key checking * policy */ private String buildConfig(@NotNull String name) { String host = name.startsWith("default-") ? "*" : name; return "host " + host + "\nIdentityFile " + SSH_PRIVATE_KEYS_PATH + "/" + name + "\nStrictHostKeyChecking = no" + "\n\n"; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/TlsProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Enables TLS on underlying endpoints implementation. Implementation of this interface must check * that tls is enabled - `che.infra.kubernetes.tls_enabled=true`. * * @param environment type */ public interface TlsProvisioner { /** * If TLS enabled, updates protocol to secure one and ensures that underlying exposure objects are * properly configured. * * @throws InfrastructureException in case of any infrastructure failure */ void provision(T k8sEnv, RuntimeIdentity identity) throws InfrastructureException; /** * Returns the secure version of the provided protocol or the same protocol if the conversion is * not known. Currently only understands "ws" and "http". */ static String getSecureProtocol(final String protocol) { if ("ws".equals(protocol)) { return "wss"; } else if ("http".equals(protocol)) { return "https"; } else { return protocol; } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/TlsProvisionerProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.AbstractExposureStrategyAwareProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType; /** * Provides {@link TlsProvisioner} based on `che.infra.kubernetes.server_strategy` and * `che.infra.kubernetes.singlehost.workspace.exposure` properties. * * @param type of environment */ public class TlsProvisionerProvider extends AbstractExposureStrategyAwareProvider> { @Inject public TlsProvisionerProvider( @Named("che.infra.kubernetes.server_strategy") String exposureStrategy, @Named("che.infra.kubernetes.singlehost.workspace.exposure") String wsExposureType, Map> mapping) { super( exposureStrategy, wsExposureType, mapping, "Could not initialize TLS provisioners for workspace exposure type '%s'."); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/UniqueNamesProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabel; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapEnvSource; import io.fabric8.kubernetes.api.model.ConfigMapKeySelector; import io.fabric8.kubernetes.api.model.ConfigMapVolumeSource; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.Constants; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; /** * Makes names of Kubernetes pods, ingresses and config maps unique whole namespace by {@link * Names}. * *

Original names will be stored in {@link Constants#CHE_ORIGINAL_NAME_LABEL} label of renamed * object. * * @author Anton Korneta * @see Names#uniqueResourceName(String, String) * @see Names#generateName(String) */ @Singleton public class UniqueNamesProvisioner implements ConfigurationProvisioner { @Override @Traced public void provision(T k8sEnv, RuntimeIdentity identity) throws InfrastructureException { final String workspaceId = identity.getWorkspaceId(); TracingTags.WORKSPACE_ID.set(workspaceId); final Map configMaps = k8sEnv.getConfigMaps(); Map configMapNameTranslation = new HashMap<>(); for (ConfigMap configMap : configMaps.values()) { final String originalName = configMap.getMetadata().getName(); putLabel(configMap.getMetadata(), Constants.CHE_ORIGINAL_NAME_LABEL, originalName); final String uniqueName = Names.uniqueResourceName(originalName, workspaceId); configMap.getMetadata().setName(uniqueName); configMapNameTranslation.put(originalName, uniqueName); } final Collection podData = k8sEnv.getPodsData().values(); for (PodData pod : podData) { final ObjectMeta podMeta = pod.getMetadata(); putLabel(podMeta, Constants.CHE_ORIGINAL_NAME_LABEL, podMeta.getName()); final String podName = Names.uniqueResourceName(podMeta.getName(), workspaceId); podMeta.setName(podName); if (configMapNameTranslation.size() > 0) { rewriteConfigMapNames(pod, configMapNameTranslation); } } // We explicitly need to modify the deployments in the environment to provision unique names // for them. final Collection deployments = k8sEnv.getDeploymentsCopy().values(); for (Deployment deployment : deployments) { final ObjectMeta deploymentMeta = deployment.getMetadata(); final String originalName = deploymentMeta.getName(); putLabel(deploymentMeta, Constants.CHE_ORIGINAL_NAME_LABEL, originalName); final String deploymentName = Names.uniqueResourceName(originalName, workspaceId); deploymentMeta.setName(deploymentName); } final Set ingresses = new HashSet<>(k8sEnv.getIngresses().values()); k8sEnv.getIngresses().clear(); for (Ingress ingress : ingresses) { final ObjectMeta ingressMeta = ingress.getMetadata(); putLabel(ingress, Constants.CHE_ORIGINAL_NAME_LABEL, ingressMeta.getName()); final String ingressName = Names.generateName("ingress"); ingressMeta.setName(ingressName); k8sEnv.getIngresses().put(ingressName, ingress); } } private void rewriteConfigMapNames(PodData pod, Map configMapNameTranslation) { // First update any env vars that reference configMaps. for (Container container : pod.getSpec().getContainers()) { // Can set env vars to key/value pairs in configmap if (container.getEnv() != null) { container.getEnv().stream() .filter( env -> env.getValueFrom() != null && env.getValueFrom().getConfigMapKeyRef() != null) .forEach( env -> { ConfigMapKeySelector configMap = env.getValueFrom().getConfigMapKeyRef(); String originalName = configMap.getName(); // Since pods can reference configmaps that don't exist, we only change the name // if the configmap does exist to aid debugging recipes (otherwise message is just // null). if (configMapNameTranslation.containsKey(originalName)) { configMap.setName(configMapNameTranslation.get(originalName)); } }); } if (container.getEnvFrom() != null) { // Can use all entries in configMap as env vars container.getEnvFrom().stream() .filter(envFrom -> envFrom.getConfigMapRef() != null) .forEach( envFrom -> { ConfigMapEnvSource configMapRef = envFrom.getConfigMapRef(); String originalName = configMapRef.getName(); if (configMapNameTranslation.containsKey(originalName)) { configMapRef.setName(configMapNameTranslation.get(originalName)); } }); } } // Next update any mounted configMaps List volumes = pod.getSpec().getVolumes(); if (pod.getSpec().getVolumes() != null) { volumes.stream() .filter(vol -> vol.getConfigMap() != null) .forEach( volume -> { ConfigMapVolumeSource configMapVolume = volume.getConfigMap(); String originalName = configMapVolume.getName(); if (configMapNameTranslation.containsKey(originalName)) { configMapVolume.setName(configMapNameTranslation.get(originalName)); } }); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/VcsSslCertificateProvisioner.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static com.google.common.base.Strings.isNullOrEmpty; import static java.util.Collections.singletonMap; import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.VolumeMount; import io.fabric8.kubernetes.api.model.VolumeMountBuilder; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole; /** * Mount configured self-signed certificate for git provider as file in each workspace machines if * configured. * * @author Vitalii Parfonov */ @Singleton public class VcsSslCertificateProvisioner implements ConfigurationProvisioner { static final String CHE_GIT_SELF_SIGNED_CERT_CONFIG_MAP_SUFFIX = "-che-git-self-signed-cert"; static final String CHE_GIT_SELF_SIGNED_VOLUME = "che-git-self-signed-cert"; static final String CERT_MOUNT_PATH = "/etc/che/git/cert/"; static final String CA_CERT_FILE = "ca.crt"; @Inject(optional = true) @Named("che.git.self_signed_cert") private String certificate; @Inject(optional = true) @Named("che.git.self_signed_cert_host") private String host; public VcsSslCertificateProvisioner() {} @VisibleForTesting VcsSslCertificateProvisioner(String certificate, String host) { this.certificate = certificate; this.host = host; } /** * @return true only if system configured for using self-signed certificate fot https git * operation */ public boolean isConfigured() { return !isNullOrEmpty(certificate); } /** * @return path to the certificate file */ public String getCertPath() { return CERT_MOUNT_PATH + CA_CERT_FILE; } /** * Return given in configuration git server host (e.g. 110.23.0.1:3000). * * @return git server host for git config if it configured in che.git.self_signed_cert_host */ public String getGitServerHost() { return host; } @Override public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { if (!isConfigured()) { return; } String selfSignedCertConfigMapName = identity.getWorkspaceId() + CHE_GIT_SELF_SIGNED_CERT_CONFIG_MAP_SUFFIX; k8sEnv .getConfigMaps() .put( selfSignedCertConfigMapName, new ConfigMapBuilder() .withNewMetadata() .withName(selfSignedCertConfigMapName) .endMetadata() .withData(singletonMap(CA_CERT_FILE, certificate)) .build()); for (PodData pod : k8sEnv.getPodsData().values()) { if (pod.getRole() != PodRole.INJECTABLE) { if (pod.getSpec().getVolumes().stream() .noneMatch(v -> v.getName().equals(CHE_GIT_SELF_SIGNED_VOLUME))) { pod.getSpec().getVolumes().add(buildCertVolume(selfSignedCertConfigMapName)); } } for (Container container : pod.getSpec().getInitContainers()) { provisionCertVolumeMountIfNeeded(container); } for (Container container : pod.getSpec().getContainers()) { provisionCertVolumeMountIfNeeded(container); } } } private void provisionCertVolumeMountIfNeeded(Container container) { if (container.getVolumeMounts().stream() .noneMatch(vm -> vm.getName().equals(CHE_GIT_SELF_SIGNED_VOLUME))) { container.getVolumeMounts().add(buildCertVolumeMount()); } } private VolumeMount buildCertVolumeMount() { return new VolumeMountBuilder() .withName(CHE_GIT_SELF_SIGNED_VOLUME) .withReadOnly(true) .withMountPath(CERT_MOUNT_PATH) .build(); } private Volume buildCertVolume(String configMapName) { return new VolumeBuilder() .withName(CHE_GIT_SELF_SIGNED_VOLUME) .withConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName).build()) .build(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/env/EnvVarsConverter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.env; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.EnvVar; import java.util.List; import java.util.Map; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.lang.TopologicalSort; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.util.EnvVars; /** * Converts environment variables in {@link MachineConfig} to Kubernetes environment variables. * * @author Alexander Garagatyi */ @Singleton public class EnvVarsConverter implements ConfigurationProvisioner { private final TopologicalSort topoSort = new TopologicalSort<>(EnvVar::getName, EnvVars::extractReferencedVariables); @Override @Traced public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { TracingTags.WORKSPACE_ID.set(identity::getWorkspaceId); for (PodData pod : k8sEnv.getPodsData().values()) { for (Container container : pod.getSpec().getContainers()) { String machineName = Names.machineName(pod, container); InternalMachineConfig machineConf = k8sEnv.getMachines().get(machineName); // we need to combine the env vars from the machine config with the variables already // present in the container. Let's key the variables by name and use the map for merging Map envVars = machineConf.getEnv().entrySet().stream() .map(e -> new EnvVar(e.getKey(), e.getValue(), null)) .collect(toMap(EnvVar::getName, identity())); // the env vars defined in our machine config take precedence over the ones already defined // in the container, if any container.getEnv().forEach(v -> envVars.putIfAbsent(v.getName(), v)); // The environment variable expansion only works if a variable that is referenced // is already defined earlier in the list of environment variables. // We need to produce a list where variables that reference others always appear later // in the list. List sorted = topoSort.sort(envVars.values()); container.setEnv(sorted); } } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/limits/ram/ContainerResourceProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.limits.ram; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_REQUEST_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_REQUEST_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.Names.machineName; import io.fabric8.kubernetes.api.model.Container; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.environment.ResourceLimitAttributesProvisioner; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSize; /** * Sets or overrides Kubernetes container RAM and CPU limit and request if corresponding attributes * are present in machine config corresponding to the container. * *

There are two memory-related properties: * *

    *
  • che.workspace.default_memory_limit_mb - defines default machine memory limit *
  • che.workspace.default_memory_request_mb - defines default requested machine memory * allocation. *
* *

Similarly, there are two CPU-related properties: * *

    *
  • che.workspace.default_cpu_limit_cores - defines default machine CPU limit *
  • che.workspace.default_cpu_request_cores - defines default machine CPU request *
* * @author Anton Korneta * @author Max Shaposhnyk */ @Singleton public class ContainerResourceProvisioner implements ConfigurationProvisioner { private final long defaultMachineMaxMemorySizeAttribute; private final long defaultMachineRequestMemorySizeAttribute; private final float defaultMachineCpuLimitAttribute; private final float defaultMachineCpuRequestAttribute; @Inject public ContainerResourceProvisioner( @Named("che.workspace.default_memory_limit_mb") long defaultMachineMaxMemorySizeAttribute, @Named("che.workspace.default_memory_request_mb") long defaultMachineRequestMemorySizeAttribute, @Named("che.workspace.default_cpu_limit_cores") String defaultMachineCpuLimitAttribute, @Named("che.workspace.default_cpu_request_cores") String defaultMachineCpuRequestAttribute) { this.defaultMachineMaxMemorySizeAttribute = defaultMachineMaxMemorySizeAttribute * 1024 * 1024; this.defaultMachineRequestMemorySizeAttribute = defaultMachineRequestMemorySizeAttribute * 1024 * 1024; this.defaultMachineCpuLimitAttribute = KubernetesSize.toCores(defaultMachineCpuLimitAttribute); this.defaultMachineCpuRequestAttribute = KubernetesSize.toCores(defaultMachineCpuRequestAttribute); } @Override @Traced public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { TracingTags.WORKSPACE_ID.set(identity::getWorkspaceId); final Map machines = k8sEnv.getMachines(); for (PodData pod : k8sEnv.getPodsData().values()) { for (Container container : pod.getSpec().getContainers()) { // make sure that machine configs have settings for RAM limit and request InternalMachineConfig machineConfig = machines.get(machineName(pod, container)); ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, Containers.getRamLimit(container), Containers.getRamRequest(container), defaultMachineMaxMemorySizeAttribute, defaultMachineRequestMemorySizeAttribute); // make sure that machine configs have settings for CPU limit and request ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, Containers.getCpuLimit(container), Containers.getCpuRequest(container), defaultMachineCpuLimitAttribute, defaultMachineCpuRequestAttribute); // reapply memory and CPU settings to k8s container to make sure that provisioned // values above are set. Non-positive value means that limit is disabled, so just // ignoring them. final Map attributes = machineConfig.getAttributes(); long memLimit = Long.parseLong(attributes.get(MEMORY_LIMIT_ATTRIBUTE)); if (memLimit > 0) { Containers.addRamLimit(container, memLimit); } long memRequest = Long.parseLong(attributes.get(MEMORY_REQUEST_ATTRIBUTE)); if (memRequest > 0) { Containers.addRamRequest(container, memRequest); } float cpuLimit = Float.parseFloat(attributes.get(CPU_LIMIT_ATTRIBUTE)); if (cpuLimit > 0) { Containers.addCpuLimit(container, cpuLimit); } float cpuRequest = Float.parseFloat(attributes.get(CPU_REQUEST_ATTRIBUTE)); if (cpuRequest > 0) Containers.addCpuRequest(container, cpuRequest); } } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/restartpolicy/RestartPolicyRewriter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy; import static java.lang.String.format; import io.fabric8.kubernetes.api.model.PodSpec; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.WarningImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner; /** * Rewrites restart policy to supported one - 'Never'. * * @author Alexander Garagatyi */ public class RestartPolicyRewriter implements ConfigurationProvisioner { static final String DEFAULT_RESTART_POLICY = "Never"; @Override @Traced public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) throws InfrastructureException { TracingTags.WORKSPACE_ID.set(identity::getWorkspaceId); for (PodData podConfig : k8sEnv.getPodsData().values()) { final String podName = podConfig.getMetadata().getName(); final PodSpec podSpec = podConfig.getSpec(); rewriteRestartPolicy(podSpec, podName, k8sEnv); } } private void rewriteRestartPolicy(PodSpec podSpec, String podName, KubernetesEnvironment env) { final String restartPolicy = podSpec.getRestartPolicy(); if (restartPolicy != null && !DEFAULT_RESTART_POLICY.equalsIgnoreCase(restartPolicy)) { final String warnMsg = format( Warnings.RESTART_POLICY_SET_TO_NEVER_WARNING_MESSAGE_FMT, restartPolicy, podName, DEFAULT_RESTART_POLICY); env.addWarning(new WarningImpl(Warnings.RESTART_POLICY_SET_TO_NEVER_WARNING_CODE, warnMsg)); } podSpec.setRestartPolicy(DEFAULT_RESTART_POLICY); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/secret/EnvironmentVariableSecretApplier.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret; import static java.lang.String.format; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_AUTOMOUNT; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_ENV_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_ENV_NAME_TEMPLATE; import com.google.common.annotations.Beta; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.EnvVarBuilder; import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretKeySelectorBuilder; import java.time.ZonedDateTime; import java.util.List; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Mounts Kubernetes secret with specific annotations as an environment variable(s) in workspace * containers. Allows per-component control of secret applying in devfile. */ @Beta @Singleton public class EnvironmentVariableSecretApplier extends KubernetesSecretApplier { @Inject private RuntimeEventsPublisher runtimeEventsPublisher; private static final Logger LOG = LoggerFactory.getLogger(EnvironmentVariableSecretApplier.class); /** * Applies secret as environment variable into workspace containers, respecting automount * attribute and optional devfile automount property override. * * @param env kubernetes environment with workspace containers configuration * @param runtimeIdentity identity of current runtime * @param secret source secret to apply * @throws InfrastructureException on misconfigured secrets or other apply error */ @Override public void applySecret(KubernetesEnvironment env, RuntimeIdentity runtimeIdentity, Secret secret) throws InfrastructureException { boolean secretAutomount = Boolean.parseBoolean(secret.getMetadata().getAnnotations().get(ANNOTATION_AUTOMOUNT)); for (PodData podData : env.getPodsData().values()) { if (!podData.getRole().equals(PodRole.DEPLOYMENT)) { continue; } for (Container container : podData.getSpec().getContainers()) { Optional component = getComponent(env, container.getName()); // skip components that explicitly disable automount if (component.isPresent() && isComponentAutomountFalse(component.get())) { continue; } // if automount disabled globally and not overridden in component if (!secretAutomount && (!component.isPresent() || !isComponentAutomountTrue(component.get()))) { continue; } for (Entry secretDataEntry : secret.getData().entrySet()) { final String mountEnvName = envName(secret, secretDataEntry.getKey(), runtimeIdentity); container .getEnv() .add( new EnvVarBuilder() .withName(mountEnvName) .withValueFrom( new EnvVarSourceBuilder() .withSecretKeyRef( new SecretKeySelectorBuilder() .withName(secret.getMetadata().getName()) .withKey(secretDataEntry.getKey()) .build()) .build()) .build()); } } } } private String envName(Secret secret, String key, RuntimeIdentity runtimeIdentity) throws InfrastructureException { String mountEnvName; if (secret.getData().size() == 1) { List providedNames = Stream.of( secret.getMetadata().getAnnotations().get(ANNOTATION_ENV_NAME), secret .getMetadata() .getAnnotations() .get(format(ANNOTATION_ENV_NAME_TEMPLATE, key))) .filter(Objects::nonNull) .collect(Collectors.toList()); if (providedNames.isEmpty()) { throw new InfrastructureException( format( "Unable to mount secret '%s': It is configured to be mount as a environment variable, but its name was not specified. Please define the '%s' annotation on the secret to specify it.", secret.getMetadata().getName(), ANNOTATION_ENV_NAME)); } if (providedNames.size() > 1) { String msg = String.format( "Secret '%s' defines multiple environment variable name annotations, but contains only one data entry. That may cause inconsistent behavior and needs to be corrected.", secret.getMetadata().getName()); LOG.warn(msg); runtimeEventsPublisher.sendRuntimeLogEvent( msg, ZonedDateTime.now().toString(), runtimeIdentity); } mountEnvName = providedNames.get(0); } else { mountEnvName = secret.getMetadata().getAnnotations().get(format(ANNOTATION_ENV_NAME_TEMPLATE, key)); if (mountEnvName == null) { throw new InfrastructureException( format( "Unable to mount key '%s' of secret '%s': It is configured to be mount as a environment variable, but its name was not specified. Please define the '%s' annotation on the secret to specify it.", key, secret.getMetadata().getName(), format(ANNOTATION_ENV_NAME_TEMPLATE, key))); } } return mountEnvName; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/secret/FileSecretApplier.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_AUTOMOUNT; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_PATH; import com.google.common.annotations.Beta; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretVolumeSourceBuilder; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.VolumeMount; import io.fabric8.kubernetes.api.model.VolumeMountBuilder; import java.nio.file.Paths; import java.util.Optional; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.K8sVersion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Mounts Kubernetes secret with specific annotations as an file in workspace containers. Via * devfile, allows per-component control of secret applying and path overrides using specific * property. */ @Beta @Singleton public class FileSecretApplier extends KubernetesSecretApplier { private static final Logger LOG = LoggerFactory.getLogger(FileSecretApplier.class); private final K8sVersion k8sVersion; @Inject public FileSecretApplier(K8sVersion k8sVersion) { this.k8sVersion = k8sVersion; } /** * Applies secret as file into workspace containers, respecting automount attribute and optional * devfile automount property and/or mount path override. * * @param env kubernetes environment with workspace containers configuration * @param runtimeIdentity identity of current runtime * @param secret source secret to apply * @throws InfrastructureException on misconfigured secrets or other apply error */ @Override public void applySecret(KubernetesEnvironment env, RuntimeIdentity runtimeIdentity, Secret secret) throws InfrastructureException { final String secretMountPath = secret.getMetadata().getAnnotations().get(ANNOTATION_MOUNT_PATH); boolean secretAutomount = Boolean.parseBoolean(secret.getMetadata().getAnnotations().get(ANNOTATION_AUTOMOUNT)); if (secretMountPath == null) { throw new InfrastructureException( format( "Unable to mount secret '%s': It is configured to be mounted as a file but the mount path was not specified. Please define the '%s' annotation on the secret to specify it.", secret.getMetadata().getName(), ANNOTATION_MOUNT_PATH)); } Volume volumeFromSecret = new VolumeBuilder() .withName(secret.getMetadata().getName()) .withSecret( new SecretVolumeSourceBuilder() .withSecretName(secret.getMetadata().getName()) .build()) .build(); for (PodData podData : env.getPodsData().values()) { if (!podData.getRole().equals(PodRole.DEPLOYMENT)) { continue; } if (podData.getSpec().getVolumes().stream() .anyMatch(v -> v.getName().equals(volumeFromSecret.getName()))) { volumeFromSecret.setName(volumeFromSecret.getName() + "_" + NameGenerator.generate("", 6)); } podData.getSpec().getVolumes().add(volumeFromSecret); for (Container container : podData.getSpec().getContainers()) { Optional component = getComponent(env, container.getName()); // skip components that explicitly disable automount if (component.isPresent() && isComponentAutomountFalse(component.get())) { continue; } // if automount disabled globally and not overridden in component if (!secretAutomount && (!component.isPresent() || !isComponentAutomountTrue(component.get()))) { continue; } // find path override if any Optional overridePathOptional = Optional.empty(); if (component.isPresent()) { overridePathOptional = getOverridenComponentPath(component.get(), secret.getMetadata().getName()); } final String componentMountPath = overridePathOptional.orElse(secretMountPath); // it's not possible to mount multiple volumes on same path on older k8s, so we // remove the existing mount here to replace it with new one. if (k8sVersion.olderThan(1, 13)) { LOG.debug( "Unable to mount multiple VolumeMounts on same path on this k8s version. Removing conflicting volumes in favor of secret mounts."); container .getVolumeMounts() .removeIf(vm -> Paths.get(vm.getMountPath()).equals(Paths.get(componentMountPath))); } container .getVolumeMounts() .addAll( secret.getData().keySet().stream() .map( secretFile -> buildVolumeMount(volumeFromSecret, componentMountPath, secretFile)) .collect(Collectors.toList())); } } } private VolumeMount buildVolumeMount( Volume volumeFromSecret, String componentMountPath, String secretFile) { VolumeMountBuilder volumeMountBuilder = new VolumeMountBuilder().withName(volumeFromSecret.getName()).withReadOnly(true); // subPaths are supported from k8s v1.13 if (k8sVersion.newerOrEqualThan(1, 13)) { volumeMountBuilder .withMountPath(componentMountPath + "/" + secretFile) .withSubPath(secretFile); } else { volumeMountBuilder.withMountPath(componentMountPath); LOG.debug("This version of k8s does not support sutPaths for VolumeMounts."); } return volumeMountBuilder.build(); } private Optional getOverridenComponentPath(ComponentImpl component, String secretName) { Optional matchedVolume = component.getVolumes().stream().filter(v -> v.getName().equals(secretName)).findFirst(); if (matchedVolume.isPresent() && !isNullOrEmpty(matchedVolume.get().getContainerPath())) { return Optional.of(matchedVolume.get().getContainerPath()); } return Optional.empty(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/secret/GitCredentialStorageFileSecretApplier.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret; import static java.lang.String.format; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_PATH; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_USER_NAME; import com.google.common.annotations.Beta; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Secret; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.K8sVersion; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitConfigProvisioner; /** * An instance of {@link FileSecretApplier} that is trying to adjust the content of git-config that * was added by {@link GitConfigProvisioner}. The adjustment is adding configuration of git * credentials store, which is pointing to the file that is going to be mount to the container from * the secret. */ @Beta @Singleton public class GitCredentialStorageFileSecretApplier extends FileSecretApplier { private static final String GIT_CREDENTIALS_FILE_STORE_PATTERN = "\n[credential]\n\thelper = store --file %s\n"; @Inject public GitCredentialStorageFileSecretApplier(K8sVersion k8sVersion) { super(k8sVersion); } @Override public void applySecret(KubernetesEnvironment env, RuntimeIdentity runtimeIdentity, Secret secret) throws InfrastructureException { super.applySecret(env, runtimeIdentity, secret); final String secretMountPath = secret.getMetadata().getAnnotations().get(ANNOTATION_MOUNT_PATH); Set keys = secret.getData().keySet(); if (keys.size() != 1) { throw new InfrastructureException( format( "Invalid git credential secret data. It should contain only 1 data item but it have %d", keys.size())); } Path gitSecretFilePath = Paths.get(secretMountPath, keys.iterator().next()); ConfigMap gitConfigMap = env.getConfigMaps().get(GitConfigProvisioner.GIT_CONFIG_MAP_NAME); if (gitConfigMap != null) { Map gitConfigMapData = gitConfigMap.getData(); String gitConfig = gitConfigMapData.get(GitConfigProvisioner.GIT_CONFIG); if (gitConfig != null) { if (gitConfig.contains("helper = store --file") && gitConfig.contains("[credential]")) { throw new InfrastructureException( format( "Multiple git credential secrets for user %s found in namespace %s. That may be caused by reinstalling product without user namespaces cleanup or using multiple instances of product with the same namespace namings template.", secret.getMetadata().getAnnotations().get(ANNOTATION_USER_NAME), secret.getMetadata().getNamespace())); } HashMap newGitConfigMapData = new HashMap<>(gitConfigMapData); newGitConfigMapData.put( GitConfigProvisioner.GIT_CONFIG, gitConfig + String.format(GIT_CREDENTIALS_FILE_STORE_PATTERN, gitSecretFilePath.toString())); gitConfigMap.setData(newGitConfigMapData); } } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/secret/KubernetesSecretAnnotationNames.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret; /** * Set of annotations used in auto-mount secrets to specify theirs type and/or expected behaviour. */ public class KubernetesSecretAnnotationNames { /** Common prefix for all annotations */ public static final String ANNOTATION_PREFIX = "che.eclipse.org"; /** Indicates the way secret should be mount. Supported values are 'file' and 'env' */ public static final String ANNOTATION_MOUNT_AS = ANNOTATION_PREFIX + "/" + "mount-as"; /** Indicates whether given secret should be automatically mount into all workspace containers */ public static final String ANNOTATION_AUTOMOUNT = ANNOTATION_PREFIX + "/" + "automount-workspace-secret"; /** Indicates whether given secret is a git credential secret. */ public static final String ANNOTATION_GIT_CREDENTIALS = ANNOTATION_PREFIX + "/" + "git-credential"; /** Defines user name given secret belongs to */ public static final String ANNOTATION_USER_NAME = ANNOTATION_PREFIX + "/" + "scm-username"; /** For 'file' type secrets defines the path where ih should be mount */ public static final String ANNOTATION_MOUNT_PATH = ANNOTATION_PREFIX + "/" + "mount-path"; /** For 'env' type secrets defines the environment variable name to mount secret with */ public static final String ANNOTATION_ENV_NAME = ANNOTATION_PREFIX + "/" + "env-name"; /** For 'env' type secrets defines the environment variable name template to mount secret with */ public static final String ANNOTATION_ENV_NAME_TEMPLATE = ANNOTATION_PREFIX + "/%s_" + "env-name"; /** Common prefix for annotations associated with devworkspaces */ public static final String DEV_WORKSPACE_PREFIX = "controller.devfile.io"; /** For 'file' type secrets defines the path where ih should be mount */ public static final String ANNOTATION_DEV_WORKSPACE_MOUNT_PATH = DEV_WORKSPACE_PREFIX + "/" + "mount-path"; private KubernetesSecretAnnotationNames() {} } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/secret/KubernetesSecretApplier.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE; import com.google.common.annotations.Beta; import io.fabric8.kubernetes.api.model.Secret; import java.util.Optional; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Base class for secret appliers. Contains common functionality to find devfile components by name * and check override automount properties. */ @Beta public abstract class KubernetesSecretApplier { /** * Applies particular secret to workspace containers. * * @param env environment to retrieve components from * @param runtimeIdentity identity of current runtime * @param secret secret to apply * @throws InfrastructureException when secret applying error */ public abstract void applySecret(E env, RuntimeIdentity runtimeIdentity, Secret secret) throws InfrastructureException; /** * Tries to retrieve devfile component by given container name. * * @param env kubernetes environment of the workspace * @param containerName name of container to find it's parent component * @return matched component */ final Optional getComponent(E env, String containerName) { InternalMachineConfig internalMachineConfig = env.getMachines().get(containerName); if (internalMachineConfig != null) { String componentName = internalMachineConfig.getAttributes().get(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE); if (componentName != null) { return env.getDevfile().getComponents().stream() .filter(c -> componentName.equals(c.getAlias())) .findFirst(); } } return Optional.empty(); } /** * @param component source component * @return {@code true} when {@code automountWorkspaceSecret} property explicitly set to {@code * false},or {@code false} otherwise. */ final boolean isComponentAutomountFalse(ComponentImpl component) { return component.getAutomountWorkspaceSecrets() != null && !component.getAutomountWorkspaceSecrets(); } /** * @param component source component * @return {@code true} when {@code automountWorkspaceSecret} property explicitly set to {@code * true},or {@code false} otherwise. */ final boolean isComponentAutomountTrue(ComponentImpl component) { return component.getAutomountWorkspaceSecrets() != null && component.getAutomountWorkspaceSecrets(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/secret/SecretAsContainerResourceProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret; import static java.lang.Boolean.parseBoolean; import static java.lang.String.format; import static java.util.stream.Collectors.toMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_GIT_CREDENTIALS; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_AS; import com.google.common.annotations.Beta; import io.fabric8.kubernetes.api.model.LabelSelector; import io.fabric8.kubernetes.api.model.LabelSelectorBuilder; import io.fabric8.kubernetes.api.model.Secret; import java.util.Arrays; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; /** * Finds secrets with specific labels in namespace, and mount their values as file or environment * variable into workspace containers. Secrets annotated with "che.eclipse.org/mount-as=env" are * mount as env variables, env name is read from "che.eclipse.org/env-name" annotation. Secrets * which having "che.eclipse.org/mount-as=file" are mounted as file in the folder specified by * "che.eclipse.org/mount-path" annotation. Refer to che docs for concrete examples. */ @Beta @Singleton public class SecretAsContainerResourceProvisioner { private final FileSecretApplier fileSecretApplier; private final EnvironmentVariableSecretApplier environmentVariableSecretApplier; private final GitCredentialStorageFileSecretApplier gitCredentialStorageFileSecretApplier; private final Map secretLabels; @Inject public SecretAsContainerResourceProvisioner( FileSecretApplier fileSecretApplier, EnvironmentVariableSecretApplier environmentVariableSecretApplier, GitCredentialStorageFileSecretApplier gitCredentialStorageFileSecretApplier, @Named("che.workspace.provision.secret.labels") String[] labels) { this.fileSecretApplier = fileSecretApplier; this.environmentVariableSecretApplier = environmentVariableSecretApplier; this.gitCredentialStorageFileSecretApplier = gitCredentialStorageFileSecretApplier; this.secretLabels = Arrays.stream(labels) .map(item -> item.split("=", 2)) .collect(toMap(p -> p[0], p -> p.length == 1 ? "" : p[1])); } public void provision(E env, RuntimeIdentity runtimeIdentity, KubernetesNamespace namespace) throws InfrastructureException { LabelSelector selector = new LabelSelectorBuilder().withMatchLabels(secretLabels).build(); for (Secret secret : namespace.secrets().get(selector)) { if (secret.getMetadata().getAnnotations() == null) { throw new InfrastructureException( format( "Unable to mount secret '%s': it has missing required annotations. Please check documentation for secret format guide.", secret.getMetadata().getName())); } String mountType = secret.getMetadata().getAnnotations().get(ANNOTATION_MOUNT_AS); if ("env".equalsIgnoreCase(mountType)) { environmentVariableSecretApplier.applySecret(env, runtimeIdentity, secret); } else if ("file".equalsIgnoreCase(mountType)) { if (parseBoolean(secret.getMetadata().getAnnotations().get(ANNOTATION_GIT_CREDENTIALS))) { gitCredentialStorageFileSecretApplier.applySecret(env, runtimeIdentity, secret); } else { fileSecretApplier.applySecret(env, runtimeIdentity, secret); } } else { throw new InfrastructureException( format( "Unable to mount secret '%s': it has missing or unknown type of the mount. Please make sure that '%s' annotation has value either 'env' or 'file'.", secret.getMetadata().getName(), ANNOTATION_MOUNT_AS)); } } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/server/ServersConverter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.server; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.PodSpec; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactoryProvider; /** * Converts {@link ServerConfig} to Kubernetes related objects to add a server into Kubernetes * runtime. * *

Adds Kubernetes objects by calling {@link KubernetesServerExposer#expose(Map)} on each machine * with servers. * * @author Alexander Garagatyi */ @Singleton public class ServersConverter implements ConfigurationProvisioner { private final ExternalServerExposer externalServerExposer; private final SecureServerExposerFactoryProvider secureServerExposerFactoryProvider; @Inject public ServersConverter( ExternalServerExposerProvider externalServerExposer, SecureServerExposerFactoryProvider secureServerExposerFactoryProvider) { this.externalServerExposer = externalServerExposer.get(); this.secureServerExposerFactoryProvider = secureServerExposerFactoryProvider; } @Override @Traced public void provision(T k8sEnv, RuntimeIdentity identity) throws InfrastructureException { TracingTags.WORKSPACE_ID.set(identity::getWorkspaceId); SecureServerExposer secureServerExposer = secureServerExposerFactoryProvider.get(k8sEnv).create(identity); for (PodData podConfig : k8sEnv.getPodsData().values()) { final PodSpec podSpec = podConfig.getSpec(); for (Container containerConfig : podSpec.getContainers()) { String machineName = Names.machineName(podConfig, containerConfig); InternalMachineConfig machineConfig = k8sEnv.getMachines().get(machineName); if (!machineConfig.getServers().isEmpty()) { KubernetesServerExposer kubernetesServerExposer = new KubernetesServerExposer<>( externalServerExposer, secureServerExposer, machineName, podConfig, containerConfig, k8sEnv); kubernetesServerExposer.expose(machineConfig.getServers()); } } } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/AbstractExposureStrategyAwareProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server; import java.util.Map; import javax.inject.Named; import javax.inject.Provider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy; /** * A simple base class for providers that are dependent on the server exposure strategy and * single-host workspace exposure. * * @param the type this provider provides. */ public abstract class AbstractExposureStrategyAwareProvider implements Provider { protected final T instance; protected final Map instanceMap; /** * Constructs a new provider returning one of the instances from the provided mapping * *

If the server exposure strategy is not "single-host", the {@link * WorkspaceExposureType#NATIVE native} is used to lookup the instance from the map. * *

If the server exposure strategy is "single-host", the appropriate instance is looked up in * the mapping that corresponds to the {@code singleHostType}. * * @param exposureStrategy the server exposure strategy * @param wsExposureType the type of workspace exposure under single-host * @param mapping the mapping for the different exposure types * @param errorMessageTemplate the template for the error message which should contain a single * '%s' that is going to be replaced by the "wsExposureType" value. * @throws IllegalStateException if the mapping doesn't contain a value for the chosen server * strategy and workspace exposure */ protected AbstractExposureStrategyAwareProvider( @Named("che.infra.kubernetes.server_strategy") String exposureStrategy, @Named("che.infra.kubernetes.singlehost.workspace.exposure") String wsExposureType, Map mapping, String errorMessageTemplate) { if (exposureStrategy.equals(SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY)) { instance = mapping.get(WorkspaceExposureType.fromConfigurationValue(wsExposureType)); } else { instance = mapping.get(WorkspaceExposureType.NATIVE); } if (instance == null) { throw new IllegalStateException(String.format(errorMessageTemplate, wsExposureType)); } instanceMap = mapping; } /** Returns the object mapped to the configured exposure type. */ public T get() { return instance; } /** Returns the object mapped to the provided exposure type. */ public T get(WorkspaceExposureType exposureType) { return instanceMap.get(exposureType); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/IngressAnnotationsProvider.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server; import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.lang.reflect.Type; import java.util.Collections; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Alexander Garagatyi */ @Singleton public class IngressAnnotationsProvider implements Provider> { private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); private static final Type type = new TypeToken>() {}.getType(); private final Map annotations; private static final Logger LOG = LoggerFactory.getLogger(IngressAnnotationsProvider.class); @Inject public IngressAnnotationsProvider( @Nullable @Named("che.infra.kubernetes.ingress.annotations_json") String annotationsString) { if (annotationsString != null) { annotations = GSON.fromJson(annotationsString, type); } else { annotations = Collections.emptyMap(); LOG.warn( "Ingresses annotations are absent. Make sure that workspace ingresses don't need " + "to be configured according to ingress controller."); } } @Override public Map get() { return annotations; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server; import static java.lang.Integer.parseInt; import static java.util.stream.Collectors.toMap; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVER_NAME_ATTRIBUTE; import static org.eclipse.che.commons.lang.NameGenerator.generate; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerPort; import io.fabric8.kubernetes.api.model.ContainerPortBuilder; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServicePortBuilder; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.Constants; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.UniqueNamesProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Helps to modify {@link KubernetesEnvironment} to make servers that are configured by {@link * ServerConfig} publicly or workspace-wide accessible. * *

To make server accessible it is needed to make sure that container port is declared, create * {@link Service}. To make it also publicly accessible it is needed to create corresponding {@link * Ingress} for exposing this port. * *

Created services and ingresses will have serialized servers which are exposed by the * corresponding object and machine name to which these servers belongs to. * *

Container, service and ingress are linked in the following way: * *

 * Pod
 * metadata:
 *   labels:
 *     type: web-app
 * spec:
 *   containers:
 *   ...
 *   - ports:
 *     - containerPort: 8080
 *       name: web-app
 *       protocol: TCP
 *   ...
 * 
* * Then services expose containers ports in the following way: * *
 * Service
 * metadata:
 *   name: service123
 * spec:
 *   selector:                        ---->> Pod.metadata.labels
 *     type: web-app
 *   ports:
 *     - name: web-app
 *       port: 8080
 *       targetPort: [8080|web-app]   ---->> Pod.spec.ports[0].[containerPort|name]
 *       protocol: TCP                ---->> Pod.spec.ports[0].protocol
 * 
* * Then, a server exposer strategy is used to expose one of the service's ports, to outside of the * cluster. Currently, Host-Based and Path-Based Ingresses can be used to expose service ports. * *

For accessing publicly accessible server user will use ingress host or its load balancer IP. * For accessing workspace-wide accessible server user will use service name. Information about * servers that are exposed by ingress and/or service are stored in annotations of a ingress or * service. * * @author Sergii Leshchenko * @author Alexander Garagatyi * @see Annotations */ public class KubernetesServerExposer { private static final Logger LOG = LoggerFactory.getLogger(KubernetesServerExposer.class); public static final int SERVER_UNIQUE_PART_SIZE = 8; public static final String SERVER_PREFIX = "server"; private final ExternalServerExposer externalServerExposer; private final SecureServerExposer secureServerExposer; private final String machineName; private final Container container; private final PodData pod; private final T k8sEnv; public KubernetesServerExposer( ExternalServerExposer externalServerExposer, SecureServerExposer secureServerExposer, String machineName, PodData pod, Container container, T k8sEnv) { this.externalServerExposer = externalServerExposer; this.secureServerExposer = secureServerExposer; this.machineName = machineName; this.pod = pod; this.container = container; this.k8sEnv = k8sEnv; } /** * A helper method to split the servers to unique sets that should be exposed together. * *

The consumer is responsible for doing the actual exposure and is supplied 2 pieces of data. * The first is the server ID, which is non-null for any unique server from the input set and null * for any compound set of servers that should be exposed together. The caller is responsible for * figuring out an appropriate ID in such case. * * @param allServers all unique and non-unique servers mixed together * @param consumer the consumer responsible for handling the split sets of servers */ private static void onEachExposableServerSet( Map allServers, ServerSetExposer consumer) throws InfrastructureException { Map nonUniqueServers = new HashMap<>(); for (Map.Entry e : allServers.entrySet()) { String serverId = makeServerNameValidForDns(e.getKey()); if (e.getValue().isUnique()) { consumer.expose(serverId, ImmutableMap.of(serverId, e.getValue())); } else { nonUniqueServers.put(serverId, e.getValue()); } } if (!nonUniqueServers.isEmpty()) { consumer.expose(null, nonUniqueServers); } } /** Replaces {@code /} with {@code -} in the provided name in an attempt to make it DNS safe. */ public static String makeServerNameValidForDns(String name) { return name.replaceAll("/", "-"); } /** * Exposes specified servers. * *

Note that created Kubernetes objects will select the corresponding pods by {@link * Constants#CHE_ORIGINAL_NAME_LABEL} label. That should be added by {@link * UniqueNamesProvisioner}. * * @param servers servers to expose * @see ConfigurationProvisioner#provision(KubernetesEnvironment, RuntimeIdentity) */ public void expose(Map servers) throws InfrastructureException { Map internalServers = new HashMap<>(); Map externalServers = new HashMap<>(); Map secureServers = new HashMap<>(); Map unsecuredPorts = new HashMap<>(); Map securedPorts = new HashMap<>(); splitServersAndPortsByExposureType( servers, internalServers, externalServers, secureServers, unsecuredPorts, securedPorts); provisionServicesForDiscoverableServers(servers); Optional serviceOpt = createService(internalServers, unsecuredPorts); if (serviceOpt.isPresent()) { Service service = serviceOpt.get(); String serviceName = service.getMetadata().getName(); k8sEnv.getServices().put(serviceName, service); exposeNonSecureServers(serviceName, externalServers, unsecuredPorts); } exposeSecureServers(secureServers, securedPorts); } // TODO: this creates discoverable services as an extra services. Service for same {@link // ServerConfig} is also created later in in {@link #exposeNonSecureServers(Map, Map, Map)} or // {@link #exposeSecureServers(Map, Map)} as a non-discoverable one. This was added during // working on adding endpoints for kubernetes/openshift components, to keep behavior consistent. // However, this logic is probably broken and should be changed. /** * Creates services with defined names for discoverable {@link ServerConfig}s. The name is taken * from {@link ServerConfig}'s attributes under {@link ServerConfig#SERVER_NAME_ATTRIBUTE} and * must be set, otherwise service won't be created. */ private void provisionServicesForDiscoverableServers( Map servers) { for (String serverName : servers.keySet()) { ServerConfig server = servers.get(serverName); if (server.getAttributes().containsKey(SERVER_NAME_ATTRIBUTE)) { // remove the name from attributes so we don't send it to the client String endpointName = server.getAttributes().remove(SERVER_NAME_ATTRIBUTE); if (server.isDiscoverable()) { Service service = new ServerServiceBuilder() .withName(endpointName) .withMachineName(machineName) .withSelectorEntry(CHE_ORIGINAL_NAME_LABEL, pod.getMetadata().getName()) .withPorts(Collections.singletonList(getServicePort(server))) .withServers(Collections.singletonMap(serverName, server)) .build(); k8sEnv.getServices().put(service.getMetadata().getName(), service); } } } } private void splitServersAndPortsByExposureType( Map servers, Map internalServers, Map externalServers, Map secureServers, Map unsecuredPorts, Map securedPorts) { servers.forEach( (key, value) -> { ServicePort sp = getServicePort(value); exposeInContainerIfNeeded(sp); if (value.isInternal()) { // Server is internal. It doesn't make sense to make an it secure since // it is available only within workspace servers internalServers.put(key, value); unsecuredPorts.put(value.getPort(), sp); } else { // Server is external. Check if it should be secure or not if (value.isSecure()) { secureServers.put(key, value); securedPorts.put(value.getPort(), sp); } else { externalServers.put(key, value); unsecuredPorts.put(value.getPort(), sp); } } }); } private void exposeNonSecureServers( String serviceName, Map externalServers, Map unsecuredPorts) throws InfrastructureException { for (ServicePort servicePort : unsecuredPorts.values()) { // expose service port related external servers if exist Map matchedExternalServers = match(externalServers, servicePort); if (!matchedExternalServers.isEmpty()) { onEachExposableServerSet( matchedExternalServers, (serverId, srvrs) -> externalServerExposer.expose( k8sEnv, machineName, serviceName, serverId, servicePort, srvrs)); } } } private Optional createService( Map internalServers, Map unsecuredPorts) { Map allInternalServers = new HashMap<>(internalServers); if (unsecuredPorts.isEmpty()) { return Optional.empty(); } Service service = new ServerServiceBuilder() .withName(generate(SERVER_PREFIX, SERVER_UNIQUE_PART_SIZE) + '-' + machineName) .withMachineName(machineName) .withSelectorEntry(CHE_ORIGINAL_NAME_LABEL, pod.getMetadata().getName()) .withPorts(new ArrayList<>(unsecuredPorts.values())) .withServers(allInternalServers) .build(); return Optional.of(service); } private void exposeSecureServers( Map securedServers, Map securedPorts) throws InfrastructureException { if (securedPorts.isEmpty()) { return; } Optional secureService = secureServerExposer.createService(securedPorts.values(), pod, machineName, securedServers); String secureServiceName = secureService .map( s -> { String n = s.getMetadata().getName(); k8sEnv.getServices().put(n, s); return n; }) .orElse(null); for (ServicePort servicePort : securedPorts.values()) { // expose service port related secure servers if exist Map matchedSecureServers = match(securedServers, servicePort); if (!matchedSecureServers.isEmpty()) { onEachExposableServerSet( matchedSecureServers, (serverId, srvrs) -> { secureServerExposer.expose( k8sEnv, pod, machineName, secureServiceName, serverId, servicePort, srvrs); }); } } } private Map match( Map servers, ServicePort servicePort) { int port = servicePort.getTargetPort().getIntVal(); return servers.entrySet().stream() .filter(e -> parseInt(e.getValue().getPort().split("/")[0]) == port) .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); } private ServicePort getServicePort(ServerConfig serverConfig) { String[] portProtocol = serverConfig.getPort().split("/"); int port = parseInt(portProtocol[0]); String protocol = portProtocol.length > 1 ? portProtocol[1].toUpperCase() : "TCP"; return new ServicePortBuilder() .withName("server-" + port) .withPort(port) .withProtocol(protocol) .withNewTargetPort(port) .build(); } private void exposeInContainerIfNeeded(ServicePort servicePort) { if (container.getPorts().stream() .noneMatch( p -> p.getContainerPort().equals(servicePort.getPort()) && servicePort.getProtocol().equals(p.getProtocol()))) { ContainerPort containerPort = new ContainerPortBuilder() .withContainerPort(servicePort.getPort()) .withProtocol(servicePort.getProtocol()) .build(); container.getPorts().add(containerPort); } } @FunctionalInterface private interface ServerSetExposer { void expose(String serverId, Map serverSet) throws InfrastructureException; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/PreviewUrlExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server; import static org.eclipse.che.commons.lang.NameGenerator.generate; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_UNIQUE_PART_SIZE; import com.google.common.annotations.VisibleForTesting; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Ingresses; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Services; /** * For Commands that have defined Preview URL, tries to find existing {@link Service} and {@link * Ingress}. When not found, we create new ones and put them into given {@link * KubernetesEnvironment}. * * @param type of the environment */ @Singleton public class PreviewUrlExposer { private final ExternalServerExposer externalServerExposer; @Inject public PreviewUrlExposer(ExternalServerExposerProvider externalServerExposer) { this(externalServerExposer.get()); } @VisibleForTesting protected PreviewUrlExposer(ExternalServerExposer externalServerExposer) { this.externalServerExposer = externalServerExposer; } public void expose(T env) throws InternalInfrastructureException { List previewUrlCommands = env.getCommands().stream() .filter(c -> c.getPreviewUrl() != null) .collect(Collectors.toList()); List portsToProvision = new ArrayList<>(); for (CommandImpl command : previewUrlCommands) { int port = command.getPreviewUrl().getPort(); Optional foundService = Services.findServiceWithPort(env.getServices().values(), port); if (foundService.isPresent()) { if (!hasMatchingEndpoint(env, foundService.get(), port)) { ServicePort servicePort = Services.findPort(foundService.get(), port) .orElseThrow( () -> new InternalInfrastructureException( String.format( "Port '%d' in service '%s' not found. This is not expected, please report a bug!", port, foundService.get().getMetadata().getName()))); String serviceName = foundService.get().getMetadata().getName(); externalServerExposer.expose( env, null, serviceName, serviceName, servicePort, Collections.emptyMap()); } } else { portsToProvision.add(createServicePort(port)); } } if (!portsToProvision.isEmpty()) { String serverName = generate(SERVER_PREFIX, SERVER_UNIQUE_PART_SIZE) + "-previewUrl"; Service service = new ServerServiceBuilder().withName(serverName).withPorts(portsToProvision).build(); env.getServices().put(serverName, service); portsToProvision.forEach( port -> externalServerExposer.expose( env, null, service.getMetadata().getName(), service.getMetadata().getName(), port, Collections.emptyMap())); } } private ServicePort createServicePort(int port) { return new ServicePort(null, "server-" + port, null, port, "TCP", new IntOrString(port)); } protected boolean hasMatchingEndpoint(T env, Service service, int port) { return Ingresses.findIngressRuleForServicePort(env.getIngresses().values(), service, port) .isPresent(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/RuntimeServerBuilder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; import org.eclipse.che.api.workspace.shared.Constants; /** * Helper class to build {@link ServerImpl} from parts like port, host, path, etc. It also adds port * that let to server creation as attribute {@link * org.eclipse.che.api.workspace.shared.Constants#SERVER_PORT_ATTRIBUTE} * * @author Oleksandr Garagatyi */ public class RuntimeServerBuilder { private String protocol; private String host; private String port; private String path; private String endpointOrigin; private Map attributes; private String targetPort; public RuntimeServerBuilder protocol(String protocol) { this.protocol = protocol; return this; } public RuntimeServerBuilder host(String host) { this.host = host; return this; } public RuntimeServerBuilder port(String port) { this.port = port; return this; } public RuntimeServerBuilder path(String path) { this.path = path; return this; } public RuntimeServerBuilder endpointOrigin(String authPath) { this.endpointOrigin = authPath; return this; } public RuntimeServerBuilder attributes(Map attributes) { this.attributes = attributes; return this; } public RuntimeServerBuilder targetPort(String targetPort) { this.targetPort = removeSuffix(targetPort); return this; } public ServerImpl build() { if (endpointOrigin == null) { endpointOrigin = "/"; } StringBuilder ub = new StringBuilder(); if (protocol != null) { ub.append(protocol).append("://"); } else { ub.append("tcp://"); } ub.append(host); if (port != null) { ub.append(':').append(removeSuffix(port)); } if (path != null) { if (!path.isEmpty() && !path.startsWith("/")) { ub.append("/"); } ub.append(path); } Map completeAttributes = new HashMap<>(attributes); completeAttributes.put(Constants.SERVER_PORT_ATTRIBUTE, targetPort); ServerConfig.setEndpointOrigin(completeAttributes, endpointOrigin); return new ServerImpl() .withUrl(ub.toString()) .withStatus(ServerStatus.UNKNOWN) .withAttributes(completeAttributes); } /** Removes suffix of {@link ServerConfig} such as "/tcp" when port value "8080/tcp". */ private String removeSuffix(String port) { return port.split("/")[0]; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ServerServiceBuilder.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; /** * Helps to build service that expose servers. * * @author Sergii Leshchenko */ public class ServerServiceBuilder { private String name; private String machineName; private final Map selector = new HashMap<>(); private List ports = Collections.emptyList(); private Map serversConfigs = Collections.emptyMap(); public ServerServiceBuilder withName(String name) { this.name = name; return this; } public ServerServiceBuilder withSelectorEntry(String key, String value) { selector.put(key, value); return this; } public ServerServiceBuilder withPorts(List ports) { this.ports = ports; return this; } public ServerServiceBuilder withServers(Map serversConfigs) { this.serversConfigs = serversConfigs; return this; } public ServerServiceBuilder withMachineName(String machineName) { this.machineName = machineName; return this; } public Service build() { io.fabric8.kubernetes.api.model.ServiceBuilder builder = new io.fabric8.kubernetes.api.model.ServiceBuilder(); return builder .withNewMetadata() .withName(name.replace("/", "-").toLowerCase()) .withAnnotations( Annotations.newSerializer() .servers(serversConfigs) .machineName(machineName) .annotations()) .endMetadata() .withNewSpec() .withSelector(selector) .withPorts(ports) .endSpec() .build(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/WorkspaceExposureType.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server; import static java.util.Objects.requireNonNull; /** Lists all the possible types of workspace exposure. */ public enum WorkspaceExposureType { GATEWAY("gateway"), NATIVE("native"); private final String configValue; WorkspaceExposureType(String configValue) { this.configValue = configValue; } public String getConfigValue() { return configValue; } public static WorkspaceExposureType fromConfigurationValue(String configValue) { requireNonNull(configValue); for (WorkspaceExposureType s : WorkspaceExposureType.values()) { if (s.configValue.equals(configValue)) { return s; } } throw new IllegalArgumentException("Unknown server resolver strategy: " + configValue); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/CombinedSingleHostServerExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static java.util.stream.Collectors.toMap; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * This {@link ExternalServerExposer} is used in single-host mode when we need to expose some * servers on subdomain, instead of subpaths. * *

It aggregates 2 {@link ExternalServerExposer}s, using one to expose servers on subdomand, and * 2nd to expose servers on subpaths. It determines which to use for individual server based on some * attribute in {@link ServerConfig#getAttributes()} (see implementation {@link * CombinedSingleHostServerExposer#expose(KubernetesEnvironment, String, String, String, * ServicePort, Map)} for the details). * * @param environment type */ public class CombinedSingleHostServerExposer implements ExternalServerExposer { private final ExternalServerExposer subdomainServerExposer; private final ExternalServerExposer subpathServerExposer; public CombinedSingleHostServerExposer( ExternalServerExposer subdomainServerExposer, ExternalServerExposer subpathServerExposer) { this.subdomainServerExposer = subdomainServerExposer; this.subpathServerExposer = subpathServerExposer; } /** * Exposes given 'externalServers' to either subdomain or subpath, using 2 different {@link * ExternalServerExposer}s. Which one to use for individual server is determined with {@link * ServerConfig#REQUIRE_SUBDOMAIN} attribute. * * @param k8sEnv environment * @param machineName machine containing servers * @param serviceName service associated with machine, mapping all machine server ports * @param serverId non-null for a unique server, null for a compound set of servers that should be * exposed together. * @param servicePort specific service port to be exposed externally * @param externalServers server configs of servers to be exposed externally */ @Override public void expose( T k8sEnv, String machineName, String serviceName, String serverId, ServicePort servicePort, Map externalServers) { if (serverId == null) { // this is the ID for non-unique servers serverId = servicePort.getName(); } Map subpathServers = getStrategyConformingServers(externalServers); Map subdomainServers = getServersRequiringSubdomain(externalServers); if (!subpathServers.isEmpty()) { subpathServerExposer.expose( k8sEnv, machineName, serviceName, serverId, servicePort, subpathServers); } if (!subdomainServers.isEmpty()) { subdomainServerExposer.expose( k8sEnv, machineName, serviceName, serverId, servicePort, subdomainServers); } } @Override public Map getStrategyConformingServers( Map externalServers) { return externalServers.entrySet().stream() .filter(e -> !e.getValue().isRequireSubdomain()) .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override public Map getServersRequiringSubdomain( Map externalServers) { return externalServers.entrySet().stream() .filter(e -> e.getValue().isRequireSubdomain()) .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/DefaultHostExternalServiceExposureStrategy.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; /** * Provides a path-based strategy for exposing service ports outside the cluster using Ingress * Ingresses will be created without an explicit host (defaulting to *). * *

This strategy uses different Ingress path entries
* Each external server is exposed with a unique path prefix. * *

This strategy imposes limitation on user-developed applications.
* It should only be used for local development with a single IP address * *

 *   Path-Based Ingress exposing service's port:
 * Ingress
 * ...
 * spec:
 *   rules:
 *     - http:
 *         paths:
 *           - path: service123/webapp        ---->> Service.metadata.name + / + Service.spec.ports[0].name
 *             backend:
 *               serviceName: service123      ---->> Service.metadata.name
 *               servicePort: [8080|web-app]  ---->> Service.spec.ports[0].[port|name]
 * 
* * @author Sergii Leshchenko * @author Guy Daich */ public class DefaultHostExternalServiceExposureStrategy implements ExternalServiceExposureStrategy { public static final String DEFAULT_HOST_STRATEGY = "default-host"; @Override public String getExternalHost(String serviceName, String serverName) { return null; } @Override public String getExternalPath(String serviceName, String serverName) { return "/" + serviceName + "/" + serverName; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/ExternalServerExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.Collections; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver.ServerResolver; /** * Helps to expose internal Che services to outside the cluster. Implementations should create * objects in given {@link KubernetesEnvironment}. These object must be properly annotated (see * {@link org.eclipse.che.workspace.infrastructure.kubernetes.Annotations}) so {@link * ServerResolver} can later use them. * * @param environment type */ public interface ExternalServerExposer { /** * Exposes service port on given service. The exposed service port is associated with a specific * Server configuration. Server configuration should be encoded in the exposing object's * annotations, to be used by {@link ServerResolver}. * * @param k8sEnv environment * @param machineName machine containing servers * @param serviceName service associated with machine, mapping all machine server ports * @param serverId non-null for a unique server, null for a compound set of servers that should be * exposed together. * @param servicePort specific service port to be exposed externally * @param externalServers server configs of servers to be exposed externally */ void expose( T k8sEnv, @Nullable String machineName, String serviceName, String serverId, ServicePort servicePort, Map externalServers); /** * Returns the servers from the provided map that should be deployed using the current configured * server exposure strategy. * * @param externalServers all the external servers that are being deployed * @return a view of the provided map */ default Map getStrategyConformingServers( Map externalServers) { return externalServers; } /** * Returns the servers from the provided map that should be deployed on a subdomain regardless of * the current configured server exposure strategy. * * @param externalServers all the external servers that are being deployed * @return a view of the provided map */ default Map getServersRequiringSubdomain( Map externalServers) { return Collections.emptyMap(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/ExternalServerExposerProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import javax.inject.Provider; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Provides {@link ExternalServerExposer} implementations based on configuration. * * @param type of environment */ public interface ExternalServerExposerProvider extends Provider> {} ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/ExternalServerIngressBuilder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.annotations.VisibleForTesting; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPath; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPathBuilder; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressRuleValue; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressRuleValueBuilder; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressBackendBuilder; import io.fabric8.kubernetes.api.model.networking.v1.IngressBuilder; import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; import io.fabric8.kubernetes.api.model.networking.v1.IngressRuleBuilder; import io.fabric8.kubernetes.api.model.networking.v1.IngressServiceBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressServiceBackendBuilder; import io.fabric8.kubernetes.api.model.networking.v1.IngressSpec; import io.fabric8.kubernetes.api.model.networking.v1.IngressSpecBuilder; import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPortBuilder; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; /** * helper class for builder ingresses. Creates an ingress with a single rule, based on hostname, * http path. Ingress maps path to a specific service and service port. * * @author Guy Daich */ public class ExternalServerIngressBuilder { private String host; private String path; private String name; private String serviceName; private String servicePortName; private Integer servicePort; private Map serversConfigs; private String machineName; private Map annotations; private Map labels; @VisibleForTesting static final String INGRESS_PATH_TYPE = "Prefix"; public ExternalServerIngressBuilder withHost(String host) { this.host = host; return this; } public ExternalServerIngressBuilder withPath(String path) { this.path = path; return this; } public ExternalServerIngressBuilder withName(String name) { this.name = name; return this; } public ExternalServerIngressBuilder withServiceName(String serviceName) { this.serviceName = serviceName; return this; } public ExternalServerIngressBuilder withAnnotations(Map annotations) { this.annotations = annotations; return this; } public ExternalServerIngressBuilder withServicePort(Integer targetPort) { this.servicePort = targetPort; return this; } public ExternalServerIngressBuilder withServicePortName(String targetPortName) { this.servicePortName = targetPortName; return this; } public ExternalServerIngressBuilder withServers( Map serversConfigs) { this.serversConfigs = serversConfigs; return this; } public ExternalServerIngressBuilder withMachineName(String machineName) { this.machineName = machineName; return this; } public ExternalServerIngressBuilder withLabels(Map labels) { this.labels = labels; return this; } public Ingress build() { ServiceBackendPortBuilder serviceBackendPortBuilder = new ServiceBackendPortBuilder(); // cannot set both port and name if (!isNullOrEmpty(servicePortName)) { serviceBackendPortBuilder.withName(servicePortName); } else if (servicePort != null) { serviceBackendPortBuilder.withNumber(servicePort); } IngressServiceBackend ingressServiceBackend = new IngressServiceBackendBuilder() .withPort(serviceBackendPortBuilder.build()) .withName(serviceName) .build(); IngressBackend ingressBackend = new IngressBackendBuilder().withService(ingressServiceBackend).build(); HTTPIngressPathBuilder httpIngressPathBuilder = new HTTPIngressPathBuilder().withBackend(ingressBackend).withPathType(INGRESS_PATH_TYPE); if (!isNullOrEmpty(path)) { httpIngressPathBuilder.withPath(path); } HTTPIngressPath httpIngressPath = httpIngressPathBuilder.build(); HTTPIngressRuleValue httpIngressRuleValue = new HTTPIngressRuleValueBuilder().withPaths(httpIngressPath).build(); IngressRuleBuilder ingressRuleBuilder = new IngressRuleBuilder().withHttp(httpIngressRuleValue); if (!isNullOrEmpty(host)) { ingressRuleBuilder.withHost(host); } IngressRule ingressRule = ingressRuleBuilder.build(); IngressSpec ingressSpec = new IngressSpecBuilder().withRules(ingressRule).build(); Map ingressAnnotations = new HashMap<>(annotations); ingressAnnotations.putAll( Annotations.newSerializer().servers(serversConfigs).machineName(machineName).annotations()); return new IngressBuilder() .withSpec(ingressSpec) .withMetadata( new ObjectMetaBuilder() .withName(name) .withAnnotations(ingressAnnotations) .withLabels(labels) .build()) .build(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/ExternalServiceExposureStrategy.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import org.eclipse.che.commons.annotation.Nullable; /** * Implementations of this strategy are used by the {@link ExternalServerExposer} to compose an * Ingress rule that exposes the services. */ public interface ExternalServiceExposureStrategy { /** Returns a host that should be used to expose the service */ @Nullable String getExternalHost(String serviceName, String serverName); /** Returns the path on which the service should be exposed */ String getExternalPath(String serviceName, String serverName); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/GatewayRouteConfigGenerator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import io.fabric8.kubernetes.api.model.ConfigMap; import java.util.Map; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; /** * Generates config for external servers that we want to expose in the Gateway. * *

Implementation provides configuration for specific Gateway technology (e.g., Traefik). */ public interface GatewayRouteConfigGenerator { /** * Add prepared {@link ConfigMap},that will hold gateway route configuration, to the generator. So * it can be generated later with {@link GatewayRouteConfigGenerator#generate(String)}. * *

Provided {@link ConfigMap} must be annotated with {@link * org.eclipse.che.api.core.model.workspace.config.ServerConfig} annotations. It's responsibility * of the caller to ensure that. The {@link GatewayRouteConfigGenerator} fails on {@link * GatewayRouteConfigGenerator#generate(String)} when invalid {@link ConfigMap}s are added to it. * * @param routeConfig config to add */ void addRouteConfig(String name, ConfigMap routeConfig) throws InfrastructureException; /** * Generates content of configurations for services, defined earlier by added {@link * GatewayRouteConfigGenerator#addRouteConfig(String, ConfigMap)}. Returned {@code Map} must be ready to be used as a {@link ConfigMap}'s data, which is further injected into * Gateway pod. * *

Implementation must ensure that Gateway configured with returned content will route the * requests on {@code path} into {@code serviceUrl}. Also it must strip {@code path} from request * url. * *

Returned Map's Keys will be used as file names, Values as their content. e.g.: * *

   *   service1.yml: {config-content-for-service-1}
   *   service2.yml: {config-content-for-service-2}
   * 
* * @return full content of configuration for the services */ Map generate(String namespace) throws InfrastructureException; } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/GatewayRouteConfigGeneratorFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; /** * This Factory provides {@link GatewayRouteConfigGenerator} instances, so implementation using * these can stay Gateway technology agnostic. */ @Singleton public class GatewayRouteConfigGeneratorFactory { private final String clusterDomain; @Inject public GatewayRouteConfigGeneratorFactory( @Nullable @Named("che.infra.kubernetes.cluster_domain") String clusterDomain) { this.clusterDomain = clusterDomain; } public GatewayRouteConfigGenerator create() { return new TraefikGatewayRouteConfigGenerator(clusterDomain); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/GatewayServerExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static java.lang.Boolean.TRUE; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVICE_NAME_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVICE_PORT_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.Annotations.CREATE_IN_CHE_INSTALLATION_NAMESPACE; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.Map; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.util.GatewayConfigmapLabels; /** * Uses gateway configured with ConfigMaps to expose servers. * * @param type of environment */ public class GatewayServerExposer implements ExternalServerExposer { private final ExternalServiceExposureStrategy strategy; private final GatewayConfigmapLabels configmapLabels; @Inject public GatewayServerExposer( ExternalServiceExposureStrategy strategy, GatewayConfigmapLabels configmapLabels) { this.strategy = strategy; this.configmapLabels = configmapLabels; } /** * Exposes service port on given service externally (outside kubernetes cluster) using the Gateway * specific configurations. * * @param k8sEnv Kubernetes environment * @param machineName machine containing servers * @param serviceName service associated with machine, mapping all machine server ports * @param serverId non-null for a unique server, null for a compound set of servers that should be * exposed together. * @param servicePort specific service port to be exposed externally * @param externalServers server configs of servers to be exposed externally */ @Override public void expose( T k8sEnv, @Nullable String machineName, String serviceName, String serverId, ServicePort servicePort, Map externalServers) { if (serverId == null) { // this is the ID for non-unique servers serverId = servicePort.getName(); } for (String esKey : externalServers.keySet()) { final String serverName = KubernetesServerExposer.makeServerNameValidForDns(serverId); final String name = createName(serviceName, serverName); k8sEnv .getConfigMaps() .put( name, createGatewayRouteConfigmap( name, machineName, serviceName, servicePort, serverName, esKey, externalServers.get(esKey))); } } private ConfigMap createGatewayRouteConfigmap( String name, String machineName, String serviceName, ServicePort servicePort, String serverName, String scRef, ServerConfig serverConfig) { final String path = ensureEndsWithSlash(strategy.getExternalPath(serviceName, serverName)); serverConfig.getAttributes().put(SERVICE_NAME_ATTRIBUTE, serviceName); ServerConfig.setEndpointOrigin(serverConfig.getAttributes(), path); serverConfig .getAttributes() .put(SERVICE_PORT_ATTRIBUTE, getTargetPort(servicePort.getTargetPort())); final Map annotations = Annotations.newSerializer() .server(scRef, serverConfig) .machineName(machineName) .annotations(); annotations.put(CREATE_IN_CHE_INSTALLATION_NAMESPACE, TRUE.toString()); ConfigMapBuilder gatewayConfigMap = new ConfigMapBuilder() .withNewMetadata() .withName(name) .withLabels(configmapLabels.getLabels()) .withAnnotations(annotations) .endMetadata(); return gatewayConfigMap.build(); } private String ensureEndsWithSlash(String path) { return path.endsWith("/") ? path : path + '/'; } private String createName(String serviceName, String serverName) { return serviceName + "-" + serverName; } private String getTargetPort(IntOrString targetPort) { return targetPort.getIntVal() != null ? targetPort.getIntVal().toString() : targetPort.getStrVal(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/IngressPathTransformInverter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static java.lang.String.format; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServerExposer.PATH_TRANSFORM_PATH_CATCH; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.commons.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is used to undo the effect of the "che.infra.kubernetes.ingress.path_transform" * configuration on the ingress paths. I.e. use this to get the path-part of the URL exposed by an * ingress given an ingress path. * *

This is usually a noop, apart from the single-host mode. */ public class IngressPathTransformInverter { private static final Logger LOGGER = LoggerFactory.getLogger(IngressPathTransformInverter.class); private static final Pattern PATH_FORMAT_DECONSTRUCTION_REGEX = Pattern.compile("(.*)" + PATH_TRANSFORM_PATH_CATCH + "(.*)"); private final Pattern pathTransformInverse; @Inject public IngressPathTransformInverter( @Nullable @Named("che.infra.kubernetes.ingress.path_transform") String pathTransformFmt) { this.pathTransformInverse = extractPathFromFmt(pathTransformFmt); } /** * Given the ingress path transformation from the configuration, this method constructs a regex * that is able to extract the original path from a value transformed by the path transformation. * E.g. when applied to the transformed path, the regex will extract the substring corresponding * to the original "%s" in the path transformation. * * @param pathTransformFmt the path transformation format * @return the regex that essentially reverts the effect of the path transformation */ private static Pattern extractPathFromFmt(String pathTransformFmt) { if (pathTransformFmt == null) { return Pattern.compile("^(.*)$"); } Matcher m = PATH_FORMAT_DECONSTRUCTION_REGEX.matcher(pathTransformFmt); if (m.matches() && m.groupCount() == 2) { String prefix = Pattern.quote(m.group(1)); String suffix = Pattern.quote(m.group(2)); return Pattern.compile("^" + prefix + "(.*)" + suffix + "$"); } else { LOGGER.warn( format( "Invalid path transformation format '%s' could not be successfully matched by the" + " deconstruction regex '%s'. Using the path transformation as is which can result in malfunctioning" + " ingresses.", pathTransformFmt, PATH_FORMAT_DECONSTRUCTION_REGEX)); return Pattern.compile(Pattern.quote(pathTransformFmt)); } } /** * Sometimes, the exposer needs to modify the path contained in the object exposing the server * (i.e. ingress in this case). Namely, this is needed to make the URL rewriting work for * single-host strategy where the path needs to contain a regular expression match group to retain * some of the path (at least in the case of the nginx ingress controller). * *

This method reverts such mangling and returns to the user a path that can be used by the * HTTP clients. * * @param path the path contained within the configuration of the object that needs to be * demangled * @return the path demangled such that it can be used in an externally reachable URL or the * untouched path if the path doesn't match the configured path format. */ public String undoPathTransformation(String path) { Matcher matcher = pathTransformInverse.matcher(path); if (matcher.matches()) { return matcher.group(1); } else { return path; } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/IngressServerExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static java.util.Collections.emptyMap; import com.google.common.base.Splitter; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer; /** * Uses Kubernetes {@link Ingress}es to expose the services. * * @see ExternalServerExposer */ @Singleton public class IngressServerExposer implements ExternalServerExposer { /** * A string to look for in the value of the "che.infra.kubernetes.ingress.path_transform" * configuration property that marks the location where the generated public path of the service * should be put in the final string representing the ingress path. */ static final String PATH_TRANSFORM_PATH_CATCH = "%s"; static final String SERVICE_NAME_PLACEHOLDER = ""; private final ExternalServiceExposureStrategy serviceExposureStrategy; private final Map preconfiguredAnnotations; private final Map labels; private final String pathTransformFmt; @Inject public IngressServerExposer( ExternalServiceExposureStrategy serviceExposureStrategy, @Named("infra.kubernetes.ingress.annotations") Map annotations, @Nullable @Named("che.infra.kubernetes.ingress.labels") String labelsProperty, @Nullable @Named("che.infra.kubernetes.ingress.path_transform") String pathTransformFmt) { this.serviceExposureStrategy = serviceExposureStrategy; this.preconfiguredAnnotations = annotations; this.pathTransformFmt = pathTransformFmt == null ? PATH_TRANSFORM_PATH_CATCH : pathTransformFmt; this.labels = labelsProperty != null ? Splitter.on(",").withKeyValueSeparator("=").split(labelsProperty) : emptyMap(); } /** * Exposes service port on given service externally (outside kubernetes cluster) using {@link * Ingress}. * * @see ExternalServerExposer#expose(KubernetesEnvironment, String, String, String, ServicePort, * Map) */ @Override public void expose( T k8sEnv, @Nullable String machineName, String serviceName, String serverId, ServicePort servicePort, Map externalServers) { if (serverId == null) { // this is the ID for non-unique servers serverId = servicePort.getName(); } Ingress ingress = generateIngress(machineName, serviceName, serverId, servicePort, externalServers); k8sEnv.getIngresses().put(ingress.getMetadata().getName(), ingress); } private Ingress generateIngress( String machineName, String serviceName, String serverId, ServicePort servicePort, Map servers) { String serverName = KubernetesServerExposer.makeServerNameValidForDns(serverId); ExternalServerIngressBuilder ingressBuilder = new ExternalServerIngressBuilder(); String host = serviceExposureStrategy.getExternalHost(serviceName, serverName); if (host != null) { ingressBuilder = ingressBuilder.withHost(host); } Map annotations = new HashMap<>(preconfiguredAnnotations); for (Map.Entry entry : annotations.entrySet()) { String value = entry.getValue(); if (value.contains(SERVICE_NAME_PLACEHOLDER)) { entry.setValue(value.replaceAll(SERVICE_NAME_PLACEHOLDER, serviceName)); } } return ingressBuilder .withPath( String.format( pathTransformFmt, ensureEndsWithSlash( serviceExposureStrategy.getExternalPath(serviceName, serverName)))) .withName(getIngressName(serviceName, serverName)) .withMachineName(machineName) .withServiceName(serviceName) .withAnnotations(annotations) .withLabels(labels) .withServicePortName(servicePort.getName()) .withServicePort(servicePort.getPort()) .withServers(servers) .build(); } private static String ensureEndsWithSlash(String path) { return path.endsWith("/") ? path : path + '/'; } private static String getIngressName(String serviceName, String serverName) { return serviceName + "-" + serverName; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/KubernetesExternalServerExposerProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.AbstractExposureStrategyAwareProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType; /** * Provides {@link ExternalServerExposer} based on `che.infra.kubernetes.server_strategy` and * `che.infra.kubernetes.singlehost.workspace.exposure` properties. * *

Based on server strategy, it can create a {@link CombinedSingleHostServerExposer} with * Kubernetes specific {@link IngressServerExposer} for exposing servers on subdomains. * * @param type of environment */ @Singleton public class KubernetesExternalServerExposerProvider extends AbstractExposureStrategyAwareProvider> implements ExternalServerExposerProvider { private final ExternalServerExposer combinedInstance; @Inject public KubernetesExternalServerExposerProvider( @Named("che.infra.kubernetes.server_strategy") String exposureStrategy, @Named("che.infra.kubernetes.singlehost.workspace.exposure") String exposureType, @Named("che.infra.kubernetes.singlehost.workspace.devfile_endpoint_exposure") String devfileEndpointsExposure, @Named("multihost-exposer") ExternalServerExposer multihostExposer, Map> exposers) { super( exposureStrategy, exposureType, exposers, "Could not find an external server exposer implementation for the exposure type '%s'."); if (SINGLE_HOST_STRATEGY.equals(exposureStrategy) && MULTI_HOST_STRATEGY.equals(devfileEndpointsExposure)) { this.combinedInstance = new CombinedSingleHostServerExposer<>(multihostExposer, instance); } else { this.combinedInstance = null; } } @Override public ExternalServerExposer get() { return combinedInstance != null ? combinedInstance : instance; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/MultiHostExternalServiceExposureStrategy.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static java.lang.String.format; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ServiceExposureStrategyProvider.STRATEGY_PROPERTY; import com.google.common.base.Strings; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.inject.ConfigurationException; /** * Provides a host-based strategy for exposing service ports outside the cluster using Ingress * *

This strategy uses different Ingress host entries
* Each external server is exposed with a unique subdomain of CHE_DOMAIN. * *

 *   Host-Based Ingress exposing service's port:
 * Ingress
 * ...
 * spec:
 *   rules:
 *     - host: service123-webapp.che-domain   ---->> Service.metadata.name + - + Service.spec.ports[0].name + . + CHE_DOMAIN
 *     - http:
 *         paths:
 *           - path: /
 *             backend:
 *               serviceName: service123      ---->> Service.metadata.name
 *               servicePort: [8080|web-app]  ---->> Service.spec.ports[0].[port|name]
 * 
* * @author Sergii Leshchenko * @author Guy Daich */ public class MultiHostExternalServiceExposureStrategy implements ExternalServiceExposureStrategy { public static final String MULTI_HOST_STRATEGY = "multi-host"; protected static final String INGRESS_DOMAIN_PROPERTY = "che.infra.kubernetes.ingress.domain"; private final String domain; @Inject public MultiHostExternalServiceExposureStrategy( @Named(INGRESS_DOMAIN_PROPERTY) String domain, @Named(STRATEGY_PROPERTY) String strategy) { if (Strings.isNullOrEmpty(domain) && MULTI_HOST_STRATEGY.equals(strategy)) { throw new ConfigurationException( format( "Strategy of generating ingress URLs for Che servers is set to '%s', " + "but property '%s' is not set", MULTI_HOST_STRATEGY, INGRESS_DOMAIN_PROPERTY)); } this.domain = domain; } @Override public String getExternalHost(String serviceName, String serverName) { return serviceName + "-" + serverName + "." + domain; } @Override public String getExternalPath(String serviceName, String serverName) { return "/"; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/MultihostIngressServerExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Uses Kubernetes {@link Ingress}es to expose the services using subdomains a.k.a. multi-host. * * @see ExternalServerExposer */ @Singleton public class MultihostIngressServerExposer extends IngressServerExposer implements ExternalServerExposer { @Inject public MultihostIngressServerExposer( MultiHostExternalServiceExposureStrategy serviceExposureStrategy, @Named("infra.kubernetes.ingress.annotations") Map annotations, @Nullable @Named("che.infra.kubernetes.ingress.labels") String labelsProperty, @Nullable @Named("che.infra.kubernetes.ingress.path_transform") String pathTransformFmt) { super(serviceExposureStrategy, annotations, labelsProperty, pathTransformFmt); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/ServiceExposureStrategyProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static java.lang.String.format; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.eclipse.che.inject.ConfigurationException; @Singleton public class ServiceExposureStrategyProvider implements Provider { public static final String STRATEGY_PROPERTY = "che.infra.kubernetes.server_strategy"; private final ExternalServiceExposureStrategy namingStrategy; private final ExternalServiceExposureStrategy multiHostStrategy; @Inject public ServiceExposureStrategyProvider( @Named(STRATEGY_PROPERTY) String strategy, Map strategies) { namingStrategy = strategies.get(strategy); if (namingStrategy == null) { throw new ConfigurationException( format("Unsupported server naming strategy '%s' configured", strategy)); } multiHostStrategy = strategies.get(MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY); if (multiHostStrategy == null) { throw new ConfigurationException( "No implementation for 'multi-host' server exposure strategy configured even though it is" + " mandatory."); } } /** Returns the configured exposure strategy. */ @Override public ExternalServiceExposureStrategy get() { return namingStrategy; } /** * Returns the multi-host exposure strategy, regardless of which default exposure strategy is * configured. This is used in workspaces with mixed endpoints (i.e. plugins by default deployed * using the configured strategy and devfile endpoints by default deployed using multi-host). */ public ExternalServiceExposureStrategy getMultiHostStrategy() { return multiHostStrategy; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/SingleHostExternalServiceExposureStrategy.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import javax.inject.Inject; import javax.inject.Named; /** * Provides a path-based strategy for exposing service ports outside the cluster using Ingress. * Ingresses will be created with a common host name for all workspaces. * *

This strategy uses different Ingress path entries
* Each external server is exposed with a unique path prefix. * *

This strategy imposes limitation on user-developed applications.
* *

 *   Path-Based Ingress exposing service's port:
 * Ingress
 * ...
 * spec:
 *   rules:
 *     - host: CHE_HOST
 *       http:
 *         paths:
 *           - path: service123/webapp        ---->> Service.metadata.name + / + Service.spec.ports[0].name
 *             backend:
 *               serviceName: service123      ---->> Service.metadata.name
 *               servicePort: [8080|web-app]  ---->> Service.spec.ports[0].[port|name]
 * 
* * @author Sergii Leshchenko * @author Guy Daich */ public class SingleHostExternalServiceExposureStrategy implements ExternalServiceExposureStrategy { public static final String SINGLE_HOST_STRATEGY = "single-host"; private final String cheHost; @Inject public SingleHostExternalServiceExposureStrategy(@Named("che.host") String cheHost) { this.cheHost = cheHost; } @Override public String getExternalHost(String serviceName, String serverName) { return cheHost; } @Override public String getExternalPath(String serviceName, String serverName) { return "/" + serviceName + "/" + serverName; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/TraefikGatewayRouteConfigGenerator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.WRITE_DOC_START_MARKER; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVICE_NAME_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVICE_PORT_ATTRIBUTE; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import com.google.common.base.Strings; import io.fabric8.kubernetes.api.model.ConfigMap; import java.io.IOException; import java.io.StringWriter; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; /** * Config generator for Traefik Gateway. * *

Content of single service configuration looks like this: * *

 * http:
 *   routers:
 *     {name}:
 *       rule: "PathPrefix(`{path}`)"
 *       service: {name}
 *       middlewares:
 *       - "{name}"
 *       priority: 100
 *   services:
 *     {name}:
 *       loadBalancer:
 *         servers:
 *         - url: '{serviceUrl}'
 *   middlewares:
 *     {name}:
 *       stripPrefix:
 *         prefixes:
 *         - "{GatewayRouteConfig#routePath}"
 * 
*/ public class TraefikGatewayRouteConfigGenerator implements GatewayRouteConfigGenerator { private static final String SERVICE_URL_FORMAT_WITH_CLUSTER_DOMAIN = "http://%s.%s.svc.%s:%s"; private static final String SERVICE_URL_FORMAT_WITHOUT_CLUSTER_DOMAIN = "http://%s.%s.svc:%s"; private final String clusterDomain; public TraefikGatewayRouteConfigGenerator(@Nullable String clusterDomain) { this.clusterDomain = clusterDomain; } private final Map routeConfigs = new HashMap<>(); @Override public void addRouteConfig(String name, ConfigMap routeConfig) { this.routeConfigs.put(name, routeConfig); } /** * Generates configuration for all configs added by {@link * TraefikGatewayRouteConfigGenerator#addRouteConfig(String, ConfigMap)} so far. It does not * change them, so this method can be used repeatedly. * *

Returned {@code Map} has keys created from {@code name} parameter of {@link * TraefikGatewayRouteConfigGenerator#addRouteConfig(String, ConfigMap)} + '.yml' suffix. Values * are full configuration for single gateway route. This map is suppose to be directly used as * {@link ConfigMap}'s data. * * @return map with added routes configurations */ @Override public Map generate(String namespace) throws InfrastructureException { Map cmData = new HashMap<>(); for (Entry routeConfig : routeConfigs.entrySet()) { Map servers = new Annotations.Deserializer(routeConfig.getValue().getMetadata().getAnnotations()) .servers(); if (servers.size() != 1) { throw new InfrastructureException( "Expected exactly 1 server [" + routeConfig.getValue().toString() + "]"); } ServerConfigImpl server = servers.get(servers.keySet().iterator().next()); String serviceName = server.getAttributes().get(SERVICE_NAME_ATTRIBUTE); String servicePort = server.getAttributes().get(SERVICE_PORT_ATTRIBUTE); String traefikRouteConfig = generate( routeConfig.getKey(), createServiceUrl(serviceName, servicePort, namespace), server.getEndpointOrigin()); cmData.put(routeConfig.getKey() + ".yml", traefikRouteConfig); } return cmData; } /** * Generates Traefik specific configuration for single service. * * @param name name of the service * @param serviceUrl url of service we want to route to * @param path path to route and strip * @return traefik service route config */ private String generate(String name, String serviceUrl, String path) throws InfrastructureException { StringWriter sw = new StringWriter(); try { YAMLGenerator generator = YAMLFactory.builder().disable(WRITE_DOC_START_MARKER).build().createGenerator(sw); generator.writeStartObject(); generator.writeFieldName("http"); generator.writeStartObject(); generator.writeFieldName("routers"); generateRouters(generator, name, path); generator.writeFieldName("services"); generateServices(generator, name, serviceUrl); generator.writeFieldName("middlewares"); generateMiddlewares(generator, name, path); generator.writeEndObject(); generator.writeEndObject(); generator.flush(); return sw.toString(); } catch (IOException e) { throw new InfrastructureException(e); } } /** * generates Routers part of Traefik config * *

   * {name}:
   *   rule: "PathPrefix(`{path}`)"
   *   service: "{name}"
   *   middlewares:
   *   - "{name}"
   *   priority: 100
   * 
*/ private void generateRouters(YAMLGenerator generator, String name, String path) throws IOException { generator.writeStartObject(); generator.writeFieldName(name); generator.writeStartObject(); generator.writeFieldName("rule"); generator.writeString("PathPrefix(`" + path + "`)"); generator.writeFieldName("service"); generator.writeString(name); generator.writeFieldName("middlewares"); generator.writeStartArray(); generator.writeString(name); generator.writeEndArray(); generator.writeFieldName("priority"); generator.writeNumber(100); generator.writeEndObject(); generator.writeEndObject(); } /** * generates Services part of Traefik config * *
   * {name}:
   *   loadBalancer:
   *     servers:
   *     - url: "{serviceUrl}"
   * 
*/ private void generateServices(YAMLGenerator generator, String name, String serviceUrl) throws IOException { generator.writeStartObject(); generator.writeFieldName(name); generator.writeStartObject(); generator.writeFieldName("loadBalancer"); generator.writeStartObject(); generator.writeFieldName("servers"); generator.writeStartArray(); generator.writeStartObject(); generator.writeFieldName("url"); generator.writeString(serviceUrl); generator.writeEndObject(); generator.writeEndArray(); generator.writeEndObject(); generator.writeEndObject(); generator.writeEndObject(); } /** * generates Middlewares part of Traefik config * *
   * {name}:
   *   stripPrefix:
   *     prefixes:
   *     - "{path}"
   * 
*/ private void generateMiddlewares(YAMLGenerator generator, String name, String path) throws IOException { generator.writeStartObject(); generator.writeFieldName(name); generator.writeStartObject(); generator.writeFieldName("stripPrefix"); generator.writeStartObject(); generator.writeFieldName("prefixes"); generator.writeStartArray(); generator.writeString(path); generator.writeEndArray(); generator.writeEndObject(); generator.writeEndObject(); generator.writeEndObject(); } private String createServiceUrl(String serviceName, String servicePort, String serviceNamespace) { if (Strings.isNullOrEmpty(clusterDomain)) { return String.format( SERVICE_URL_FORMAT_WITHOUT_CLUSTER_DOMAIN, serviceName, serviceNamespace, servicePort); } else { return String.format( SERVICE_URL_FORMAT_WITH_CLUSTER_DOMAIN, serviceName, serviceNamespace, clusterDomain, servicePort); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/resolver/AbstractServerResolver.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import io.fabric8.kubernetes.api.model.Service; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.server.RuntimeServerBuilder; /** * {@link ServerResolver} implementations that uses {@link Service} for internal servers can use * this abstract class. The implementation then must define how to resolve external servers by * implementing {@link AbstractServerResolver#resolveExternalServers(String)}. */ public abstract class AbstractServerResolver implements ServerResolver { private final Multimap services; public AbstractServerResolver(Iterable services) { this.services = ArrayListMultimap.create(); for (Service service : services) { String machineName = Annotations.newDeserializer(service.getMetadata().getAnnotations()).machineName(); this.services.put(machineName, service); } } /** * Joins together the two URL path fragments together and makes sure the returned path ends with a * slash. * * @param fragment1 the root path fragment * @param fragment2 the sub-path fragment * @return the two path fragments joined together */ protected static String buildPath(String fragment1, @Nullable String fragment2) { StringBuilder sb = new StringBuilder(fragment1); if (!isNullOrEmpty(fragment2)) { if (!fragment1.endsWith("/")) { sb.append('/'); } if (fragment2.startsWith("/")) { sb.append(fragment2.substring(1)); } else { sb.append(fragment2); } } // always end server URLs with a slash, so that they can be safely sub-path'd.. if (sb.charAt(sb.length() - 1) != '/') { sb.append('/'); } return sb.toString(); } @Override public final Map resolve(String machineName) { Map servers = new HashMap<>(); servers.putAll(resolveInternalServers(machineName)); servers.putAll(resolveExternalServers(machineName)); return servers; } private Map resolveInternalServers(String machineName) { return services.get(machineName).stream() .map(this::resolveServiceServers) .flatMap(s -> s.entrySet().stream()) .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (s1, s2) -> s2)); } private Map resolveServiceServers(Service service) { return Annotations.newDeserializer(service.getMetadata().getAnnotations()) .servers() .entrySet() .stream() .collect( Collectors.toMap( Entry::getKey, e -> // this is only used for internal servers, for which it doesn't make sense to be // secure. Therefore we don't // define the authOrigin() on these... new RuntimeServerBuilder() .protocol(e.getValue().getProtocol()) .host(service.getMetadata().getName()) .port(e.getValue().getPort()) .path(e.getValue().getPath()) .attributes(e.getValue().getAttributes()) .targetPort(e.getValue().getPort()) .build(), (s1, s2) -> s2)); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/resolver/AbstractServerResolverFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Service; import java.util.List; import java.util.Map; import javax.inject.Named; import org.eclipse.che.workspace.infrastructure.kubernetes.server.AbstractExposureStrategyAwareProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType; /** * An abstract class that specifies the logic for creating the server resolvers for given cluster * type. * * @param Type representing the object for exposing endpoints in the cluster. I.e. ingress or * route. */ public class AbstractServerResolverFactory { private final ResolverConstructorProvider constructorProvider; protected AbstractServerResolverFactory( @Named("che.infra.kubernetes.server_strategy") String exposureStrategy, @Named("che.infra.kubernetes.singlehost.workspace.exposure") String wsExposureType, Map> mapping, String errorMessageTemplate) { constructorProvider = new ResolverConstructorProvider( exposureStrategy, wsExposureType, mapping, errorMessageTemplate); } /** * Create {@link ServerResolver} for configured server strategy. * * @return {@link ServerResolver} instance */ public ServerResolver create( List services, List ingresses, List configMaps) { return constructorProvider.get().create(services, ingresses, configMaps); } /** Constructs a new {@link ServerResolver} instance from the provided parameters. */ protected interface ResolverConstructor { ServerResolver create(List services, List ingresses, List configMaps); } /** * Let's reuse the logic for picking the right "thing" based on the server and workspace exposure * types that is implemented in {@link AbstractExposureStrategyAwareProvider}. */ private static final class ResolverConstructorProvider extends AbstractExposureStrategyAwareProvider> { protected ResolverConstructorProvider( String exposureStrategy, String wsExposureType, Map> mapping, String errorMessageTemplate) { super(exposureStrategy, wsExposureType, mapping, errorMessageTemplate); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/resolver/ConfigMapServerResolver.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Service; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.server.RuntimeServerBuilder; /** Resolves servers from ConfigMaps, used with Gateway based single-host */ public class ConfigMapServerResolver extends AbstractServerResolver { private final Multimap configMaps; private final String cheHost; private final ServerResolver nativeServerResolver; public ConfigMapServerResolver( Iterable services, Iterable configMaps, String cheHost, ServerResolver nativeServerResolver) { super(services); this.nativeServerResolver = nativeServerResolver; this.cheHost = cheHost; this.configMaps = ArrayListMultimap.create(); for (ConfigMap configMap : configMaps) { String machineName = Annotations.newDeserializer(configMap.getMetadata().getAnnotations()).machineName(); if (machineName != null) { this.configMaps.put(machineName, configMap); } } } @Override public Map resolveExternalServers(String machineName) { Map serverMap = new HashMap<>(); serverMap.putAll(nativeServerResolver.resolveExternalServers(machineName)); serverMap.putAll( configMaps.get(machineName).stream() .map(this::fillGatewayServers) .flatMap(m -> m.entrySet().stream()) .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (s1, s2) -> s2))); return serverMap; } private Map fillGatewayServers(ConfigMap configMap) { return Annotations.newDeserializer(configMap.getMetadata().getAnnotations()) .servers() .entrySet() .stream() .collect( Collectors.toMap( Entry::getKey, e -> { boolean requiresSubdomain = e.getValue().isRequireSubdomain(); String root = requiresSubdomain ? "/" : e.getValue().getEndpointOrigin(); String path = buildPath(root, e.getValue().getPath()); return new RuntimeServerBuilder() .protocol(e.getValue().getProtocol()) .host(cheHost) .path(path) .endpointOrigin(root) .attributes(e.getValue().getAttributes()) .targetPort(e.getValue().getPort()) .build(); }, (s1, s2) -> s2)); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/resolver/IngressServerResolver.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.RuntimeServerBuilder; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressPathTransformInverter; /** * Helps to resolve {@link ServerImpl servers} by machine name according to specified {@link Ingress * ingresses} and {@link Service services}. * *

Objects annotations are used to check if {@link Service service} or {@link Ingress ingress} * exposes the specified machine servers. * * @author Sergii Leshchenko * @author Alexander Garagatyi * @see KubernetesServerExposer * @see Annotations */ public class IngressServerResolver extends AbstractServerResolver { private final Multimap ingresses; private final IngressPathTransformInverter pathTransformInverter; public IngressServerResolver( IngressPathTransformInverter pathTransformInverter, List services, List ingresses) { super(services); this.pathTransformInverter = pathTransformInverter; this.ingresses = ArrayListMultimap.create(); for (Ingress ingress : ingresses) { String machineName = Annotations.newDeserializer(ingress.getMetadata().getAnnotations()).machineName(); this.ingresses.put(machineName, ingress); } } @Override public Map resolveExternalServers(String machineName) { return ingresses.get(machineName).stream() .map(this::fillIngressServers) .flatMap(m -> m.entrySet().stream()) .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (v1, v2) -> v2)); } private Map fillIngressServers(Ingress ingress) { IngressRule ingressRule = ingress.getSpec().getRules().get(0); // host either set by rule, or determined by LB ip final String host = ingressRule.getHost() != null ? ingressRule.getHost() : ingress.getStatus().getLoadBalancer().getIngress().get(0).getIp(); return Annotations.newDeserializer(ingress.getMetadata().getAnnotations()) .servers() .entrySet() .stream() .collect( Collectors.toMap( Entry::getKey, e -> { String root = pathTransformInverter.undoPathTransformation( ingressRule.getHttp().getPaths().get(0).getPath()); String path = buildPath(root, e.getValue().getPath()); // the /jwt/auth needs to be based on the webroot of the server, not the path of // the endpoint. String endpointOrigin = buildPath(root, "/"); return new RuntimeServerBuilder() .protocol(e.getValue().getProtocol()) .host(host) .path(path) .endpointOrigin(endpointOrigin) .attributes(e.getValue().getAttributes()) .targetPort(e.getValue().getPort()) .build(); })); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/resolver/KubernetesServerResolverFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType.GATEWAY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType.NATIVE; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressPathTransformInverter; /** * Factory that decides by configuration, which {@link ServerResolver} implementation to use in * Kubernetes environment. */ @Singleton public class KubernetesServerResolverFactory extends AbstractServerResolverFactory { @Inject public KubernetesServerResolverFactory( IngressPathTransformInverter pathTransformInverter, @Named("che.host") String cheHost, @Named("che.infra.kubernetes.server_strategy") String exposureStrategy, @Named("che.infra.kubernetes.singlehost.workspace.exposure") String wsExposureType) { super( exposureStrategy, wsExposureType, ImmutableMap.of( GATEWAY, (ss, is, cs) -> new ConfigMapServerResolver( ss, cs, cheHost, new IngressServerResolver(pathTransformInverter, ss, is)), NATIVE, (ss, is, cs) -> new IngressServerResolver(pathTransformInverter, ss, is)), "Failed to initialize KubernetesServerResolverFactory for workspace exposure type '%s'."); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/resolver/ServerResolver.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver; import io.fabric8.kubernetes.api.model.Service; import java.util.Map; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; /** * Helps to resolve {@link ServerImpl servers} by machine name according to {@link Service services} * and implementation specific k8s objects. * *

Objects annotations are used to check if they exposes the specified machine servers. * * @see ExternalServerExposer * @see Annotations */ public interface ServerResolver { /** * Resolves servers by the specified machine name. * * @param machineName machine to resolve servers * @return resolved servers */ Map resolve(String machineName); /** * Resolve external servers from implementation specific k8s object and it's annotations. * * @param machineName machine to resolve servers * @return resolved servers */ Map resolveExternalServers(String machineName); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/DefaultSecureServerExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure; import com.google.common.annotations.VisibleForTesting; import com.google.inject.assistedinject.Assisted; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.Collection; import java.util.Map; import java.util.Optional; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.PassThroughProxyProvisioner; /** * Exposes secure servers using a proxy. * *

To expose secure servers it provisions proxy objects into environment with {@link * org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.ProxyProvisioner}. Then proxy * service port is made public accessible by {@link ExternalServerExposer }. * *

In this way, requests to exposed secure servers will be routed via the proxy that is added one * per workspace. * * @see * org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner * @see PassThroughProxyProvisioner */ public class DefaultSecureServerExposer implements SecureServerExposer { private final ExternalServerExposer exposer; private final ProxyProvisioner proxyProvisioner; @VisibleForTesting public DefaultSecureServerExposer( ProxyProvisioner jwtProxyProvisioner, ExternalServerExposer exposer) { this.exposer = exposer; this.proxyProvisioner = jwtProxyProvisioner; } @Inject public DefaultSecureServerExposer( @Assisted RuntimeIdentity identity, ProxyProvisionerFactory proxyProvisionerFactory, ExternalServerExposerProvider exposer) { this.exposer = exposer.get(); this.proxyProvisioner = proxyProvisionerFactory.create(identity); } /** * This always returns an empty optional because JWT proxy is injected into the workspace pod and * assumes the servers it exposes listen on localhost. * * @see * org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer#createService(Collection, * PodData, String, Map) */ @Override public Optional createService( Collection allSecurePorts, PodData pod, String machineName, Map secureServers) { return Optional.empty(); } @Override public void expose( T k8sEnv, PodData pod, String machineName, @Nullable String serviceName, @Nullable String serverId, ServicePort servicePort, Map secureServers) throws InfrastructureException { Map conformingServers = exposer.getStrategyConformingServers(secureServers); Map subdomainServers = exposer.getServersRequiringSubdomain(secureServers); if (!conformingServers.isEmpty()) { doExpose( k8sEnv, pod, machineName, serviceName, serverId, servicePort, false, conformingServers); } if (!subdomainServers.isEmpty()) { doExpose( k8sEnv, pod, machineName, serviceName, serverId, servicePort, true, subdomainServers); } } private void doExpose( T k8sEnv, PodData pod, String machineName, @Nullable String serviceName, @Nullable String serverId, ServicePort servicePort, boolean requireSubdomain, Map secureServers) throws InfrastructureException { ServicePort exposedServicePort = proxyProvisioner.expose( k8sEnv, pod, machineName, serviceName, servicePort, servicePort.getProtocol(), requireSubdomain, secureServers); exposer.expose( k8sEnv, machineName, proxyProvisioner.getServiceName(), serverId, exposedServicePort, secureServers); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/ProxyProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; /** * A proxy provisioner is a class responsible for provisioning and exposing a reverse proxy that * should proxy the access to a backend service. */ public interface ProxyProvisioner { int FIRST_AVAILABLE_PROXY_PORT = 4400; /** * Modifies the provided environment with Kubernetes objects needed for the proxy and creates a * service that is pointing to the proxy that can then be used to expose the proxy. * *

Note that this method is called multiple times (once for each backend service) and so has to * build the kubernetes objects and configuration iteratively, if required. * * @param k8sEnv Kubernetes environment to modify * @param pod the pod that runs the server being exposed * @param backendServiceName service name that will be exposed * @param backendServicePort service port that will be exposed * @param protocol protocol that will be used for exposed port * @param requireSubdomain if true, the supplied servers are supposed to require a subdomain, if * false the servers are considered to follow the configured exposure strategy * @param secureServers secure servers to expose * @return JWTProxy service port that expose the specified one * @throws InfrastructureException if any exception occurs during port exposing */ ServicePort expose( KubernetesEnvironment k8sEnv, PodData pod, String machineName, @Nullable String backendServiceName, ServicePort backendServicePort, String protocol, boolean requireSubdomain, Map secureServers) throws InfrastructureException; /** The name of the service handling the traffic to the proxy. */ String getServiceName(); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/ProxyProvisionerFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; /** Interface to help create instance of {@link ProxyProvisioner} using Guice. */ public interface ProxyProvisionerFactory { ProxyProvisioner create(RuntimeIdentity identity); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.Collection; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; /** * Modifies the specified Kubernetes environment to expose secure servers. * *

Note that ONE {@link SecureServerExposer} instance should be used for one workspace start. * * @author Sergii Leshchenko */ public interface SecureServerExposer { String AUTH_ENDPOINT_PATH = "/jwt/auth"; /** * Creates a service that should handle the traffic for the provided secure ports that are exposed * on the container from the pod. * *

The exposer may choose to not create such service or to create a service for only a subset * of the ports. * * @param allSecurePorts the secure ports on the container * @param pod the pod containing the container * @return an optional service to put "in front of" the pod to service the ports, empty if no such * service should be created. */ Optional createService( Collection allSecurePorts, PodData pod, String machineName, Map secureServers); /** * Modifies the specified Kubernetes environment to expose secure servers. * * @param k8sEnv Kubernetes environment that should be modified. * @param pod the pod containing the exposed server * @param machineName machine name to which secure servers belong to * @param serviceName service name that exposes secure servers. Will be null if {@link * #createService(Collection, PodData, String, Map)} returned empty optional * @param serverId non-null for a unique server, null for a compound set of servers that should be * exposed together. * @param servicePort service port that exposes secure servers * @param secureServers secure servers to expose * @throws InfrastructureException when any exception occurs during servers exposing */ void expose( T k8sEnv, PodData pod, String machineName, @Nullable String serviceName, @Nullable String serverId, ServicePort servicePort, Map secureServers) throws InfrastructureException; } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactory.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Helps to create {@link SecureServerExposer} instances. * *

Note that ONE {@link SecureServerExposer} instance should be used for one workspace start. * * @author Sergii Leshchenko */ public interface SecureServerExposerFactory { SecureServerExposer create(RuntimeIdentity runtimeId); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactoryProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure; import static java.lang.String.format; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.workspace.server.model.impl.WarningImpl; import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Provides implementation of {@link SecureServerExposerFactory} according to workspace config * attribute with name `che.server.secure_exposer` if it is present and contains bound * implementation key or configuration property with name `che.server.secure_exposer` otherwise. * * @author Sergii Leshchenko * @author Oleksandr Garagatyi */ public class SecureServerExposerFactoryProvider { public static final String SECURE_EXPOSER_IMPL_PROPERTY = "che.server.secure_exposer"; public static final String UNKNOWN_EXPOSER_ERROR_TEMPLATE = "Unknown secure servers exposer '%s' is configured in workspace. Currently supported: %s."; private final Map> factories; private final String unknownExposerErrorTemplate; private final SecureServerExposerFactory serverExposerFactory; @Inject public SecureServerExposerFactoryProvider( @Named(SECURE_EXPOSER_IMPL_PROPERTY) String serverExposer, Map> factories) { String knownExposers = String.join(", ", factories.keySet()); serverExposerFactory = factories.get(serverExposer); if (serverExposerFactory == null) { throw new ConfigurationException( format( "Unknown secure servers exposer '%s' is configured. Currently supported: %s.", serverExposer, knownExposers)); } this.unknownExposerErrorTemplate = format(UNKNOWN_EXPOSER_ERROR_TEMPLATE, "%s", knownExposers); this.factories = factories; } /** * Creates instance of {@link SecureServerExposerFactory} that will expose secure servers of * provided Kubernetes environment for runtime with the specified runtime identity. */ public SecureServerExposerFactory get(T k8sEnv) { String envExposerImpl = k8sEnv.getAttributes().get(SECURE_EXPOSER_IMPL_PROPERTY); if (envExposerImpl != null) { if (factories.containsKey(envExposerImpl)) { return factories.get(envExposerImpl); } k8sEnv .getWarnings() .add( new WarningImpl( Warnings.UNKNOWN_SECURE_SERVER_EXPOSER_CONFIGURED_IN_WS_WARNING_CODE, format(unknownExposerErrorTemplate, envExposerImpl))); } return this.serverExposerFactory; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/AbstractJwtProxyProvisioner.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_REQUEST_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_REQUEST_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.CONTAINER_SOURCE_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.TOOL_CONTAINER_SOURCE; import static org.eclipse.che.commons.lang.NameGenerator.generate; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_UNIQUE_PART_SIZE; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServicePortBuilder; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.VolumeMount; import java.security.KeyPair; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.commons.lang.Size; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.server.ServerServiceBuilder; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.ProxyProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory.JwtProxyConfigBuilderFactory; /** * A base class for both {@link JwtProxyProvisioner} and {@link PassThroughProxyProvisioner} that * contains the bulk of the provisioning logic. */ abstract class AbstractJwtProxyProvisioner implements ProxyProvisioner { static final String JWT_PROXY_MACHINE_NAME = "che-jwtproxy"; public static final String JWT_PROXY_POD_NAME = JWT_PROXY_MACHINE_NAME; static final int MEGABYTES_TO_BYTES_DIVIDER = 1024 * 1024; static final String PUBLIC_KEY_HEADER = "-----BEGIN PUBLIC KEY-----\n"; static final String PUBLIC_KEY_FOOTER = "\n-----END PUBLIC KEY-----"; static final String JWT_PROXY_CONFIG_FILE = "config.yaml"; static final String JWT_PROXY_CONFIG_FOLDER = "/che-jwtproxy-config"; static final String JWT_PROXY_PUBLIC_KEY_FILE = "mykey.pub"; private final JwtProxyConfigBuilder proxyConfigBuilder; private final String jwtProxyImage; private final Map attributes; private final String serviceName; private final ExternalServiceExposureStrategy externalServiceExposureStrategy; private final ExternalServiceExposureStrategy multiHostExternalServiceExposureStrategy; private final CookiePathStrategy cookiePathStrategy; private final MultiHostCookiePathStrategy multihostCookiePathStrategy; private int availablePort; private final KeyPair keyPair; private final boolean detectCookieAuth; /** * Constructor! * * @param signatureKeyPair the key pair for JWT proxy SSH comms * @param jwtProxyConfigBuilderFactory factory to create a JWT proxy config builder * @param externalServiceExposureStrategy the strategy to expose external servers * @param cookiePathStrategy the strategy for the cookie path of the JWT auth cookies, if used * @param jwtProxyImage the image of JWT proxy to use * @param memoryLimitBytes the memory limit of the JWT proxy container * @param workspaceId the workspace ID being started * @param detectCookieAuth whether to look for cookie auth requirements in the proxied servers or * whether to ignore such requirements */ AbstractJwtProxyProvisioner( KeyPair signatureKeyPair, JwtProxyConfigBuilderFactory jwtProxyConfigBuilderFactory, ExternalServiceExposureStrategy externalServiceExposureStrategy, ExternalServiceExposureStrategy multiHostStrategy, CookiePathStrategy cookiePathStrategy, MultiHostCookiePathStrategy multihostCookiePathStrategy, String jwtProxyImage, String memoryRequestBytes, String memoryLimitBytes, String cpuRequestCores, String cpuLimitCores, String workspaceId, boolean detectCookieAuth) { this.keyPair = signatureKeyPair; this.proxyConfigBuilder = jwtProxyConfigBuilderFactory.create(workspaceId); this.jwtProxyImage = jwtProxyImage; this.externalServiceExposureStrategy = externalServiceExposureStrategy; this.multiHostExternalServiceExposureStrategy = multiHostStrategy; this.cookiePathStrategy = cookiePathStrategy; this.multihostCookiePathStrategy = multihostCookiePathStrategy; this.serviceName = generate(SERVER_PREFIX, SERVER_UNIQUE_PART_SIZE) + "-jwtproxy"; this.availablePort = FIRST_AVAILABLE_PROXY_PORT; long memoryLimitLong = Size.parseSizeToMegabytes(memoryLimitBytes) * MEGABYTES_TO_BYTES_DIVIDER; long memoryRequestLong = Size.parseSizeToMegabytes(memoryRequestBytes) * MEGABYTES_TO_BYTES_DIVIDER; this.attributes = ImmutableMap.of( MEMORY_LIMIT_ATTRIBUTE, Long.toString(memoryLimitLong), MEMORY_REQUEST_ATTRIBUTE, Long.toString(memoryRequestLong), CPU_LIMIT_ATTRIBUTE, cpuLimitCores, CPU_REQUEST_ATTRIBUTE, cpuRequestCores, CONTAINER_SOURCE_ATTRIBUTE, TOOL_CONTAINER_SOURCE); this.detectCookieAuth = detectCookieAuth; } /** * Returns an exposure configuration of the provided server. * * @param serverConfig the server configuration * @return the exposure configuration for the server */ protected abstract ExposureConfiguration getExposureConfiguration(ServerConfig serverConfig); /** * Modifies Kubernetes environment to expose the specified service port via JWTProxy. * * @param k8sEnv Kubernetes environment to modify * @param pod the pod that runs the server being exposed * @param backendServiceName service name that will be exposed * @param backendServicePort service port that will be exposed * @param protocol protocol that will be used for exposed port * @param secureServers secure servers to expose * @return JWTProxy service port that expose the specified one * @throws InfrastructureException if any exception occurs during port exposing */ @Override public ServicePort expose( KubernetesEnvironment k8sEnv, PodData pod, String machineName, String backendServiceName, ServicePort backendServicePort, String protocol, boolean requireSubdomain, Map secureServers) throws InfrastructureException { Preconditions.checkArgument( secureServers != null && !secureServers.isEmpty(), "Secure servers are missing"); ensureJwtProxyInjected(k8sEnv, machineName, pod); Set excludes = new HashSet<>(); Boolean cookiesAuthEnabled = null; for (ServerConfig serverConfig : secureServers.values()) { ExposureConfiguration config = getExposureConfiguration(serverConfig); // accumulate unsecured paths if (config.excludedPaths != null) { excludes.addAll(config.excludedPaths); } // calculate `cookiesAuthEnabled` attributes if (detectCookieAuth) { if (cookiesAuthEnabled == null) { cookiesAuthEnabled = config.cookiesAuthEnabled; } else { if (!cookiesAuthEnabled.equals(config.cookiesAuthEnabled)) { throw new InfrastructureException( "Secure servers which expose the same port should have the same `cookiesAuthEnabled` value."); } } } } int listenPort = availablePort++; ServicePort exposedPort = new ServicePortBuilder() .withName("server-" + listenPort) .withPort(listenPort) .withProtocol(protocol) .withNewTargetPort(listenPort) .build(); k8sEnv.getServices().get(serviceName).getSpec().getPorts().add(exposedPort); CookiePathStrategy actualCookiePathStrategy = requireSubdomain ? multihostCookiePathStrategy : cookiePathStrategy; ExternalServiceExposureStrategy actualExposureStrategy = requireSubdomain ? multiHostExternalServiceExposureStrategy : externalServiceExposureStrategy; // JwtProxySecureServerExposer creates no service for the exposed secure servers and // assumes everything will be proxied from localhost, because JWT proxy is collocated // with the workspace pod (because it is added to the environment as an injectable pod). // This method historically supported proxying secure servers exposed through a service // (which is not secure in absence of a appropriate network policy). The support for // accessing the backend server through a service was kept here because it doesn't add // any additional complexity to this method and keeps the door open for the // JwtProxySecureServerExposer to be enhanced in the future with support for service-handled // secure servers. backendServiceName = backendServiceName == null ? "127.0.0.1" : backendServiceName; proxyConfigBuilder.addVerifierProxy( listenPort, "http://" + backendServiceName + ":" + backendServicePort.getTargetPort().getIntVal(), excludes, cookiesAuthEnabled == null ? false : cookiesAuthEnabled, actualCookiePathStrategy.get(serviceName, exposedPort), actualExposureStrategy.getExternalPath(serviceName, exposedPort.getName())); k8sEnv .getConfigMaps() .get(getConfigMapName()) .getData() .put(JWT_PROXY_CONFIG_FILE, proxyConfigBuilder.build()); return exposedPort; } /** Returns service name that exposed JWTProxy Pod. */ public String getServiceName() { return serviceName; } /** Returns config map name that will be mounted into JWTProxy Pod. */ @VisibleForTesting String getConfigMapName() { return "jwtproxy-config"; } private void ensureJwtProxyInjected(KubernetesEnvironment k8sEnv, String machineName, PodData pod) throws InfrastructureException { if (!k8sEnv.getMachines().containsKey(JWT_PROXY_MACHINE_NAME)) { k8sEnv.getMachines().put(JWT_PROXY_MACHINE_NAME, createJwtProxyMachine()); Pod jwtProxyPod = createJwtProxyPod(); k8sEnv.addInjectablePod(machineName, JWT_PROXY_MACHINE_NAME, jwtProxyPod); Map initConfigMapData = new HashMap<>(); initConfigMapData.put( JWT_PROXY_PUBLIC_KEY_FILE, PUBLIC_KEY_HEADER + java.util.Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()) + PUBLIC_KEY_FOOTER); initConfigMapData.put(JWT_PROXY_CONFIG_FILE, proxyConfigBuilder.build()); ConfigMap jwtProxyConfigMap = new ConfigMapBuilder() .withNewMetadata() .withName(getConfigMapName()) .endMetadata() .withData(initConfigMapData) .build(); k8sEnv.getConfigMaps().put(jwtProxyConfigMap.getMetadata().getName(), jwtProxyConfigMap); Service jwtProxyService = new ServerServiceBuilder() .withName(serviceName) // we're merely injecting the pod, so we need a selector that is going to hit the // pod that runs the server that we're exposing .withSelectorEntry(CHE_ORIGINAL_NAME_LABEL, pod.getMetadata().getName()) .withMachineName(JWT_PROXY_MACHINE_NAME) .withPorts(emptyList()) .build(); k8sEnv.getServices().put(jwtProxyService.getMetadata().getName(), jwtProxyService); } } private InternalMachineConfig createJwtProxyMachine() { return new InternalMachineConfig(emptyMap(), emptyMap(), attributes, null); } private Pod createJwtProxyPod() { String containerName = Names.generateName("che-jwtproxy"); return new PodBuilder() .withNewMetadata() .withName(JWT_PROXY_POD_NAME) .withAnnotations(Names.createMachineNameAnnotations(containerName, JWT_PROXY_MACHINE_NAME)) .endMetadata() .withNewSpec() .withContainers( new ContainerBuilder() .withName(containerName) .withImage(jwtProxyImage) .withVolumeMounts( new VolumeMount( JWT_PROXY_CONFIG_FOLDER + "/", null, "che-jwtproxy-config-volume", false, null, null, null)) .withArgs("-config", JWT_PROXY_CONFIG_FOLDER + "/" + JWT_PROXY_CONFIG_FILE) .addNewEnv() .withName("XDG_CONFIG_HOME") .withValue(JWT_PROXY_CONFIG_FOLDER) .endEnv() .build()) .withVolumes( new VolumeBuilder() .withName("che-jwtproxy-config-volume") .withNewConfigMap() .withName(getConfigMapName()) .endConfigMap() .build()) .endSpec() .build(); } protected static final class ExposureConfiguration { private final List excludedPaths; private final Boolean cookiesAuthEnabled; public ExposureConfiguration(ServerConfig serverConfig) { this(serverConfig.getUnsecuredPaths(), serverConfig.isCookiesAuthEnabled()); } public ExposureConfiguration(List excludedPaths, Boolean cookiesAuthEnabled) { this.excludedPaths = excludedPaths; this.cookiesAuthEnabled = cookiesAuthEnabled; } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/CookiePathStrategy.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import static java.lang.String.format; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.DefaultHostExternalServiceExposureStrategy.DEFAULT_HOST_STRATEGY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.function.BiFunction; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ServiceExposureStrategyProvider; /** * The cookie path for the access token cookie is server-strategy dependent. This class represents * the different strategies for getting the cookie path. * *

Note that instead of going with full-blown strategy pattern and different implementations of * some interface and a provider for the currently active strategy (as is done for example with * {@link * org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy}), * this class merely internally uses different functions for different service exposure strategies. * This is done because the full-blown strategy pattern implementation felt like over-engineering * when compared with the simplicity of the functions. */ @Singleton public class CookiePathStrategy { private final BiFunction getCookiePath; @Inject public CookiePathStrategy( @Named(ServiceExposureStrategyProvider.STRATEGY_PROPERTY) String serverStrategy) { switch (serverStrategy) { case MULTI_HOST_STRATEGY: getCookiePath = (__, ___) -> "/"; break; case SINGLE_HOST_STRATEGY: case DEFAULT_HOST_STRATEGY: getCookiePath = (serviceName, __) -> serviceName; break; default: throw new IllegalArgumentException( format("Unsupported server strategy: %s", serverStrategy)); } } public String get(String serviceName, ServicePort port) { return getCookiePath.apply(serviceName, port); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyConfigBuilder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.JWT_PROXY_CONFIG_FOLDER; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.JWT_PROXY_PUBLIC_KEY_FILE; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.common.collect.ImmutableMap; import com.google.inject.assistedinject.Assisted; import jakarta.ws.rs.core.UriBuilder; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model.Config; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model.JWTProxy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model.RegistrableComponentConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model.SignerProxyConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model.VerifierConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model.VerifierProxyConfig; /** * Helps to build JWTProxy config with several verifier proxies. * * @author Sergii Leshchenko */ public class JwtProxyConfigBuilder { private static final ObjectMapper YAML_PARSER = new ObjectMapper(new YAMLFactory()) .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); private final String workspaceId; private final URI authPageUrl; private final String issuer; private final String ttl; private final List verifierProxies = new ArrayList<>(); @Inject public JwtProxyConfigBuilder( @Named("che.api") URI apiEndpoint, @Named("che.server.secure_exposer.jwtproxy.token.issuer") String issuer, @Named("che.server.secure_exposer.jwtproxy.token.ttl") String ttl, @Nullable @Named("che.server.secure_exposer.jwtproxy.auth.loader.path") String loaderPath, @Assisted String workspaceId) { this.workspaceId = workspaceId; this.authPageUrl = isNullOrEmpty(loaderPath) ? null : UriBuilder.fromUri(apiEndpoint).replacePath(loaderPath).build(); this.issuer = issuer; this.ttl = ttl; } /** * Adds a proxy before a service that will perform the JWT authentication on its behalf. * * @param listenPort the port to listen on * @param upstream the URL to the backend service this proxy should be put in front of * @param excludes the list of unsecured paths that the proxy should let pass through * @param cookiesAuthEnabled should the JWT proxy use cookies? * @param cookiePath the path of the cookie. This is should either be "/" or some portion of the * URL the JWT proxy will be exposed on. It is used to enable using different proxies for * different services, each with a different auth cookie. Super useful for having multiple * workspaces, each authenticated with its machine token. * @param publicBasePath the prefix used to generate the redirect back to the original page from * the auth page by the JWT proxy. This is to work across path-rewriting proxies like nginx * ingress controller. */ public void addVerifierProxy( Integer listenPort, String upstream, Set excludes, boolean cookiesAuthEnabled, String cookiePath, String publicBasePath) { verifierProxies.add( new VerifierProxy( listenPort, upstream, excludes, cookiesAuthEnabled, cookiePath, publicBasePath)); } public String build() throws InternalInfrastructureException { List proxyConfigs = new ArrayList<>(); Config config = new Config() .withJWTProxy( new JWTProxy() .withSignerProxy(new SignerProxyConfig().withEnabled(false)) .withVerifiedProxyConfigs(proxyConfigs)); for (VerifierProxy verifierProxy : verifierProxies) { VerifierConfig verifierConfig = new VerifierConfig() .withAudience(workspaceId) .withUpstream(verifierProxy.upstream) .withMaxSkew("1m") .withMaxTtl(ttl) .withKeyServer( new RegistrableComponentConfig() .withType("preshared") .withOptions( ImmutableMap.of( "issuer", issuer, "key_id", workspaceId, "public_key_path", JWT_PROXY_CONFIG_FOLDER + '/' + JWT_PROXY_PUBLIC_KEY_FILE))) .withCookiesEnabled(verifierProxy.cookiesAuthEnabled) .withCookiePath(ensureStartsWithSlash(verifierProxy.cookiePath)) .withClaimsVerifier( Collections.singleton( new RegistrableComponentConfig() .withType("static") .withOptions(ImmutableMap.of("iss", issuer)))) .withNonceStorage(new RegistrableComponentConfig().withType("void")); if (!verifierProxy.excludes.isEmpty()) { verifierConfig.setExcludes(verifierProxy.excludes); } if (verifierProxy.cookiesAuthEnabled && authPageUrl != null) { verifierConfig.setAuthUrl(authPageUrl.toString()); } if (verifierProxy.publicBasePath != null) { verifierConfig.setPublicBasePath(verifierProxy.publicBasePath); } VerifierProxyConfig proxyConfig = new VerifierProxyConfig() .withListenAddr(":" + verifierProxy.listenPort) .withVerifierConfig(verifierConfig); proxyConfigs.add(proxyConfig); } try { return YAML_PARSER.writeValueAsString(config); } catch (JsonProcessingException e) { throw new InternalInfrastructureException( "Error during creation of JWTProxy config YAML: " + e.getMessage(), e); } } private static final class VerifierProxy { final Integer listenPort; final String upstream; final Set excludes; final boolean cookiesAuthEnabled; final String cookiePath; final String publicBasePath; VerifierProxy( Integer listenPort, String upstream, Set excludes, boolean cookiesAuthEnabled, String cookiePath, String publicBasePath) { this.listenPort = listenPort; this.upstream = upstream; this.excludes = excludes; this.cookiesAuthEnabled = cookiesAuthEnabled; this.cookiePath = cookiePath; this.publicBasePath = publicBasePath; } } private static String ensureStartsWithSlash(String val) { if (isNullOrEmpty(val)) { return "/"; } else { return val.charAt(0) == '/' ? val : "/" + val; } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyProvisioner.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import com.google.inject.assistedinject.Assisted; import java.security.KeyPair; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManagerException; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ServiceExposureStrategyProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory.JwtProxyConfigBuilderFactory; /** * Modifies Kubernetes environment to expose the specified service port via JWTProxy. * *

Exposing includes the following operation: * *

    *
  • Putting Machine configuration into Kubernetes environment if absent; *
  • Putting JwtProxy pod with one container if absent; *
  • Putting JwtProxy service that will expose added JWTProxy pod if absent; *
  • Putting JwtProxy ConfigMap that contains public key and JwtProxy config in yaml format if * absent; *
  • Updating JwtProxy Service to expose port for secure server; *
  • Updating JwtProxy configuration in config map by adding the corresponding verifier proxy * there; *
* * @see JwtProxyConfigBuilder * @see SignatureKeyManager * @author Sergii Leshchenko */ public class JwtProxyProvisioner extends AbstractJwtProxyProvisioner { @Inject public JwtProxyProvisioner( SignatureKeyManager signatureKeyManager, JwtProxyConfigBuilderFactory jwtProxyConfigBuilderFactory, ServiceExposureStrategyProvider serviceExposureStrategyProvider, CookiePathStrategy cookiePathStrategy, MultiHostCookiePathStrategy multiHostCookiePathStrategy, @Named("che.server.secure_exposer.jwtproxy.image") String jwtProxyImage, @Named("che.server.secure_exposer.jwtproxy.memory_request") String memoryRequestBytes, @Named("che.server.secure_exposer.jwtproxy.memory_limit") String memoryLimitBytes, @Named("che.server.secure_exposer.jwtproxy.cpu_request") String cpuRequestCores, @Named("che.server.secure_exposer.jwtproxy.cpu_limit") String cpuLimitCores, @Assisted RuntimeIdentity identity) throws InternalInfrastructureException { super( constructKeyPair(signatureKeyManager, identity), jwtProxyConfigBuilderFactory, serviceExposureStrategyProvider.get(), serviceExposureStrategyProvider.getMultiHostStrategy(), cookiePathStrategy, multiHostCookiePathStrategy, jwtProxyImage, memoryRequestBytes, memoryLimitBytes, cpuRequestCores, cpuLimitCores, identity.getWorkspaceId(), true); } private static KeyPair constructKeyPair( SignatureKeyManager signatureKeyManager, RuntimeIdentity identity) throws InternalInfrastructureException { try { return signatureKeyManager.getOrCreateKeyPair(identity.getWorkspaceId()); } catch (SignatureKeyManagerException e) { throw new InternalInfrastructureException( "Signature key pair for machine authentication cannot be retrieved. Reason: " + e.getMessage()); } } @Override protected ExposureConfiguration getExposureConfiguration(ServerConfig serverConfig) { return new ExposureConfiguration(serverConfig); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxySecureServerExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import com.google.common.annotations.VisibleForTesting; import com.google.inject.assistedinject.Assisted; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.DefaultSecureServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory.JwtProxyProvisionerFactory; /** * Exposes secure servers with JWTProxy. * *

To expose secure servers it provisions JwtProxy objects into environment with {@link * JwtProxyProvisioner}. Then JwtProxy service port is made public accessible by {@link * ExternalServerExposer }. * *

In this way, requests to exposed secure servers will be routed via JwtProxy pod that is added * one per workspace. And it will be impossible to requests secure servers if there is no machine * token in request. * * @see JwtProxyProvisioner * @author Sergii Leshchenko */ public class JwtProxySecureServerExposer extends DefaultSecureServerExposer { @VisibleForTesting JwtProxySecureServerExposer( JwtProxyProvisioner jwtProxyProvisioner, ExternalServerExposer exposer) { super(jwtProxyProvisioner, exposer); } @Inject public JwtProxySecureServerExposer( @Assisted RuntimeIdentity identity, JwtProxyProvisionerFactory jwtProxyProvisionerFactory, ExternalServerExposerProvider exposer) { super(identity, jwtProxyProvisionerFactory, exposer); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/MultiHostCookiePathStrategy.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import javax.inject.Singleton; /** * A specialization of the {@link CookiePathStrategy} for multi-host server strategy. We need this * declared specifically to be able to use both the configured strategy and multi-host in case of * workspaces with mixed endpoints. */ @Singleton public class MultiHostCookiePathStrategy extends CookiePathStrategy { public MultiHostCookiePathStrategy() { super(MULTI_HOST_STRATEGY); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/PassThroughProxyProvisioner.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import static java.util.Collections.singletonList; import com.google.inject.assistedinject.Assisted; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ServiceExposureStrategyProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory.JwtProxyConfigBuilderFactory; /** * A Jwt Proxy provisioner that configures JWT proxy to just pass through all the traffic without * any authentication. Should be used in the single user mode only for "securing" the secure * servers. */ public class PassThroughProxyProvisioner extends AbstractJwtProxyProvisioner { @Inject public PassThroughProxyProvisioner( JwtProxyConfigBuilderFactory jwtProxyConfigBuilderFactory, ServiceExposureStrategyProvider serviceExposureStrategyProvider, CookiePathStrategy cookiePathStrategy, MultiHostCookiePathStrategy multiHostCookiePathStrategy, @Named("che.server.secure_exposer.jwtproxy.image") String jwtImage, @Named("che.server.secure_exposer.jwtproxy.memory_request") String memoryRequestBytes, @Named("che.server.secure_exposer.jwtproxy.memory_limit") String memoryLimitBytes, @Named("che.server.secure_exposer.jwtproxy.cpu_request") String cpuRequestCores, @Named("che.server.secure_exposer.jwtproxy.cpu_limit") String cpuLimitCores, @Assisted RuntimeIdentity identity) throws InternalInfrastructureException { super( constructSignatureKeyPair(), jwtProxyConfigBuilderFactory, serviceExposureStrategyProvider.get(), serviceExposureStrategyProvider.getMultiHostStrategy(), cookiePathStrategy, multiHostCookiePathStrategy, jwtImage, memoryRequestBytes, memoryLimitBytes, cpuRequestCores, cpuLimitCores, identity.getWorkspaceId(), false); } /** * Constructs a key pair to satisfy JWT proxy which needs a key pair in its configuration. In case * of pass-through proxy, this key pair is unused so we just generate a random one. * * @return a random key pair * @throws InternalInfrastructureException if RSA is not available as a key pair generator. This * should not happen. */ private static KeyPair constructSignatureKeyPair() throws InternalInfrastructureException { try { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(512); return kpg.generateKeyPair(); } catch (NoSuchAlgorithmException e) { throw new InternalInfrastructureException( "Could not generate a fake key pair to support JWT proxy in single-user mode."); } } @Override protected ExposureConfiguration getExposureConfiguration(ServerConfig serverConfig) { // exclude everything on each server from JWT proxy auth, making it effectively a passthrough // proxy return new ExposureConfiguration(singletonList("/"), false); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/PassThroughProxySecureServerExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import com.google.common.annotations.VisibleForTesting; import com.google.inject.assistedinject.Assisted; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.DefaultSecureServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory.PassThroughProxyProvisionerFactory; /** * Exposes secure servers with JWTProxy. * *

To expose secure servers it provisions JwtProxy objects into environment with {@link * PassThroughProxyProvisioner}. Then JwtProxy service port is made public accessible by {@link * ExternalServerExposer }. * *

In this way, requests to exposed secure servers will be routed via JwtProxy pod that is added * one per workspace. * * @see PassThroughProxyProvisioner */ public class PassThroughProxySecureServerExposer extends DefaultSecureServerExposer { @VisibleForTesting PassThroughProxySecureServerExposer( PassThroughProxyProvisioner passThroughProxyProvisioner, ExternalServerExposer exposer) { super(passThroughProxyProvisioner, exposer); } @Inject public PassThroughProxySecureServerExposer( @Assisted RuntimeIdentity identity, PassThroughProxyProvisionerFactory jwtProxyProvisionerFactory, ExternalServerExposerProvider exposer) { super(identity, jwtProxyProvisionerFactory, exposer); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/factory/JwtProxyConfigBuilderFactory.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyConfigBuilder; public interface JwtProxyConfigBuilderFactory { JwtProxyConfigBuilder create(String workspaceId); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/factory/JwtProxyProvisionerFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.ProxyProvisionerFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner; /** * Helps to create {@link JwtProxyProvisioner} with fields injected from DI container. * * @author Sergii Leshchenko */ public interface JwtProxyProvisionerFactory extends ProxyProvisionerFactory { JwtProxyProvisioner create(RuntimeIdentity runtimeId); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/factory/JwtProxySecureServerExposerFactory.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxySecureServerExposer; /** * Helps to create {@link JwtProxySecureServerExposerFactory} with fields injected from DI * container. * * @author Sergii Leshchenko */ public interface JwtProxySecureServerExposerFactory extends SecureServerExposerFactory { @Override JwtProxySecureServerExposer create(RuntimeIdentity identity); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/factory/PassThroughProxyProvisionerFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.ProxyProvisionerFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.PassThroughProxyProvisioner; /** Helps to create {@link PassThroughProxyProvisioner} with fields injected from DI container. */ public interface PassThroughProxyProvisionerFactory extends ProxyProvisionerFactory { @Override PassThroughProxyProvisioner create(RuntimeIdentity runtimeId); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/factory/PassThroughProxySecureServerExposerFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.PassThroughProxySecureServerExposer; /** * Helps to create {@link PassThroughProxySecureServerExposer} with fields injected from DI * container. */ public interface PassThroughProxySecureServerExposerFactory extends SecureServerExposerFactory { @Override PassThroughProxySecureServerExposer create(RuntimeIdentity identity); } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/model/Config.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model; import com.fasterxml.jackson.annotation.JsonProperty; /** * Describes parent node of jwtproxy config. * * @author Mykhailo Kuznietsov */ public class Config { @JsonProperty("jwtproxy") private JWTProxy jwtProxy; public JWTProxy getJwtProxy() { return jwtProxy; } public void setJwtProxy(JWTProxy jwtProxy) { this.jwtProxy = jwtProxy; } public Config withJWTProxy(JWTProxy jwtProxy) { this.jwtProxy = jwtProxy; return this; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/model/JWTProxy.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; /** * Describes single signer and multiple verifier proxy configs. * * @author Mykhailo Kuznietsov */ public class JWTProxy { @JsonProperty("verifier_proxies") private List verifiedProxyConfigs; @JsonProperty("signer_proxy") private SignerProxyConfig signerProxy; public List getVerifiedProxyConfigs() { return verifiedProxyConfigs; } public void setVerifiedProxyConfigs(List verifiedProxyConfigs) { this.verifiedProxyConfigs = verifiedProxyConfigs; } public JWTProxy withVerifiedProxyConfigs(List verifiedProxyConfigs) { this.verifiedProxyConfigs = verifiedProxyConfigs; return this; } public SignerProxyConfig getSignerProxy() { return signerProxy; } public void setSignerProxy(SignerProxyConfig signerProxy) { this.signerProxy = signerProxy; } public JWTProxy withSignerProxy(SignerProxyConfig signerProxy) { this.signerProxy = signerProxy; return this; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/model/RegistrableComponentConfig.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import java.util.Map; /** * Describes jwtproxy standard registrable component (type + options map). * * @author Mykhailo Kuznietsov */ @JsonInclude(Include.NON_NULL) public class RegistrableComponentConfig { private String type; private Map options; public String getType() { return type; } public void setType(String type) { this.type = type; } public RegistrableComponentConfig withType(String type) { this.type = type; return this; } public Map getOptions() { return options; } public void setOptions(Map options) { this.options = options; } public RegistrableComponentConfig withOptions(Map options) { this.options = options; return this; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/model/SignerProxyConfig.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model; /** * Describes signer proxy configuration * * @author Mykhailo Kuznietsov */ public class SignerProxyConfig { private boolean enabled; public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public SignerProxyConfig withEnabled(boolean enabled) { this.enabled = enabled; return this; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/model/VerifierConfig.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Set; @JsonInclude(Include.NON_NULL) /** * Describes JWT verifier configuration * * @author Mykhailo Kuznietsov */ public class VerifierConfig { private String upstream; private String audience; @JsonProperty("max_skew") private String maxSkew; @JsonProperty("max_ttl") private String maxTtl; @JsonProperty("nonce_storage") private RegistrableComponentConfig nonceStorage; @JsonProperty("key_server") private RegistrableComponentConfig keyServer; @JsonProperty("claims_verifiers") private Set claimsVerifiers; @JsonProperty("auth_redirect_url") private String authUrl; @JsonProperty("auth_cookies_enabled") private boolean cookiesEnabled; private Set excludes; @JsonProperty("public_base_path") private String publicBasePath; @JsonProperty("cookie_path") private String cookiePath; public String getAudience() { return audience; } public void setAudience(String audience) { this.audience = audience; } public VerifierConfig withAudience(String audience) { this.audience = audience; return this; } public String getMaxSkew() { return maxSkew; } public void setMaxSkew(String maxSkew) { this.maxSkew = maxSkew; } public VerifierConfig withMaxSkew(String maxSkew) { this.maxSkew = maxSkew; return this; } public String getMaxTtl() { return maxTtl; } public void setMaxTtl(String maxTtl) { this.maxTtl = maxTtl; } public VerifierConfig withMaxTtl(String maxTtl) { this.maxTtl = maxTtl; return this; } public String getUpstream() { return upstream; } public void setUpstream(String upstream) { this.upstream = upstream; } public VerifierConfig withUpstream(String upstream) { this.upstream = upstream; return this; } public RegistrableComponentConfig getNonceStorage() { return nonceStorage; } public void setNonceStorage(RegistrableComponentConfig nonceStorage) { this.nonceStorage = nonceStorage; } public VerifierConfig withNonceStorage(RegistrableComponentConfig nonceStorage) { this.nonceStorage = nonceStorage; return this; } public RegistrableComponentConfig getKeyServer() { return keyServer; } public void setKeyServer(RegistrableComponentConfig keyServer) { this.keyServer = keyServer; } public VerifierConfig withKeyServer(RegistrableComponentConfig keyServer) { this.keyServer = keyServer; return this; } public Set getClaimsVerifiers() { return claimsVerifiers; } public void setClaimsVerifiers(Set claimsVerifiers) { this.claimsVerifiers = claimsVerifiers; } public VerifierConfig withClaimsVerifier(Set claimsVerifiers) { this.claimsVerifiers = claimsVerifiers; return this; } public Set getExcludes() { return excludes; } public void setExcludes(Set excludes) { this.excludes = excludes; } public VerifierConfig withExcludes(Set excludes) { this.excludes = excludes; return this; } public String getAuthUrl() { return authUrl; } public void setAuthUrl(String authUrl) { this.authUrl = authUrl; } public VerifierConfig withAuthUrl(String authUrl) { this.authUrl = authUrl; return this; } public boolean getCookiesEnabled() { return cookiesEnabled; } public VerifierConfig setCookiesEnabled(boolean cookiesEnabled) { this.cookiesEnabled = cookiesEnabled; return this; } public VerifierConfig withCookiesEnabled(boolean cookiesEnabled) { this.cookiesEnabled = cookiesEnabled; return this; } public String getPublicBasePath() { return publicBasePath; } public void setPublicBasePath(String publicBasePath) { this.publicBasePath = publicBasePath; } public VerifierConfig withPublicBasePath(String publicBasePath) { this.publicBasePath = publicBasePath; return this; } public String getCookiePath() { return cookiePath; } public void setCookiePath(String cookiePath) { this.cookiePath = cookiePath; } public VerifierConfig withCookiePath(String cookiePath) { this.cookiePath = cookiePath; return this; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/model/VerifierProxyConfig.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.model; import com.fasterxml.jackson.annotation.JsonProperty; /** * Describes configuration of the verifier proxy. * * @author Mykhailo Kuznietsov */ public class VerifierProxyConfig { @JsonProperty("listen_addr") private String listenAddr; @JsonProperty("verifier") private VerifierConfig verifierConfig; public String getListenAddr() { return listenAddr; } public void setListenAddr(String listenAddr) { this.listenAddr = listenAddr; } public VerifierProxyConfig withListenAddr(String listenAddr) { this.listenAddr = listenAddr; return this; } public VerifierConfig getVerifierConfig() { return verifierConfig; } public void setVerifierConfig(VerifierConfig verifierConfig) { this.verifierConfig = verifierConfig; } public VerifierProxyConfig withVerifierConfig(VerifierConfig verifierConfig) { this.verifierConfig = verifierConfig; return this; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/Containers.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.Quantity; import io.fabric8.kubernetes.api.model.ResourceRequirements; import io.fabric8.kubernetes.api.model.ResourceRequirementsBuilder; /** * Helps to simplify the interaction with the {@link Container}. * * @author Anton Korneta */ public class Containers { private Containers() {} /** * Returns the RAM limit in bytes, if it is present in given container otherwise 0 will be * returned. */ public static long getRamLimit(Container container) { final ResourceRequirements resources = container.getResources(); final Quantity quantity; if (resources != null && resources.getLimits() != null && (quantity = resources.getLimits().get("memory")) != null && quantity.getAmount() != null) { return Quantity.getAmountInBytes(quantity).longValue(); } return 0; } /** * Sets given RAM limit in bytes to specified container. Note if the container already contains a * RAM limit, it will be overridden, other resources won't be affected. */ public static void addRamLimit(Container container, long ramLimit) { final ResourceRequirementsBuilder resourceBuilder; if (container.getResources() != null) { resourceBuilder = new ResourceRequirementsBuilder(container.getResources()); } else { resourceBuilder = new ResourceRequirementsBuilder(); } container.setResources( resourceBuilder.addToLimits("memory", new Quantity(String.valueOf(ramLimit))).build()); } /** * Sets given RAM limit in kubernetes notion to specified container. Note if the container already * contains a RAM limit, it will be overridden, other resources won't be affected. */ public static void addRamLimit(Container container, String limitInK8sNotion) { final ResourceRequirementsBuilder resourceBuilder; if (container.getResources() != null) { resourceBuilder = new ResourceRequirementsBuilder(container.getResources()); } else { resourceBuilder = new ResourceRequirementsBuilder(); } container.setResources( resourceBuilder.addToLimits("memory", new Quantity(limitInK8sNotion)).build()); } /** * Returns the RAM request in bytes, if it is present in given container otherwise 0 will be * returned. */ public static long getRamRequest(Container container) { final ResourceRequirements resources = container.getResources(); final Quantity quantity; if (resources != null && resources.getRequests() != null && (quantity = resources.getRequests().get("memory")) != null && quantity.getAmount() != null) { return Quantity.getAmountInBytes(quantity).longValue(); } return 0; } /** * Sets given RAM request in bytes to specified container. Note if the container already contains * a RAM limit, it will be overridden, other resources won't be affected. */ public static void addRamRequest(Container container, long ramRequest) { final ResourceRequirementsBuilder resourceBuilder; if (container.getResources() != null) { resourceBuilder = new ResourceRequirementsBuilder(container.getResources()); } else { resourceBuilder = new ResourceRequirementsBuilder(); } container.setResources( resourceBuilder.addToRequests("memory", new Quantity(String.valueOf(ramRequest))).build()); } /** * Sets given RAM request in kubernetes notion to specified container. Note if the container * already contains a RAM request, it will be overridden, other resources won't be affected. */ public static void addRamRequest(Container container, String limitInK8sNotion) { final ResourceRequirementsBuilder resourceBuilder; if (container.getResources() != null) { resourceBuilder = new ResourceRequirementsBuilder(container.getResources()); } else { resourceBuilder = new ResourceRequirementsBuilder(); } container.setResources( resourceBuilder.addToRequests("memory", new Quantity(limitInK8sNotion)).build()); } /** * Returns the CPU limit in cores, if it is present in given container otherwise 0 will be * returned. */ public static float getCpuLimit(Container container) { final ResourceRequirements resources = container.getResources(); final Quantity quantity; if (resources != null && resources.getLimits() != null && (quantity = resources.getLimits().get("cpu")) != null && quantity.getAmount() != null) { return KubernetesSize.toCores(quantity); } return 0; } /** * Sets given CPU limit in cores to specified container. Note if the container already contains a * CPU limit, it will be overridden, other resources won't be affected. */ public static void addCpuLimit(Container container, float cpuLimit) { final ResourceRequirementsBuilder resourceBuilder; if (container.getResources() != null) { resourceBuilder = new ResourceRequirementsBuilder(container.getResources()); } else { resourceBuilder = new ResourceRequirementsBuilder(); } container.setResources( resourceBuilder.addToLimits("cpu", new Quantity(Float.toString(cpuLimit))).build()); } /** * Sets given CPU limit in kubernetes notion to specified container. Note if the container already * contains a CPU limit, it will be overridden, other resources won't be affected. */ public static void addCpuLimit(Container container, String limitInK8sNotion) { final ResourceRequirementsBuilder resourceBuilder; if (container.getResources() != null) { resourceBuilder = new ResourceRequirementsBuilder(container.getResources()); } else { resourceBuilder = new ResourceRequirementsBuilder(); } container.setResources( resourceBuilder.addToLimits("cpu", new Quantity(limitInK8sNotion)).build()); } /** * Returns the CPU request in bytes, if it is present in given container otherwise 0 will be * returned. */ public static float getCpuRequest(Container container) { final ResourceRequirements resources = container.getResources(); final Quantity quantity; if (resources != null && resources.getRequests() != null && (quantity = resources.getRequests().get("cpu")) != null && quantity.getAmount() != null) { return KubernetesSize.toCores(quantity); } return 0; } /** * Sets given CPU request in bytes to specified container. Note if the container already contains * a CPU limit, it will be overridden, other resources won't be affected. */ public static void addCpuRequest(Container container, float cpuRequest) { final ResourceRequirementsBuilder resourceBuilder; if (container.getResources() != null) { resourceBuilder = new ResourceRequirementsBuilder(container.getResources()); } else { resourceBuilder = new ResourceRequirementsBuilder(); } container.setResources( resourceBuilder.addToRequests("cpu", new Quantity(Float.toString(cpuRequest))).build()); } /** * Sets given CPU request in kubernetes notion to specified container. Note if the container * already contains a CPU request, it will be overridden, other resources won't be affected. */ public static void addCpuRequest(Container container, String limitInK8sNotion) { final ResourceRequirementsBuilder resourceBuilder; if (container.getResources() != null) { resourceBuilder = new ResourceRequirementsBuilder(container.getResources()); } else { resourceBuilder = new ResourceRequirementsBuilder(); } container.setResources( resourceBuilder.addToRequests("cpu", new Quantity(limitInK8sNotion)).build()); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/EnvVars.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.EnvVar; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; import org.eclipse.che.api.core.model.workspace.devfile.Env; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; /** Utility class for dealing with environment variables */ public class EnvVars { private static final Pattern REFERENCE_PATTERN = Pattern.compile("\\$\\(\\w+\\)"); /** * Applies the specified env vars list to the specified pod data(containers and init containers). * *

If a container does not have the corresponding env - it will be provisioned, if it has - the * value will be overridden. * * @param podData pod to supply env vars * @param env env vars to apply */ public void apply(PodData podData, List env) { Stream.concat( podData.getSpec().getInitContainers().stream(), podData.getSpec().getContainers().stream()) .forEach(c -> apply(c, env)); } /** * Applies the specified env vars list to the specified containers. * *

If a container does not have the corresponding env - it will be provisioned, if it has - the * value will be overridden. * * @param container pod to supply env vars * @param toApply env vars to apply */ public void apply(Container container, List toApply) { List targetEnv = container.getEnv(); if (targetEnv == null) { targetEnv = new ArrayList<>(); container.setEnv(targetEnv); } for (Env env : toApply) { apply(targetEnv, env); } } private void apply(List targetEnv, Env env) { Optional existingOpt = targetEnv.stream().filter(e -> e.getName().equals(env.getName())).findAny(); if (existingOpt.isPresent()) { EnvVar envVar = existingOpt.get(); envVar.setValue(env.getValue()); envVar.setValueFrom(null); } else { targetEnv.add(new EnvVar(env.getName(), env.getValue(), null)); } } /** * Looks at the value of the provided environment variable and returns a set of environment * variable references in the Kubernetes convention of {@literal $(VAR_NAME)}. * *

See API * docs and/or documentation. * * @param var the environment variable to analyze * @return a set of variable references, never null */ public static Set extractReferencedVariables(EnvVar var) { String val = var.getValue(); if (val == null) { return Collections.emptySet(); } Matcher matcher = REFERENCE_PATTERN.matcher(val); // let's just keep the initial size small, because usually there are not that many references // present. Set ret = new HashSet<>(2); while (matcher.find()) { int start = matcher.start(); // the variable reference can be escaped using a double $, e.g. $$(VAR) is not a reference if (start > 0 && val.charAt(start - 1) == '$') { continue; } // extract the variable name out of the reference $(NAME) -> NAME String refName = matcher.group().substring(2, matcher.group().length() - 1); ret.add(refName); } return ret; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/GatewayConfigmapLabels.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.isLabeled; import com.google.common.base.Splitter; import io.fabric8.kubernetes.api.model.ConfigMap; import java.util.Map; import java.util.Map.Entry; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.inject.ConfigurationException; /** Little utility bean helping with Gateway ConfigMaps labels. */ @Singleton public class GatewayConfigmapLabels { private final Map labels; @Inject public GatewayConfigmapLabels( @Named("che.infra.kubernetes.singlehost.gateway.configmap_labels") String labelsProperty) { if (isNullOrEmpty(labelsProperty)) { throw new ConfigurationException( "for gateway single-host, 'che.infra.kubernetes.singlehost.gateway.configmap_labels' property must be defined"); } try { this.labels = Splitter.on(",").trimResults().withKeyValueSeparator("=").split(labelsProperty); } catch (IllegalArgumentException iae) { throw new ConfigurationException( "'che.infra.kubernetes.singlehost.gateway.configmap_labels' is set to invalid value. It must be in format `name1=value1,name2=value2`. Check the documentation for further details.", iae); } } public Map getLabels() { return labels; } /** * Check whether configmap is gateway route configuration. That is defined with labels provided by * `che.infra.kubernetes.singlehost.gateway.configmap_labels` configuration property. * * @param configMap to check * @return `true` if ConfigMap is gateway route configuration, `false` otherwise */ public boolean isGatewayConfig(ConfigMap configMap) { for (Entry labelEntry : labels.entrySet()) { if (!isLabeled(configMap, labelEntry.getKey(), labelEntry.getValue())) { return false; } } return true; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/Ingresses.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPath; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPort; import java.util.Collection; import java.util.Optional; /** Util class that helps working with k8s Ingresses */ public class Ingresses { /** * In given {@code ingresses} finds {@link IngressRule} for given {@code service} and {@code * port}. * * @return found {@link IngressRule} or {@link Optional#empty()} */ public static Optional findIngressRuleForServicePort( Collection ingresses, Service service, int port) { Optional foundPort = Services.findPort(service, port); if (!foundPort.isPresent()) { return Optional.empty(); } for (Ingress ingress : ingresses) { for (IngressRule rule : ingress.getSpec().getRules()) { for (HTTPIngressPath path : rule.getHttp().getPaths()) { IngressBackend backend = path.getBackend(); if (backend.getService().getName().equals(service.getMetadata().getName()) && matchesServicePort(backend.getService().getPort(), foundPort.get())) { return Optional.of(rule); } } } } return Optional.empty(); } private static boolean matchesServicePort( ServiceBackendPort backendPort, ServicePort servicePort) { if (backendPort.getName() != null && backendPort.getName().equals(servicePort.getName())) { return true; } if (backendPort.getNumber() != null && backendPort.getNumber().equals(servicePort.getPort())) { return true; } return false; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/KubernetesSharedPool.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.commons.observability.ExecutorServiceWrapper; /** * Provides single {@link ExecutorService} instance with daemon threads for Kubernetes/Openshfit * infrastructures components. * * @author Anton Korneta */ @Singleton public class KubernetesSharedPool { private final ExecutorService executor; @Inject public KubernetesSharedPool(ExecutorServiceWrapper executorServiceWrapper) { final ThreadFactory factory = new ThreadFactoryBuilder() .setNameFormat("KubernetesMachineSharedPool-%d") .setDaemon(true) .build(); this.executor = executorServiceWrapper.wrap( Executors.newCachedThreadPool(factory), KubernetesSharedPool.class.getName()); } public ExecutorService getExecutor() { return executor; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/KubernetesSize.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import io.fabric8.kubernetes.api.model.Quantity; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Helps to convert bytes from/to Kubernetes format. * * @author Anton Korneta */ public class KubernetesSize { // si system multipliers private static final int K = 1000; private static final long M = K * K; private static final long G = M * K; private static final long T = G * K; private static final long P = T * K; private static final long E = P * K; // power of 2 multipliers private static final int KI = 1024; private static final long MI = KI * KI; private static final long GI = MI * KI; private static final long TI = GI * KI; private static final long PI = TI * KI; private static final long EI = PI * KI; private static final Pattern HUMAN_SIZE_MEMORY_PATTERN = Pattern.compile("^([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)\\s*(\\S+)?$"); private static final Pattern HUMAN_SIZE_CPU_PATTERN = Pattern.compile("^([-+]?[0-9]*\\.?[0-9]+)(m)?$"); /** * Converts memory in Kubernetes format to bytes. * *

Format: "< number >< modifier >"
* Where modifier is one of the following (case-insensitive): b, bi, k, ki, kib, m, mi, mib, g, * gi, gib, t, ti, tib, p, pi, pib, e, ei, eib * *

    * Conversion rules: *
  • b, bi conversion not needed *
  • k multiplied by 1000 *
  • ki, kib multiplied by 1024 *
  • m multiplied by 1048576 *
  • mi, mib multiplied by 1000000 *
  • g multiplied by 1073741824 *
  • gi, gib multiplied by 1000000000 *
  • t multiplied by 1,09951162778e+12 *
  • ti,tib multiplied by 1e+12 *
  • p multiplied by 1,12589990684e+15 *
  • pi, pib multiplied by 1e+15 *
  • e multiplied by 1,1529215046e+18 *
  • ei, eib multiplied by 1e+18 *
* * @throws IllegalArgumentException if specified string can not be parsed */ public static long toBytes(String sizeString) { final Matcher matcher; if ((matcher = HUMAN_SIZE_MEMORY_PATTERN.matcher(sizeString)).matches()) { final float size = Float.parseFloat(matcher.group(1)); final String suffix = matcher.group(3); if (suffix == null) { return (long) size; } switch (suffix.toLowerCase()) { case "b": case "bi": return (long) size; case "k": return (long) (size * K); case "ki": case "kib": return (long) (size * KI); case "m": return (long) (size * M); case "mi": case "mib": return (long) (size * MI); case "g": return (long) (size * G); case "gi": case "gib": return (long) (size * GI); case "t": return (long) (size * T); case "ti": case "tib": return (long) (size * TI); case "p": return (long) (size * P); case "pi": case "pib": return (long) (size * PI); case "e": return (long) (size * E); case "ei": case "eib": return (long) (size * EI); } } throw new IllegalArgumentException("Invalid Kubernetes size format provided: " + sizeString); } /** Converts memory from bytes into Kubernetes human readable format. */ public static String toKubeSize(long bytes, boolean si) { final int multiplier = si ? K : KI; if (bytes < multiplier) { return bytes + "B"; } final int e = (int) (Math.log(bytes) / Math.log(multiplier)); final String unit = (si ? "kMGTPE" : "KMGTPE").charAt(e - 1) + (si ? "" : "i"); final float size = bytes / (float) Math.pow(multiplier, e); return String.format((size % 1.0f == 0) ? "%.0f%s" : "%.1f%s", size, unit); } /** * Converts CPU resource in Kubernetes format to cores. * *

Format: "< number >< m>"
* *

    * Conversion rules: *
  • m divided by 1000 *
* * @throws IllegalArgumentException if specified string can not be parsed */ public static float toCores(String cpuString) { final Matcher matcher; if ((matcher = HUMAN_SIZE_CPU_PATTERN.matcher(cpuString)).matches()) { final float size = Float.parseFloat(matcher.group(1)); final String suffix = matcher.group(2); if (suffix == null) { return size; } if (suffix.toLowerCase().equals("m")) { return size / K; } } throw new IllegalArgumentException("Invalid Kubernetes CPU size format provided: " + cpuString); } /** * Converts CPU resource from {@link Quantity} object to cores. * *

see {@link KubernetesSize#toCores(String)} for conversion rules * * @param quantity to convert * @return value in cores */ public static float toCores(Quantity quantity) { return toCores(quantity.getAmount() + quantity.getFormat()); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/NonTlsDistributedClusterModeNotifier.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import jakarta.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.util.ApiInfoLogInformer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Prints warning into logs on startup when Che is running in non-TLS mode. */ @Singleton public class NonTlsDistributedClusterModeNotifier { private static final Logger LOG = LoggerFactory.getLogger(ApiInfoLogInformer.class); private final boolean isTlsEnabled; @Inject public NonTlsDistributedClusterModeNotifier( @Named("che.infra.kubernetes.tls_enabled") boolean isTlsEnabled) { this.isTlsEnabled = isTlsEnabled; } @PostConstruct public void printWarnOnStartup() { if (!isTlsEnabled) { LOG.warn( "Eclipse Che deployed on non-TLS mode. This may cause client-side problems opening workspaces on multi-cluster installations." + " See https://eclipse.org/che/docs/che-7/introduction-to-eclipse-che/#problems-opening-workspace-in-newest-chrome-versions-on-non-tls-installations-on-distributed-clusters for details."); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/PodEvents.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import com.google.common.base.Strings; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEvent; /** * Helps to simplify the interaction with the {@link PodEvent}. * * @author Ilya Buziuk */ public final class PodEvents { private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssX"; private PodEvents() {} /** * Converts the time of {@link PodEvent} e.g. '2018-05-15T16:17:54Z' to the {@link Date} format */ public static Date convertEventTimestampToDate(String timestamp) throws ParseException { if (Strings.isNullOrEmpty(timestamp)) { throw new IllegalArgumentException("Pod event timestamp can not be blank"); } return new SimpleDateFormat(DATE_FORMAT).parse(timestamp); } /** Converts the {@link Date} to {@link PodEvent} timestamp format e.g. '2018-05-15T16:17:54Z' */ public static String convertDateToEventTimestamp(Date date) { return new SimpleDateFormat(DATE_FORMAT).format(date); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/RuntimeEventsPublisher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.MachineStatus; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.server.DtoConverter; import org.eclipse.che.api.workspace.server.event.RuntimeAbnormalStoppedEvent; import org.eclipse.che.api.workspace.server.event.RuntimeAbnormalStoppingEvent; import org.eclipse.che.api.workspace.shared.dto.event.MachineStatusEvent; import org.eclipse.che.api.workspace.shared.dto.event.RuntimeLogEvent; import org.eclipse.che.api.workspace.shared.dto.event.ServerStatusEvent; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.event.WatchLogStartedEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.event.WatchLogStoppedEvent; /** * @author Anton Korneta */ @Singleton public class RuntimeEventsPublisher { private final EventService eventService; @Inject public RuntimeEventsPublisher(EventService eventService) { this.eventService = eventService; } public void sendStartingEvent(String machineName, RuntimeIdentity runtimeId) { eventService.publish( DtoFactory.newDto(MachineStatusEvent.class) .withIdentity(DtoConverter.asDto(runtimeId)) .withEventType(MachineStatus.STARTING) .withMachineName(machineName)); } public void sendRunningEvent(String machineName, RuntimeIdentity runtimeId) { eventService.publish( DtoFactory.newDto(MachineStatusEvent.class) .withIdentity(DtoConverter.asDto(runtimeId)) .withEventType(MachineStatus.RUNNING) .withMachineName(machineName)); } public void sendFailedEvent(String machineName, String message, RuntimeIdentity runtimeId) { eventService.publish( DtoFactory.newDto(MachineStatusEvent.class) .withIdentity(DtoConverter.asDto(runtimeId)) .withEventType(MachineStatus.FAILED) .withMachineName(machineName) .withError(message)); } public void sendServerStatusEvent( String machineName, String serverName, Server server, RuntimeIdentity runtimeId) { eventService.publish( DtoFactory.newDto(ServerStatusEvent.class) .withIdentity(DtoConverter.asDto(runtimeId)) .withMachineName(machineName) .withServerName(serverName) .withStatus(server.getStatus()) .withServerUrl(server.getUrl())); } public void sendServerRunningEvent( String machineName, String serverName, String serverUrl, RuntimeIdentity runtimeId) { eventService.publish( DtoFactory.newDto(ServerStatusEvent.class) .withIdentity(DtoConverter.asDto(runtimeId)) .withMachineName(machineName) .withServerName(serverName) .withStatus(ServerStatus.RUNNING) .withServerUrl(serverUrl)); } public void sendMachineLogEvent( String machineName, String text, String time, RuntimeIdentity runtimeId) { eventService.publish( DtoFactory.newDto(RuntimeLogEvent.class) .withMachineName(machineName) .withRuntimeId(DtoConverter.asDto(runtimeId)) .withText(text) .withTime(time)); } public void sendRuntimeLogEvent(String text, String time, RuntimeIdentity runtimeId) { eventService.publish( DtoFactory.newDto(RuntimeLogEvent.class) .withRuntimeId(DtoConverter.asDto(runtimeId)) .withText(text) .withTime(time)); } public void sendAbnormalStoppedEvent(RuntimeIdentity runtimeId, String reason) { eventService.publish(new RuntimeAbnormalStoppedEvent(runtimeId, reason)); } public void sendAbnormalStoppingEvent(RuntimeIdentity runtimeId, String reason) { eventService.publish(new RuntimeAbnormalStoppingEvent(runtimeId, reason)); } public void sendWatchLogStartedEvent(String container) { eventService.publish(new WatchLogStartedEvent(container)); } public void sendWatchLogStoppedEvent(String container) { eventService.publish(new WatchLogStoppedEvent(container)); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/Services.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.Collection; import java.util.Optional; /** Utility class to help work with {@link Service}s */ public class Services { /** * Try to find port in given service. * * @return {@link Optional} of found {@link ServicePort}, or {@link Optional#empty()} when not * found. */ public static Optional findPort(Service service, int port) { if (service == null || service.getSpec() == null || service.getSpec().getPorts() == null) { return Optional.empty(); } return service.getSpec().getPorts().stream() .filter(p -> p.getPort() != null && p.getPort() == port) .findFirst(); } /** * Go through all given services and finds one that has given port. * * @return {@link Optional} of found {@link Service}, or {@link Optional#empty()} when not found. */ public static Optional findServiceWithPort(Collection services, int port) { if (services == null) { return Optional.empty(); } return services.stream().filter(s -> Services.findPort(s, port).isPresent()).findFirst(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/TracingSpanConstants.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; /** * Constants used as names for spans when tracing kubernetes infra events. * * @author amisevsk */ public class TracingSpanConstants { /** Span name for the wait phase after objects are created until they are ready */ public static final String WAIT_MACHINES_START = "WaitMachinesStart"; /** Span name for async wait for pods to be running */ public static final String WAIT_RUNNING_ASYNC = "WaitRunningAsync"; /** Span name for async wait for servers to be running */ public static final String CHECK_SERVERS = "CheckServers"; /** Span name for wait for broker storage to be ready */ public static final String PREPARE_STORAGE_PHASE = "PrepareStorage"; /** Span name for wait for plugin broker to be deployed */ public static final String DEPLOY_BROKER_PHASE = "DeployBroker"; /** Span name for wait for plugin broker's results */ public static final String WAIT_BROKERS_RESULT_PHASE = "WaitBrokerResult"; } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/UnrecoverablePodEventListener.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import com.google.common.base.Strings; import java.util.Set; import java.util.function.Consumer; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEventHandler; /** * Listens Pod events and propagates unrecoverable events via the specified handler. * * @author Sergii Leshchenko * @author Ilya Buziuk */ public class UnrecoverablePodEventListener implements PodEventHandler { private final Set pods; private final Consumer unrecoverableEventHandler; private final Set unrecoverableEvents; public UnrecoverablePodEventListener( Set unrecoverableEvents, Set pods, Consumer unrecoverableEventHandler) { this.unrecoverableEvents = unrecoverableEvents; this.pods = pods; this.unrecoverableEventHandler = unrecoverableEventHandler; } @Override public void handle(PodEvent event) { if (isWorkspaceEvent(event) && (isFailedContainer(event) || isUnrecoverable(event))) { unrecoverableEventHandler.accept(event); } } /** Returns true if event belongs to one of the workspace pods, false otherwise */ private boolean isWorkspaceEvent(PodEvent event) { String podName = event.getPodName(); if (Strings.isNullOrEmpty(podName)) { return false; } // Note it is necessary to compare via startsWith rather than equals here, as pods managed by // deployments have their name set as [deploymentName]-[hash]. `workspacePodName` is used to // define the deployment name, so pods that are created aren't an exact match. return pods.stream().anyMatch(podName::startsWith); } /** * Returns true if event reason or message matches one of the comma separated values defined in * 'che.infra.kubernetes.workspace_unrecoverable_events',false otherwise * * @param event event to check */ private boolean isUnrecoverable(PodEvent event) { boolean isUnrecoverable = false; String reason = event.getReason(); String message = event.getMessage(); // Consider unrecoverable if event reason 'equals' one of the property values e.g. "Failed // Mount" if (unrecoverableEvents.contains(reason)) { isUnrecoverable = true; } else { for (String e : unrecoverableEvents) { // Consider unrecoverable if event message 'startsWith' one of the property values e.g. // "Failed to pull image" if (message != null && message.startsWith(e)) { isUnrecoverable = true; } } } return isUnrecoverable; } /** * This method detects whether the pod event corresponds to a container that failed to start. Note * that this is handled differently from the the {@link #isUnrecoverable(PodEvent)} because we are * specifically looking at failed containers and don't consider the event message in the logic. * * @param event an event on the pod * @return true if the event is about a failed container, false otherwise */ private boolean isFailedContainer(PodEvent event) { return "Failed".equals(event.getReason()) && event.getContainerName() != null; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/UnrecoverablePodEventListenerFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import com.google.common.collect.ImmutableSet; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEvent; /** * Helps to create {@link UnrecoverablePodEventListener} instaces. * * @author Sergii Leshchenko */ @Singleton public class UnrecoverablePodEventListenerFactory { private final Set unrecoverableEvents; @Inject public UnrecoverablePodEventListenerFactory( @Named("che.infra.kubernetes.workspace_unrecoverable_events") String[] unrecoverableEvents) { this.unrecoverableEvents = ImmutableSet.copyOf(unrecoverableEvents); } /** * Creates unrecoverable events listener. * * @param pods pods which unrecoverable events should be propagated * @param unrecoverableEventHandler handler which is invoked when unrecoverable event occurs * @return created unrecoverable events listener * @throws IllegalStateException is unrecoverable events are not configured. * @see #isConfigured() */ public UnrecoverablePodEventListener create( Set pods, Consumer unrecoverableEventHandler) { if (!isConfigured()) { throw new IllegalStateException("Unrecoverable events are not configured"); } return new UnrecoverablePodEventListener(unrecoverableEvents, pods, unrecoverableEventHandler); } public UnrecoverablePodEventListener create( KubernetesEnvironment environment, Consumer unrecoverableEventHandler) { if (!isConfigured()) { throw new IllegalStateException("Unrecoverable events are not configured"); } Set toWatch = environment.getPodsData().values().stream() .map(podData -> podData.getMetadata().getName()) .collect(Collectors.toSet()); return new UnrecoverablePodEventListener( unrecoverableEvents, toWatch, unrecoverableEventHandler); } /** * Returns true if unrecoverable events are configured and it's possible to create unrecoverable * events listener, false otherwise */ public boolean isConfigured() { return !unrecoverableEvents.isEmpty(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/BrokersResult.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; /** * @author Alexander Garagatyi */ @Beta public class BrokersResult { private final CompletableFuture> future; private final AtomicBoolean started; public BrokersResult() { future = new CompletableFuture<>(); started = new AtomicBoolean(); } /** * Submits exception indicating an error if the brokering process. * *

Completes call of {@link #get(long, TimeUnit)} with {@link ExecutionException} containing * provided exception * * @param e exception indicating brokering error * @throws IllegalStateException if called before the call of {@link #get(long, TimeUnit)} */ public void error(Exception e) { if (!started.get()) { throw new IllegalStateException( "Submitting a broker error is not allowed before calling BrokerResult#get"); } future.completeExceptionally(e); } /** * Submits the result of a broker execution. * * @param toolingFromBroker tooling evaluated by a broker that needs to be added into a workspace * @throws InfrastructureException if called second time which indicates incorrect usage of the * {@link BrokersResult} * @throws IllegalStateException if called before the call of {@link #get(long, TimeUnit)} */ public void setResult(List toolingFromBroker) throws InfrastructureException { if (!started.get()) { throw new IllegalStateException( "Submitting a broker result is not allowed before calling BrokerResult#get"); } if (future.isDone()) { throw new InfrastructureException( "Plugins brokering result is unexpectedly submitted more than one time. This indicates unexpected behavior of the system"); } future.complete(new ArrayList<>(toolingFromBroker)); } /** * Waits for the tooling that needs to be injected into a workspace being submitted by a call of * {@link #setResult(List)}. * *

If provided timeout elapses before needed call of {@link #setResult(List)} method ends with * an exception. This method is based on {@link CompletableFuture#get(long, TimeUnit)} so it also * inherits parameters and thrown exception. * * @return tooling submitted by broker that needs to be injected into a workspace * @throws IllegalStateException if called more than one time * @see CompletableFuture#get(long, TimeUnit) */ public List get(long waitTime, TimeUnit tu) throws ExecutionException, InterruptedException, TimeoutException { if (started.compareAndSet(false, true)) { return future.get(waitTime, tu); } else { throw new IllegalStateException("BrokerResult#get doesn't support multiple calls"); } } @VisibleForTesting boolean isStarted() { return started.get(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/ChePluginsVolumeApplier.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.newVolume; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.newVolumeMount; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.VolumeBuilder; import java.util.Collection; import java.util.Optional; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.wsplugins.model.Volume; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Components for applying workspace plugin volumes to the kubernetes {@link * io.fabric8.kubernetes.api.model.Container}. */ @Singleton public class ChePluginsVolumeApplier { @Inject public ChePluginsVolumeApplier() {} public void applyVolumes( KubernetesEnvironment.PodData pod, Container container, Collection volumes, KubernetesEnvironment k8sEnv) { for (Volume volume : volumes) { String podVolumeName = provisionPodVolume(volume, pod, k8sEnv); container.getVolumeMounts().add(newVolumeMount(podVolumeName, volume.getMountPath(), "")); } } private String provisionPodVolume( Volume volume, KubernetesEnvironment.PodData pod, KubernetesEnvironment k8sEnv) { if (volume.isEphemeral()) { addEmptyDirVolumeIfAbsent(pod.getSpec(), volume.getName()); return volume.getName(); } else { return provisionPVCPodVolume(volume, pod, k8sEnv).getName(); } } private io.fabric8.kubernetes.api.model.Volume provisionPVCPodVolume( Volume volume, KubernetesEnvironment.PodData pod, KubernetesEnvironment k8sEnv) { String pvcName = volume.getName(); PodSpec podSpec = pod.getSpec(); Optional volumeOpt = podSpec.getVolumes().stream() .filter( vm -> vm.getPersistentVolumeClaim() != null && pvcName.equals(vm.getPersistentVolumeClaim().getClaimName())) .findAny(); io.fabric8.kubernetes.api.model.Volume podVolume; if (volumeOpt.isPresent()) { podVolume = volumeOpt.get(); } else { podVolume = newVolume(pvcName, pvcName); podSpec.getVolumes().add(podVolume); } return podVolume; } private void addEmptyDirVolumeIfAbsent(PodSpec podSpec, String uniqueVolumeName) { if (podSpec.getVolumes().stream() .noneMatch(volume -> volume.getName().equals(uniqueVolumeName))) { podSpec .getVolumes() .add( new VolumeBuilder() .withName(uniqueVolumeName) .withNewEmptyDir() .endEmptyDir() .build()); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/K8sContainerResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.Math.min; import static java.lang.String.format; import static java.util.Collections.emptyList; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.ContainerPort; import io.fabric8.kubernetes.api.model.ContainerPortBuilder; import io.fabric8.kubernetes.api.model.ExecAction; import java.util.List; import java.util.stream.Collectors; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint; import org.eclipse.che.api.workspace.server.wsplugins.model.EnvVar; import org.eclipse.che.api.workspace.server.wsplugins.model.Exec; import org.eclipse.che.api.workspace.server.wsplugins.model.Handler; import org.eclipse.che.api.workspace.server.wsplugins.model.Lifecycle; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSize; /** * Resolves Kubernetes container {@link Container} configuration from Che workspace sidecar and Che * plugin endpoints. * * @author Oleksandr Garagatyi */ public class K8sContainerResolver { private final String imagePullPolicy; private final CheContainer cheContainer; private final List containerEndpoints; public K8sContainerResolver( String imagePullPolicy, CheContainer container, List containerEndpoints) { this.imagePullPolicy = imagePullPolicy; this.cheContainer = container; this.containerEndpoints = containerEndpoints; } public List getEndpoints() { return containerEndpoints; } public Container resolve() throws InfrastructureException { Container container = new ContainerBuilder() .withImage(cheContainer.getImage()) .withImagePullPolicy(imagePullPolicy) .withName(buildContainerName(cheContainer.getName())) .withEnv(toK8sEnv(cheContainer.getEnv())) .withPorts(getContainerPorts()) .withCommand(cheContainer.getCommand()) .withArgs(cheContainer.getArgs()) .withLifecycle(toK8sLifecycle(cheContainer.getLifecycle())) .build(); provisionMemoryLimit(container, cheContainer); provisionMemoryRequest(container, cheContainer); provisionCpuLimit(container, cheContainer); provisionCpuRequest(container, cheContainer); return container; } private io.fabric8.kubernetes.api.model.Lifecycle toK8sLifecycle(Lifecycle lifecycle) { if (lifecycle == null) { return null; } io.fabric8.kubernetes.api.model.LifecycleHandler postStart = toK8sHandler(lifecycle.getPostStart()); io.fabric8.kubernetes.api.model.LifecycleHandler preStop = toK8sHandler(lifecycle.getPreStop()); io.fabric8.kubernetes.api.model.Lifecycle k8sLifecycle = new io.fabric8.kubernetes.api.model.Lifecycle(postStart, preStop, "SIGTERM"); return k8sLifecycle; } private io.fabric8.kubernetes.api.model.LifecycleHandler toK8sHandler(Handler handler) { if (handler == null || handler.getExec() == null) { return null; } ExecAction exec = toExecAction(handler.getExec()); if (exec == null) { return null; } // TODO: add 'httpGetAction' and 'tcpSocketAction' support io.fabric8.kubernetes.api.model.LifecycleHandler k8SHandler = new io.fabric8.kubernetes.api.model.LifecycleHandler(exec, null, null, null); return k8SHandler; } private ExecAction toExecAction(Exec exec) { if (exec == null || exec.getCommand().isEmpty()) { return null; } return new ExecAction(exec.getCommand()); } private void provisionMemoryLimit(Container container, CheContainer cheContainer) throws InfrastructureException { String memoryLimit = cheContainer.getMemoryLimit(); if (isNullOrEmpty(memoryLimit)) { return; } try { KubernetesSize.toBytes(memoryLimit); } catch (IllegalArgumentException e) { throw new InfrastructureException( format( "Sidecar memory limit field contains illegal value '%s'. Error: '%s'", memoryLimit, e.getMessage())); } Containers.addRamLimit(container, memoryLimit); } private void provisionMemoryRequest(Container container, CheContainer cheContainer) throws InfrastructureException { String memoryRequest = cheContainer.getMemoryRequest(); if (isNullOrEmpty(memoryRequest)) { return; } try { KubernetesSize.toBytes(memoryRequest); } catch (IllegalArgumentException e) { throw new InfrastructureException( format( "Sidecar memory request field contains illegal value '%s'. Error: '%s'", memoryRequest, e.getMessage())); } Containers.addRamRequest(container, memoryRequest); } private void provisionCpuLimit(Container container, CheContainer cheContainer) throws InfrastructureException { String cpuLimit = cheContainer.getCpuLimit(); if (isNullOrEmpty(cpuLimit)) { return; } try { KubernetesSize.toCores(cpuLimit); } catch (IllegalArgumentException e) { throw new InfrastructureException( format( "Sidecar CPU limit field contains illegal value '%s'. Error: '%s'", cpuLimit, e.getMessage())); } Containers.addCpuLimit(container, cpuLimit); } private void provisionCpuRequest(Container container, CheContainer cheContainer) throws InfrastructureException { String cpuRequest = cheContainer.getCpuRequest(); if (isNullOrEmpty(cpuRequest)) { return; } try { KubernetesSize.toCores(cpuRequest); } catch (IllegalArgumentException e) { throw new InfrastructureException( format( "Sidecar CPU request field contains illegal value '%s'. Error: '%s'", cpuRequest, e.getMessage())); } Containers.addCpuRequest(container, cpuRequest); } private List getContainerPorts() { return containerEndpoints.stream() .map( endpoint -> new ContainerPortBuilder() .withContainerPort(endpoint.getTargetPort()) .withProtocol("TCP") .build()) .collect(Collectors.toList()); } private List toK8sEnv(List env) { if (env == null) { return emptyList(); } return env.stream() .map(e -> new io.fabric8.kubernetes.api.model.EnvVar(e.getName(), e.getValue(), null)) .collect(Collectors.toList()); } private String buildContainerName(String cheContainerName) { String uniqueName = NameGenerator.generate(cheContainerName, 3).toLowerCase(); return uniqueName.substring(0, min(uniqueName.length(), Names.MAX_CONTAINER_NAME_LENGTH)); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/K8sContainerResolverBuilder.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainerPort; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint; /** * @author Oleksandr Garagatyi */ public class K8sContainerResolverBuilder { private String imagePullPolicy; private CheContainer container; private List pluginEndpoints; public K8sContainerResolverBuilder setContainer(CheContainer container) { this.container = container; return this; } public K8sContainerResolverBuilder setPluginEndpoints(List pluginEndpoints) { this.pluginEndpoints = pluginEndpoints; return this; } public K8sContainerResolverBuilder setImagePullPolicy(String imagePullPolicy) { this.imagePullPolicy = imagePullPolicy; return this; } public K8sContainerResolver build() { if (container == null || pluginEndpoints == null) { throw new IllegalStateException(); } List containerEndpoints = getContainerEndpoints(container.getPorts(), pluginEndpoints); return new K8sContainerResolver(imagePullPolicy, container, containerEndpoints); } private List getContainerEndpoints( List ports, List endpoints) { if (ports == null || ports.isEmpty()) { return Collections.emptyList(); } return ports.stream() .map(CheContainerPort::getExposedPort) .flatMap(port -> endpoints.stream().filter(e -> e.getTargetPort() == port)) .collect(Collectors.toList()); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/KubernetesArtifactsBrokerApplier.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import com.google.common.annotations.Beta; import com.google.inject.Inject; import io.fabric8.kubernetes.api.model.Container; import java.util.Collection; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.BrokerEnvironmentFactory; /** * Given a {@link InternalEnvironment} representing a workspace, adds the artifacts plugin broker as * an init container to the workspace Pod in order to ensure that any extensions are downloaded. * *

This API is in Beta and is subject to changes or removal. * * @author Angel Misevski */ @Beta public class KubernetesArtifactsBrokerApplier { private final BrokerEnvironmentFactory brokerEnvironmentFactory; @Inject public KubernetesArtifactsBrokerApplier(BrokerEnvironmentFactory brokerEnvironmentFactory) { this.brokerEnvironmentFactory = brokerEnvironmentFactory; } /** * Apply plugin broker as init container to workspace environment. Workspace environment will have * broker's configmap, machines, and volumes added in addition to the init container */ public void apply( E workspaceEnvironment, RuntimeIdentity runtimeID, Collection pluginFQNs, boolean mergePlugins) throws InfrastructureException { E brokerEnvironment = brokerEnvironmentFactory.createForArtifactsBroker(pluginFQNs, runtimeID, mergePlugins); Map workspacePods = workspaceEnvironment.getPodsData(); if (workspacePods.size() != 1) { throw new InfrastructureException( "Che plugins tooling configuration can be applied to a workspace with one pod only."); } PodData workspacePod = workspacePods.values().iterator().next(); Map brokerPods = brokerEnvironment.getPodsData(); if (brokerPods.size() != 1) { throw new InfrastructureException("Broker environment must have only one Pod."); } PodData brokerPod = brokerPods.values().iterator().next(); // Add broker machines to workspace environment so that the init containers can be provisioned. List brokerContainers = brokerPod.getSpec().getContainers(); for (Container container : brokerContainers) { InternalMachineConfig brokerMachine = brokerEnvironment.getMachines().get(Names.machineName(brokerPod, container)); if (brokerMachine == null) { throw new InfrastructureException( String.format( "Could not retrieve the specification of the plugin broker container %s", container.getName())); } workspaceEnvironment .getMachines() .put(Names.machineName(workspacePod, container), brokerMachine); } workspaceEnvironment.getConfigMaps().putAll(brokerEnvironment.getConfigMaps()); workspacePod.getSpec().getInitContainers().addAll(brokerPod.getSpec().getContainers()); workspacePod.getSpec().getVolumes().addAll(brokerPod.getSpec().getVolumes()); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/KubernetesPluginsToolingApplier.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static java.lang.String.format; import static java.util.Collections.emptyList; import static org.eclipse.che.api.core.model.workspace.config.Command.MACHINE_NAME_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.Command.WORKING_DIRECTORY_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.CONTAINER_SOURCE_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.PLUGIN_MACHINE_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.TOOL_CONTAINER_SOURCE; import com.google.common.annotations.Beta; import com.google.common.collect.ArrayListMultimap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.Service; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.WarningImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.wsplugins.ChePluginsApplier; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint; import org.eclipse.che.api.workspace.server.wsplugins.model.Command; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.util.EnvVars; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSize; /** * Applies Che plugins tooling configuration to a kubernetes internal runtime object. * * @author Oleksander Garagatyi * @author Sergii Leshchenko */ @Beta @Singleton public class KubernetesPluginsToolingApplier implements ChePluginsApplier { private static final String CHE_WORKSPACE_POD = "che-workspace-pod"; private final String defaultSidecarMemoryLimitBytes; private final String defaultSidecarMemoryRequestBytes; private final String defaultSidecarCpuLimitCores; private final String defaultSidecarCpuRequestCores; private final boolean isAuthEnabled; private final ChePluginsVolumeApplier chePluginsVolumeApplier; private final EnvVars envVars; @Inject public KubernetesPluginsToolingApplier( @Named("che.workspace.sidecar.default_memory_limit_mb") long defaultSidecarMemoryLimitMB, @Named("che.workspace.sidecar.default_memory_request_mb") long defaultSidecarMemoryRequestMB, @Named("che.workspace.sidecar.default_cpu_limit_cores") String defaultSidecarCpuLimitCores, @Named("che.workspace.sidecar.default_cpu_request_cores") String defaultSidecarCpuRequestCores, @Named("che.agents.auth_enabled") boolean isAuthEnabled, ChePluginsVolumeApplier chePluginsVolumeApplier, EnvVars envVars) { this.defaultSidecarMemoryLimitBytes = toBytesString(defaultSidecarMemoryLimitMB); this.defaultSidecarMemoryRequestBytes = toBytesString(defaultSidecarMemoryRequestMB); this.defaultSidecarCpuLimitCores = Float.toString(KubernetesSize.toCores(defaultSidecarCpuLimitCores)); this.defaultSidecarCpuRequestCores = Float.toString(KubernetesSize.toCores(defaultSidecarCpuRequestCores)); this.isAuthEnabled = isAuthEnabled; this.chePluginsVolumeApplier = chePluginsVolumeApplier; this.envVars = envVars; } @Override public void apply( RuntimeIdentity runtimeIdentity, InternalEnvironment internalEnvironment, Collection chePlugins) throws InfrastructureException { if (chePlugins.isEmpty()) { return; } KubernetesEnvironment k8sEnv = (KubernetesEnvironment) internalEnvironment; Map pods = k8sEnv.getPodsData(); switch (pods.size()) { case 0: addToolingPod(k8sEnv); pods = k8sEnv.getPodsData(); break; case 1: break; default: throw new InfrastructureException( "Che plugins tooling configuration can be applied to a workspace with one pod only"); } PodData pod = pods.values().iterator().next(); CommandsResolver commandsResolver = new CommandsResolver(k8sEnv); for (ChePlugin chePlugin : chePlugins) { Map devfilePlugins = k8sEnv.getDevfile().getComponents().stream() .filter(c -> c.getType().equals("cheEditor") || c.getType().equals("chePlugin")) .collect(Collectors.toMap(ComponentImpl::getId, Function.identity())); if (!devfilePlugins.containsKey(chePlugin.getId())) { throw new InfrastructureException( String.format( "The downloaded plugin '%s' configuration does not have the " + "corresponding component in devfile. Devfile contains the following cheEditor/chePlugins: %s", chePlugin.getId(), devfilePlugins.keySet())); } ComponentImpl pluginRelatedComponent = devfilePlugins.get(chePlugin.getId()); for (CheContainer container : chePlugin.getInitContainers()) { Container k8sInitContainer = toK8sContainer(container); envVars.apply(k8sInitContainer, pluginRelatedComponent.getEnv()); chePluginsVolumeApplier.applyVolumes(pod, k8sInitContainer, container.getVolumes(), k8sEnv); pod.getSpec().getInitContainers().add(k8sInitContainer); } Collection pluginRelatedCommands = commandsResolver.resolve(chePlugin); for (CheContainer container : chePlugin.getContainers()) { addSidecar( pod, container, chePlugin, k8sEnv, pluginRelatedCommands, pluginRelatedComponent, runtimeIdentity); } } chePlugins.forEach(chePlugin -> populateWorkspaceEnvVars(chePlugin, k8sEnv)); } private void addToolingPod(KubernetesEnvironment kubernetesEnvironment) { Pod pod = new PodBuilder() .withNewMetadata() .withName(CHE_WORKSPACE_POD) .endMetadata() .withNewSpec() .endSpec() .build(); kubernetesEnvironment.addPod(pod); } private void populateWorkspaceEnvVars( ChePlugin chePlugin, KubernetesEnvironment kubernetesEnvironment) { List workspaceEnv = toK8sEnvVars(chePlugin.getWorkspaceEnv()); kubernetesEnvironment.getPodsData().values().stream() .flatMap(pod -> pod.getSpec().getContainers().stream()) .forEach(container -> container.getEnv().addAll(workspaceEnv)); } private List toK8sEnvVars( List workspaceEnv) { if (workspaceEnv == null) { return emptyList(); } return workspaceEnv.stream() .map(e -> new EnvVar(e.getName(), e.getValue(), null)) .collect(Collectors.toList()); } private Container toK8sContainer(CheContainer container) throws InfrastructureException { return toK8sContainerResolver(container, emptyList()).resolve(); } private K8sContainerResolver toK8sContainerResolver( CheContainer container, List endpoints) { return new K8sContainerResolverBuilder() .setContainer(container) .setPluginEndpoints(endpoints) .build(); } /** * Adds k8s and Che specific configuration of a sidecar into the environment. For example: *

  • k8s container configuration {@link Container} *
  • k8s service configuration {@link Service} *
  • Che machine config {@link InternalMachineConfig} *
  • Fill in machine name attribute in related commands * * @throws InfrastructureException when any error occurs */ private void addSidecar( PodData pod, CheContainer container, ChePlugin chePlugin, KubernetesEnvironment k8sEnv, Collection sidecarRelatedCommands, Component pluginRelatedComponent, RuntimeIdentity runtimeIdentity) throws InfrastructureException { K8sContainerResolver k8sContainerResolver = toK8sContainerResolver(container, chePlugin.getEndpoints()); List containerEndpoints = k8sContainerResolver.getEndpoints(); Container k8sContainer = k8sContainerResolver.resolve(); envVars.apply(k8sContainer, pluginRelatedComponent.getEnv()); chePluginsVolumeApplier.applyVolumes(pod, k8sContainer, container.getVolumes(), k8sEnv); String machineName = k8sContainer.getName(); Names.putMachineName(pod.getMetadata(), k8sContainer.getName(), machineName); pod.getSpec().getContainers().add(k8sContainer); MachineResolver machineResolver = new MachineResolverBuilder() .setCheContainer(container) .setContainer(k8sContainer) .setContainerEndpoints(containerEndpoints) .setDefaultSidecarMemoryLimitAttribute(defaultSidecarMemoryLimitBytes) .setDefaultSidecarMemoryRequestAttribute(defaultSidecarMemoryRequestBytes) .setDefaultSidecarCpuLimitAttribute(defaultSidecarCpuLimitCores) .setDefaultSidecarCpuRequestAttribute(defaultSidecarCpuRequestCores) .setComponent(pluginRelatedComponent) .build(); InternalMachineConfig machineConfig = machineResolver.resolve(); machineConfig.getAttributes().put(CONTAINER_SOURCE_ATTRIBUTE, TOOL_CONTAINER_SOURCE); machineConfig.getAttributes().put(PLUGIN_MACHINE_ATTRIBUTE, chePlugin.getId()); k8sEnv.getMachines().put(machineName, machineConfig); sidecarRelatedCommands.forEach( c -> c.getAttributes() .put( org.eclipse.che.api.core.model.workspace.config.Command.MACHINE_NAME_ATTRIBUTE, machineName)); container.getCommands().stream() .map(c -> asCommand(machineName, c)) .forEach(c -> k8sEnv.getCommands().add(c)); SidecarServicesProvisioner sidecarServicesProvisioner = new SidecarServicesProvisioner(containerEndpoints, pod.getMetadata().getName()); sidecarServicesProvisioner.provision(k8sEnv); } private CommandImpl asCommand(String machineName, Command command) { CommandImpl cmd = new CommandImpl(command.getName(), String.join(" ", command.getCommand()), "custom"); cmd.getAttributes().put(WORKING_DIRECTORY_ATTRIBUTE, command.getWorkingDir()); cmd.getAttributes().put(MACHINE_NAME_ATTRIBUTE, machineName); return cmd; } private String toBytesString(long memoryLimitMB) { return String.valueOf(memoryLimitMB * 1024L * 1024L); } private static class CommandsResolver { private final KubernetesEnvironment k8sEnvironment; private final ArrayListMultimap pluginRefToCommand; public CommandsResolver(KubernetesEnvironment k8sEnvironment) { this.k8sEnvironment = k8sEnvironment; pluginRefToCommand = ArrayListMultimap.create(); k8sEnvironment .getCommands() .forEach( (c) -> { String pluginRef = c.getAttributes() .get( org.eclipse.che.api.core.model.workspace.config.Command .PLUGIN_ATTRIBUTE); if (pluginRef != null) { pluginRefToCommand.put(pluginRef, c); } }); } private Collection resolve(ChePlugin chePlugin) { List containers = chePlugin.getContainers(); String pluginRef = chePlugin.getId(); Collection pluginsCommands = pluginRefToCommand.removeAll(pluginRef); if (pluginsCommands.isEmpty()) { // specified plugin doesn't have configured commands return emptyList(); } if (containers.isEmpty()) { k8sEnvironment .getWarnings() .add( new WarningImpl( Warnings.COMMAND_IS_CONFIGURED_IN_PLUGIN_WITHOUT_CONTAINERS_WARNING_CODE, format( Warnings .COMMAND_IS_CONFIGURED_IN_PLUGIN_WITHOUT_CONTAINERS_WARNING_MESSAGE_FMT, pluginRef))); return emptyList(); } if (containers.size() > 1) { k8sEnvironment .getWarnings() .add( new WarningImpl( Warnings.COMMAND_IS_CONFIGURED_IN_PLUGIN_WITH_MULTIPLY_CONTAINERS_WARNING_CODE, format( Warnings .COMMAND_IS_CONFIGURED_IN_PLUGIN_WITH_MULTIPLY_CONTAINERS_WARNING_MESSAGE_FMT, pluginRef))); } return pluginsCommands; } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/KubernetesPluginsToolingValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static java.lang.String.format; import java.util.List; import java.util.regex.Pattern; import javax.inject.Singleton; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; @Singleton public class KubernetesPluginsToolingValidator { // Pattern is from K8S Container class private static final Pattern namePattern = Pattern.compile("[a-z0-9]([-a-z0-9]*[a-z0-9])?"); public void validatePluginNames(List plugins) throws ValidationException { for (ChePlugin plugin : plugins) { if (plugin.getName() != null) { final String formattedPluginName = plugin.getName().toLowerCase(); checkValid( formattedPluginName, "Plugin name `%s` contains unacceptable symbols and cannot be used as part of container naming."); } for (CheContainer container : plugin.getContainers()) { if (container.getName() != null) { final String formattedContainerName = container.getName().toLowerCase(); checkValid( formattedContainerName, "Container name `%s` contains unacceptable symbols and cannot be used as part of container naming."); } } } } private void checkValid(String input, String errorMessage) throws ValidationException { if (!namePattern.matcher(input).matches()) { throw new ValidationException(format(errorMessage, input)); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/MachineResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.Collections.emptyMap; import static java.util.stream.Collectors.toMap; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_REQUEST_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_REQUEST_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.PROJECTS_VOLUME_NAME; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.Quantity; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint; import org.eclipse.che.api.workspace.server.wsplugins.model.Volume; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSize; /** * @author Oleksandr Garagatyi */ public class MachineResolver { private final Container container; private final CheContainer cheContainer; private final String defaultSidecarMemoryLimitBytes; private final String defaultSidecarMemoryRequestBytes; private final String defaultSidecarCpuLimitCores; private final String defaultSidecarCpuRequestCores; private final List containerEndpoints; private final Component component; public MachineResolver( Container container, CheContainer cheContainer, String defaultSidecarMemoryLimitBytes, String defaultSidecarMemoryRequestBytes, String defaultSidecarCpuLimitCores, String defaultSidecarCpuRequestCores, List containerEndpoints, Component component) { this.container = container; this.cheContainer = cheContainer; this.defaultSidecarMemoryLimitBytes = defaultSidecarMemoryLimitBytes; this.defaultSidecarMemoryRequestBytes = defaultSidecarMemoryRequestBytes; this.defaultSidecarCpuLimitCores = defaultSidecarCpuLimitCores; this.defaultSidecarCpuRequestCores = defaultSidecarCpuRequestCores; this.containerEndpoints = containerEndpoints; this.component = component; } public InternalMachineConfig resolve() throws InfrastructureException { InternalMachineConfig machineConfig = new InternalMachineConfig( toServers(containerEndpoints), emptyMap(), resolveMachineAttributes(), toWorkspaceVolumes(cheContainer)); applyDevfileVolumes(machineConfig); applyDevfileEndpoints(machineConfig); normalizeMemory(container, machineConfig); normalizeCpu(container, machineConfig); return machineConfig; } private void applyDevfileVolumes(InternalMachineConfig machineConfig) { for (org.eclipse.che.api.core.model.workspace.devfile.Volume volume : component.getVolumes()) { machineConfig .getVolumes() .put(volume.getName(), new VolumeImpl().withPath(volume.getContainerPath())); } } private void applyDevfileEndpoints(InternalMachineConfig machineConfig) throws InfrastructureException { for (org.eclipse.che.api.core.model.workspace.devfile.Endpoint endpoint : component.getEndpoints()) { if (!machineConfig.getServers().containsKey(endpoint.getName())) { machineConfig .getServers() .put(endpoint.getName(), ServerConfigImpl.createFromEndpoint(endpoint)); } else { throw new InfrastructureException( format( "Devfile overrides the endpoint '%s' of the plugin/editor component '%s'. " + "This is not allowed because it would most probably cause the workspace " + "to malfunction. Please change the name of the endpoint in the devfile and try " + "to start the workspace again.", component.getId(), endpoint.getName())); } } } private Map resolveMachineAttributes() { Map attributes = new HashMap<>(); String alias = component.getAlias(); if (alias != null) { attributes.put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, alias); } return attributes; } private void normalizeMemory(Container container, InternalMachineConfig machineConfig) { long ramLimit = Containers.getRamLimit(container); if (ramLimit == 0) { machineConfig.getAttributes().put(MEMORY_LIMIT_ATTRIBUTE, defaultSidecarMemoryLimitBytes); } String overriddenSidecarMemLimit = component.getMemoryLimit(); if (!isNullOrEmpty(overriddenSidecarMemLimit)) { machineConfig .getAttributes() .put( MEMORY_LIMIT_ATTRIBUTE, Long.toString( Quantity.getAmountInBytes(Quantity.parse(overriddenSidecarMemLimit)) .longValue())); } long ramRequest = Containers.getRamRequest(container); if (ramRequest == 0) { machineConfig.getAttributes().put(MEMORY_REQUEST_ATTRIBUTE, defaultSidecarMemoryRequestBytes); } String overriddenSidecarMemRequest = component.getMemoryRequest(); if (!isNullOrEmpty(overriddenSidecarMemRequest)) { machineConfig .getAttributes() .put( MEMORY_REQUEST_ATTRIBUTE, Long.toString( Quantity.getAmountInBytes(Quantity.parse(overriddenSidecarMemRequest)) .longValue())); } } private void normalizeCpu(Container container, InternalMachineConfig machineConfig) { float cpuLimit = Containers.getCpuLimit(container); if (cpuLimit == 0) { machineConfig.getAttributes().put(CPU_LIMIT_ATTRIBUTE, defaultSidecarCpuLimitCores); } String overriddenSidecarCpuLimit = component.getCpuLimit(); if (!isNullOrEmpty(overriddenSidecarCpuLimit)) { machineConfig .getAttributes() .put( CPU_LIMIT_ATTRIBUTE, Float.toString(KubernetesSize.toCores(overriddenSidecarCpuLimit))); } float cpuRequest = Containers.getCpuRequest(container); if (cpuRequest == 0) { machineConfig.getAttributes().put(CPU_REQUEST_ATTRIBUTE, defaultSidecarCpuRequestCores); } String overriddenSidecarCpuRequest = component.getCpuRequest(); if (!isNullOrEmpty(overriddenSidecarCpuRequest)) { machineConfig .getAttributes() .put( CPU_REQUEST_ATTRIBUTE, Float.toString(KubernetesSize.toCores(overriddenSidecarCpuRequest))); } } private Map toWorkspaceVolumes(CheContainer container) throws InfrastructureException { Map result = new HashMap<>(); for (Volume volume : container.getVolumes()) { if (volume.getName().equals(PROJECTS_VOLUME_NAME)) { throw new InfrastructureException( "Plugin '%s' tried to manually mount the '%s' volume into its container '%s' on" + " path '%s'. This is illegal because sources need to be mounted to '%s'. Set" + " the mountSources attribute to true instead and remove the manual volume" + " mount in the plugin."); } } return result; } private Map toServers(List endpoints) { return endpoints.stream().collect(toMap(ChePluginEndpoint::getName, this::toServer)); } private ServerConfigImpl toServer(ChePluginEndpoint endpoint) { ServerConfigImpl serverConfig = new ServerConfigImpl().withPort(endpoint.getTargetPort() + "/tcp"); serverConfig.getAttributes().put("internal", Boolean.toString(!endpoint.isPublic())); for (Entry attribute : endpoint.getAttributes().entrySet()) { switch (attribute.getKey()) { case "protocol": serverConfig.setProtocol(attribute.getValue()); break; case "path": serverConfig.setPath(attribute.getValue()); break; default: serverConfig.getAttributes().put(attribute.getKey(), attribute.getValue()); } } return serverConfig; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/MachineResolverBuilder.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import io.fabric8.kubernetes.api.model.Container; import java.util.List; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint; /** * @author Alexander Garagatyi */ public class MachineResolverBuilder { private Container container; private CheContainer cheContainer; private String defaultSidecarMemoryLimitAttribute; private String defaultSidecarMemoryRequestAttribute; private String defaultSidecarCpuLimitAttribute; private String defaultSidecarCpuRequestAttribute; private List containerEndpoints; private Component component; public MachineResolver build() { if (container == null || cheContainer == null || defaultSidecarMemoryLimitAttribute == null || defaultSidecarMemoryRequestAttribute == null || defaultSidecarCpuLimitAttribute == null || defaultSidecarCpuRequestAttribute == null || containerEndpoints == null) { throw new IllegalStateException( "Unable to build MachineResolver because some fields are null"); } return new MachineResolver( container, cheContainer, defaultSidecarMemoryLimitAttribute, defaultSidecarMemoryRequestAttribute, defaultSidecarCpuLimitAttribute, defaultSidecarCpuRequestAttribute, containerEndpoints, component); } public MachineResolverBuilder setContainer(Container container) { this.container = container; return this; } public MachineResolverBuilder setCheContainer(CheContainer cheContainer) { this.cheContainer = cheContainer; return this; } public MachineResolverBuilder setDefaultSidecarMemoryLimitAttribute( String defaultSidecarMemoryLimitAttribute) { this.defaultSidecarMemoryLimitAttribute = defaultSidecarMemoryLimitAttribute; return this; } public MachineResolverBuilder setDefaultSidecarMemoryRequestAttribute( String defaultSidecarMemoryRequestAttribute) { this.defaultSidecarMemoryRequestAttribute = defaultSidecarMemoryRequestAttribute; return this; } public MachineResolverBuilder setDefaultSidecarCpuLimitAttribute( String defaultSidecarCpuLimitAttribute) { this.defaultSidecarCpuLimitAttribute = defaultSidecarCpuLimitAttribute; return this; } public MachineResolverBuilder setDefaultSidecarCpuRequestAttribute( String defaultSidecarCpuRequestAttribute) { this.defaultSidecarCpuRequestAttribute = defaultSidecarCpuRequestAttribute; return this; } public MachineResolverBuilder setContainerEndpoints(List containerEndpoints) { this.containerEndpoints = containerEndpoints; return this; } public MachineResolverBuilder setComponent(Component component) { this.component = component; return this; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/PluginBrokerManager.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import com.google.common.annotations.Beta; import io.opentracing.Tracer; import java.util.Collection; import java.util.List; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesEnvironmentProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.StartSynchronizer; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.eclipse.che.workspace.infrastructure.kubernetes.util.UnrecoverablePodEventListenerFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.BrokerEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.DeployBroker; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.ListenBrokerEvents; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.PrepareStorage; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.WaitBrokerResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Deploys Che plugin broker in a workspace, receives result of its execution and return resolved * workspace tooling or error of plugin broker execution. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksandr Garagatyi */ @Beta public class PluginBrokerManager { private static final Logger LOG = LoggerFactory.getLogger(PluginBrokerManager.class); private final int pluginBrokerWaitingTimeout; private final KubernetesNamespaceFactory factory; private final EventService eventService; private final KubernetesPluginsToolingValidator pluginsValidator; private final BrokerEnvironmentFactory brokerEnvironmentFactory; private final KubernetesEnvironmentProvisioner environmentProvisioner; private final UnrecoverablePodEventListenerFactory unrecoverablePodEventListenerFactory; private final RuntimeEventsPublisher runtimeEventsPublisher; private final Tracer tracer; @Inject public PluginBrokerManager( KubernetesNamespaceFactory factory, EventService eventService, KubernetesPluginsToolingValidator pluginsValidator, KubernetesEnvironmentProvisioner environmentProvisioner, BrokerEnvironmentFactory brokerEnvironmentFactory, UnrecoverablePodEventListenerFactory unrecoverablePodEventListenerFactory, @Named("che.workspace.plugin_broker.wait_timeout_min") int pluginBrokerWaitingTimeout, RuntimeEventsPublisher runtimeEventsPublisher, Tracer tracer) { this.factory = factory; this.eventService = eventService; this.pluginsValidator = pluginsValidator; this.brokerEnvironmentFactory = brokerEnvironmentFactory; this.environmentProvisioner = environmentProvisioner; this.pluginBrokerWaitingTimeout = pluginBrokerWaitingTimeout; this.unrecoverablePodEventListenerFactory = unrecoverablePodEventListenerFactory; this.runtimeEventsPublisher = runtimeEventsPublisher; this.tracer = tracer; } /** * Deploys Che plugin brokers in a workspace, receives result of theirs execution and returns * resolved workspace tooling or error of plugins brokering execution. * *

    This API is in Beta and is subject to changes or removal. */ @Beta @Traced public List getTooling( RuntimeIdentity identity, StartSynchronizer startSynchronizer, Collection pluginFQNs, boolean mergePlugins, Map startOptions) throws InfrastructureException { String workspaceId = identity.getWorkspaceId(); KubernetesNamespace kubernetesNamespace = factory.getOrCreate(identity); BrokersResult brokersResult = new BrokersResult(); E brokerEnvironment = brokerEnvironmentFactory.createForMetadataBroker(pluginFQNs, identity, mergePlugins); environmentProvisioner.provision(brokerEnvironment, identity); ListenBrokerEvents listenBrokerEvents = getListenEventPhase(workspaceId, brokersResult); PrepareStorage prepareStorage = getPrepareStoragePhase(identity, startSynchronizer, brokerEnvironment); WaitBrokerResult waitBrokerResult = getWaitBrokerPhase(workspaceId, brokersResult); DeployBroker deployBroker = getDeployBrokerPhase( identity, kubernetesNamespace, brokerEnvironment, brokersResult, startOptions); LOG.debug("Entering plugin brokers deployment chain workspace '{}'", workspaceId); listenBrokerEvents.then(prepareStorage).then(deployBroker).then(waitBrokerResult); return listenBrokerEvents.execute(); } private ListenBrokerEvents getListenEventPhase(String workspaceId, BrokersResult brokersResult) { return new ListenBrokerEvents(workspaceId, pluginsValidator, brokersResult, eventService); } private PrepareStorage getPrepareStoragePhase( RuntimeIdentity identity, StartSynchronizer startSynchronizer, KubernetesEnvironment brokerEnvironment) { return new PrepareStorage(identity, brokerEnvironment, startSynchronizer, tracer); } private DeployBroker getDeployBrokerPhase( RuntimeIdentity runtimeId, KubernetesNamespace kubernetesNamespace, KubernetesEnvironment brokerEnvironment, BrokersResult brokersResult, Map startOptions) { return new DeployBroker( runtimeId, kubernetesNamespace, brokerEnvironment, brokersResult, unrecoverablePodEventListenerFactory, runtimeEventsPublisher, tracer, startOptions); } private WaitBrokerResult getWaitBrokerPhase(String workspaceId, BrokersResult brokersResult) { return new WaitBrokerResult(workspaceId, brokersResult, pluginBrokerWaitingTimeout, tracer); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/SidecarServicesProvisioner.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static java.lang.String.format; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServicePortBuilder; import java.util.List; import java.util.Map; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Resolves Kubernetes {@link Service}s needed for a proper accessibility of a Che workspace * sidecar. * *

    Proper accessibility here means that sidecar endpoint should be discoverable by an endpoint * name inside of a workspace. * * @author Oleksandr Garagatyi */ public class SidecarServicesProvisioner { private final List endpoints; private final String podName; public SidecarServicesProvisioner(List containerEndpoints, String podName) { this.endpoints = containerEndpoints; this.podName = podName; } /** * Add k8s Service objects to environment to provide service discovery in sidecar based * workspaces. */ public void provision(KubernetesEnvironment kubernetesEnvironment) throws InfrastructureException { for (ChePluginEndpoint endpoint : endpoints) { if (!isDiscoverable(endpoint)) { continue; } String serviceName = endpoint.getName(); Service service = createService(serviceName, podName, endpoint.getTargetPort()); Map services = kubernetesEnvironment.getServices(); if (!services.containsKey(serviceName)) { services.put(serviceName, service); } else { throw new InfrastructureException( format( "Applying of sidecar tooling failed. Kubernetes service with name '%s' already exists in the workspace environment.", serviceName)); } } } private boolean isDiscoverable(ChePluginEndpoint endpoint) { return Boolean.parseBoolean(endpoint.getAttributes().getOrDefault("discoverable", "true")); } private Service createService(String name, String podName, int port) { ServicePort servicePort = new ServicePortBuilder().withPort(port).withProtocol("TCP").withNewTargetPort(port).build(); return new ServiceBuilder() .withNewMetadata() .withName(name) .endMetadata() .withNewSpec() .withSelector(singletonMap(CHE_ORIGINAL_NAME_LABEL, podName)) .withPorts(singletonList(servicePort)) .endSpec() .build(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/BrokerEnvironmentFactory.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases; import static com.google.common.base.Strings.isNullOrEmpty; import static java.util.Collections.singletonMap; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.EnvVarBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.VolumeMount; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.provision.env.AgentAuthEnableEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.MachineTokenEnvVarProvider; import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers; /** * Creates {@link KubernetesEnvironment} with everything needed to deploy Plugin brokers. * *

    It has to be extended to be used in the kubernetes or openshift infrastructures because of the * usage of a complex inheritance between components of these infrastructures. * * @author Oleksandr Garagatyi */ public abstract class BrokerEnvironmentFactory { @VisibleForTesting static final String CONFIG_MAP_NAME_SUFFIX = "broker-config-map"; @VisibleForTesting static final String CONFIG_FILE = "config.json"; private static final String BROKER_VOLUME = "broker-config-volume"; private static final String CONF_FOLDER = "/broker-config"; private static final String PLUGINS_VOLUME_NAME = "plugins"; private static final String BROKERS_POD_NAME = "che-plugin-broker"; private final ObjectMapper objectMapper = new ObjectMapper(); private final String cheWebsocketEndpoint; private final String brokerPullPolicy; private final AgentAuthEnableEnvVarProvider authEnableEnvVarProvider; private final MachineTokenEnvVarProvider machineTokenEnvVarProvider; private final String artifactsBrokerImage; private final String metadataBrokerImage; private final String pluginRegistryUrl; private final String certificateMountPath; private final CertificateProvisioner certProvisioner; public BrokerEnvironmentFactory( String cheWebsocketEndpoint, String cheWebsocketInternalEndpoint, String brokerPullPolicy, AgentAuthEnableEnvVarProvider authEnableEnvVarProvider, MachineTokenEnvVarProvider machineTokenEnvVarProvider, String artifactsBrokerImage, String metadataBrokerImage, String pluginRegistryExternalUrl, String pluginRegistryInternalUrl, String certificateMountPath, CertificateProvisioner certProvisioner) { this.cheWebsocketEndpoint = isNullOrEmpty(cheWebsocketInternalEndpoint) ? cheWebsocketEndpoint : cheWebsocketInternalEndpoint; this.brokerPullPolicy = brokerPullPolicy; this.authEnableEnvVarProvider = authEnableEnvVarProvider; this.machineTokenEnvVarProvider = machineTokenEnvVarProvider; this.artifactsBrokerImage = artifactsBrokerImage; this.metadataBrokerImage = metadataBrokerImage; this.pluginRegistryUrl = isNullOrEmpty(pluginRegistryInternalUrl) ? pluginRegistryExternalUrl : pluginRegistryInternalUrl; this.certificateMountPath = certificateMountPath; this.certProvisioner = certProvisioner; } /** * Creates {@link KubernetesEnvironment} with everything needed to deploy metadata plugin broker. * * @param pluginFQNs fully qualified names of plugins that needs to be resolved by the broker * @param runtimeID ID of the runtime the broker would be started * @return kubernetes environment (or its extension) with the Plugin broker objects */ public E createForMetadataBroker( Collection pluginFQNs, RuntimeIdentity runtimeID, boolean mergePlugins) throws InfrastructureException { BrokersConfigs brokersConfigs = getBrokersConfigs(pluginFQNs, runtimeID, metadataBrokerImage, mergePlugins); return doCreate(brokersConfigs); } /** * Creates {@link KubernetesEnvironment} with everything needed to deploy artifacts plugin broker. * * @param pluginFQNs fully qualified names of plugins that needs to be resolved by the broker * @param runtimeID ID of the runtime the broker would be started * @param mergePlugins whether the broker should be configured to merge plugins where possible * @return kubernetes environment (or its extension) with the Plugin broker objects */ public E createForArtifactsBroker( Collection pluginFQNs, RuntimeIdentity runtimeID, boolean mergePlugins) throws InfrastructureException { BrokersConfigs brokersConfigs = getBrokersConfigs(pluginFQNs, runtimeID, artifactsBrokerImage, mergePlugins); brokersConfigs .machines .values() .forEach( m -> m.getVolumes().put(PLUGINS_VOLUME_NAME, new VolumeImpl().withPath("/plugins"))); return doCreate(brokersConfigs); } protected BrokersConfigs getBrokersConfigs( Collection pluginFQNs, RuntimeIdentity runtimeID, String brokerImage, boolean mergePlugins) throws InfrastructureException { String configMapName = generateUniqueName(CONFIG_MAP_NAME_SUFFIX); String configMapVolume = generateUniqueName(BROKER_VOLUME); ConfigMap configMap = newConfigMap(configMapName, pluginFQNs); Pod pod = newPod(); List envVars = Stream.of( authEnableEnvVarProvider.get(runtimeID), machineTokenEnvVarProvider.get(runtimeID)) .map(this::asEnvVar) .collect(Collectors.toList()); Container container = newContainer(runtimeID, envVars, brokerImage, configMapVolume, mergePlugins); pod.getSpec().getContainers().add(container); pod.getSpec().getVolumes().add(newConfigMapVolume(configMapName, configMapVolume)); InternalMachineConfig machineConfig = new InternalMachineConfig(); String machineName = Names.machineName(pod.getMetadata(), container); BrokersConfigs configs = new BrokersConfigs(); configs.configMaps = singletonMap(configMapName, configMap); configs.machines = singletonMap(machineName, machineConfig); configs.pods = singletonMap(pod.getMetadata().getName(), pod); return configs; } /** Needed to implement this component in both - Kubernetes and Openshift infrastructures. */ protected abstract E doCreate(BrokersConfigs brokerConfigs); private Container newContainer( RuntimeIdentity runtimeId, List envVars, String image, @Nullable String brokerVolumeName, boolean mergePlugins) { String containerName = generateContainerNameFromImageRef(image); String[] cmdArgs = getCommandLineArgs(runtimeId, mergePlugins).toArray(new String[0]); final ContainerBuilder cb = new ContainerBuilder() .withName(containerName) .withImage(image) .withArgs(cmdArgs) .withImagePullPolicy(brokerPullPolicy) .withEnv(envVars); if (brokerVolumeName != null) { cb.withVolumeMounts( new VolumeMount(CONF_FOLDER + "/", null, brokerVolumeName, true, null, null, null)); cb.addToArgs("--metas", CONF_FOLDER + "/" + CONFIG_FILE); } Container container = cb.build(); Containers.addRamLimit(container, "250Mi"); Containers.addRamRequest(container, "250Mi"); Containers.addCpuLimit(container, "300m"); Containers.addCpuRequest(container, "300m"); return container; } protected List getCommandLineArgs(RuntimeIdentity runtimeId, boolean mergePlugins) { ArrayList args = new ArrayList<>( Arrays.asList( "--push-endpoint", cheWebsocketEndpoint, "--runtime-id", String.format( "%s:%s:%s", runtimeId.getWorkspaceId(), MoreObjects.firstNonNull(runtimeId.getEnvName(), ""), runtimeId.getOwnerId()), "--cacert", certProvisioner.isConfigured() ? certProvisioner.getCertPath() : "", "--registry-address", Strings.nullToEmpty(pluginRegistryUrl))); if (mergePlugins) { args.add("--merge-plugins"); } return args; } private Pod newPod() { return new PodBuilder() .withNewMetadata() .withName(BROKERS_POD_NAME) .endMetadata() .withNewSpec() .withRestartPolicy("Never") .endSpec() .build(); } private ConfigMap newConfigMap(String configMapName, Collection pluginFQNs) throws InternalInfrastructureException { try { return new ConfigMapBuilder() .withNewMetadata() .withName(configMapName) .endMetadata() .withData(singletonMap(CONFIG_FILE, objectMapper.writeValueAsString(pluginFQNs))) .build(); } catch (JsonProcessingException e) { throw new InternalInfrastructureException(e.getMessage(), e); } } private Volume newConfigMapVolume(String configMapName, String configMapVolume) { return new VolumeBuilder() .withName(configMapVolume) .withNewConfigMap() .withName(configMapName) .endConfigMap() .build(); } private EnvVar asEnvVar(Pair envVar) { return new EnvVarBuilder().withName(envVar.first).withValue(envVar.second).build(); } private String generateUniqueName(String suffix) { return NameGenerator.generate(suffix, 6); } /** * Generate a container name from an image reference. Since full image references can be over 63 * characters, we need to strip registry and organization from the image reference to limit name * length. */ @VisibleForTesting protected String generateContainerNameFromImageRef(String image) { String containerName; if (image.contains("@")) { // Image is tagged with digest; we trim digest to 10 chars and remove "sha256" String[] parts = image.split("@"); String imagePart = parts[0]; String digest = parts[1]; if (digest.contains(":")) { digest = digest.split(":")[1]; } if (digest.length() > 10) { digest = digest.substring(0, 10); } image = String.format("%s-%s", imagePart, digest); } containerName = image.toLowerCase().replaceAll("[^/]*/", "").replaceAll("[^\\d\\w-]", "-"); if (containerName.length() > 63) { containerName = containerName.substring(0, 63); } return containerName; } public static class BrokersConfigs { public Map machines; public Map configMaps; public Map pods; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/BrokerPhase.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases; import com.google.common.annotations.Beta; import java.util.List; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; /** * Phase of Che plugin broker lifecycle used to separate and simplify different stages on Che plugin * broker execution. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksandr Garagatyi */ @Beta public abstract class BrokerPhase { protected BrokerPhase nextPhase; @Beta public BrokerPhase then(BrokerPhase next) { this.nextPhase = next; return next; } /** * Executes this phase. Broker phase implementation should call next {@link BrokerPhase} if it is * set. * *

    This API is in Beta and is subject to changes or removal. * * @throws InfrastructureException when an error occurs during the progressing of this stage */ @Beta public abstract List execute() throws InfrastructureException; } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/DeployBroker.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases; import static java.lang.String.format; import static org.eclipse.che.workspace.infrastructure.kubernetes.util.TracingSpanConstants.DEPLOY_BROKER_PHASE; import static org.slf4j.LoggerFactory.getLogger; import com.google.common.annotations.Beta; import com.google.common.collect.ImmutableSet; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.Secret; import io.opentracing.Span; import io.opentracing.Tracer; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.RuntimeLogsPublisher; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesDeployments; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.LogWatchTimeouts; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.LogWatcher; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.PodLogToEventPublisher; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.eclipse.che.workspace.infrastructure.kubernetes.util.UnrecoverablePodEventListener; import org.eclipse.che.workspace.infrastructure.kubernetes.util.UnrecoverablePodEventListenerFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.BrokersResult; import org.slf4j.Logger; /** * Deploys Che plugin broker in a workspace, calls next {@link BrokerPhase} and removes deployment * after next phase completes. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksandr Garagatyi */ @Beta public class DeployBroker extends BrokerPhase { private static final Logger LOG = getLogger(DeployBroker.class); private final RuntimeEventsPublisher runtimeEventsPublisher; private final KubernetesNamespace namespace; private final KubernetesEnvironment brokerEnvironment; private final BrokersResult brokersResult; private final UnrecoverablePodEventListenerFactory factory; private final RuntimeIdentity runtimeId; private final Tracer tracer; private final Map startOptions; public DeployBroker( RuntimeIdentity runtimeId, KubernetesNamespace namespace, KubernetesEnvironment brokerEnvironment, BrokersResult brokersResult, UnrecoverablePodEventListenerFactory factory, RuntimeEventsPublisher runtimeEventsPublisher, Tracer tracer, Map startOptions) { this.runtimeId = runtimeId; this.namespace = namespace; this.brokerEnvironment = brokerEnvironment; this.brokersResult = brokersResult; this.factory = factory; this.runtimeEventsPublisher = runtimeEventsPublisher; this.tracer = tracer; this.startOptions = startOptions; } @Override public List execute() throws InfrastructureException { LOG.debug("Starting brokers pod for workspace '{}'", runtimeId.getWorkspaceId()); Span tracingSpan = tracer.buildSpan(DEPLOY_BROKER_PHASE).start(); TracingTags.WORKSPACE_ID.set(tracingSpan, runtimeId.getWorkspaceId()); KubernetesDeployments deployments = namespace.deployments(); try { // Creates config map that can inject Che tooling plugins meta files into a Che plugin // broker in a workspace. for (ConfigMap configMap : brokerEnvironment.getConfigMaps().values()) { namespace.configMaps().create(configMap); } for (Secret secret : brokerEnvironment.getSecrets().values()) { namespace.secrets().create(secret); } Pod pluginBrokerPod = getPluginBrokerPod(brokerEnvironment.getPodsCopy()); if (factory.isConfigured()) { UnrecoverablePodEventListener unrecoverableEventListener = factory.create( ImmutableSet.of(pluginBrokerPod.getMetadata().getName()), this::handleUnrecoverableEvent); namespace.deployments().watchEvents(unrecoverableEventListener); } namespace .deployments() .watchEvents( new RuntimeLogsPublisher( runtimeEventsPublisher, runtimeId, ImmutableSet.of(pluginBrokerPod.getMetadata().getName()))); deployments.create(pluginBrokerPod); watchLogsIfDebugEnabled(startOptions, pluginBrokerPod); LOG.debug("Brokers pod is created for workspace '{}'", runtimeId.getWorkspaceId()); tracingSpan.finish(); return nextPhase.execute(); } catch (InfrastructureException e) { namespace.deployments().stopWatch(true); // Ensure span is finished with exception message TracingTags.setErrorStatus(tracingSpan, e); tracingSpan.finish(); throw e; } finally { namespace.deployments().stopWatch(); try { deployments.delete(); } catch (InfrastructureException e) { LOG.error("Brokers pod removal failed. Error: " + e.getLocalizedMessage(), e); } try { namespace.secrets().delete(); } catch (InfrastructureException ex) { LOG.error("Brokers secret removal failed. Error: " + ex.getLocalizedMessage(), ex); } try { namespace.configMaps().delete(); } catch (InfrastructureException ex) { LOG.error("Brokers config map removal failed. Error: " + ex.getLocalizedMessage(), ex); } } } private void watchLogsIfDebugEnabled(Map startOptions, Pod pluginBrokerPod) throws InfrastructureException { if (LogWatcher.shouldWatchLogs(startOptions)) { LOG.debug( "Will watch the logs of plugin broker of workspace '{}'", runtimeId.getWorkspaceId()); namespace .deployments() .watchLogs( new PodLogToEventPublisher(runtimeEventsPublisher, runtimeId), runtimeEventsPublisher, LogWatchTimeouts.AGGRESSIVE, ImmutableSet.of(pluginBrokerPod.getMetadata().getName()), LogWatcher.getLogLimitBytes(startOptions)); } } private void handleUnrecoverableEvent(PodEvent podEvent) { String reason = podEvent.getReason(); String message = podEvent.getMessage(); LOG.error( "Unrecoverable event occurred during plugin brokering for workspace '{}' startup: {}, {}, {}", runtimeId.getWorkspaceId(), reason, message, podEvent.getPodName()); brokersResult.error( new InfrastructureException( format( "Unrecoverable event occurred: '%s', '%s', '%s'", reason, message, podEvent.getPodName()))); } private Pod getPluginBrokerPod(Map pods) throws InfrastructureException { if (pods.size() != 1) { throw new InternalInfrastructureException( format( "Plugin broker environment must have only " + "one pod. Workspace `%s` contains `%s` pods.", runtimeId.getWorkspaceId(), pods.size())); } return pods.values().iterator().next(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/KubernetesBrokerEnvironmentFactory.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases; import com.google.common.annotations.Beta; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.workspace.server.spi.provision.env.AgentAuthEnableEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.MachineTokenEnvVarProvider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner; /** * Extends {@link BrokerEnvironmentFactory} to be used in the kubernetes infrastructure. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksandr Garagatyi */ @Beta public class KubernetesBrokerEnvironmentFactory extends BrokerEnvironmentFactory { @Inject public KubernetesBrokerEnvironmentFactory( @Named("che.websocket.endpoint") String cheWebsocketEndpoint, @Nullable @Named("che.websocket.internal.endpoint") String cheWebsocketInternalEndpoint, @Named("che.workspace.plugin_broker.pull_policy") String brokerPullPolicy, @Named("che.infra.kubernetes.trusted_ca.mount_path") String certificateMountPath, AgentAuthEnableEnvVarProvider authEnableEnvVarProvider, MachineTokenEnvVarProvider machineTokenEnvVarProvider, @Named("che.workspace.plugin_broker.artifacts.image") String artifactsBrokerImage, @Named("che.workspace.plugin_broker.metadata.image") String metadataBrokerImage, @Nullable @Named("che.workspace.plugin_registry_url") String pluginRegistryExternalUrl, @Nullable @Named("che.workspace.plugin_registry_internal_url") String pluginRegistryInternalUrl, CertificateProvisioner certProvisioner) { super( cheWebsocketEndpoint, cheWebsocketInternalEndpoint, brokerPullPolicy, authEnableEnvVarProvider, machineTokenEnvVarProvider, artifactsBrokerImage, metadataBrokerImage, pluginRegistryExternalUrl, pluginRegistryInternalUrl, certificateMountPath, certProvisioner); } @Override protected KubernetesEnvironment doCreate(BrokersConfigs brokersConfigs) { return KubernetesEnvironment.builder() .setConfigMaps(brokersConfigs.configMaps) .setMachines(brokersConfigs.machines) .setPods(brokersConfigs.pods) .build(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/ListenBrokerEvents.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases; import com.google.common.annotations.Beta; import java.util.List; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.BrokersResult; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.KubernetesPluginsToolingValidator; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events.BrokerEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events.BrokerStatusListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Subscribes to Che plugin broker events, passes future that should be completed upon broker result * received to {@link BrokerStatusListener} and calls next {@link BrokerPhase}. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksandr Garagatyi */ @Beta public class ListenBrokerEvents extends BrokerPhase { private static final Logger LOG = LoggerFactory.getLogger(ListenBrokerEvents.class); private final String workspaceId; private final BrokersResult brokersResult; private final EventService eventService; private final KubernetesPluginsToolingValidator pluginsValidator; public ListenBrokerEvents( String workspaceId, KubernetesPluginsToolingValidator pluginsValidator, BrokersResult brokersResult, EventService eventService) { this.workspaceId = workspaceId; this.pluginsValidator = pluginsValidator; this.brokersResult = brokersResult; this.eventService = eventService; } @Override public List execute() throws InfrastructureException { BrokerStatusListener brokerStatusListener = new BrokerStatusListener(workspaceId, pluginsValidator, brokersResult); try { LOG.debug("Subscribing broker events listener for workspace '{}'", workspaceId); eventService.subscribe(brokerStatusListener, BrokerEvent.class); return nextPhase.execute(); } finally { eventService.unsubscribe(brokerStatusListener); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/PrepareStorage.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases; import static org.eclipse.che.workspace.infrastructure.kubernetes.util.TracingSpanConstants.PREPARE_STORAGE_PHASE; import com.google.common.annotations.Beta; import io.opentracing.Span; import io.opentracing.Tracer; import java.util.List; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.StartSynchronizer; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Prepares PVC in a workspace and calls next {@link BrokerPhase}. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksandr Garagatyi */ @Beta public class PrepareStorage extends BrokerPhase { private final RuntimeIdentity identity; private final Tracer tracer; public PrepareStorage( RuntimeIdentity identity, KubernetesEnvironment brokerEnvironment, StartSynchronizer startSynchronizer, Tracer tracer) { this.identity = identity; this.tracer = tracer; } @Override public List execute() throws InfrastructureException { Span tracingSpan = tracer.buildSpan(PREPARE_STORAGE_PHASE).start(); TracingTags.WORKSPACE_ID.set(tracingSpan, identity.getWorkspaceId()); return nextPhase.execute(); } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/WaitBrokerResult.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases; import static org.eclipse.che.workspace.infrastructure.kubernetes.util.TracingSpanConstants.WAIT_BROKERS_RESULT_PHASE; import com.google.common.annotations.Beta; import io.opentracing.Span; import io.opentracing.Tracer; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.BrokersResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Wait until Che plugin broker future finishes and returns resulting workspace tooling or error. * Also calls next {@link BrokerPhase}. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksandr Garagatyi */ @Beta public class WaitBrokerResult extends BrokerPhase { private static final Logger LOG = LoggerFactory.getLogger(WaitBrokerResult.class); private final BrokersResult brokersResult; private final String workspaceId; private final Tracer tracer; private final int resultWaitingTimeout; public WaitBrokerResult( String workspaceId, BrokersResult brokersResult, int resultWaitingTimeout, Tracer tracer) { this.workspaceId = workspaceId; this.brokersResult = brokersResult; this.resultWaitingTimeout = resultWaitingTimeout; this.tracer = tracer; } @Override public List execute() throws InfrastructureException { Span tracingSpan = tracer.buildSpan(WAIT_BROKERS_RESULT_PHASE).start(); TracingTags.WORKSPACE_ID.set(tracingSpan, workspaceId); try { LOG.debug("Trying to get brokers result for workspace '{}'", workspaceId); return brokersResult.get(resultWaitingTimeout, TimeUnit.MINUTES); } catch (InterruptedException e) { TracingTags.setErrorStatus(tracingSpan, e); throw new InfrastructureException( "Plugins installation process was interrupted. Error: " + e.getMessage(), e); } catch (ExecutionException e) { TracingTags.setErrorStatus(tracingSpan, e); throw new InfrastructureException( "Plugins installation process failed. Error: " + e.getCause().getMessage(), e.getCause()); } catch (TimeoutException e) { TracingTags.setErrorStatus(tracingSpan, e); throw new InfrastructureException("Plugins installation process timed out"); } finally { tracingSpan.finish(); } } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/events/BrokerEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events; import com.google.common.annotations.Beta; import java.util.List; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.eclipse.che.api.workspace.shared.dto.BrokerStatus; import org.eclipse.che.api.workspace.shared.dto.event.BrokerStatusChangedEvent; /** * Event sent by a plugin broker with results of broker invocation. * *

    This class differs from {@link BrokerStatusChangedEvent} it is version of latter with a * prettier format. It has workspace tooling in a POJO representation instead of stringified JSON. * *

    This API is in Beta and is subject to changes or removal. * * @see BrokerStatusChangedEvent */ @Beta public class BrokerEvent { private BrokerStatus status; private RuntimeIdentity runtimeId; private String error; private List tooling; @SuppressWarnings("unused") public BrokerEvent() {} public BrokerEvent(BrokerStatusChangedEvent resultEvent, List tooling) { this.error = resultEvent.getError(); this.status = resultEvent.getStatus(); this.runtimeId = resultEvent.getRuntimeId(); this.tooling = tooling; } public BrokerStatus getStatus() { return status; } public BrokerEvent withStatus(BrokerStatus status) { this.status = status; return this; } public RuntimeIdentity getRuntimeId() { return runtimeId; } public BrokerEvent withRuntimeId(RuntimeIdentity runtimeId) { this.runtimeId = runtimeId; return this; } public String getError() { return error; } public BrokerEvent withError(String error) { this.error = error; return this; } List getTooling() { return tooling; } BrokerEvent withTooling(List tooling) { this.tooling = tooling; return this; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/events/BrokerService.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events; import static com.google.common.base.Strings.isNullOrEmpty; import static org.slf4j.LoggerFactory.getLogger; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.Beta; import java.io.IOException; import java.util.List; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.eclipse.che.api.workspace.shared.dto.event.BrokerLogEvent; import org.eclipse.che.api.workspace.shared.dto.event.BrokerStatusChangedEvent; import org.eclipse.che.api.workspace.shared.dto.event.RuntimeLogEvent; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.dto.server.DtoFactory; import org.slf4j.Logger; /** * Configure JSON_RPC consumers of Che plugin broker events. Also converts {@link * BrokerStatusChangedEvent} to {@link BrokerEvent}. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksander Garagatyi */ @Beta @Singleton public class BrokerService { private static final Logger LOG = getLogger(BrokerService.class); public static final String BROKER_RESULT_METHOD = "broker/result"; public static final String BROKER_STATUS_CHANGED_METHOD = "broker/statusChanged"; public static final String BROKER_LOG_METHOD = "broker/log"; private final ObjectMapper objectMapper = new ObjectMapper(); private final EventService eventService; @Inject public BrokerService(EventService eventService) { this.eventService = eventService; } @Inject public void configureMethods(RequestHandlerConfigurator requestHandler) { requestHandler .newConfiguration() .methodName(BROKER_STATUS_CHANGED_METHOD) .paramsAsDto(BrokerStatusChangedEvent.class) .noResult() .withConsumer(this::handle); requestHandler .newConfiguration() .methodName(BROKER_RESULT_METHOD) .paramsAsDto(BrokerStatusChangedEvent.class) .noResult() .withConsumer(this::handle); requestHandler .newConfiguration() .methodName(BROKER_LOG_METHOD) .paramsAsDto(BrokerLogEvent.class) .noResult() .withConsumer(this::handle); } private void handle(BrokerLogEvent brokerLogEvent) { eventService.publish( DtoFactory.newDto(RuntimeLogEvent.class) .withRuntimeId(brokerLogEvent.getRuntimeId()) .withText(brokerLogEvent.getText()) .withTime(brokerLogEvent.getTime())); } private void handle(BrokerStatusChangedEvent event) { String encodedTooling = event.getTooling(); RuntimeIdentity runtimeId = event.getRuntimeId(); if (event.getStatus() == null || runtimeId == null || runtimeId.getWorkspaceId() == null) { LOG.error("Broker event skipped due to illegal content: {}", event); return; } eventService.publish(new BrokerEvent(event, parseTooling(encodedTooling))); } @Nullable private List parseTooling(String toolingString) { if (!isNullOrEmpty(toolingString)) { try { List plugins = objectMapper.readValue(toolingString, new TypeReference>() {}); // when id of plugin is not set, we can compose it from publisher, name and version return plugins.stream().map(this::composePluginIdWhenNull).collect(Collectors.toList()); } catch (IOException e) { LOG.error("Parsing Che plugin broker event failed. Error: " + e.getMessage(), e); } } return null; } private ChePlugin composePluginIdWhenNull(ChePlugin plugin) { if (isNullOrEmpty(plugin.getId()) && !isNullOrEmpty(plugin.getPublisher()) && !isNullOrEmpty(plugin.getName()) && !isNullOrEmpty(plugin.getVersion())) { plugin.setId(plugin.getPublisher() + "/" + plugin.getName() + "/" + plugin.getVersion()); } return plugin; } } ================================================ FILE: infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/events/BrokerStatusListener.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events; import static java.lang.String.format; import static org.slf4j.LoggerFactory.getLogger; import com.google.common.annotations.Beta; import java.util.List; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.BrokersResult; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.KubernetesPluginsToolingValidator; import org.slf4j.Logger; /** * Listens for {@link BrokerEvent} and completes or exceptionally completes a start and done futures * depending on the event state. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksandr Garagatyi */ @Beta public class BrokerStatusListener implements EventSubscriber { private static final Logger LOG = getLogger(BrokerStatusListener.class); private final String workspaceId; private final KubernetesPluginsToolingValidator pluginsValidator; private final BrokersResult brokersResult; public BrokerStatusListener( String workspaceId, KubernetesPluginsToolingValidator pluginsValidator, BrokersResult brokersResult) { this.workspaceId = workspaceId; this.pluginsValidator = pluginsValidator; this.brokersResult = brokersResult; } @Override public void onEvent(BrokerEvent event) { if (event.getRuntimeId() == null || !workspaceId.equals(event.getRuntimeId().getWorkspaceId())) { return; } switch (event.getStatus()) { case DONE: List tooling = event.getTooling(); if (tooling != null) { try { pluginsValidator.validatePluginNames(tooling); } catch (ValidationException e) { brokersResult.error(e); return; } try { brokersResult.setResult(tooling); } catch (InfrastructureException e) { LOG.error(e.getLocalizedMessage(), e); } } else { brokersResult.error( new InternalInfrastructureException( format( "Plugin brokering process for workspace `%s` is finished but plugins list is missing", workspaceId))); } break; case FAILED: brokersResult.error( new InfrastructureException( format( "Plugin brokering process for workspace %s failed with error: %s", workspaceId, event.getError()))); break; case STARTED: default: // do nothing } } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/AnnotationsTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.testng.annotations.Test; /** * Test for {@link Annotations}. * * @author Sergii Leshchenko */ public class AnnotationsTest { static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); static final Map ATTRIBUTES = singletonMap("key", "value"); static final String stringAttributes = GSON.toJson(ATTRIBUTES); static final String stringEmptyAttributes = GSON.toJson(emptyMap()); @Test public void serialization() { Map serialized = Annotations.newSerializer() .server( "my-server1/http", new ServerConfigImpl("8000/tcp", "http", "/api/info", ATTRIBUTES)) .servers( ImmutableMap.of( "my-server2", new ServerConfigImpl("8080/tcp", "ws", "/connect", emptyMap()))) .machineName("test-machine") .annotations(); Map expected = ImmutableMap.builder() .put("org.eclipse.che.server.my-server1/http.port", "8000/tcp") .put("org.eclipse.che.server.my-server1/http.protocol", "http") .put("org.eclipse.che.server.my-server1/http.path", "/api/info") .put("org.eclipse.che.server.my-server1/http.attributes", stringAttributes) .put("org.eclipse.che.server.my-server2.port", "8080/tcp") .put("org.eclipse.che.server.my-server2.protocol", "ws") .put("org.eclipse.che.server.my-server2.path", "/connect") .put("org.eclipse.che.server.my-server2.attributes", stringEmptyAttributes) .put("org.eclipse.che.machine.name", "test-machine") .build(); assertEquals(serialized, expected); } @Test public void deserialization() { ImmutableMap annotations = ImmutableMap.builder() .put("custom-label", "value") .put("org.eclipse.che.server.my-server1/http.port", "8000/tcp") .put("org.eclipse.che.server.my-server1/http.protocol", "http") .put("org.eclipse.che.server.my-server1/http.path", "/api/info") .put("org.eclipse.che.server.my-server2.port", "8080/tcp") .put("org.eclipse.che.server.my-server2.protocol", "ws") .put("org.eclipse.che.server.my-server2.path", "/connect") .put("org.eclipse.che.server.my-server2.attributes", stringAttributes) .put("org.eclipse.che.server.my-server3.port", "7070/tcp") .put("org.eclipse.che.server.my-server3.protocol", "http") .put("org.eclipse.che.server.my-server3.attributes", stringEmptyAttributes) .put("org.eclipse.che.machine.name", "test-machine") .build(); Annotations.Deserializer deserializer = Annotations.newDeserializer(annotations); Map servers = deserializer.servers(); assertEquals(deserializer.machineName(), "test-machine"); Map expected = new HashMap<>(); expected.put("my-server1/http", new ServerConfigImpl("8000/tcp", "http", "/api/info", null)); expected.put("my-server2", new ServerConfigImpl("8080/tcp", "ws", "/connect", ATTRIBUTES)); expected.put("my-server3", new ServerConfigImpl("7070/tcp", "http", null, emptyMap())); assertEquals(servers, expected); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/K8sInfraNamespaceWsAttributeValidatorTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import static java.util.Collections.emptyMap; import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import com.google.common.collect.ImmutableMap; import javax.inject.Provider; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link K8sInfraNamespaceWsAttributeValidator}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class K8sInfraNamespaceWsAttributeValidatorTest { @Mock private KubernetesNamespaceFactory namespaceFactory; @Mock private Provider namespaceFactoryProvider; @InjectMocks private K8sInfraNamespaceWsAttributeValidator validator; @BeforeMethod public void setUp() { lenient().when(namespaceFactoryProvider.get()).thenReturn(namespaceFactory); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "The specified namespace [a]{64} is invalid: must be no more than 63 characters") public void testWhenNamespaceNameContainsMoreThan63Characters() throws ValidationException { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 64; i++) { sb.append('a'); } validator.validate( ImmutableMap.of(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, sb.toString())); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "The specified namespace .* is invalid: a DNS\\-1123 label must consist " + "of lower case alphanumeric characters or '\\-', and must start and end with an " + "alphanumeric character \\(e\\.g\\. .*", dataProvider = "invalidNamespaces") public void testWhenNamespaceNameHasNoValidFormat(String name) throws ValidationException { validator.validate(ImmutableMap.of(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, name)); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "The specified namespace name is not valid") public void testWhenNamespaceFactoryThrowsErrorOnCheckingIfNamespaceIsAllowed() throws ValidationException { doThrow(new ValidationException("The specified namespace name is not valid")) .when(namespaceFactory) .checkIfNamespaceIsAllowed(anyString()); validator.validate(ImmutableMap.of(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "any")); } @Test public void shouldDoNothingWhenNamespaceAttributeIsMissing() throws ValidationException { validator.validate(emptyMap()); verifyNoMoreInteractions(namespaceFactory); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "The namespace from the provided object \"new\" does not match the actual namespace \"actual\"") public void shouldDoNotAllowToChangeNamespaceAttribute() throws ValidationException { validator.validateUpdate( ImmutableMap.of(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "actual"), ImmutableMap.of(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "new")); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "The namespace information must not be updated or deleted. You must provide \"infrastructureNamespace\" attribute with \"che-workspaces\" as a value") public void shouldDoNotAllowToRemoveNamespaceAttribute() throws ValidationException { validator.validateUpdate( ImmutableMap.of(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "che-workspaces"), emptyMap()); } @Test public void shouldValidateFullyIfExistingIsEmpty() throws ValidationException { validator.validateUpdate( emptyMap(), ImmutableMap.of(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "che-workspaces")); verify(namespaceFactory).checkIfNamespaceIsAllowed(eq("che-workspaces")); } @Test public void shouldNotValidateFullyIfExistingIsNotEmpty() throws ValidationException { validator.validateUpdate( ImmutableMap.of(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "che-workspaces"), ImmutableMap.of(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "che-workspaces")); verify(namespaceFactory, never()).checkIfNamespaceIsAllowed(any()); } @DataProvider public Object[][] invalidNamespaces() { return new String[][] {{"name!space"}, {"name@space"}, {"-namespace"}, {"namespace-"}}; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesArtifactsBrokerApplierTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import static org.eclipse.che.workspace.infrastructure.kubernetes.Names.createMachineNameAnnotations; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.doReturn; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeBuilder; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.KubernetesArtifactsBrokerApplier; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.BrokerEnvironmentFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link KubernetesArtifactsBrokerApplier}. * * @author Angel Misevski */ @Listeners(MockitoTestNGListener.class) public class KubernetesArtifactsBrokerApplierTest { private static final String WORKSPACE_POD_NAME = "workspacePod"; private static final String WORKSPACE_MACHINE_NAME = "workspaceMachine"; private static final String WORKSPACE_CONTAINER_NAME = "workspaceContainer"; private static final Map workspacePodAnnotations = createMachineNameAnnotations(WORKSPACE_CONTAINER_NAME, WORKSPACE_MACHINE_NAME); private static final String BROKER_POD_NAME = "brokerPod"; private static final String BROKER_MACHINE_NAME = "brokerMachine"; private static final String BROKER_CONTAINER_NAME = "brokerContainer"; private static final String BROKER_CONFIGMAP_NAME = "brokerConfigMap"; private static final Map brokerPodAnnotations = createMachineNameAnnotations(BROKER_CONTAINER_NAME, BROKER_MACHINE_NAME); private static final Map brokerConfigMapData = ImmutableMap.of("brokerConfigKey", "brokerConfigValue"); @Mock private BrokerEnvironmentFactory brokerEnvironmentFactory; @Mock private RuntimeIdentity runtimeID; @Mock private Collection pluginFQNs; // Broker Environment mocks @Mock private InternalMachineConfig brokerMachine; private Volume brokerVolume; private ConfigMap brokerConfigMap; private Container brokerContainer; // Workspace Environment mocks private KubernetesEnvironment workspaceEnvironment; private Pod workspacePod; private KubernetesArtifactsBrokerApplier applier; @BeforeMethod public void setUp() throws Exception { // Workspace env setup ObjectMeta workspacePodMeta = new ObjectMetaBuilder().withAnnotations(workspacePodAnnotations).build(); workspacePod = new PodBuilder().withMetadata(workspacePodMeta).withSpec(new PodSpec()).build(); Map workspaceConfigMaps = new HashMap<>(); workspaceEnvironment = KubernetesEnvironment.builder() .setPods(ImmutableMap.of(WORKSPACE_POD_NAME, workspacePod)) .setMachines(new HashMap<>()) .setConfigMaps(workspaceConfigMaps) .build(); // Broker env setup ObjectMeta brokerPodMeta = new ObjectMetaBuilder().withAnnotations(brokerPodAnnotations).build(); brokerContainer = new ContainerBuilder().withName(BROKER_CONTAINER_NAME).build(); brokerVolume = new VolumeBuilder().build(); Pod brokerPod = new PodBuilder() .withMetadata(brokerPodMeta) .withNewSpec() .withContainers(brokerContainer) .withVolumes(brokerVolume) .endSpec() .build(); brokerConfigMap = new ConfigMapBuilder().addToData(brokerConfigMapData).build(); KubernetesEnvironment brokerEnvironment = KubernetesEnvironment.builder() .setPods(ImmutableMap.of(BROKER_POD_NAME, brokerPod)) .setConfigMaps(ImmutableMap.of(BROKER_CONFIGMAP_NAME, brokerConfigMap)) .setMachines(ImmutableMap.of(BROKER_MACHINE_NAME, brokerMachine)) .build(); doReturn(brokerEnvironment) .when(brokerEnvironmentFactory) .createForArtifactsBroker(any(), any(), anyBoolean()); applier = new KubernetesArtifactsBrokerApplier<>(brokerEnvironmentFactory); } @Test public void shouldAddBrokerMachineToWorkspaceEnvironment() throws Exception { applier.apply(workspaceEnvironment, runtimeID, pluginFQNs, false); assertNotNull(workspaceEnvironment.getMachines()); assertTrue(workspaceEnvironment.getMachines().values().contains(brokerMachine)); } @Test public void shouldAddBrokerConfigMapsToWorkspaceEnvironment() throws Exception { applier.apply(workspaceEnvironment, runtimeID, pluginFQNs, false); ConfigMap workspaceConfigMap = workspaceEnvironment.getConfigMaps().get(BROKER_CONFIGMAP_NAME); assertNotNull(workspaceConfigMap); assertFalse(workspaceConfigMap.getData().isEmpty()); assertTrue( workspaceConfigMap.getData().entrySet().stream() .allMatch(e -> brokerConfigMap.getData().get(e.getKey()).equals(e.getValue()))); } @Test public void shouldAddBrokerAsInitContainerOnWorkspacePod() throws Exception { applier.apply(workspaceEnvironment, runtimeID, pluginFQNs, false); List initContainers = workspacePod.getSpec().getInitContainers(); assertEquals(initContainers.size(), 1); assertEquals(initContainers.iterator().next(), brokerContainer); } @Test public void shouldAddBrokerVolumesToWorkspacePod() throws Exception { applier.apply(workspaceEnvironment, runtimeID, pluginFQNs, false); List workspaceVolumes = workspacePod.getSpec().getVolumes(); assertEquals(workspaceVolumes.size(), 1); assertEquals(workspaceVolumes.iterator().next(), brokerVolume); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesEnvironmentProvisionerTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.when; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesEnvironmentProvisioner.KubernetesEnvironmentProvisionerImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GatewayRouterProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitConfigProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ImagePullSecretProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.PodTerminationGracePeriodProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecurityContextProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ServiceAccountProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SshKeysProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisionerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.UniqueNamesProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSslCertificateProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.env.EnvVarsConverter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.limits.ram.ContainerResourceProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter; import org.eclipse.che.workspace.infrastructure.kubernetes.server.PreviewUrlExposer; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link KubernetesEnvironmentProvisioner}. * * @author Anton Korneta */ @Listeners(MockitoTestNGListener.class) public class KubernetesEnvironmentProvisionerTest { @Mock private UniqueNamesProvisioner uniqueNamesProvisioner; @Mock private KubernetesEnvironment k8sEnv; @Mock private RuntimeIdentity runtimeIdentity; @Mock private EnvVarsConverter envVarsProvisioner; @Mock private ServersConverter serversProvisioner; @Mock private RestartPolicyRewriter restartPolicyRewriter; @Mock private ContainerResourceProvisioner ramLimitProvisioner; @Mock private SecurityContextProvisioner securityContextProvisioner; @Mock private PodTerminationGracePeriodProvisioner podTerminationGracePeriodProvisioner; @Mock private TlsProvisionerProvider externalServerIngressTlsProvisionerProvider; @Mock private TlsProvisioner externalServerIngressTlsProvisioner; @Mock private ImagePullSecretProvisioner imagePullSecretProvisioner; @Mock private ServiceAccountProvisioner serviceAccountProvisioner; @Mock private CertificateProvisioner certificateProvisioner; @Mock private SshKeysProvisioner sshKeysProvisioner; @Mock private GitConfigProvisioner gitConfigProvisioner; @Mock private PreviewUrlExposer previewUrlExposer; @Mock private VcsSslCertificateProvisioner vcsSslCertificateProvisioner; @Mock private GatewayRouterProvisioner gatewayRouterProvisioner; private KubernetesEnvironmentProvisioner k8sInfraProvisioner; private InOrder provisionOrder; @BeforeMethod public void setUp() { when(externalServerIngressTlsProvisionerProvider.get()) .thenReturn(externalServerIngressTlsProvisioner); k8sInfraProvisioner = new KubernetesEnvironmentProvisionerImpl( uniqueNamesProvisioner, serversProvisioner, envVarsProvisioner, restartPolicyRewriter, ramLimitProvisioner, securityContextProvisioner, podTerminationGracePeriodProvisioner, externalServerIngressTlsProvisionerProvider, imagePullSecretProvisioner, serviceAccountProvisioner, certificateProvisioner, sshKeysProvisioner, gitConfigProvisioner, previewUrlExposer, vcsSslCertificateProvisioner, gatewayRouterProvisioner); provisionOrder = inOrder( uniqueNamesProvisioner, serversProvisioner, envVarsProvisioner, restartPolicyRewriter, ramLimitProvisioner, securityContextProvisioner, podTerminationGracePeriodProvisioner, externalServerIngressTlsProvisioner, imagePullSecretProvisioner, serviceAccountProvisioner, certificateProvisioner, gitConfigProvisioner, previewUrlExposer, gatewayRouterProvisioner); } @Test public void performsOrderedProvisioning() throws Exception { k8sInfraProvisioner.provision(k8sEnv, runtimeIdentity); provisionOrder.verify(serversProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(envVarsProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(restartPolicyRewriter).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(ramLimitProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder .verify(externalServerIngressTlsProvisioner) .provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(securityContextProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder .verify(podTerminationGracePeriodProvisioner) .provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(imagePullSecretProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(serviceAccountProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(certificateProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(gitConfigProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(gatewayRouterProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(uniqueNamesProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verifyNoMoreInteractions(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/StartSynchronizerTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import java.lang.reflect.Field; import java.util.Collections; import java.util.Set; import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.server.spi.RuntimeStartInterruptedException; import org.eclipse.che.workspace.infrastructure.kubernetes.event.KubernetesRuntimeStoppedEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.event.KubernetesRuntimeStoppingEvent; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link StartSynchronizer} * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class StartSynchronizerTest { @Mock private EventService eventService; private RuntimeIdentityImpl runtimeId; private StartSynchronizer startSynchronizer; @BeforeMethod public void setUp() { runtimeId = new RuntimeIdentityImpl("workspace123", "envName", "ownerId", "infraNamespace"); startSynchronizer = new StartSynchronizer(eventService, runtimeId); } @Test public void testSuccessfulStartCompletion() throws Exception { // given startSynchronizer.setStartThread(); // when startSynchronizer.complete(); // then assertTrue(startSynchronizer.isCompleted()); assertTrue(startSynchronizer.getStartFailure().isDone()); assertFalse(startSynchronizer.getStartFailure().isCompletedExceptionally()); } @Test public void testFailureStartCompletion() throws Exception { // given startSynchronizer.setStartThread(); RuntimeStartInterruptedException expected = new RuntimeStartInterruptedException(runtimeId); // when startSynchronizer.completeExceptionally(expected); // then assertTrue(startSynchronizer.isCompleted()); assertTrue(startSynchronizer.getStartFailure().isCompletedExceptionally()); try { startSynchronizer.getStartFailure().getNow(null); fail("Start failure is empty"); } catch (CompletionException actual) { assertEquals(actual.getCause(), expected); } } @Test(expectedExceptions = RuntimeStartInterruptedException.class) public void shouldThrowExceptionOnCheckingFailureIfStartIsCompletedExceptionally() throws Exception { // given startSynchronizer.start(); RuntimeStartInterruptedException expected = new RuntimeStartInterruptedException(runtimeId); startSynchronizer.completeExceptionally(expected); // when startSynchronizer.checkFailure(); } @Test public void shouldNotThrowExceptionOnCheckingFailureIfStartIsNotCompleted() throws Exception { // given startSynchronizer.start(); // when startSynchronizer.checkFailure(); } @Test public void shouldNotThrowExceptionOnCheckingFailureIfStartIsCompleted() throws Exception { // given startSynchronizer.start(); startSynchronizer.complete(); // when startSynchronizer.checkFailure(); } @Test public void shouldInterruptStartThread() throws Exception { // given startSynchronizer.start(); startSynchronizer.setStartThread(); // when startSynchronizer.interrupt(); // then assertTrue(Thread.interrupted()); } @Test( expectedExceptions = InternalInfrastructureException.class, expectedExceptionsMessageRegExp = "Runtime is already started") public void shouldNotSetStartThreadIfItIsAlreadySet() throws Exception { // given startSynchronizer.setStartThread(); // when startSynchronizer.setStartThread(); } @Test public void shouldNotInterruptThreadIfItWasNotSet() { // given startSynchronizer.start(); // when startSynchronizer.interrupt(); // then assertFalse(Thread.interrupted()); } @Test public void shouldSubscribeOnRuntimeStoppingAndStoppedEventsWhenStartIsCalled() { // when startSynchronizer.start(); // then verify(eventService).subscribe(any(), eq(KubernetesRuntimeStoppingEvent.class)); verify(eventService).subscribe(any(), eq(KubernetesRuntimeStoppedEvent.class)); } @Test(expectedExceptions = RuntimeStartInterruptedException.class) public void shouldInterruptStartWhenStoppingEventIsPublished() throws Exception { // given EventService eventService = new EventService(); StartSynchronizer localStartSynchronizer = new StartSynchronizer(eventService, runtimeId); localStartSynchronizer.start(); // when eventService.publish(new KubernetesRuntimeStoppingEvent("workspace123")); // then localStartSynchronizer.checkFailure(); } @Test public void shouldCompleteStartWhenStoppedEventIsPublished() { // given EventService eventService = new EventService(); StartSynchronizer localStartSynchronizer = new StartSynchronizer(eventService, runtimeId); localStartSynchronizer.start(); // when eventService.publish(new KubernetesRuntimeStoppedEvent("workspace123")); // then assertTrue(localStartSynchronizer.isCompleted()); } @Test(expectedExceptions = RuntimeStartInterruptedException.class) public void shouldRethrowOriginalExceptionIfStartCompletedExceptionallyOnCompletion() throws Exception { // given startSynchronizer.completeExceptionally(new RuntimeStartInterruptedException(runtimeId)); // when startSynchronizer.complete(); } @Test public void shouldUnsubscribeEventsWhenItIsCompleted() throws Exception { // given runtimeId = new RuntimeIdentityImpl("workspace123", "envName", "ownerId", "infraNamespace"); EventService eventService = new EventService(); startSynchronizer = new StartSynchronizer(eventService, runtimeId); startSynchronizer.start(); // when startSynchronizer.complete(); // then assertSubscribersNumber(eventService, KubernetesRuntimeStoppingEvent.class, 0); assertSubscribersNumber(eventService, KubernetesRuntimeStoppedEvent.class, 0); } @Test public void shouldUnsubscribeEventsWhenItIscCompleteExceptionally() throws Exception { // given runtimeId = new RuntimeIdentityImpl("workspace123", "envName", "ownerId", "infraNamespace"); EventService eventService = new EventService(); startSynchronizer = new StartSynchronizer(eventService, runtimeId); startSynchronizer.start(); // when startSynchronizer.completeExceptionally(new Exception()); // then assertSubscribersNumber(eventService, KubernetesRuntimeStoppingEvent.class, 0); assertSubscribersNumber(eventService, KubernetesRuntimeStoppedEvent.class, 0); } @Test public void shouldNotHaveAnyEventSubscribersBeforeStart() throws Exception { // given runtimeId = new RuntimeIdentityImpl("workspace123", "envName", "ownerId", "infraNamespace"); EventService eventService = new EventService(); // when startSynchronizer = new StartSynchronizer(eventService, runtimeId); // then assertSubscriberTypesNumber(eventService, 0); } @Test public void shouldAddSubscribersAfterStart() throws Exception { // given runtimeId = new RuntimeIdentityImpl("workspace123", "envName", "ownerId", "infraNamespace"); EventService eventService = new EventService(); startSynchronizer = new StartSynchronizer(eventService, runtimeId); // when startSynchronizer.start(); // then assertSubscriberTypesNumber(eventService, 2); assertSubscribersNumber(eventService, KubernetesRuntimeStoppingEvent.class, 1); assertSubscribersNumber(eventService, KubernetesRuntimeStoppedEvent.class, 1); } @Test public void shouldAwaitTerminationWhenItIsCompleted() throws Exception { // given startSynchronizer.complete(); // when boolean isInterrupted = startSynchronizer.awaitInterruption(1, TimeUnit.SECONDS); // then assertFalse(isInterrupted); } @Test public void shouldAwaitTerminationWhenItIsCompletedExceptionally() throws Exception { // given startSynchronizer.completeExceptionally(new RuntimeStartInterruptedException(runtimeId)); // when boolean isInterrupted = startSynchronizer.awaitInterruption(1, TimeUnit.SECONDS); // then assertTrue(isInterrupted); } @Test public void shouldReturnFalseOnAwaitingTerminationWhenItIsNotCompletedInTime() throws Exception { // given startSynchronizer.start(); // when boolean isInterrupted = startSynchronizer.awaitInterruption(10, TimeUnit.MILLISECONDS); // then assertFalse(isInterrupted); } @Test public void shouldNotInterruptStartIfItIsNotStarted() { // when boolean isInterrupted = startSynchronizer.interrupt(); // then assertFalse(isInterrupted); } @Test public void shouldWrapExceptionIntoInternalExcWhenItIsCompletedWithNonInfraException() { // given RuntimeException toThrow = new RuntimeException("test exception"); startSynchronizer.completeExceptionally(toThrow); // when InfrastructureException startFailure = startSynchronizer.getStartFailureNow(); // then assertTrue(startFailure instanceof InternalInfrastructureException); assertEquals(startFailure.getCause(), toThrow); } @Test public void shouldReturnStartFailureWhenItIsCompletedExceptionally() { // given InfrastructureException toThrow = new RuntimeStartInterruptedException(runtimeId); startSynchronizer.completeExceptionally(toThrow); // when InfrastructureException startFailure = startSynchronizer.getStartFailureNow(); // then assertTrue(startFailure instanceof RuntimeStartInterruptedException); assertEquals(startFailure, toThrow); } @Test public void shouldReturnNullOnGettingStartFailureWhenItIsNotCompletedExceptionally() throws Exception { // given startSynchronizer.complete(); // when InfrastructureException startFailure = startSynchronizer.getStartFailureNow(); // then assertNull(startFailure); } @Test public void shouldReturnStartFailureWhenItIsNotCompletedYet() { // when InfrastructureException startFailure = startSynchronizer.getStartFailureNow(); // then assertNull(startFailure); } private static void assertSubscribersNumber( EventService service, Class eventType, int expectNumberOfSubscribers) throws Exception { Field privateStringField = EventService.class.getDeclaredField("subscribersByEventType"); privateStringField.setAccessible(true); ConcurrentMap, Set> subscribersByEventType = (ConcurrentMap, Set>) privateStringField.get(service); Set subscribers = subscribersByEventType.getOrDefault(eventType, Collections.emptySet()); assertEquals(subscribers.size(), expectNumberOfSubscribers); } private static void assertSubscriberTypesNumber( EventService service, int expectNumberOfSubscribers) throws Exception { Field privateStringField = EventService.class.getDeclaredField("subscribersByEventType"); privateStringField.setAccessible(true); ConcurrentMap, Set> subscribersByEventType = (ConcurrentMap, Set>) privateStringField.get(service); assertEquals(subscribersByEventType.size(), expectNumberOfSubscribers); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/api/server/KubernetesNamespaceServiceTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.api.server; import static io.restassured.RestAssured.given; import static java.util.Collections.singletonList; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; import static org.everrest.assured.JettyHttpServer.SECURE_PATH; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableMap; import io.restassured.response.Response; import java.util.Collections; import java.util.List; import org.eclipse.che.api.core.rest.ApiExceptionMapper; import org.eclipse.che.api.core.rest.CheJsonProvider; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.KubernetesNamespaceMetaDto; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.NamespaceProvisioner; import org.everrest.assured.EverrestJetty; import org.everrest.core.Filter; import org.everrest.core.GenericContainerRequest; import org.everrest.core.RequestFilter; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests for {@link KubernetesNamespaceService} * * @author Sergii Leshchenko */ @Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class}) public class KubernetesNamespaceServiceTest { @SuppressWarnings("unused") private static final ApiExceptionMapper MAPPER = new ApiExceptionMapper(); @SuppressWarnings("unused") private static final EnvironmentFilter FILTER = new EnvironmentFilter(); private static final Subject SUBJECT = new SubjectImpl("john", Collections.emptyList(), "id-123", "token", false); @SuppressWarnings("unused") // is declared for deploying by everrest-assured private CheJsonProvider jsonProvider = new CheJsonProvider(Collections.emptySet()); @Mock private KubernetesNamespaceFactory namespaceFactory; @Mock private NamespaceProvisioner namespaceProvisioner; @InjectMocks private KubernetesNamespaceService service; @Test public void shouldReturnNamespaces() throws Exception { KubernetesNamespaceMetaImpl namespaceMeta = new KubernetesNamespaceMetaImpl( "ws-namespace", ImmutableMap.of("phase", "active", "default", "true")); when(namespaceFactory.list()).thenReturn(singletonList(namespaceMeta)); final Response response = given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/kubernetes/namespace"); assertEquals(response.getStatusCode(), 200); List namespaces = unwrapDtoList(response, KubernetesNamespaceMetaDto.class); assertEquals(namespaces.size(), 1); assertEquals(new KubernetesNamespaceMetaImpl(namespaces.get(0)), namespaceMeta); verify(namespaceFactory).list(); } @Test public void shouldProvisionNamespace() throws Exception { // given KubernetesNamespaceMetaImpl namespaceMeta = new KubernetesNamespaceMetaImpl( "ws-namespace", ImmutableMap.of("phase", "active", "default", "true")); when(namespaceProvisioner.provision(any(NamespaceResolutionContext.class))) .thenReturn(namespaceMeta); // when final Response response = given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .post(SECURE_PATH + "/kubernetes/namespace/provision"); // then assertEquals(response.getStatusCode(), 200); KubernetesNamespaceMetaDto actual = unwrapDto(response, KubernetesNamespaceMetaDto.class); assertEquals(actual.getName(), namespaceMeta.getName()); assertEquals(actual.getAttributes(), namespaceMeta.getAttributes()); verify(namespaceProvisioner).provision(any(NamespaceResolutionContext.class)); } @Test public void shouldProvisionNamespaceWithCorrectContext() throws Exception { // given KubernetesNamespaceMetaImpl namespaceMeta = new KubernetesNamespaceMetaImpl( "ws-namespace", ImmutableMap.of("phase", "active", "default", "true")); when(namespaceProvisioner.provision(any(NamespaceResolutionContext.class))) .thenReturn(namespaceMeta); // when final Response response = given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .post(SECURE_PATH + "/kubernetes/namespace/provision"); // then assertEquals(response.getStatusCode(), 200); ArgumentCaptor captor = ArgumentCaptor.forClass(NamespaceResolutionContext.class); verify(namespaceProvisioner).provision(captor.capture()); NamespaceResolutionContext actualContext = captor.getValue(); assertEquals(actualContext.getUserId(), SUBJECT.getUserId()); assertEquals(actualContext.getUserName(), SUBJECT.getUserName()); Assert.assertNull(actualContext.getWorkspaceId()); } private static List unwrapDtoList(Response response, Class dtoClass) { return DtoFactory.getInstance().createListDtoFromJson(response.body().print(), dtoClass); } private static T unwrapDto(Response response, Class dtoClass) { return DtoFactory.getInstance().createDtoFromJson(response.body().print(), dtoClass); } @Filter public static class EnvironmentFilter implements RequestFilter { public void doFilter(GenericContainerRequest request) { EnvironmentContext.getCurrent().setSubject(SUBJECT); } } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/authorization/KubernetesAuthorizationCheckerTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.authorization; import java.util.Arrays; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class KubernetesAuthorizationCheckerTest { @Mock private CheServerKubernetesClientFactory clientFactory; private static Subject user1 = new SubjectImpl("user1", Arrays.asList("group1"), "id", "token", false); @Test(dataProvider = "advancedAuthorizationData") public void advancedAuthorization( Subject subject, String allowedUsers, String allowedGroups, String deniedUsers, String deniedGroups, boolean expectedIsAuthorized) throws InfrastructureException { // give KubernetesOIDCAuthorizationCheckerImpl authorizationChecker = new KubernetesOIDCAuthorizationCheckerImpl( allowedUsers, allowedGroups, deniedUsers, deniedGroups, ","); // when boolean isAuthorized = authorizationChecker.isAuthorized(subject); // then Assert.assertEquals(isAuthorized, expectedIsAuthorized); } @DataProvider public static Object[][] advancedAuthorizationData() { return new Object[][] { {user1, "", "", "", "", true}, {user1, "", "", "", "group1", false}, {user1, "", "", "", "group2", true}, {user1, "", "", "user1", "", false}, {user1, "", "", "user1", "group1", false}, {user1, "", "", "user1", "group2", false}, {user1, "", "", "user2", "", true}, {user1, "", "", "user2", "group1", false}, {user1, "", "", "user2", "group2", true}, {user1, "", "group1", "", "", true}, {user1, "", "group1", "", "group1", false}, {user1, "", "group1", "", "group2", true}, {user1, "", "group1", "user1", "", false}, {user1, "", "group1", "user1", "group1", false}, {user1, "", "group1", "user1", "group2", false}, {user1, "", "group1", "user2", "", true}, {user1, "", "group1", "user2", "group1", false}, {user1, "", "group1", "user2", "group2", true}, {user1, "", "group2", "", "", false}, {user1, "", "group2", "", "group1", false}, {user1, "", "group2", "", "group2", false}, {user1, "", "group2", "user1", "", false}, {user1, "", "group2", "user1", "group1", false}, {user1, "", "group2", "user1", "group2", false}, {user1, "", "group2", "user2", "", false}, {user1, "", "group2", "user2", "group1", false}, {user1, "", "group2", "user2", "group2", false}, {user1, "user1", "", "", "", true}, {user1, "user1", "", "", "group1", false}, {user1, "user1", "", "", "group2", true}, {user1, "user1", "", "user1", "", false}, {user1, "user1", "", "user1", "group1", false}, {user1, "user1", "", "user1", "group2", false}, {user1, "user1", "", "user2", "", true}, {user1, "user1", "", "user2", "group1", false}, {user1, "user1", "", "user2", "group2", true}, {user1, "user1", "group1", "", "", true}, {user1, "user1", "group1", "", "group1", false}, {user1, "user1", "group1", "", "group2", true}, {user1, "user1", "group1", "user1", "", false}, {user1, "user1", "group1", "user1", "group1", false}, {user1, "user1", "group1", "user1", "group2", false}, {user1, "user1", "group1", "user2", "", true}, {user1, "user1", "group1", "user2", "group1", false}, {user1, "user1", "group1", "user2", "group2", true}, {user1, "user1", "group2", "", "", true}, {user1, "user1", "group2", "", "group1", false}, {user1, "user1", "group2", "", "group2", true}, {user1, "user1", "group2", "user1", "", false}, {user1, "user1", "group2", "user1", "group1", false}, {user1, "user1", "group2", "user1", "group2", false}, {user1, "user1", "group2", "user2", "", true}, {user1, "user1", "group2", "user2", "group1", false}, {user1, "user1", "group2", "user2", "group2", true}, {user1, "user2", "", "", "", false}, {user1, "user2", "", "", "group1", false}, {user1, "user2", "", "", "group2", false}, {user1, "user2", "", "user1", "", false}, {user1, "user2", "", "user1", "group1", false}, {user1, "user2", "", "user1", "group2", false}, {user1, "user2", "", "user2", "", false}, {user1, "user2", "", "user2", "group1", false}, {user1, "user2", "", "user2", "group2", false}, {user1, "user2", "group1", "", "", true}, {user1, "user2", "group1", "", "group1", false}, {user1, "user2", "group1", "", "group2", true}, {user1, "user2", "group1", "user1", "", false}, {user1, "user2", "group1", "user1", "group1", false}, {user1, "user2", "group1", "user1", "group2", false}, {user1, "user2", "group1", "user2", "", true}, {user1, "user2", "group1", "user2", "group1", false}, {user1, "user2", "group1", "user2", "group2", true}, {user1, "user2", "group2", "", "", false}, {user1, "user2", "group2", "", "group1", false}, {user1, "user2", "group2", "", "group2", false}, {user1, "user2", "group2", "user1", "", false}, {user1, "user2", "group2", "user1", "group1", false}, {user1, "user2", "group2", "user1", "group2", false}, {user1, "user2", "group2", "user2", "", false}, {user1, "user2", "group2", "user2", "group1", false}, {user1, "user2", "group2", "user2", "group2", false}, }; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/ContainerSearchTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import static java.lang.String.format; import static java.util.Arrays.asList; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.PodTemplate; import io.fabric8.kubernetes.api.model.PodTemplateBuilder; import io.fabric8.kubernetes.api.model.ReplicationController; import io.fabric8.kubernetes.api.model.ReplicationControllerBuilder; import io.fabric8.kubernetes.api.model.apps.DaemonSet; import io.fabric8.kubernetes.api.model.apps.DaemonSetBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.api.model.apps.ReplicaSet; import io.fabric8.kubernetes.api.model.apps.ReplicaSetBuilder; import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder; import io.fabric8.kubernetes.api.model.batch.v1.CronJob; import io.fabric8.kubernetes.api.model.batch.v1.CronJobBuilder; import io.fabric8.kubernetes.api.model.batch.v1.Job; import io.fabric8.kubernetes.api.model.batch.v1.JobBuilder; import io.fabric8.openshift.api.model.DeploymentConfig; import io.fabric8.openshift.api.model.DeploymentConfigBuilder; import io.fabric8.openshift.api.model.Template; import io.fabric8.openshift.api.model.TemplateBuilder; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class ContainerSearchTest { private List testList; @BeforeMethod public void setup() { // These are all the object types that can be contained in a KubernetesList which can contain a // container: // Pod, PodTemplate, DaemonSet, Deployment, Job, ReplicaSet, ReplicationController, StatefulSet, // CronJob, DeploymentConfig, Template Container container1 = new ContainerBuilder().withName("container1").build(); Container container2 = new ContainerBuilder().withName("container2").build(); Container container3 = new ContainerBuilder().withName("container3").build(); Container container4 = new ContainerBuilder().withName("container4").build(); Container container5 = new ContainerBuilder().withName("container5").build(); Container container6 = new ContainerBuilder().withName("container6").build(); Container container7 = new ContainerBuilder().withName("container7").build(); Container container8 = new ContainerBuilder().withName("container8").build(); Container container9 = new ContainerBuilder().withName("container9").build(); Container container10 = new ContainerBuilder().withName("container10").build(); Container container11 = new ContainerBuilder().withName("container11").build(); Container container12 = new ContainerBuilder().withName("container12").build(); Pod podWithName = new PodBuilder() .withNewMetadata() .withName("podWithName") .addToLabels("app", "che") .endMetadata() .withNewSpec() .withContainers(container1) .endSpec() .build(); Pod podWithGenerateName = new PodBuilder() .withNewMetadata() .withGenerateName("podWithGenerateName") .endMetadata() .withNewSpec() .withContainers(container2) .endSpec() .build(); PodTemplate podTemplate = new PodTemplateBuilder() .withNewMetadata() .withName("podTemplate") .addToLabels("app", "che") .endMetadata() .withNewTemplate() .withNewSpec() .withContainers(container3) .endSpec() .endTemplate() .build(); DaemonSet daemonSet = new DaemonSetBuilder() .withNewMetadata() .withName("daemonSet") .endMetadata() .withNewSpec() .withNewTemplate() .withNewSpec() .withContainers(container4) .endSpec() .endTemplate() .endSpec() .build(); Deployment deployment = new DeploymentBuilder() .withNewMetadata() .withName("deployment") .addToLabels("app", "che") .endMetadata() .withNewSpec() .withNewTemplate() .withNewMetadata() .withName("podWithName") .endMetadata() .withNewSpec() .withContainers(container5) .endSpec() .endTemplate() .endSpec() .build(); Job job = new JobBuilder() .withNewMetadata() .withName("job") .endMetadata() .withNewSpec() .withNewTemplate() .withNewSpec() .withContainers(container6) .endSpec() .endTemplate() .endSpec() .build(); ReplicaSet replicaSet = new ReplicaSetBuilder() .withNewMetadata() .withName("replicaSet") .addToLabels("app", "che") .endMetadata() .withNewSpec() .withNewTemplate() .withNewSpec() .withContainers(container7) .endSpec() .endTemplate() .endSpec() .build(); ReplicationController replicationController = new ReplicationControllerBuilder() .withNewMetadata() .withName("replicationController") .endMetadata() .withNewSpec() .withNewTemplate() .withNewSpec() .withContainers(container8) .endSpec() .endTemplate() .endSpec() .build(); StatefulSet statefulSet = new StatefulSetBuilder() .withNewMetadata() .withName("statefulSet") .addToLabels("app", "che") .endMetadata() .withNewSpec() .withNewTemplate() .withNewSpec() .withContainers(container9) .endSpec() .endTemplate() .endSpec() .build(); CronJob cronJob = new CronJobBuilder() .withNewMetadata() .withName("cronJob") .endMetadata() .withNewSpec() .withNewJobTemplate() .withNewSpec() .withNewTemplate() .withNewSpec() .withContainers(container10) .endSpec() .endTemplate() .endSpec() .endJobTemplate() .endSpec() .build(); DeploymentConfig deploymentConfig = new DeploymentConfigBuilder() .withNewMetadata() .withName("deploymentConfig") .addToLabels("app", "che") .endMetadata() .withNewSpec() .withNewTemplate() .withNewSpec() .withContainers(container11) .endSpec() .endTemplate() .endSpec() .build(); Template template = new TemplateBuilder() .addToObjects( new DeploymentBuilder() .withNewMetadata() .withName("deploymentWithName") .endMetadata() .withNewSpec() .withNewTemplate() .withNewMetadata() .withName("podWithName") .endMetadata() .withNewSpec() .withContainers(container12) .endSpec() .endTemplate() .endSpec() .build()) .build(); // Pod, PodTemplate, DaemonSet, Deployment, Job, ReplicaSet, ReplicationController, StatefulSet, // CronJob, DeploymentConfig, Template testList = asList( podWithName, podWithGenerateName, podTemplate, daemonSet, deployment, job, replicaSet, replicationController, statefulSet, cronJob, deploymentConfig, template); } @Test public void shouldFindAllContainersIfNotRestricted() { ContainerSearch search = new ContainerSearch(null, null, null); List results = search.search(testList); Assert.assertEquals(results.size(), 12); assertContainsContainer(results, "container1"); assertContainsContainer(results, "container2"); assertContainsContainer(results, "container3"); assertContainsContainer(results, "container4"); assertContainsContainer(results, "container5"); assertContainsContainer(results, "container6"); assertContainsContainer(results, "container7"); assertContainsContainer(results, "container8"); assertContainsContainer(results, "container9"); assertContainsContainer(results, "container10"); assertContainsContainer(results, "container11"); assertContainsContainer(results, "container12"); } @Test public void shouldRestrictByName() { ContainerSearch search = new ContainerSearch("podWithName", null, null); List results = search.search(testList); Assert.assertEquals(results.size(), 1); assertContainsContainer(results, "container1"); } @Test public void shouldRestrictByGenerateName() { ContainerSearch search = new ContainerSearch("podWithGenerateName", null, null); List results = search.search(testList); Assert.assertEquals(results.size(), 1); assertContainsContainer(results, "container2"); } @Test public void shouldRestrictByContainerName() { ContainerSearch search = new ContainerSearch(null, null, "container7"); List results = search.search(testList); Assert.assertEquals(results.size(), 1); assertContainsContainer(results, "container7"); } @Test public void shouldRestrictByParentSelector() { Map selector = ImmutableMap.of("app", "che"); ContainerSearch search = new ContainerSearch(null, selector, null); List results = search.search(testList); Assert.assertEquals(results.size(), 6); assertContainsContainer(results, "container1"); assertContainsContainer(results, "container3"); assertContainsContainer(results, "container5"); assertContainsContainer(results, "container7"); assertContainsContainer(results, "container9"); assertContainsContainer(results, "container11"); } @Test public void shouldConsiderEmptySelectorAsNotPresent() { ContainerSearch search = new ContainerSearch(null, Collections.emptyMap(), null); List results = search.search(testList); Assert.assertEquals(results.size(), 12); assertContainsContainer(results, "container1"); assertContainsContainer(results, "container2"); assertContainsContainer(results, "container3"); assertContainsContainer(results, "container4"); assertContainsContainer(results, "container5"); assertContainsContainer(results, "container6"); assertContainsContainer(results, "container7"); assertContainsContainer(results, "container8"); assertContainsContainer(results, "container9"); assertContainsContainer(results, "container10"); assertContainsContainer(results, "container11"); assertContainsContainer(results, "container12"); } private static void assertContainsContainer(Collection containers, String name) { Optional containerOptional = containers.stream().filter(c -> name.equals(c.getName())).findAny(); if (containerOptional.isEmpty()) { throw new AssertionError(format("Expected a container called %s but didn't find any.", name)); } } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/DockerimageComponentToWorkspaceApplierTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.DOCKERIMAGE_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.PUBLIC_ENDPOINT_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.PROJECTS_VOLUME_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.devfile.DockerimageComponentToWorkspaceApplier.CHE_COMPONENT_NAME_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.PodTemplateSpec; import io.fabric8.kubernetes.api.model.Quantity; import io.fabric8.kubernetes.api.model.apps.Deployment; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.environment.MachineConfigsValidator; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class DockerimageComponentToWorkspaceApplierTest { private WorkspaceConfigImpl workspaceConfig; private DockerimageComponentToWorkspaceApplier dockerimageComponentApplier; @Mock private KubernetesEnvironmentProvisioner k8sEnvProvisioner; @Captor private ArgumentCaptor> objectsCaptor; @Captor private ArgumentCaptor> machinesCaptor; @BeforeMethod public void setUp() throws Exception { dockerimageComponentApplier = new DockerimageComponentToWorkspaceApplier(MULTI_HOST_STRATEGY, k8sEnvProvisioner); workspaceConfig = new WorkspaceConfigImpl(); } @Test public void shouldProvisionK8sEnvironmentWithMachineConfigAndGeneratedDeploymentForSpecifiedDockerimage() throws Exception { // given ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); MachineConfigImpl machineConfig = machinesCaptor.getValue().get("jdk"); assertNotNull(machineConfig); List objects = objectsCaptor.getValue(); assertEquals(objects.size(), 1); assertTrue(objects.get(0) instanceof Deployment); Deployment deployment = (Deployment) objects.get(0); PodTemplateSpec podTemplate = deployment.getSpec().getTemplate(); ObjectMeta podMeta = podTemplate.getMetadata(); assertEquals(podMeta.getName(), "jdk"); Map deploymentSelector = deployment.getSpec().getSelector().getMatchLabels(); assertFalse(deploymentSelector.isEmpty()); assertTrue(podMeta.getLabels().entrySet().containsAll(deploymentSelector.entrySet())); Container container = podTemplate.getSpec().getContainers().get(0); assertEquals(container.getName(), "jdk"); assertEquals(container.getImage(), "eclipse/ubuntu_jdk8:latest"); assertEquals(Names.machineName(podMeta, container), "jdk"); } @Test public void shouldBeAbleToSetupSpecificImagePullPolicy() throws DevfileException { ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); dockerimageComponentApplier = new DockerimageComponentToWorkspaceApplier(MULTI_HOST_STRATEGY, k8sEnvProvisioner); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( any(), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); MachineConfigImpl machineConfig = machinesCaptor.getValue().get("eclipse-ubuntu_jdk8-latest"); assertNotNull(machineConfig); List objects = objectsCaptor.getValue(); assertEquals(objects.size(), 1); assertTrue(objects.get(0) instanceof Deployment); Deployment deployment = (Deployment) objects.get(0); PodTemplateSpec podTemplate = deployment.getSpec().getTemplate(); ObjectMeta podMeta = podTemplate.getMetadata(); assertEquals(podMeta.getName(), "eclipse-ubuntu_jdk8-latest"); Map deploymentSelector = deployment.getSpec().getSelector().getMatchLabels(); assertFalse(deploymentSelector.isEmpty()); assertTrue(podMeta.getLabels().entrySet().containsAll(deploymentSelector.entrySet())); Container container = podTemplate.getSpec().getContainers().get(0); assertEquals(container.getName(), "eclipse-ubuntu_jdk8-latest"); } @Test public void shouldProvisionK8sEnvironmentWithMachineConfigFromSpecifiedDockerimageWithoutAlias() throws Exception { ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( any(), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); MachineConfigImpl machineConfig = machinesCaptor.getValue().get("eclipse-ubuntu_jdk8-latest"); assertNotNull(machineConfig); List objects = objectsCaptor.getValue(); assertEquals(objects.size(), 1); assertTrue(objects.get(0) instanceof Deployment); Deployment deployment = (Deployment) objects.get(0); PodTemplateSpec podTemplate = deployment.getSpec().getTemplate(); ObjectMeta podMeta = podTemplate.getMetadata(); assertEquals(podMeta.getName(), "eclipse-ubuntu_jdk8-latest"); Map deploymentSelector = deployment.getSpec().getSelector().getMatchLabels(); assertFalse(deploymentSelector.isEmpty()); assertTrue(podMeta.getLabels().entrySet().containsAll(deploymentSelector.entrySet())); Container container = podTemplate.getSpec().getContainers().get(0); assertEquals(container.getName(), "eclipse-ubuntu_jdk8-latest"); assertEquals(container.getImage(), "eclipse/ubuntu_jdk8:latest"); assertEquals( Names.machineName(podTemplate.getMetadata(), container), "eclipse-ubuntu_jdk8-latest"); } @Test public void shouldProvisionSpecifiedEnvVars() throws Exception { // given ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); dockerimageComponent.setEnv(singletonList(new EnvImpl("envName", "envValue"))); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); List objects = objectsCaptor.getValue(); assertEquals(objects.size(), 1); assertTrue(objects.get(0) instanceof Deployment); Deployment deployment = (Deployment) objects.get(0); PodTemplateSpec podTemplate = deployment.getSpec().getTemplate(); assertEquals(podTemplate.getSpec().getContainers().size(), 1); Container container = podTemplate.getSpec().getContainers().get(0); int env = container.getEnv().size(); assertEquals(env, 1); EnvVar containerEnvVar = container.getEnv().get(0); assertEquals(containerEnvVar.getName(), "envName"); assertEquals(containerEnvVar.getValue(), "envValue"); } @Test public void shouldProvisionContainerWithMemoryResourcesSpecified() throws Exception { // given ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); dockerimageComponent.setMemoryRequest("128M"); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); List objects = objectsCaptor.getValue(); assertEquals(objects.size(), 1); assertTrue(objects.get(0) instanceof Deployment); Deployment deployment = (Deployment) objects.get(0); PodTemplateSpec podTemplate = deployment.getSpec().getTemplate(); assertEquals(podTemplate.getSpec().getContainers().size(), 1); Container container = podTemplate.getSpec().getContainers().get(0); Quantity memoryLimit = container.getResources().getLimits().get("memory"); assertEquals(memoryLimit.getAmount(), "1"); assertEquals(memoryLimit.getFormat(), "G"); Quantity memoryRequest = container.getResources().getRequests().get("memory"); assertEquals(memoryRequest.getAmount(), "128"); assertEquals(memoryRequest.getFormat(), "M"); } @Test public void shouldProvisionContainerWithCpuLimitsSpecified() throws Exception { // given ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); dockerimageComponent.setCpuRequest("1576m"); dockerimageComponent.setCpuLimit("2.22"); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); List objects = objectsCaptor.getValue(); assertEquals(objects.size(), 1); assertTrue(objects.get(0) instanceof Deployment); Deployment deployment = (Deployment) objects.get(0); PodTemplateSpec podTemplate = deployment.getSpec().getTemplate(); assertEquals(podTemplate.getSpec().getContainers().size(), 1); Container container = podTemplate.getSpec().getContainers().get(0); Quantity cpuLimit = container.getResources().getLimits().get("cpu"); Quantity cpuRequest = container.getResources().getRequests().get("cpu"); assertEquals(cpuRequest.getAmount(), "1.576"); assertEquals(cpuLimit.getAmount(), "2.22"); } @Test public void shouldProvisionMachineConfigWithConfiguredServers() throws Exception { // given EndpointImpl endpoint = new EndpointImpl( "jdk-ls", 4923, ImmutableMap.of( "protocol", "http", "path", "/ls", PUBLIC_ENDPOINT_ATTRIBUTE, "false", "secure", "false")); ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); dockerimageComponent.setEndpoints(singletonList(endpoint)); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); MachineConfigImpl machineConfig = machinesCaptor.getValue().get("jdk"); assertNotNull(machineConfig); assertEquals(machineConfig.getServers().size(), 1); ServerConfigImpl serverConfig = machineConfig.getServers().get("jdk-ls"); assertEquals(serverConfig.getProtocol(), "http"); assertEquals(serverConfig.getPath(), "/ls"); assertEquals(serverConfig.getPort(), "4923"); Map attributes = serverConfig.getAttributes(); assertEquals(attributes.get(ServerConfig.INTERNAL_SERVER_ATTRIBUTE), "true"); assertEquals(attributes.get("secure"), "false"); assertEquals(attributes.get(ServerConfig.REQUIRE_SUBDOMAIN), "true"); } @Test public void shouldProvisionServiceForDiscoverableServer() throws Exception { // given EndpointImpl endpoint = new EndpointImpl( "jdk-ls", 4923, ImmutableMap.of( "protocol", "http", "path", "/ls", PUBLIC_ENDPOINT_ATTRIBUTE, "false", "secure", "false", "discoverable", "true")); ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); dockerimageComponent.setEndpoints(singletonList(endpoint)); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); List objects = objectsCaptor.getValue(); assertEquals(objects.size(), 1); assertTrue(objects.get(0) instanceof Deployment); Deployment deployment = (Deployment) objects.get(0); assertEquals( deployment.getSpec().getTemplate().getMetadata().getLabels().get(CHE_COMPONENT_NAME_LABEL), "jdk"); } @Test public void shouldProvisionServersWithHttpPortIsTheCorrespondingAttrIsMissing() throws Exception { // given EndpointImpl endpoint = new EndpointImpl("jdk-ls", 4923, emptyMap()); ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); dockerimageComponent.setEndpoints(singletonList(endpoint)); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); MachineConfigImpl machineConfig = machinesCaptor.getValue().get("jdk"); assertNotNull(machineConfig); assertEquals(machineConfig.getServers().size(), 1); ServerConfigImpl serverConfig = machineConfig.getServers().get("jdk-ls"); assertEquals(serverConfig.getProtocol(), "http"); } @Test public void shouldProvisionMachineConfigWithConfiguredVolumes() throws Exception { // given ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); dockerimageComponent.setVolumes(singletonList(new VolumeImpl("data", "/tmp/data/"))); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); MachineConfigImpl machineConfig = machinesCaptor.getValue().get("jdk"); assertNotNull(machineConfig); org.eclipse.che.api.workspace.server.model.impl.VolumeImpl volume = machineConfig.getVolumes().get("data"); assertNotNull(volume); assertEquals(volume.getPath(), "/tmp/data/"); } @Test public void shouldProvisionMachineConfigWithMountSources() throws Exception { // given ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); dockerimageComponent.setMountSources(true); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); MachineConfigImpl machineConfig = machinesCaptor.getValue().get("jdk"); assertNotNull(machineConfig); } @Test public void shouldProvisionMachineConfigWithAliasAttribute() throws Exception { // given ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk-alias"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); MachineConfigImpl machineConfig = machinesCaptor.getValue().get("jdk-alias"); assertNotNull(machineConfig); assertEquals(machineConfig.getAttributes().get(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE), "jdk-alias"); } @Test public void shouldProvisionMachineConfigWithoutSourcesByDefault() throws Exception { // given ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); MachineConfigImpl machineConfig = machinesCaptor.getValue().get("jdk"); assertNotNull(machineConfig); assertFalse(machineConfig.getVolumes().containsKey(PROJECTS_VOLUME_NAME)); } @Test public void shouldProvisionCommandAndArgs() throws Exception { // given List command = singletonList("/usr/bin/rf"); List args = Arrays.asList("-r", "f"); ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("1G"); dockerimageComponent.setCommand(command); dockerimageComponent.setArgs(args); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); List objects = objectsCaptor.getValue(); assertEquals(objects.size(), 1); assertTrue(objects.get(0) instanceof Deployment); Deployment deployment = (Deployment) objects.get(0); PodTemplateSpec podTemplate = deployment.getSpec().getTemplate(); assertEquals(podTemplate.getSpec().getContainers().size(), 1); Container container = podTemplate.getSpec().getContainers().get(0); assertEquals(container.getCommand(), command); assertEquals(container.getArgs(), args); } @Test public void serverMustHaveRequireSubdomainWhenNonSinglehostDevfileExpose() throws DevfileException { dockerimageComponentApplier = new DockerimageComponentToWorkspaceApplier(MULTI_HOST_STRATEGY, k8sEnvProvisioner); // given ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("100M"); dockerimageComponent.setEndpoints( Arrays.asList( new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); MachineConfigImpl machineConfig = machinesCaptor.getValue().get("jdk"); assertNotNull(machineConfig); assertEquals(machineConfig.getServers().size(), 2); assertTrue( ServerConfig.isRequireSubdomain(machineConfig.getServers().get("e1").getAttributes())); assertTrue( ServerConfig.isRequireSubdomain(machineConfig.getServers().get("e2").getAttributes())); } @Test public void serverCantHaveRequireSubdomainWhenSinglehostDevfileExpose() throws DevfileException { dockerimageComponentApplier = new DockerimageComponentToWorkspaceApplier(SINGLE_HOST_STRATEGY, k8sEnvProvisioner); // given ComponentImpl dockerimageComponent = new ComponentImpl(); dockerimageComponent.setAlias("jdk"); dockerimageComponent.setType(DOCKERIMAGE_COMPONENT_TYPE); dockerimageComponent.setImage("eclipse/ubuntu_jdk8:latest"); dockerimageComponent.setMemoryLimit("100M"); dockerimageComponent.setEndpoints( Arrays.asList( new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); // when dockerimageComponentApplier.apply(workspaceConfig, dockerimageComponent, null); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), machinesCaptor.capture()); MachineConfigImpl machineConfig = machinesCaptor.getValue().get("jdk"); assertNotNull(machineConfig); assertEquals(machineConfig.getServers().size(), 2); assertFalse( ServerConfig.isRequireSubdomain(machineConfig.getServers().get("e1").getAttributes())); assertFalse( ServerConfig.isRequireSubdomain(machineConfig.getServers().get("e2").getAttributes())); } @Test(dataProvider = "imageNames") public void testGeneratesValidMachineNameFromImageName(String imageName) throws ValidationException, DevfileException { // given String machineName = DockerimageComponentToWorkspaceApplier.toMachineName(imageName); MachineConfigsValidator validator = new MachineConfigsValidator(); Map configs = new HashMap<>(); configs.put(machineName, new InternalMachineConfig()); // when validator.validate(configs); // then no exception is thrown } @DataProvider public static Object[][] imageNames() { return new Object[][] { new Object[] {"maven"}, new Object[] {"maven-3.6"}, new Object[] {"eclipse/che-3.6:latest"} }; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentIntegrityValidatorTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import io.fabric8.kubernetes.api.model.PodBuilder; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class KubernetesComponentIntegrityValidatorTest { @Mock private KubernetesRecipeParser kubernetesRecipeParser; @Mock private FileContentProvider contentProvider; private KubernetesComponentValidator validator; @BeforeMethod public void setup() { Set k8sComponentTypes = new HashSet<>(); k8sComponentTypes.add(KUBERNETES_COMPONENT_TYPE); validator = new KubernetesComponentValidator(kubernetesRecipeParser, k8sComponentTypes); } @Test public void shouldApplySelector() throws Exception { // given when(kubernetesRecipeParser.parse(any(String.class))) .thenReturn( Arrays.asList( new PodBuilder().withNewMetadata().addToLabels("app", "test").endMetadata().build(), new PodBuilder() .withNewMetadata() .addToLabels("app", "other") .endMetadata() .build())); Map selector = new HashMap<>(); selector.put("app", "test"); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference("ref"); component.setSelector(selector); component.setReferenceContent("content"); // when validator.validateComponent(component, contentProvider); // then no exception is thrown } @Test public void shouldApplyEntrypoint() throws Exception { // given when(kubernetesRecipeParser.parse(any(String.class))) .thenReturn( Arrays.asList( new PodBuilder() .withNewSpec() .addNewContainer() .withName("container_a") .endContainer() .endSpec() .build(), new PodBuilder() .withNewSpec() .addNewContainer() .withName("container_b") .endContainer() .endSpec() .build())); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReferenceContent("content"); component.setReference("ref"); EntrypointImpl entrypoint = new EntrypointImpl(); entrypoint.setContainerName("container_a"); component.setEntrypoints(Collections.singletonList(entrypoint)); // when validator.validateComponent(component, contentProvider); // then no exception is thrown } @Test public void shouldValidateContainerMatchingEntrypointInPodMatchingSelector() throws Exception { // given when(kubernetesRecipeParser.parse(any(String.class))) .thenReturn( Arrays.asList( new PodBuilder() .withNewMetadata() .addToLabels("app", "test") .endMetadata() .withNewSpec() .addNewContainer() .withName("container_a") .endContainer() .endSpec() .build(), new PodBuilder() .withNewMetadata() .addToLabels("app", "other") .endMetadata() .withNewSpec() .addNewContainer() .withName("container_a") .endContainer() .endSpec() .build())); Map selector = new HashMap<>(); selector.put("app", "test"); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference("ref"); component.setSelector(selector); component.setReferenceContent("content"); EntrypointImpl entrypoint = new EntrypointImpl(); entrypoint.setContainerName("container_a"); component.setEntrypoints(Collections.singletonList(entrypoint)); // when validator.validateComponent(component, contentProvider); // then no exception is thrown } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Failed to validate content reference of component 'ref' of type 'kubernetes': The selector of the component 'ref' of type 'kubernetes' filters out all objects from the list.") public void shouldThrowExceptionOnSelectorFilteringOutEverything() throws Exception { // given when(kubernetesRecipeParser.parse(any(String.class))) .thenReturn( Collections.singletonList( new PodBuilder() .withNewMetadata() .addToLabels("app", "test") .endMetadata() .build())); Map selector = new HashMap<>(); selector.put("app", "a different value"); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference("ref"); component.setSelector(selector); component.setReferenceContent("content"); // when validator.validateComponent(component, contentProvider); // then exception is thrown } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Failed to validate content reference of component 'ref' of type 'kubernetes': Component 'ref' of type 'kubernetes' contains an entry point that doesn't match any container.") public void shouldThrowExceptionOnEntrypointNotMatchingAnyContainer() throws Exception { // given when(kubernetesRecipeParser.parse(any(String.class))) .thenReturn( Collections.singletonList( new PodBuilder() .withNewSpec() .addNewContainer() .withName("container") .endContainer() .endSpec() .build())); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReferenceContent("content"); component.setReference("ref"); EntrypointImpl entrypoint = new EntrypointImpl(); entrypoint.setContainerName("not that container"); component.setEntrypoints(Collections.singletonList(entrypoint)); // when validator.validateComponent(component, contentProvider); // then exception is thrown } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Failed to validate content reference of component 'ref' of type 'kubernetes': Component 'ref' of type 'kubernetes' contains an entry point that doesn't match any container.") public void shouldThrowExceptionOnEntrypointNotMatchingAnyContainerOfPodsMatchingSelector() throws Exception { // given when(kubernetesRecipeParser.parse(any(String.class))) .thenReturn( Arrays.asList( new PodBuilder() .withNewMetadata() .addToLabels("app", "test") .endMetadata() .withNewSpec() .addNewContainer() .withName("container_a") .endContainer() .endSpec() .build(), new PodBuilder() .withNewMetadata() .addToLabels("app", "other") .endMetadata() .withNewSpec() .addNewContainer() .withName("container_b") .endContainer() .endSpec() .build())); Map selector = new HashMap<>(); selector.put("app", "test"); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReferenceContent("content"); component.setReference("ref"); component.setSelector(selector); EntrypointImpl entrypoint = new EntrypointImpl(); entrypoint.setContainerName("container_b"); component.setEntrypoints(Collections.singletonList(entrypoint)); // when validator.validateComponent(component, contentProvider); // then exception is thrown } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesComponentToWorkspaceApplierTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import static io.fabric8.kubernetes.client.utils.Serialization.unmarshal; import static java.util.Arrays.asList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.config.Command.MACHINE_NAME_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.REQUIRE_SUBDOMAIN; import static org.eclipse.che.api.workspace.server.devfile.Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.OPENSHIFT_COMPONENT_TYPE; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesList; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser; import org.eclipse.che.workspace.infrastructure.kubernetes.util.EnvVars; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import org.testng.reporters.Files; /** * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class KubernetesComponentToWorkspaceApplierTest { public static final String REFERENCE_FILENAME = "reference.yaml"; public static final String COMPONENT_NAME = "foo"; private WorkspaceConfigImpl workspaceConfig; private KubernetesComponentToWorkspaceApplier applier; @Mock private KubernetesRecipeParser k8sRecipeParser; @Mock private KubernetesEnvironmentProvisioner k8sEnvProvisioner; @Mock private EnvVars envVars; @Mock private FileContentProvider contentProvider; @Captor private ArgumentCaptor> objectsCaptor; private Set k8sBasedComponents; @BeforeMethod public void setUp() { k8sBasedComponents = new HashSet<>(); k8sBasedComponents.add(KUBERNETES_COMPONENT_TYPE); k8sBasedComponents.add("openshift"); // so that we can work with the petclinic.yaml applier = new KubernetesComponentToWorkspaceApplier( k8sRecipeParser, k8sEnvProvisioner, envVars, MULTI_HOST_STRATEGY, k8sBasedComponents); workspaceConfig = new WorkspaceConfigImpl(); } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Fetching content of file `reference.yaml` specified in `reference` field of component `foo` is not " + "supported. Please provide its content in `referenceContent` field. Cause: fetch is not supported") public void shouldThrowExceptionWhenRecipeComponentIsPresentAndContentProviderDoesNotSupportFetching() throws Exception { // given when(contentProvider.fetchContent(anyString())) .thenThrow(new DevfileException("fetch is not supported")); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); // when applier.apply(workspaceConfig, component, contentProvider); } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Error occurred during parsing list from file " + REFERENCE_FILENAME + " for component '" + COMPONENT_NAME + "': .*") public void shouldThrowExceptionWhenRecipeContentIsNotAValidYaml() throws Exception { // given doThrow(new ValidationException("non valid")).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); when(contentProvider.fetchContent(anyString())).thenReturn("some_non_yaml_content"); // when applier.apply(workspaceConfig, component, contentProvider); } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Error during recipe content retrieval for component 'foo' with type 'kubernetes': fetch failed") public void shouldThrowExceptionWhenExceptionHappensOnContentProvider() throws Exception { // given when(contentProvider.fetchContent(anyString())).thenThrow(new IOException("fetch failed")); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); // when applier.apply(workspaceConfig, component, contentProvider); } @Test public void shouldProvisionEnvironmentWithCorrectRecipeTypeAndContentFromK8SList() throws Exception { // given String yamlRecipeContent = getResource("devfile/petclinic.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); // when applier.apply(workspaceConfig, component, contentProvider); // then KubernetesList list = toK8SList(yamlRecipeContent); verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), eq(list.getItems()), anyMap()); } @Test public void shouldUseReferenceContentAsRecipeIfPresent() throws Exception { String yamlRecipeContent = getResource("devfile/petclinic.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setReferenceContent(yamlRecipeContent); component.setAlias(COMPONENT_NAME); applier.apply(workspaceConfig, component, new URLFileContentProvider(null, null)); KubernetesList list = toK8SList(yamlRecipeContent); verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), eq(list.getItems()), anyMap()); } @Test public void shouldProvisionDevfileVolumesIfSpecifiedIntoMachineConfig() throws Exception { // given String yamlRecipeContent = getResource("devfile/petclinic.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); List k8sList = toK8SList(yamlRecipeContent).getItems(); doReturn(k8sList).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setVolumes(asList(new VolumeImpl("foo", "/foo1"), new VolumeImpl("bar", "/bar1"))); ArgumentCaptor> mapCaptor = ArgumentCaptor.forClass(Map.class); // when applier.apply(workspaceConfig, component, contentProvider); // then verify(k8sEnvProvisioner).provision(any(), any(), any(), mapCaptor.capture()); Map configMaps = mapCaptor.getValue(); for (MachineConfig config : configMaps.values()) { assertEquals(config.getVolumes().size(), 2); assertTrue( config.getVolumes().entrySet().stream() .anyMatch( entry -> entry.getKey().equals("foo") && entry.getValue().getPath().equals("/foo1"))); assertTrue( config.getVolumes().entrySet().stream() .anyMatch( entry -> entry.getKey().equals("bar") && entry.getValue().getPath().equals("/bar1"))); } } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Conflicting volume with same name \\('foo_volume'\\) but different path \\('/foo1'\\) found for component 'foo' and its container 'server'.") public void shouldThrowExceptionWhenDevfileVolumeNameExists() throws Exception { // given String yamlRecipeContent = getResource("devfile/petclinic.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); List k8sList = toK8SList(yamlRecipeContent).getItems(); doReturn(k8sList).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setVolumes(Collections.singletonList(new VolumeImpl("foo_volume", "/foo1"))); // when applier.apply(workspaceConfig, component, contentProvider); } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Conflicting volume with same path \\('/foo/bar'\\) but different name \\('foo'\\) found for component 'foo' and its container 'server'.") public void shouldThrowExceptionWhenDevfileVolumePathExists() throws Exception { // given String yamlRecipeContent = getResource("devfile/petclinic.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); List k8sList = toK8SList(yamlRecipeContent).getItems(); doReturn(k8sList).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setVolumes(Collections.singletonList(new VolumeImpl("foo", "/foo/bar"))); // when applier.apply(workspaceConfig, component, contentProvider); } @Test public void shouldProvisionEnvIntoK8SList() throws Exception { // given List k8sList = new ArrayList<>(); Pod pod1 = new PodBuilder() .withNewMetadata() .withName("pod1") .endMetadata() .withNewSpec() .endSpec() .build(); Pod pod2 = new PodBuilder() .withNewMetadata() .withName("pod2") .endMetadata() .withNewSpec() .endSpec() .build(); k8sList.add(pod1); k8sList.add(pod2); doReturn(k8sList).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); List envToApply = singletonList(new EnvImpl("TEST_ENV", "anyValue")); component.setEnv(envToApply); when(contentProvider.fetchContent(anyString())).thenReturn("content"); // when applier.apply(workspaceConfig, component, contentProvider); // then envVars.apply(new PodData(pod1), envToApply); envVars.apply(new PodData(pod2), envToApply); } @Test public void shouldProvisionMachinesMapWithComponentAttributePreSet() throws Exception { @SuppressWarnings("unchecked") ArgumentCaptor> mapCaptor = ArgumentCaptor.forClass(Map.class); // given String yamlRecipeContent = getResource("devfile/petclinic.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); List k8sList = toK8SList(yamlRecipeContent).getItems(); doReturn(k8sList).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setMountSources(true); // when applier.apply(workspaceConfig, component, contentProvider); // then verify(k8sEnvProvisioner).provision(any(), any(), any(), mapCaptor.capture()); Map machines = mapCaptor.getValue(); assertTrue( machines.values().stream() .allMatch( config -> config .getAttributes() .get(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE) .equals(component.getAlias()))); } @Test public void shouldFilterRecipeWithGivenSelectors() throws Exception { // given String yamlRecipeContent = getResource("devfile/petclinic.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); final Map selector = singletonMap("app.kubernetes.io/component", "webapp"); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setSelector(selector); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); // when applier.apply(workspaceConfig, component, contentProvider); // then verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), anyMap()); List resultItemsList = objectsCaptor.getValue(); assertEquals(resultItemsList.size(), 3); assertEquals(1, resultItemsList.stream().filter(it -> "Pod".equals(it.getKind())).count()); assertEquals(1, resultItemsList.stream().filter(it -> "Service".equals(it.getKind())).count()); assertEquals(1, resultItemsList.stream().filter(it -> "Route".equals(it.getKind())).count()); } @Test(dependsOnMethods = "shouldFilterRecipeWithGivenSelectors", enabled = false) public void shouldSetMachineNameAttributeToCommandConfiguredInOpenShiftComponentWithOneContainer() throws Exception { // given String yamlRecipeContent = getResource("devfile/petclinic.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); final Map selector = singletonMap("app.kubernetes.io/component", "webapp"); ComponentImpl component = new ComponentImpl(); component.setType(OPENSHIFT_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setSelector(selector); CommandImpl command = new CommandImpl(); command.getAttributes().put(COMPONENT_ALIAS_COMMAND_ATTRIBUTE, COMPONENT_NAME); workspaceConfig.getCommands().add(command); // when applier.apply(workspaceConfig, component, contentProvider); // then CommandImpl actualCommand = workspaceConfig.getCommands().get(0); assertEquals(actualCommand.getAttributes().get(MACHINE_NAME_ATTRIBUTE), "petclinic/server"); } @Test public void shouldNotSetMachineNameAttributeToCommandConfiguredInOpenShiftComponentWithMultipleContainers() throws Exception { // given String yamlRecipeContent = getResource("devfile/petclinic.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(OPENSHIFT_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); CommandImpl command = new CommandImpl(); command.getAttributes().put(COMPONENT_ALIAS_COMMAND_ATTRIBUTE, COMPONENT_NAME); workspaceConfig.getCommands().add(command); // when applier.apply(workspaceConfig, component, contentProvider); // then CommandImpl actualCommand = workspaceConfig.getCommands().get(0); assertNull(actualCommand.getAttributes().get(MACHINE_NAME_ATTRIBUTE)); } @Test public void shouldChangeEntrypointsOnMatchingContainers() throws Exception { // given String yamlRecipeContent = getResource("devfile/petclinic.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); List command = asList("teh", "command"); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); EntrypointImpl entrypoint = new EntrypointImpl(); entrypoint.setParentName("petclinic"); entrypoint.setCommand(command); component.setEntrypoints(singletonList(entrypoint)); // when applier.apply(workspaceConfig, component, contentProvider); // then verify(k8sEnvProvisioner).provision(any(), any(), objectsCaptor.capture(), any()); List list = objectsCaptor.getValue(); for (HasMetadata o : list) { if (o instanceof Pod) { Pod p = (Pod) o; // ignore pods that don't have containers if (p.getSpec() == null) { continue; } Container c = p.getSpec().getContainers().get(0); if (o.getMetadata().getName().equals("petclinic")) { assertEquals(c.getCommand(), command); } else { assertTrue(c.getCommand() == null || c.getCommand().isEmpty()); } } } } @Test public void shouldProvisionEndpointsToAllMachines() throws IOException, ValidationException, InfrastructureException, DevfileException { // given String endpointName = "petclinic-endpoint"; Integer endpointPort = 8081; String yamlRecipeContent = getResource("devfile/petclinicPods.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setEndpoints(singletonList(new EndpointImpl(endpointName, endpointPort, emptyMap()))); // when applier.apply(workspaceConfig, component, contentProvider); // then @SuppressWarnings("unchecked") ArgumentCaptor> objectsCaptor = ArgumentCaptor.forClass(Map.class); verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); Map configs = objectsCaptor.getValue(); assertEquals(configs.size(), 3); configs .values() .forEach( machineConfig -> { Map serverConfigs = machineConfig.getServers(); assertEquals(serverConfigs.size(), 1); assertTrue(serverConfigs.containsKey(endpointName)); assertEquals(serverConfigs.get(endpointName).getPort(), endpointPort.toString()); assertNull(serverConfigs.get(endpointName).getPath()); }); } @Test public void shouldProvisionEndpointWithAttributes() throws IOException, ValidationException, InfrastructureException, DevfileException { // given String endpointName = "petclinic-endpoint"; Integer endpointPort = 8081; String endpointProtocol = "tcp"; String endpointPath = "path"; String endpointPublic = "false"; String endpointSecure = "false"; String yamlRecipeContent = getResource("devfile/petclinicPods.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setEndpoints( singletonList( new EndpointImpl( endpointName, endpointPort, ImmutableMap.of( "protocol", endpointProtocol, "path", endpointPath, "public", endpointPublic, "secure", endpointSecure)))); // when applier.apply(workspaceConfig, component, contentProvider); // then @SuppressWarnings("unchecked") ArgumentCaptor> objectsCaptor = ArgumentCaptor.forClass(Map.class); verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); Map configs = objectsCaptor.getValue(); assertEquals(configs.size(), 3); configs .values() .forEach( machineConfig -> { Map serverConfigs = machineConfig.getServers(); assertEquals(serverConfigs.size(), 1); assertTrue(serverConfigs.containsKey(endpointName)); assertEquals(serverConfigs.get(endpointName).getPort(), endpointPort.toString()); assertEquals(serverConfigs.get(endpointName).getPath(), endpointPath); assertEquals(serverConfigs.get(endpointName).getProtocol(), endpointProtocol); assertEquals( serverConfigs.get(endpointName).getAttributes().get(REQUIRE_SUBDOMAIN), "true"); assertEquals( serverConfigs.get(endpointName).isSecure(), Boolean.parseBoolean(endpointSecure)); assertEquals( serverConfigs.get(endpointName).isInternal(), !Boolean.parseBoolean(endpointPublic)); }); } @Test public void serverCantHaveRequireSubdomainWhenSinglehostDevfileExpose() throws DevfileException, IOException, ValidationException, InfrastructureException { applier = new KubernetesComponentToWorkspaceApplier( k8sRecipeParser, k8sEnvProvisioner, envVars, "Always", SINGLE_HOST_STRATEGY, k8sBasedComponents); String yamlRecipeContent = getResource("devfile/petclinic.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); // given when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setEndpoints( Arrays.asList( new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); // when applier.apply(workspaceConfig, component, contentProvider); // then @SuppressWarnings("unchecked") ArgumentCaptor> objectsCaptor = ArgumentCaptor.forClass(Map.class); verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); Map machineConfigs = objectsCaptor.getValue(); assertEquals(machineConfigs.size(), 4); machineConfigs .values() .forEach( machineConfig -> { assertEquals(machineConfig.getServers().size(), 2); assertFalse( ServerConfig.isRequireSubdomain( machineConfig.getServers().get("e1").getAttributes())); assertFalse( ServerConfig.isRequireSubdomain( machineConfig.getServers().get("e2").getAttributes())); }); } @Test public void serverMustHaveRequireSubdomainWhenNonSinglehostDevfileExpose() throws DevfileException, IOException, ValidationException, InfrastructureException { applier = new KubernetesComponentToWorkspaceApplier( k8sRecipeParser, k8sEnvProvisioner, envVars, "Always", MULTI_HOST_STRATEGY, k8sBasedComponents); String yamlRecipeContent = getResource("devfile/petclinic.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); // given ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setEndpoints( Arrays.asList( new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); // when applier.apply(workspaceConfig, component, contentProvider); // then @SuppressWarnings("unchecked") ArgumentCaptor> objectsCaptor = ArgumentCaptor.forClass(Map.class); verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); Map machineConfigs = objectsCaptor.getValue(); assertEquals(machineConfigs.size(), 4); machineConfigs .values() .forEach( machineConfig -> { assertEquals(machineConfig.getServers().size(), 2); assertTrue( ServerConfig.isRequireSubdomain( machineConfig.getServers().get("e1").getAttributes())); assertTrue( ServerConfig.isRequireSubdomain( machineConfig.getServers().get("e2").getAttributes())); }); } private KubernetesList toK8SList(String content) { return unmarshal(content, KubernetesList.class); } private String getResource(String resourceName) throws IOException { return Files.readFile(getClass().getClassLoader().getResourceAsStream(resourceName)); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/devfile/KubernetesEnvironmentProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.devfile; import static io.fabric8.kubernetes.client.utils.Serialization.unmarshal; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.shared.Constants.PROJECTS_VOLUME_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesEnvironmentProvisioner.YAML_CONTENT_TYPE; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.newPVC; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesList; import io.fabric8.kubernetes.api.model.KubernetesListBuilder; import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.client.utils.Serialization; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import org.testng.reporters.Files; /** * Tests {@link KubernetesEnvironmentProvisioner} * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class KubernetesEnvironmentProvisionerTest { private WorkspaceConfigImpl workspaceConfig; @Mock private KubernetesRecipeParser k8sRecipeParser; private KubernetesEnvironmentProvisioner k8sEnvProvisioner; @BeforeMethod public void setUp() { workspaceConfig = new WorkspaceConfigImpl(); // "openshift" is what we use in the test devfile files and what we need to test the upgrade // and multiple k8s-based types Map> allowedUpgrades = new HashMap<>(); allowedUpgrades .compute("openshift", (__, ___) -> new HashSet<>()) .add(KubernetesEnvironment.TYPE); Set k8sEnvTypes = new HashSet<>(); k8sEnvTypes.add(KubernetesEnvironment.TYPE); k8sEnvTypes.add("openshift"); k8sEnvProvisioner = new KubernetesEnvironmentProvisioner(k8sRecipeParser, allowedUpgrades, k8sEnvTypes); } @Test public void shouldProvisionEnvironmentWithCorrectRecipeTypeAndContentFromK8SList() throws Exception { // given String yamlRecipeContent = getResource("devfile/petclinic.yaml"); List componentsObjects = toK8SList(yamlRecipeContent).getItems(); // when k8sEnvProvisioner.provision( workspaceConfig, KubernetesEnvironment.TYPE, componentsObjects, emptyMap()); // then String defaultEnv = workspaceConfig.getDefaultEnv(); assertNotNull(defaultEnv); EnvironmentImpl environment = workspaceConfig.getEnvironments().get(defaultEnv); assertNotNull(environment); RecipeImpl recipe = environment.getRecipe(); assertNotNull(recipe); assertEquals(recipe.getType(), KUBERNETES_COMPONENT_TYPE); assertEquals(recipe.getContentType(), YAML_CONTENT_TYPE); // it is expected that applier wrap original recipes objects in new Kubernetes list KubernetesList expectedKubernetesList = new KubernetesListBuilder().withItems(toK8SList(yamlRecipeContent).getItems()).build(); assertEquals(toK8SList(recipe.getContent()).getItems(), expectedKubernetesList.getItems()); } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Environment already contains machine 'machine'") public void shouldThrowAnExceptionIfEnvironmentContainMachineWithSpecifiedName() throws Exception { // given workspaceConfig.setDefaultEnv("default"); workspaceConfig.setEnvironments( ImmutableMap.of( "default", new EnvironmentImpl(null, ImmutableMap.of("machine", new MachineConfigImpl())))); // when k8sEnvProvisioner.provision( workspaceConfig, KubernetesEnvironment.TYPE, emptyList(), ImmutableMap.of("machine", new MachineConfigImpl())); } @Test public void shouldUpgradeKubernetesEnvironmentToOpenShiftTypeOnOpenShiftComponentProvisioning() throws Exception { // given workspaceConfig.setDefaultEnv("default"); RecipeImpl existingRecipe = new RecipeImpl(KubernetesEnvironment.TYPE, "yaml", "existing-content", null); workspaceConfig .getEnvironments() .put("default", new EnvironmentImpl(existingRecipe, emptyMap())); List componentsObject = new ArrayList<>(); Deployment componentDeployment = new DeploymentBuilder() .withNewMetadata() .withName("web-app") .endMetadata() .withNewSpec() .endSpec() .build(); componentsObject.add(new DeploymentBuilder(componentDeployment).build()); doReturn(new ArrayList<>()).when(k8sRecipeParser).parse(anyString()); // when k8sEnvProvisioner.provision(workspaceConfig, "openshift", componentsObject, emptyMap()); // then EnvironmentImpl resultEnv = workspaceConfig.getEnvironments().get(workspaceConfig.getDefaultEnv()); RecipeImpl resultRecipe = resultEnv.getRecipe(); assertEquals(resultRecipe.getType(), "openshift"); } @Test public void shouldProvisionComponentObjectsIntoExistingKubernetesRecipe() throws Exception { // given workspaceConfig.setDefaultEnv("default"); RecipeImpl existingRecipe = new RecipeImpl(KUBERNETES_COMPONENT_TYPE, "yaml", "existing-content", null); workspaceConfig .getEnvironments() .put("default", new EnvironmentImpl(existingRecipe, emptyMap())); List recipeObjects = new ArrayList<>(); Deployment recipeDeployment = new DeploymentBuilder() .withNewMetadata() .withName("db") .endMetadata() .withNewSpec() .endSpec() .build(); recipeObjects.add(new DeploymentBuilder(recipeDeployment).build()); doReturn(recipeObjects).when(k8sRecipeParser).parse(anyString()); List componentsObject = new ArrayList<>(); Deployment componentDeployment = new DeploymentBuilder() .withNewMetadata() .withName("web-app") .endMetadata() .withNewSpec() .endSpec() .build(); componentsObject.add(new DeploymentBuilder(componentDeployment).build()); // when k8sEnvProvisioner.provision( workspaceConfig, KubernetesEnvironment.TYPE, componentsObject, emptyMap()); // then // it is expected that applier wrap original recipes objects in new Kubernetes list KubernetesList expectedKubernetesList = new KubernetesListBuilder() .withItems(Arrays.asList(recipeDeployment, componentDeployment)) .build(); EnvironmentImpl resultEnv = workspaceConfig.getEnvironments().get(workspaceConfig.getDefaultEnv()); assertEquals( toK8SList(resultEnv.getRecipe().getContent()).getItems(), expectedKubernetesList.getItems()); } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Kubernetes component can only be applied to a workspace with any of kubernetes or openshift " + "recipe type but workspace has a recipe of type 'any'") public void shouldThrowAnExceptionIfWorkspaceAlreadyContainNonK8sNorOSRecipe() throws Exception { // given workspaceConfig.setDefaultEnv("default"); RecipeImpl existingRecipe = new RecipeImpl("any", "yaml", "existing-content", null); workspaceConfig .getEnvironments() .put("default", new EnvironmentImpl(existingRecipe, emptyMap())); List componentsObject = new ArrayList<>(); Deployment componentDeployment = new DeploymentBuilder() .withNewMetadata() .withName("db") .endMetadata() .withNewSpec() .endSpec() .build(); componentsObject.add(new DeploymentBuilder(componentDeployment).build()); // when k8sEnvProvisioner.provision( workspaceConfig, KubernetesEnvironment.TYPE, componentsObject, emptyMap()); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Components can not have objects with the same name and kind " + "but there are multiple objects with kind 'Service' and name 'db'") public void shouldThrowExceptionIfDifferentComponentsHaveObjectsWithTheSameKindAndName() throws Exception { // given List componentsObject = new ArrayList<>(); Service service1 = new ServiceBuilder() .withNewMetadata() .withName("db") .endMetadata() .withNewSpec() .endSpec() .build(); Service service2 = new ServiceBuilder() .withNewMetadata() .withName("db") .endMetadata() .withNewSpec() .endSpec() .build(); componentsObject.add(new ServiceBuilder(service1).build()); componentsObject.add(new ServiceBuilder(service2).build()); // when k8sEnvProvisioner.provision( workspaceConfig, KubernetesEnvironment.TYPE, componentsObject, emptyMap()); } @Test public void shouldMergeProjectPVCIntoOne() throws Exception { // given PersistentVolumeClaim volumeClaim = newPVC(PROJECTS_VOLUME_NAME, "ReadWriteMany", "1Gb"); workspaceConfig.setDefaultEnv("default"); RecipeImpl existingRecipe = new RecipeImpl("kubernetes", YAML_CONTENT_TYPE, Serialization.asYaml(volumeClaim), null); doReturn(singletonList(volumeClaim)).when(k8sRecipeParser).parse(anyString()); workspaceConfig .getEnvironments() .put("default", new EnvironmentImpl(existingRecipe, emptyMap())); // try add same claim one more time (like another component adds it) List componentsObject = new ArrayList<>(); componentsObject.add(volumeClaim); // when k8sEnvProvisioner.provision( workspaceConfig, KubernetesEnvironment.TYPE, componentsObject, emptyMap()); // we still have only one PVC EnvironmentImpl resultEnv = workspaceConfig.getEnvironments().get(workspaceConfig.getDefaultEnv()); assertEquals(toK8SList(resultEnv.getRecipe().getContent()).getItems().size(), 1); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Components can not have objects with the same name and kind but there are multiple objects with kind 'Service' and name 'db'") public void shouldThrowExceptionIfComponentHasMultipleObjectsWithTheSameKindAndName() throws Exception { // given List objects = new ArrayList<>(); Service service = new ServiceBuilder() .withNewMetadata() .withName("db") .endMetadata() .withNewSpec() .endSpec() .build(); objects.add(new ServiceBuilder(service).build()); objects.add(new ServiceBuilder(service).build()); // when k8sEnvProvisioner.provision(workspaceConfig, KubernetesEnvironment.TYPE, objects, emptyMap()); } private KubernetesList toK8SList(String content) { return unmarshal(content, KubernetesList.class); } private String getResource(String resourceName) throws IOException { return Files.readFile(getClass().getClassLoader().getResourceAsStream(resourceName)); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/docker/auth/UserSpecificDockerRegistryCredentialsProviderTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.docker.auth; import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.DockerAuthConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.DockerAuthConfigs; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Mykola Morhun */ @Listeners(MockitoTestNGListener.class) public class UserSpecificDockerRegistryCredentialsProviderTest { private static final String DOCKER_REGISTRY_CREDENTIALS_KEY = "dockerCredentials"; @Mock private PreferenceManager preferenceManager; private UserSpecificDockerRegistryCredentialsProvider dockerCredentials; @BeforeMethod private void before() { dockerCredentials = new UserSpecificDockerRegistryCredentialsProvider(preferenceManager); } @Test public void shouldParseCredentialsFromUserPreferences() throws ServerException { String base64encodedCredentials = "eyJyZWdpc3RyeS5jb206NTAwMCI6eyJ1c2VybmFtZSI6ImxvZ2luIiwicGFzc3dvcmQiOiJwYXNzIn19"; setCredentialsIntoPreferences(base64encodedCredentials); String registry = "registry.com:5000"; DockerAuthConfig dockerAuthConfig = DtoFactory.newDto(DockerAuthConfig.class).withUsername("login").withPassword("pass"); DockerAuthConfigs parsedDockerAuthConfigs = dockerCredentials.getCredentials(); DockerAuthConfig parsedDockerAuthConfig = parsedDockerAuthConfigs.getConfigs().get(registry); assertNotNull(parsedDockerAuthConfig); assertEquals(parsedDockerAuthConfig.getUsername(), dockerAuthConfig.getUsername()); assertEquals(parsedDockerAuthConfig.getPassword(), dockerAuthConfig.getPassword()); } @Test public void shouldReturnNullIfDataFormatIsCorruptedInPreferences() throws ServerException { String base64encodedCredentials = "sdJfpwJwkek59kafj239lFfkHjhek5l1"; setCredentialsIntoPreferences(base64encodedCredentials); DockerAuthConfigs parsedDockerAuthConfigs = dockerCredentials.getCredentials(); assertNull(parsedDockerAuthConfigs); } @Test public void shouldReturnNullIfDataFormatIsWrong() throws ServerException { String base64encodedCredentials = "eyJpbnZhbGlkIjoianNvbiJ9"; setCredentialsIntoPreferences(base64encodedCredentials); DockerAuthConfigs parsedDockerAuthConfigs = dockerCredentials.getCredentials(); assertNull(parsedDockerAuthConfigs); } @Test public void shouldReturnConfigsWithEmptyMapIfNoCredentialsDataInUserPreferences() throws ServerException { String base64encodedCredentials = "e30="; setCredentialsIntoPreferences(base64encodedCredentials); DockerAuthConfigs parsedDockerAuthConfigs = dockerCredentials.getCredentials(); assertEquals(parsedDockerAuthConfigs.getConfigs().size(), 0); } private void setCredentialsIntoPreferences(String base64encodedCredentials) throws ServerException { Map preferences = new HashMap<>(); preferences.put(DOCKER_REGISTRY_CREDENTIALS_KEY, base64encodedCredentials); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("name", Collections.emptyList(), "id", "token1234", false)); when(preferenceManager.find(anyObject(), anyObject())).thenReturn(preferences); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/CheInstallationLocationTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import static org.testng.Assert.*; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.testng.annotations.Test; public class CheInstallationLocationTest { @Test public void returnKubernetesNamespaceWhenBothSet() throws InfrastructureException { CheInstallationLocation cheInstallationLocation = new CheInstallationLocation(); cheInstallationLocation.kubernetesNamespace = "kube"; cheInstallationLocation.podNamespace = "pod"; assertEquals(cheInstallationLocation.getInstallationLocationNamespace(), "kube"); } @Test public void returnKubernetesNamespaceWhenItsOnlySet() throws InfrastructureException { CheInstallationLocation cheInstallationLocation = new CheInstallationLocation(); cheInstallationLocation.kubernetesNamespace = "kube"; cheInstallationLocation.podNamespace = null; assertEquals(cheInstallationLocation.getInstallationLocationNamespace(), "kube"); } @Test public void returnPodNamespaceWhenKubernetesNamespaceNotSet() throws InfrastructureException { CheInstallationLocation cheInstallationLocation = new CheInstallationLocation(); cheInstallationLocation.kubernetesNamespace = null; cheInstallationLocation.podNamespace = "pod"; assertEquals(cheInstallationLocation.getInstallationLocationNamespace(), "pod"); } @Test(expectedExceptions = InfrastructureException.class) public void throwExceptionWhenNoneSet() throws InfrastructureException { CheInstallationLocation cheInstallationLocation = new CheInstallationLocation(); cheInstallationLocation.kubernetesNamespace = null; cheInstallationLocation.podNamespace = null; cheInstallationLocation.getInstallationLocationNamespace(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentFactoryTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static org.eclipse.che.workspace.infrastructure.kubernetes.Warnings.INGRESSES_IGNORED_WARNING_CODE; import static org.eclipse.che.workspace.infrastructure.kubernetes.Warnings.INGRESSES_IGNORED_WARNING_MESSAGE; import static org.eclipse.che.workspace.infrastructure.kubernetes.environment.PodMerger.DEPLOYMENT_NAME_LABEL; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.PodTemplateSpec; import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder; import io.fabric8.kubernetes.api.model.Quantity; import io.fabric8.kubernetes.api.model.ResourceRequirements; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.api.model.networking.v1.IngressBuilder; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.workspace.server.model.impl.WarningImpl; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link KubernetesEnvironmentFactory}. * * @author Anton Korneta */ @Listeners(MockitoTestNGListener.class) public class KubernetesEnvironmentFactoryTest { public static final String MACHINE_NAME_1 = "machine1"; public static final String MACHINE_NAME_2 = "machine2"; private KubernetesEnvironmentFactory k8sEnvFactory; @Mock private KubernetesEnvironmentValidator k8sEnvValidator; @Mock private InternalEnvironment internalEnvironment; @Mock private InternalRecipe internalRecipe; @Mock private InternalMachineConfig machineConfig1; @Mock private InternalMachineConfig machineConfig2; @Mock private KubernetesRecipeParser k8sRecipeParser; @Mock private PodMerger podMerger; @BeforeMethod public void setup() throws Exception { k8sEnvFactory = new KubernetesEnvironmentFactory(null, null, k8sRecipeParser, k8sEnvValidator, podMerger); lenient().when(internalEnvironment.getRecipe()).thenReturn(internalRecipe); } @Test public void shouldCreateK8sEnvironmentWithServicesFromRecipe() throws Exception { // given Service service1 = new ServiceBuilder().withNewMetadata().withName("service1").endMetadata().build(); Service service2 = new ServiceBuilder().withNewMetadata().withName("service2").endMetadata().build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(service1, service2)); // when KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then assertEquals(k8sEnv.getServices().size(), 2); assertEquals(k8sEnv.getServices().get("service1"), service1); assertEquals(k8sEnv.getServices().get("service2"), service2); } @Test public void shouldCreateK8sEnvironmentWithPVCsFromRecipe() throws Exception { // given PersistentVolumeClaim pvc1 = new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc1").endMetadata().build(); PersistentVolumeClaim pvc2 = new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc2").endMetadata().build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(pvc1, pvc2)); // when KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then assertEquals(k8sEnv.getPersistentVolumeClaims().size(), 2); assertEquals(k8sEnv.getPersistentVolumeClaims().get("pvc1"), pvc1); assertEquals(k8sEnv.getPersistentVolumeClaims().get("pvc2"), pvc2); } @Test public void ignoreIgressesWhenRecipeContainsThem() throws Exception { when(k8sRecipeParser.parse(any(InternalRecipe.class))) .thenReturn( asList( new IngressBuilder().withNewMetadata().withName("ingress1").endMetadata().build(), new IngressBuilder().withNewMetadata().withName("ingress2").endMetadata().build())); final KubernetesEnvironment parsed = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); assertTrue(parsed.getIngresses().isEmpty()); assertEquals(parsed.getWarnings().size(), 1); assertEquals( parsed.getWarnings().get(0), new WarningImpl(INGRESSES_IGNORED_WARNING_CODE, INGRESSES_IGNORED_WARNING_MESSAGE)); } @Test public void addSecretsWhenRecipeContainsThem() throws Exception { Secret secret = new SecretBuilder().withNewMetadata().withName("test-secret").endMetadata().build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(secret)); final KubernetesEnvironment parsed = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); assertEquals(parsed.getSecrets().size(), 1); assertEquals( parsed.getSecrets().get("test-secret").getMetadata().getName(), secret.getMetadata().getName()); } @Test public void addConfigMapsWhenRecipeContainsThem() throws Exception { ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("test-configmap").endMetadata().build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(configMap)); final KubernetesEnvironment parsed = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); assertEquals(parsed.getConfigMaps().size(), 1); assertEquals(parsed.getConfigMaps().get("test-configmap"), configMap); } @Test public void addPodsWhenRecipeContainsThem() throws Exception { // given Pod pod = new PodBuilder() .withNewMetadata() .withName("pod") .endMetadata() .withSpec(new PodSpec()) .build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(pod)); // when KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then assertEquals(k8sEnv.getPodsCopy().size(), 1); assertEquals(k8sEnv.getPodsCopy().get("pod"), pod); assertEquals(k8sEnv.getPodsData().size(), 1); assertEquals(k8sEnv.getPodsData().get("pod").getMetadata(), pod.getMetadata()); assertEquals(k8sEnv.getPodsData().get("pod").getSpec(), pod.getSpec()); } @Test public void addDeploymentsWhenRecipeContainsThem() throws Exception { // given PodTemplateSpec podTemplate = new PodTemplateSpecBuilder() .withNewMetadata() .withName("deployment-pod") .endMetadata() .withNewSpec() .endSpec() .build(); Deployment deployment = new DeploymentBuilder() .withNewMetadata() .withName("deployment-test") .endMetadata() .withNewSpec() .withTemplate(podTemplate) .endSpec() .build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(deployment)); // when final KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then assertEquals(k8sEnv.getDeploymentsCopy().size(), 1); assertEquals(k8sEnv.getDeploymentsCopy().get("deployment-test"), deployment); assertEquals(k8sEnv.getPodsData().size(), 1); assertEquals( k8sEnv.getPodsData().get("deployment-test").getMetadata(), podTemplate.getMetadata()); assertEquals(k8sEnv.getPodsData().get("deployment-test").getSpec(), podTemplate.getSpec()); } @Test public void shouldUseDeploymentNameAsPodTemplateNameIfItIsMissing() throws Exception { // given PodTemplateSpec podTemplate = new PodTemplateSpecBuilder().withNewSpec().endSpec().build(); Deployment deployment = new DeploymentBuilder() .withNewMetadata() .withName("deployment-test") .endMetadata() .withNewSpec() .withTemplate(podTemplate) .endSpec() .build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(deployment)); // when final KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then Deployment deploymentTest = k8sEnv.getDeploymentsCopy().get("deployment-test"); assertNotNull(deploymentTest); PodTemplateSpec resultPodTemplate = deploymentTest.getSpec().getTemplate(); assertEquals(resultPodTemplate.getMetadata().getName(), "deployment-test"); } @Test public void shouldMergeDeploymentAndPodIntoOneDeployment() throws Exception { // given PodTemplateSpec podTemplate = new PodTemplateSpecBuilder() .withNewMetadata() .withName("deployment-pod") .endMetadata() .withNewSpec() .endSpec() .build(); Deployment deployment = new DeploymentBuilder() .withNewMetadata() .withName("deployment-test") .endMetadata() .withNewSpec() .withTemplate(podTemplate) .endSpec() .build(); Pod pod = new PodBuilder() .withNewMetadata() .withName("bare-pod") .endMetadata() .withNewSpec() .endSpec() .build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(deployment, pod)); Deployment merged = createEmptyDeployment("merged"); when(podMerger.merge(any())).thenReturn(merged); // when final KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then verify(podMerger).merge(asList(new PodData(pod), new PodData(deployment))); assertEquals(k8sEnv.getPodsData().size(), 1); assertTrue(k8sEnv.getPodsCopy().isEmpty()); assertEquals(k8sEnv.getDeploymentsCopy().size(), 1); assertEquals(k8sEnv.getDeploymentsCopy().get("merged"), merged); } @Test public void shouldReconfigureServiceToMatchMergedDeployment() throws Exception { // given Pod pod1 = new PodBuilder() .withNewMetadata() .withName("bare-pod1") .withLabels(ImmutableMap.of("name", "pod1")) .endMetadata() .withNewSpec() .endSpec() .build(); Pod pod2 = new PodBuilder() .withNewMetadata() .withName("bare-pod2") .withLabels(ImmutableMap.of("name", "pod2")) .endMetadata() .withNewSpec() .endSpec() .build(); Service service1 = new ServiceBuilder() .withNewMetadata() .withName("pod1-service") .endMetadata() .withNewSpec() .withSelector(ImmutableMap.of("name", "pod1")) .endSpec() .build(); Service service2 = new ServiceBuilder() .withNewMetadata() .withName("pod2-service") .endMetadata() .withNewSpec() .withSelector(ImmutableMap.of("name", "pod2")) .endSpec() .build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))) .thenReturn(asList(pod1, pod2, service1, service2)); Deployment merged = createEmptyDeployment("merged"); when(podMerger.merge(any())).thenReturn(merged); // when final KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then verify(podMerger).merge(asList(new PodData(pod1), new PodData(pod2))); PodData mergedPodData = k8sEnv.getPodsData().get("merged"); assertEquals(mergedPodData.getMetadata().getLabels().get(DEPLOYMENT_NAME_LABEL), "merged"); assertTrue( k8sEnv.getServices().values().stream() .allMatch( s -> ImmutableMap.of(DEPLOYMENT_NAME_LABEL, "merged") .equals(s.getSpec().getSelector()))); } @Test(expectedExceptions = ValidationException.class) public void exceptionOnRecipeLoadError() throws Exception { when(k8sRecipeParser.parse(any(InternalRecipe.class))) .thenThrow(new ValidationException("Could not parse recipe")); k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Environment can not contain two 'Service' objects with the same name 'db'") public void exceptionOnObjectsWithTheSameNameAndKind() throws Exception { HasMetadata object1 = new ServiceBuilder().withNewMetadata().withName("db").endMetadata().build(); HasMetadata object2 = new ServiceBuilder().withNewMetadata().withName("db").endMetadata().build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(object1, object2)); k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Environment contains object without specified kind field") public void exceptionOnObjectWithNoKindSpecified() throws Exception { HasMetadata object = mock(HasMetadata.class); when(object.getKind()).thenReturn(null); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object)); k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "MyObject metadata must not be null") public void exceptionOnObjectWithNoMetadataSpecified() throws Exception { HasMetadata object = mock(HasMetadata.class); when(object.getKind()).thenReturn("MyObject"); when(object.getMetadata()).thenReturn(null); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object)); k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "MyObject name must not be null") public void exceptionOnObjectWithNoNameSpecified() throws Exception { HasMetadata object = mock(HasMetadata.class); when(object.getKind()).thenReturn("MyObject"); when(object.getMetadata()).thenReturn(new ObjectMetaBuilder().withName(null).build()); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object)); k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); } private static PodData createPodData(String machineName, long ramLimit, long ramRequest) { final String containerName = "container_" + machineName; final Container containerMock = mock(Container.class); final ResourceRequirements resourcesMock = mock(ResourceRequirements.class); final Quantity limitQuantityMock = mock(Quantity.class); final Quantity requestQuantityMock = mock(Quantity.class); final PodSpec specMock = mock(PodSpec.class); final ObjectMeta metadataMock = mock(ObjectMeta.class); when(limitQuantityMock.getAmount()).thenReturn(String.valueOf(ramLimit)); when(requestQuantityMock.getAmount()).thenReturn(String.valueOf(ramRequest)); when(resourcesMock.getLimits()).thenReturn(ImmutableMap.of("memory", limitQuantityMock)); when(resourcesMock.getRequests()).thenReturn(ImmutableMap.of("memory", requestQuantityMock)); when(containerMock.getName()).thenReturn(containerName); when(containerMock.getResources()).thenReturn(resourcesMock); when(metadataMock.getAnnotations()) .thenReturn(Names.createMachineNameAnnotations(containerName, machineName)); when(specMock.getContainers()).thenReturn(ImmutableList.of(containerMock)); return new PodData(specMock, metadataMock); } private Deployment createEmptyDeployment(String name) { return new DeploymentBuilder() .withNewMetadata() .withName(name) .endMetadata() .withNewSpec() .withNewTemplate() .withNewMetadata() .endMetadata() .withNewSpec() .endSpec() .endTemplate() .endSpec() .build(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentPodsValidatorTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.VolumeMountBuilder; import java.util.Arrays; import java.util.stream.Collectors; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link KubernetesEnvironmentPodsValidator}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class KubernetesEnvironmentPodsValidatorTest { @Mock private KubernetesEnvironment kubernetesEnvironment; private KubernetesEnvironmentPodsValidator podsValidator; @BeforeMethod public void setUp() { podsValidator = new KubernetesEnvironmentPodsValidator(); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Environment should contain at least 1 pod or deployment") public void shouldThrowExceptionWhenEnvDoesNotHaveAnyPods() throws Exception { // given when(kubernetesEnvironment.getPodsData()).thenReturn(emptyMap()); // when podsValidator.validate(kubernetesEnvironment); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Environment contains machines that are missing in recipe: pod1/db") public void shouldThrowExceptionWhenMachineIsDeclaredButThereIsNotContainerInKubernetesRecipe() throws Exception { // given String podName = "pod1"; Pod pod = createPod("pod1", "main"); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); when(kubernetesEnvironment.getPodsData()).thenReturn(ImmutableMap.of(podName, podData)); when(kubernetesEnvironment.getMachines()) .thenReturn(ImmutableMap.of(podName + "/db", mock(InternalMachineConfig.class))); // when podsValidator.validate(kubernetesEnvironment); } @Test public void shouldPassWhenMachineIsDeclaredAndIsInitContainerInKubernetesRecipe() throws Exception { // given String podName = "pod1"; Pod pod = new PodBuilder() .withNewMetadata() .withName(podName) .endMetadata() .withNewSpec() .withInitContainers(singletonList(createContainer("init"))) .endSpec() .build(); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); when(kubernetesEnvironment.getPodsData()).thenReturn(ImmutableMap.of(podName, podData)); when(kubernetesEnvironment.getMachines()) .thenReturn(ImmutableMap.of(podName + "/init", mock(InternalMachineConfig.class))); // when podsValidator.validate(kubernetesEnvironment); // then // no exception means machine matches init container - it's expected } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Environment contains pod with missing metadata") public void shouldThrowExceptionWhenPodHasNoMetadata() throws Exception { // given PodData podData = new PodData(new PodSpec(), null); when(kubernetesEnvironment.getPodsData()).thenReturn(ImmutableMap.of("", podData)); // when podsValidator.validate(kubernetesEnvironment); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Pod 'pod1' with missing metadata") public void shouldThrowExceptionWhenPodHasNoSpec() throws Exception { // given PodData podData = new PodData(null, new ObjectMetaBuilder().withName("pod1").build()); when(kubernetesEnvironment.getPodsData()).thenReturn(ImmutableMap.of("pod1", podData)); // when podsValidator.validate(kubernetesEnvironment); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Pod 'pod1' contains volume 'user-data' with PVC sources that references missing PVC 'non-existing'") public void shouldThrowExceptionWhenPodHasVolumeThatReferencesMissingPVC() throws Exception { // given String podName = "pod1"; Pod pod = createPod("pod1", "main"); pod.getSpec() .getVolumes() .add( new VolumeBuilder() .withName("user-data") .withNewPersistentVolumeClaim() .withClaimName("non-existing") .endPersistentVolumeClaim() .build()); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); when(kubernetesEnvironment.getPodsData()).thenReturn(ImmutableMap.of(podName, podData)); when(kubernetesEnvironment.getMachines()) .thenReturn(ImmutableMap.of(podName + "/main", mock(InternalMachineConfig.class))); // when podsValidator.validate(kubernetesEnvironment); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Container 'main' in pod 'pod1' contains volume mount that references missing volume 'non-existing'") public void shouldThrowExceptionWhenContainerHasVolumeMountThatReferencesMissingPodVolume() throws Exception { // given String podName = "pod1"; Pod pod = createPod("pod1", "main"); pod.getSpec() .getContainers() .get(0) .getVolumeMounts() .add(new VolumeMountBuilder().withName("non-existing").withMountPath("/tmp/data").build()); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); when(kubernetesEnvironment.getPodsData()).thenReturn(ImmutableMap.of(podName, podData)); when(kubernetesEnvironment.getMachines()) .thenReturn(ImmutableMap.of(podName + "/main", mock(InternalMachineConfig.class))); // when podsValidator.validate(kubernetesEnvironment); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Container 'foo' in pod 'pod1' contains volume mount that references missing volume 'non-existing'") public void shouldThrowExceptionWhenInitContainerHasVolumeMountThatReferencesMissingPodVolume() throws Exception { // given String podName = "pod1"; Pod pod = createPod("pod1", "main"); pod.getSpec() .getInitContainers() .add( new ContainerBuilder() .withName("foo") .withVolumeMounts( new VolumeMountBuilder() .withName("non-existing") .withMountPath("/tmp/data") .build()) .build()); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); when(kubernetesEnvironment.getPodsData()).thenReturn(ImmutableMap.of(podName, podData)); when(kubernetesEnvironment.getMachines()) .thenReturn(ImmutableMap.of(podName + "/main", mock(InternalMachineConfig.class))); // when podsValidator.validate(kubernetesEnvironment); } private Pod createPod(String name, String... containers) { return new PodBuilder() .withNewMetadata() .withName(name) .endMetadata() .withNewSpec() .withContainers( Arrays.stream(containers).map(this::createContainer).collect(Collectors.toList())) .endSpec() .build(); } private Container createContainer(String name) { return new ContainerBuilder().withName(name).build(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentValidatorTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link KubernetesEnvironmentValidator}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class KubernetesEnvironmentValidatorTest { @Mock private KubernetesEnvironmentPodsValidator podsValidator; @Mock private KubernetesEnvironment kubernetesEnvironment; @InjectMocks private KubernetesEnvironmentValidator environmentValidator; @Test public void shouldPerformChecksOnEnvironmentValidation() throws Exception { // when environmentValidator.validate(kubernetesEnvironment); // then podsValidator.validate(kubernetesEnvironment); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/PodMergerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment; import static org.eclipse.che.api.workspace.shared.Constants.PROJECTS_VOLUME_NAME; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.LocalObjectReference; import io.fabric8.kubernetes.api.model.LocalObjectReferenceBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.PodSecurityContext; import io.fabric8.kubernetes.api.model.PodSecurityContextBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.PodSpecBuilder; import io.fabric8.kubernetes.api.model.PodTemplateSpec; import io.fabric8.kubernetes.api.model.Toleration; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * Tests {@link PodMerger}. * * @author Sergii Leshchenko */ public class PodMergerTest { private PodMerger podMerger; @BeforeMethod public void setUp() { podMerger = new PodMerger(); } @Test public void shouldMergeMetasOfPodsData() throws Exception { // given ObjectMeta podMeta1 = new ObjectMetaBuilder() .withName("ignored-1") .withAnnotations(ImmutableMap.of("ann1", "v1")) .withLabels(ImmutableMap.of("label1", "v1")) .build(); podMeta1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(new PodSpecBuilder().build(), podMeta1); ObjectMeta podMeta2 = new ObjectMetaBuilder() .withName("ignored-2") .withAnnotations(ImmutableMap.of("ann2", "v2")) .withLabels(ImmutableMap.of("label2", "v2")) .build(); podMeta2.setAdditionalProperty("add2", 2L); PodData podData2 = new PodData(new PodSpecBuilder().build(), podMeta2); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then PodTemplateSpec podTemplate = merged.getSpec().getTemplate(); ObjectMeta podMeta = podTemplate.getMetadata(); verifyContainsAllFrom(podMeta, podData1.getMetadata()); verifyContainsAllFrom(podMeta, podData2.getMetadata()); } @Test public void shouldMatchMergedPodTemplateLabelsWithDeploymentSelector() throws Exception { // given ObjectMeta podMeta1 = new ObjectMetaBuilder() .withName("ignored-1") .withAnnotations(ImmutableMap.of("ann1", "v1")) .withLabels(ImmutableMap.of("label1", "v1")) .build(); podMeta1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(new PodSpecBuilder().build(), podMeta1); // when Deployment merged = podMerger.merge(Collections.singletonList(podData1)); // then PodTemplateSpec podTemplate = merged.getSpec().getTemplate(); ObjectMeta podMeta = podTemplate.getMetadata(); Map deploymentSelector = merged.getSpec().getSelector().getMatchLabels(); assertTrue(podMeta.getLabels().entrySet().containsAll(deploymentSelector.entrySet())); } @Test public void shouldMergeSpecsOfPodsData() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder() .withContainers(new ContainerBuilder().withName("c1").build()) .withInitContainers(new ContainerBuilder().withName("initC1").build()) .withVolumes(new VolumeBuilder().withName("v1").build()) .withNodeSelector(Map.of("foo1", "bar1")) .withImagePullSecrets(new LocalObjectReferenceBuilder().withName("secret1").build()) .withTolerations(new Toleration("Effect", "key", "operator", 0L, "value1")) .build(); podSpec1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder() .withContainers(new ContainerBuilder().withName("c2").build()) .withInitContainers(new ContainerBuilder().withName("initC2").build()) .withVolumes(new VolumeBuilder().withName("v2").build()) .withNodeSelector(Map.of("foo2", "bar2")) .withImagePullSecrets(new LocalObjectReferenceBuilder().withName("secret2").build()) .withTolerations( new Toleration("Effect", "key", "operator", 0L, "value1"), new Toleration("Effect", "key", "operator", 0L, "value2")) .build(); podSpec2.setAdditionalProperty("add2", 2L); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then PodTemplateSpec podTemplate = merged.getSpec().getTemplate(); verifyContainsAllFrom(podTemplate.getSpec(), podData1.getSpec()); verifyContainsAllFrom(podTemplate.getSpec(), podData2.getSpec()); } @Test public void shouldGenerateContainerNamesIfCollisionHappened() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder().withContainers(new ContainerBuilder().withName("c").build()).build(); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder().withContainers(new ContainerBuilder().withName("c").build()).build(); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then PodTemplateSpec podTemplate = merged.getSpec().getTemplate(); List containers = podTemplate.getSpec().getContainers(); assertEquals(containers.size(), 2); Container container1 = containers.get(0); assertEquals(container1.getName(), "c"); Container container2 = containers.get(1); assertNotEquals(container2.getName(), "c"); assertTrue(container2.getName().startsWith("c")); } @Test public void shouldGenerateInitContainerNamesIfCollisionHappened() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder() .withInitContainers(new ContainerBuilder().withName("initC").build()) .build(); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder() .withInitContainers(new ContainerBuilder().withName("initC").build()) .build(); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then PodTemplateSpec podTemplate = merged.getSpec().getTemplate(); List initContainers = podTemplate.getSpec().getInitContainers(); assertEquals(initContainers.size(), 2); Container container1 = initContainers.get(0); assertEquals(container1.getName(), "initC"); Container container2 = initContainers.get(1); assertNotEquals(container2.getName(), "initC"); assertTrue(container2.getName().startsWith("initC")); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Pods have to have volumes with unique names but there are multiple `volume` volumes") public void shouldThrownAnExceptionIfVolumeNameCollisionHappened() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder().withVolumes(new VolumeBuilder().withName("volume").build()).build(); podSpec1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder().withVolumes(new VolumeBuilder().withName("volume").build()).build(); podSpec2.setAdditionalProperty("add2", 2L); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when podMerger.merge(Arrays.asList(podData1, podData2)); } @Test public void shouldMergeProjectVolumesWithoutException() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder() .withVolumes(new VolumeBuilder().withName(PROJECTS_VOLUME_NAME).build()) .build(); podSpec1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder() .withVolumes(new VolumeBuilder().withName(PROJECTS_VOLUME_NAME).build()) .build(); podSpec2.setAdditionalProperty("add2", 2L); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then PodTemplateSpec podTemplate = merged.getSpec().getTemplate(); assertEquals(podTemplate.getSpec().getVolumes().size(), 1); } @Test public void shouldNotAddImagePullPolicyTwice() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder() .withImagePullSecrets(new LocalObjectReferenceBuilder().withName("secret").build()) .build(); podSpec1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder() .withImagePullSecrets(new LocalObjectReferenceBuilder().withName("secret").build()) .build(); podSpec2.setAdditionalProperty("add2", 2L); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then PodTemplateSpec podTemplate = merged.getSpec().getTemplate(); List imagePullSecrets = podTemplate.getSpec().getImagePullSecrets(); assertEquals(imagePullSecrets.size(), 1); assertEquals(imagePullSecrets.get(0).getName(), "secret"); } @Test(dataProvider = "terminationGracePeriodProvider") public void shouldBeAbleToMergeTerminationGracePeriodS( List terminationGracePeriods, Long expectedResultLong) throws ValidationException { List podData = terminationGracePeriods.stream() .map( p -> new PodData( new PodSpecBuilder().withTerminationGracePeriodSeconds(p).build(), new ObjectMetaBuilder().build())) .collect(Collectors.toList()); // when Deployment merged = podMerger.merge(podData); // then PodTemplateSpec podTemplate = merged.getSpec().getTemplate(); assertEquals(podTemplate.getSpec().getTerminationGracePeriodSeconds(), expectedResultLong); } @DataProvider(name = "terminationGracePeriodProvider") public Object[][] terminationGracePeriodProvider() { return new Object[][] { {Arrays.asList(32L, 30L, 27L), 32L}, {Arrays.asList(null, null, null), null}, {Arrays.asList(null, 30L, 27L), 30L}, {Arrays.asList(32L, null, 27L), 32L}, {Arrays.asList(null, 27L), 27L}, {Arrays.asList(27L, 27L), 27L}, {Arrays.asList(27L, null), 27L} }; } @Test public void shouldAssignSecurityContextSharedByPods() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder() .withSecurityContext(new PodSecurityContextBuilder().withRunAsUser(42L).build()) .build(); podSpec1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder() .withSecurityContext(new PodSecurityContextBuilder().withRunAsUser(42L).build()) .build(); podSpec2.setAdditionalProperty("add2", 2L); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then PodTemplateSpec podTemplate = merged.getSpec().getTemplate(); PodSecurityContext sc = podTemplate.getSpec().getSecurityContext(); assertEquals(sc.getRunAsUser(), (Long) 42L); } @Test(expectedExceptions = ValidationException.class) public void shouldFailIfSecurityContextDiffersInPods() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder() .withSecurityContext(new PodSecurityContextBuilder().withRunAsUser(42L).build()) .build(); podSpec1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder() .withSecurityContext(new PodSecurityContextBuilder().withRunAsUser(43L).build()) .build(); podSpec2.setAdditionalProperty("add2", 2L); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then // exception is thrown } @Test public void shouldAssignServiceAccountSharedByPods() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder().withServiceAccount("sa").build(); podSpec1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder().withServiceAccount("sa").build(); podSpec2.setAdditionalProperty("add2", 2L); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then PodTemplateSpec podTemplate = merged.getSpec().getTemplate(); String sa = podTemplate.getSpec().getServiceAccount(); assertEquals(sa, "sa"); } @Test(expectedExceptions = ValidationException.class) public void shouldFailServiceAccountDiffersInPods() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder().withServiceAccount("sa").build(); podSpec1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder().withServiceAccount("sb").build(); podSpec2.setAdditionalProperty("add2", 2L); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then // exception is thrown } @Test public void shouldAssignServiceAccountNameSharedByPods() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder().withServiceAccountName("sa").build(); podSpec1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder().withServiceAccountName("sa").build(); podSpec2.setAdditionalProperty("add2", 2L); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then PodTemplateSpec podTemplate = merged.getSpec().getTemplate(); String sa = podTemplate.getSpec().getServiceAccountName(); assertEquals(sa, "sa"); } @Test(expectedExceptions = ValidationException.class) public void shouldFailServiceAccountNameDiffersInPods() throws Exception { // given PodSpec podSpec1 = new PodSpecBuilder().withServiceAccountName("sa").build(); podSpec1.setAdditionalProperty("add1", 1L); PodData podData1 = new PodData(podSpec1, new ObjectMetaBuilder().build()); PodSpec podSpec2 = new PodSpecBuilder().withServiceAccountName("sb").build(); podSpec2.setAdditionalProperty("add2", 2L); PodData podData2 = new PodData(podSpec2, new ObjectMetaBuilder().build()); // when Deployment merged = podMerger.merge(Arrays.asList(podData1, podData2)); // then // exception is thrown } private void verifyContainsAllFrom(ObjectMeta source, ObjectMeta toCheck) { assertTrue(source.getLabels().entrySet().containsAll(toCheck.getLabels().entrySet())); assertTrue(source.getAnnotations().entrySet().containsAll(toCheck.getAnnotations().entrySet())); assertTrue( source .getAdditionalProperties() .entrySet() .containsAll(toCheck.getAdditionalProperties().entrySet())); } private void verifyContainsAllFrom(PodSpec source, PodSpec toCheck) { assertTrue(source.getContainers().containsAll(toCheck.getContainers())); assertTrue(source.getInitContainers().containsAll(toCheck.getInitContainers())); assertTrue(source.getVolumes().containsAll(toCheck.getVolumes())); assertTrue(source.getImagePullSecrets().containsAll(toCheck.getImagePullSecrets())); assertTrue( source.getNodeSelector().entrySet().containsAll(toCheck.getNodeSelector().entrySet())); assertTrue( source .getAdditionalProperties() .entrySet() .containsAll(toCheck.getAdditionalProperties().entrySet())); assertTrue(source.getTolerations().containsAll(toCheck.getTolerations())); assertEquals(toCheck.getTolerations().size(), new HashSet<>(toCheck.getTolerations()).size()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/util/EntryPointParserTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.environment.util; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.testng.AssertJUnit.assertEquals; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class EntryPointParserTest { @Test public void shouldParseCommandAndArgFromYAMLList() throws Exception { Map cfg = new HashMap<>(); cfg.put(MachineConfig.CONTAINER_COMMAND_ATTRIBUTE, "['/bin/sh', '''yaml quoting ftw''']"); cfg.put(MachineConfig.CONTAINER_ARGS_ATTRIBUTE, "['x', 'y', '''z']"); EntryPointParser parser = new EntryPointParser(); EntryPoint ep = parser.parse(cfg); assertEquals(asList("/bin/sh", "'yaml quoting ftw'"), ep.getCommand()); assertEquals(asList("x", "y", "'z"), ep.getArguments()); } @Test public void shouldParseCommandFromYAMLList() throws Exception { Map cfg = new HashMap<>(); cfg.put(MachineConfig.CONTAINER_COMMAND_ATTRIBUTE, "['/bin/sh', '''yaml quoting ftw''']"); EntryPointParser parser = new EntryPointParser(); EntryPoint ep = parser.parse(cfg); assertEquals(asList("/bin/sh", "'yaml quoting ftw'"), ep.getCommand()); assertEquals(emptyList(), ep.getArguments()); } @Test public void shouldParseArgsFromYAMLList() throws Exception { Map cfg = new HashMap<>(); cfg.put(MachineConfig.CONTAINER_ARGS_ATTRIBUTE, "['x', 'y', '''z', --yes]"); EntryPointParser parser = new EntryPointParser(); EntryPoint ep = parser.parse(cfg); assertEquals(emptyList(), ep.getCommand()); assertEquals(asList("x", "y", "'z", "--yes"), ep.getArguments()); } @Test(dataProvider = "invalidEntryProvider", expectedExceptions = InfrastructureException.class) public void shouldFailOnOtherYAMLDataType(String invalidEntry) throws InfrastructureException { Map cfg = new HashMap<>(); cfg.put(MachineConfig.CONTAINER_ARGS_ATTRIBUTE, invalidEntry); EntryPointParser parser = new EntryPointParser(); parser.parse(cfg); } @DataProvider public static Object[][] invalidEntryProvider() { return new Object[][] { new String[] {"key: value"}, new String[] {"42"}, new String[] {"true"}, new String[] {"string value"}, new String[] {"[a, b, [c]]"} }; } @Test public void shouldSerializeValidData() { // given List data = asList("/bin/sh", "-c"); EntryPointParser parser = new EntryPointParser(); // when String serialized = parser.serializeEntry(data); // then // this is dependent on the configuration of the YAML generator used by the YAMLMapper used in // the EntryPointParser so this may start failing on jackson-dataformat-yaml library upgrade assertEquals(serialized, "---\n- \"/bin/sh\"\n- \"-c\"\n"); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/multiuser/oauth/KubernetesOidcProviderConfigFactoryTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.multiuser.oauth; import static org.testng.Assert.assertEquals; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.ConfigBuilder; import java.util.Collections; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.SubjectImpl; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class KubernetesOidcProviderConfigFactoryTest { private static final String TEST_TOKEN = "touken"; private Config defaultConfig; private KubernetesOidcProviderConfigFactory kubernetesOidcProviderConfigFactory = new KubernetesOidcProviderConfigFactory(null, null); @BeforeMethod public void setUp() { EnvironmentContext.reset(); defaultConfig = new ConfigBuilder().build(); } @Test public void getDefaultConfigWhenNoTokenSet() { Config resultConfig = kubernetesOidcProviderConfigFactory.buildConfig(defaultConfig, null); assertEquals(resultConfig, defaultConfig); } @Test public void getConfigWithTokenWhenTokenIsSet() { EnvironmentContext.getCurrent() .setSubject( new SubjectImpl("test_name", Collections.emptyList(), "test_id", TEST_TOKEN, false)); Config resultConfig = kubernetesOidcProviderConfigFactory.buildConfig(defaultConfig, null); assertEquals(resultConfig.getOauthToken(), TEST_TOKEN); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/K8sVersionTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static io.fabric8.kubernetes.client.VersionInfo.VersionKeys.BUILD_DATE_FORMAT; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.VersionInfo; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class K8sVersionTest { private K8sVersion k8sVersion; @Mock KubernetesClientFactory kubernetesClientFactory; @Mock KubernetesClient kubernetesClient; @BeforeMethod public void setUp() throws InfrastructureException { k8sVersion = new K8sVersion(kubernetesClientFactory); when(kubernetesClientFactory.create()).thenReturn(kubernetesClient); } @Test(dataProvider = "greaterThanData") public void testGreaterOrEqual(VersionInfo versionInfo, int major, int minor, boolean expected) { when(kubernetesClient.getVersion()).thenReturn(versionInfo); assertEquals(k8sVersion.newerOrEqualThan(major, minor), expected); } @Test(dataProvider = "olderThanData") public void testOlderThan(VersionInfo versionInfo, int major, int minor, boolean expected) { when(kubernetesClient.getVersion()).thenReturn(versionInfo); assertEquals(k8sVersion.olderThan(major, minor), expected); } @Test public void testGreaterOrEqualTrueWhenInfrastructureFails() throws InfrastructureException { when(kubernetesClientFactory.create()).thenThrow(new InfrastructureException("eh")); assertTrue(k8sVersion.newerOrEqualThan(1, 1)); assertTrue(k8sVersion.newerOrEqualThan(-1, -1)); assertTrue(k8sVersion.newerOrEqualThan(0, 1)); assertTrue(k8sVersion.newerOrEqualThan(0, 0)); assertTrue(k8sVersion.newerOrEqualThan(1337, 1337)); assertTrue(k8sVersion.newerOrEqualThan(6655321, 6655321)); } @Test public void testOlderThanWhenInfrastructureFails() throws InfrastructureException { when(kubernetesClientFactory.create()).thenThrow(new InfrastructureException("eh")); assertFalse(k8sVersion.olderThan(1, 1)); assertFalse(k8sVersion.olderThan(-1, -1)); assertFalse(k8sVersion.olderThan(0, 1)); assertFalse(k8sVersion.olderThan(0, 0)); assertFalse(k8sVersion.olderThan(1337, 1337)); assertFalse(k8sVersion.olderThan(6655321, 6655321)); } @Test public void testGreaterOrEqualWhenParseFailure() throws ParseException { when(kubernetesClient.getVersion()).thenReturn(createDummyVersionInfo("abc", "cde")); assertTrue(k8sVersion.newerOrEqualThan(1, 1)); assertTrue(k8sVersion.newerOrEqualThan(-1, -1)); assertTrue(k8sVersion.newerOrEqualThan(0, 1)); assertTrue(k8sVersion.newerOrEqualThan(0, 0)); assertTrue(k8sVersion.newerOrEqualThan(1337, 1337)); assertTrue(k8sVersion.newerOrEqualThan(6655321, 6655321)); } @Test public void testOlderThanWhenParseFailure() throws ParseException { when(kubernetesClient.getVersion()).thenReturn(createDummyVersionInfo("abc", "cde")); assertFalse(k8sVersion.olderThan(1, 1)); assertFalse(k8sVersion.olderThan(-1, -1)); assertFalse(k8sVersion.olderThan(0, 1)); assertFalse(k8sVersion.olderThan(0, 0)); assertFalse(k8sVersion.olderThan(1337, 1337)); assertFalse(k8sVersion.olderThan(6655321, 6655321)); } @DataProvider public Object[][] greaterThanData() throws ParseException { VersionInfo versionInfo = createDummyVersionInfo("2", "10"); return new Object[][] { {versionInfo, 1, 9, true}, {versionInfo, 1, 10, true}, {versionInfo, 1, 11, true}, {versionInfo, 2, 9, true}, {versionInfo, 2, 10, true}, {versionInfo, 2, 11, false}, {versionInfo, 3, 9, false}, {versionInfo, 3, 10, false}, {versionInfo, 3, 11, false}, {createDummyVersionInfo("1", "17+"), 1, 17, true}, {createDummyVersionInfo("1", "17+"), 1, 18, false}, {createDummyVersionInfo("1", "17+"), 1, 16, true}, {createDummyVersionInfo("1", "11+"), 1, 17, false}, }; } @DataProvider public Object[][] olderThanData() throws ParseException { VersionInfo versionInfo = createDummyVersionInfo("2", "10"); return new Object[][] { {versionInfo, 1, 9, false}, {versionInfo, 1, 10, false}, {versionInfo, 1, 11, false}, {versionInfo, 2, 9, false}, {versionInfo, 2, 10, false}, {versionInfo, 2, 11, true}, {versionInfo, 3, 9, true}, {versionInfo, 3, 10, true}, {versionInfo, 3, 11, true}, {createDummyVersionInfo("1", "17+"), 1, 17, false}, {createDummyVersionInfo("1", "17+"), 1, 11, false}, {createDummyVersionInfo("1", "11+"), 1, 17, true}, }; } private VersionInfo createDummyVersionInfo(String major, String minor) throws ParseException { return new VersionInfo.Builder() .withBuildDate(new SimpleDateFormat(BUILD_DATE_FORMAT).format(new Date())) .withGitCommit("3f6f40d") .withGitVersion("1.17.1+3f6f40d") .withGitTreeState("clean") .withGoVersion("go1.13.4") .withPlatform("linux/amd64") .withCompiler("gc") .withMajor(major) .withMinor(minor) .build(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesDeploymentsTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static io.fabric8.kubernetes.api.model.DeletionPropagation.BACKGROUND; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.POD_STATUS_PHASE_FAILED; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.POD_STATUS_PHASE_RUNNING; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.POD_STATUS_PHASE_SUCCEEDED; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ContainerStateBuilder; import io.fabric8.kubernetes.api.model.ContainerStatus; import io.fabric8.kubernetes.api.model.Event; import io.fabric8.kubernetes.api.model.EventList; import io.fabric8.kubernetes.api.model.LabelSelector; import io.fabric8.kubernetes.api.model.LocalObjectReference; import io.fabric8.kubernetes.api.model.LocalObjectReferenceBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.ObjectReference; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodList; import io.fabric8.kubernetes.api.model.PodStatus; import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.ServiceAccountBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.api.model.apps.DeploymentList; import io.fabric8.kubernetes.api.model.apps.DeploymentSpec; import io.fabric8.kubernetes.api.model.apps.DeploymentSpecBuilder; import io.fabric8.kubernetes.client.GracePeriodConfigurable; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.dsl.AppsAPIGroupDSL; import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.PodResource; import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.dsl.RollableScalableResource; import io.fabric8.kubernetes.client.dsl.V1APIGroupDSL; import io.fabric8.kubernetes.client.server.mock.KubernetesMixedDispatcher; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.mockwebserver.Context; import io.fabric8.mockwebserver.MockWebServer; import io.fabric8.mockwebserver.ServerRequest; import io.fabric8.mockwebserver.ServerResponse; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEventHandler; import org.eclipse.che.workspace.infrastructure.kubernetes.util.PodEvents; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class KubernetesDeploymentsTest { private static final String POD_NAME = "podName"; private static final String POD_OBJECT_KIND = "Pod"; private static final String REPLICASET_OBJECT_KIND = "ReplicaSet"; private static final String DEPLOYMENT_OBJECT_KIND = "Deployment"; @Mock private KubernetesClientFactory clientFactory; @Mock private Executor executor; @Mock private KubernetesClient kubernetesClient; // Deployments Mocks @Mock private AppsAPIGroupDSL apps; @Mock private MixedOperation> deploymentsMixedOperation; @Mock private NonNamespaceOperation> deploymentsNamespaceOperation; @Mock private RollableScalableResource deploymentResource; @Mock private Deployment deployment; @Mock private ObjectMeta deploymentMetadata; @Mock private DeploymentSpec deploymentSpec; @Mock private GracePeriodConfigurable gracePeriodConfigurable; // Pod Mocks @Mock private Pod pod; @Mock private PodStatus status; @Mock private PodResource podResource; @Mock private ObjectMeta metadata; @Mock private MixedOperation podsMixedOperation; @Mock private NonNamespaceOperation podsNamespaceOperation; @Captor private ArgumentCaptor> watcherCaptor; // Event Mocks @Mock private Event event; @Mock private ObjectReference objectReference; @Mock private PodEventHandler podEventHandler; @Mock private MixedOperation> eventMixedOperation; @Mock private NonNamespaceOperation> eventNamespaceMixedOperation; @Captor private ArgumentCaptor> eventWatcherCaptor; @Mock private V1APIGroupDSL v1APIGroupDSL; private KubernetesDeployments kubernetesDeployments; private KubernetesMockServer kubernetesMockServer; @BeforeMethod public void setUp() throws Exception { lenient().when(clientFactory.create(anyString())).thenReturn(kubernetesClient); lenient().when(pod.getStatus()).thenReturn(status); lenient().when(pod.getMetadata()).thenReturn(metadata); lenient().when(metadata.getName()).thenReturn(POD_NAME); // Model DSL: client.pods().inNamespace(...).withName(...).get().getMetadata().getName(); lenient().doReturn(podsMixedOperation).when(kubernetesClient).pods(); lenient().doReturn(podsNamespaceOperation).when(podsMixedOperation).inNamespace(anyString()); lenient().doReturn(podResource).when(podsNamespaceOperation).withName(anyString()); lenient().doReturn(pod).when(podResource).get(); // Model DSL: // client.apps().deployments(...).inNamespace(...).withName(...).get().getMetadata().getName(); lenient().doReturn(apps).when(kubernetesClient).apps(); lenient().doReturn(deploymentsMixedOperation).when(apps).deployments(); lenient() .doReturn(deploymentsNamespaceOperation) .when(deploymentsMixedOperation) .inNamespace(anyString()); lenient() .doReturn(deploymentResource) .when(deploymentsNamespaceOperation) .withName(anyString()); lenient().doReturn(deployment).when(deploymentResource).get(); lenient().doReturn(deploymentMetadata).when(deployment).getMetadata(); lenient().doReturn(deploymentSpec).when(deployment).getSpec(); // Model DSL: client.events().inNamespace(...).watch(...) // event.getInvolvedObject().getKind() lenient().when(kubernetesClient.v1()).thenReturn(v1APIGroupDSL); lenient().when(v1APIGroupDSL.events()).thenReturn(eventMixedOperation); lenient().when(eventMixedOperation.inNamespace(any())).thenReturn(eventNamespaceMixedOperation); lenient().when(event.getInvolvedObject()).thenReturn(objectReference); lenient().when(event.getMetadata()).thenReturn(new ObjectMeta()); // Workaround to ensure mocked event happens 'after' watcher initialisation. Date futureDate = new Date(); futureDate.setYear(3000); lenient() .when(event.getLastTimestamp()) .thenReturn(PodEvents.convertDateToEventTimestamp(futureDate)); kubernetesDeployments = new KubernetesDeployments("namespace", "workspace123", clientFactory, executor); final Map> responses = new HashMap<>(); kubernetesMockServer = new KubernetesMockServer( new Context(), new MockWebServer(), responses, new KubernetesMixedDispatcher(responses), true); kubernetesMockServer.init(); } @AfterMethod public void cleanUp() { kubernetesMockServer.destroy(); } @Test public void shouldReturnEmptyOptionalWhenPodNorDeploymentWasNotFound() throws Exception { // given when(podResource.get()).thenReturn(null); when(deploymentResource.get()).thenReturn(null); // when Optional pod = kubernetesDeployments.get("non-existing"); // then assertFalse(pod.isPresent()); } @Test public void shouldReturnOptionalWithPodWhenPodWithSpecifiedNameExists() throws Exception { // given when(podResource.get()).thenReturn(pod); // when Optional fetchedPodOpt = kubernetesDeployments.get("existing"); // then assertTrue(fetchedPodOpt.isPresent()); verify(podsNamespaceOperation).withName("existing"); assertEquals(fetchedPodOpt.get(), pod); } @Test public void shouldReturnOptionalWithPodWhenPodWasNotFoundButDeploymentExists() throws Exception { // given when(podResource.get()).thenReturn(null); when(deploymentResource.get()).thenReturn(deployment); LabelSelector labelSelector = mock(LabelSelector.class); doReturn(labelSelector).when(deploymentSpec).getSelector(); doReturn(ImmutableMap.of("deployment", "existing")).when(labelSelector).getMatchLabels(); FilterWatchListDeletable filterList = mock(FilterWatchListDeletable.class); doReturn(filterList).when(podsNamespaceOperation).withLabels(any()); PodList podList = mock(PodList.class); doReturn(singletonList(pod)).when(podList).getItems(); doReturn(podList).when(filterList).list(); // when Optional fetchedPodOpt = kubernetesDeployments.get("existing"); // then assertTrue(fetchedPodOpt.isPresent()); verify(podsNamespaceOperation).withName("existing"); verify(deploymentsNamespaceOperation).withName("existing"); assertEquals(fetchedPodOpt.get(), pod); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Found multiple pods in Deployment 'existing'") public void shouldThrowExceptionWhenMultiplePodsExistsForDeploymentsOnPodFetching() throws Exception { // given when(podResource.get()).thenReturn(null); when(deploymentResource.get()).thenReturn(deployment); LabelSelector labelSelector = mock(LabelSelector.class); doReturn(labelSelector).when(deploymentSpec).getSelector(); doReturn(ImmutableMap.of("deployment", "existing")).when(labelSelector).getMatchLabels(); FilterWatchListDeletable filterList = mock(FilterWatchListDeletable.class); doReturn(filterList).when(podsNamespaceOperation).withLabels(any()); PodList podList = mock(PodList.class); doReturn(asList(pod, pod)).when(podList).getItems(); doReturn(podList).when(filterList).list(); // when kubernetesDeployments.get("existing"); } @Test public void shouldCompleteFutureForWaitingPodIfStatusIsRunning() { // given when(status.getPhase()).thenReturn(POD_STATUS_PHASE_RUNNING); CompletableFuture future = kubernetesDeployments.waitRunningAsync(POD_NAME); // when verify(podResource).watch(watcherCaptor.capture()); Watcher watcher = watcherCaptor.getValue(); watcher.eventReceived(Watcher.Action.MODIFIED, pod); // then assertTrue(future.isDone()); } @Test public void shouldCompleteExceptionallyFutureForWaitingPodIfStatusIsRunningButSomeContainersAreTerminated() { // given ContainerStatus containerStatus = mock(ContainerStatus.class); when(containerStatus.getName()).thenReturn("FailingContainer"); when(containerStatus.getState()) .thenReturn( new ContainerStateBuilder() .withNewTerminated() .withReason("Completed") .endTerminated() .build()); when(status.getPhase()).thenReturn(POD_STATUS_PHASE_RUNNING); when(status.getContainerStatuses()).thenReturn(singletonList(containerStatus)); CompletableFuture future = kubernetesDeployments.waitRunningAsync(POD_NAME); // when verify(podResource).watch(watcherCaptor.capture()); Watcher watcher = watcherCaptor.getValue(); watcher.eventReceived(Watcher.Action.MODIFIED, pod); // then assertTrue(future.isDone()); assertTrue(future.isCompletedExceptionally()); } @Test public void shouldCompleteExceptionallyFutureForWaitingPodIfStatusIsRunningButSomeContainersAreWaitingAndTerminatedBefore() { // given ContainerStatus containerStatus = mock(ContainerStatus.class); when(containerStatus.getName()).thenReturn("FailingContainer"); when(containerStatus.getState()) .thenReturn( new ContainerStateBuilder().withNewWaiting().withMessage("bah").endWaiting().build()); when(containerStatus.getLastState()) .thenReturn( new ContainerStateBuilder() .withNewTerminated() .withReason("Completed") .endTerminated() .build()); when(status.getPhase()).thenReturn(POD_STATUS_PHASE_RUNNING); when(status.getContainerStatuses()).thenReturn(singletonList(containerStatus)); CompletableFuture future = kubernetesDeployments.waitRunningAsync(POD_NAME); // when verify(podResource).watch(watcherCaptor.capture()); Watcher watcher = watcherCaptor.getValue(); watcher.eventReceived(Watcher.Action.MODIFIED, pod); // then assertTrue(future.isDone()); assertTrue(future.isCompletedExceptionally()); } @Test public void shouldCompleteExceptionallyFutureForWaitingPodIfStatusIsSucceeded() throws Exception { // given when(status.getPhase()).thenReturn(POD_STATUS_PHASE_SUCCEEDED); CompletableFuture future = kubernetesDeployments.waitRunningAsync(POD_NAME); // when verify(podResource).watch(watcherCaptor.capture()); Watcher watcher = watcherCaptor.getValue(); watcher.eventReceived(Watcher.Action.MODIFIED, pod); // then assertTrue(future.isCompletedExceptionally()); try { future.get(); } catch (ExecutionException e) { assertEquals( e.getCause().getMessage(), "Pod container has been terminated. Container must be configured to use a non-terminating command."); } } @Test public void shouldCompleteExceptionallyFutureForWaitingPodIfStatusIsFailed() throws Exception { // given when(status.getPhase()).thenReturn(POD_STATUS_PHASE_FAILED); when(pod.getStatus().getReason()).thenReturn("Evicted"); CompletableFuture future = kubernetesDeployments.waitRunningAsync(POD_NAME); // when verify(podResource).watch(watcherCaptor.capture()); Watcher watcher = watcherCaptor.getValue(); watcher.eventReceived(Watcher.Action.MODIFIED, pod); // then assertTrue(future.isCompletedExceptionally()); try { future.get(); } catch (ExecutionException e) { assertEquals(e.getCause().getMessage(), "Pod 'podName' failed to start. Reason: Evicted"); } } @Test public void shouldCompleteExceptionallyFutureForWaitingPodIfStatusIsFailedAndReasonIsNotAvailable() throws Exception { // given when(status.getPhase()).thenReturn(POD_STATUS_PHASE_FAILED); when(podResource.getLog()).thenReturn("Pod fail log"); CompletableFuture future = kubernetesDeployments.waitRunningAsync(POD_NAME); // when verify(podResource).watch(watcherCaptor.capture()); Watcher watcher = watcherCaptor.getValue(); watcher.eventReceived(Watcher.Action.MODIFIED, pod); // then assertTrue(future.isCompletedExceptionally()); try { future.get(); } catch (ExecutionException e) { assertEquals( e.getCause().getMessage(), "Pod 'podName' failed to start. Pod logs: Pod fail log"); } } @Test public void shouldCompleteExceptionallyFutureForWaitingPodIfStatusIsFailedAndReasonNorLogsAreNotAvailable() throws Exception { // given CompletableFuture future = kubernetesDeployments.waitRunningAsync(POD_NAME); when(status.getPhase()).thenReturn(POD_STATUS_PHASE_FAILED); doThrow(new InfrastructureException("Unable to create client")) .when(clientFactory) .create(anyString()); // when verify(podResource).watch(watcherCaptor.capture()); Watcher watcher = watcherCaptor.getValue(); watcher.eventReceived(Watcher.Action.MODIFIED, pod); // then assertTrue(future.isCompletedExceptionally()); try { future.get(); } catch (ExecutionException e) { assertEquals( e.getCause().getMessage(), "Pod 'podName' failed to start. Error occurred while fetching pod logs: Unable to create client"); } } @Test public void shouldCallHandlerForEventsOnPods() throws Exception { // Given when(objectReference.getKind()).thenReturn(POD_OBJECT_KIND); kubernetesDeployments.watchEvents(podEventHandler); verify(eventNamespaceMixedOperation).watch(eventWatcherCaptor.capture()); Watcher watcher = eventWatcherCaptor.getValue(); // When watcher.eventReceived(Watcher.Action.ADDED, event); // Then verify(podEventHandler).handle(any()); } @Test public void shouldCallHandlerForEventsOnReplicaSets() throws Exception { // Given when(objectReference.getKind()).thenReturn(REPLICASET_OBJECT_KIND); kubernetesDeployments.watchEvents(podEventHandler); verify(eventNamespaceMixedOperation).watch(eventWatcherCaptor.capture()); Watcher watcher = eventWatcherCaptor.getValue(); // When watcher.eventReceived(Watcher.Action.ADDED, event); // Then verify(podEventHandler).handle(any()); } @Test public void shouldCallHandlerForEventsOnDeployments() throws Exception { // Given when(objectReference.getKind()).thenReturn(DEPLOYMENT_OBJECT_KIND); kubernetesDeployments.watchEvents(podEventHandler); verify(eventNamespaceMixedOperation).watch(eventWatcherCaptor.capture()); Watcher watcher = eventWatcherCaptor.getValue(); // When watcher.eventReceived(Watcher.Action.ADDED, event); // Then verify(podEventHandler).handle(any()); } @Test public void testDeleteNonExistingPodBeforeWatch() throws Exception { final String POD_NAME = "nonExistingPod"; doReturn(emptyList()).when(podResource).delete(); doReturn(podResource).when(podResource).withPropagationPolicy(eq(BACKGROUND)); Watch watch = mock(Watch.class); doReturn(watch).when(podResource).watch(any()); new KubernetesDeployments("", "", clientFactory, executor) .doDeletePod(POD_NAME) .get(5, TimeUnit.SECONDS); verify(watch).close(); } @Test public void testDeletePodThrowingKubernetesClientExceptionShouldCloseWatch() throws Exception { final String POD_NAME = "nonExistingPod"; doReturn(podResource).when(podResource).withPropagationPolicy(eq(BACKGROUND)); doThrow(KubernetesClientException.class).when(podResource).delete(); Watch watch = mock(Watch.class); doReturn(watch).when(podResource).watch(any()); try { new KubernetesDeployments("", "", clientFactory, executor) .doDeletePod(POD_NAME) .get(5, TimeUnit.SECONDS); } catch (KubernetesInfrastructureException e) { assertTrue(e.getCause() instanceof KubernetesClientException); verify(watch).close(); return; } fail("The exception should have been rethrown"); } @Test public void testDeleteNonExistingDeploymentBeforeWatch() throws Exception { final String DEPLOYMENT_NAME = "nonExistingPod"; doReturn(deploymentResource).when(deploymentResource).withPropagationPolicy(eq(BACKGROUND)); doReturn(emptyList()).when(deploymentResource).delete(); Watch watch = mock(Watch.class); doReturn(watch).when(podResource).watch(any()); new KubernetesDeployments("", "", clientFactory, executor) .doDeleteDeployment(DEPLOYMENT_NAME) .get(5, TimeUnit.SECONDS); verify(watch).close(); } @Test public void testDeleteDeploymentThrowingKubernetesClientExceptionShouldCloseWatch() throws Exception { final String DEPLOYMENT_NAME = "nonExistingPod"; doThrow(KubernetesClientException.class).when(deploymentResource).delete(); doReturn(deploymentResource).when(deploymentResource).withPropagationPolicy(eq(BACKGROUND)); Watch watch = mock(Watch.class); doReturn(watch).when(podResource).watch(any()); try { new KubernetesDeployments("", "", clientFactory, executor) .doDeleteDeployment(DEPLOYMENT_NAME) .get(5, TimeUnit.SECONDS); } catch (KubernetesInfrastructureException e) { assertTrue(e.getCause() instanceof KubernetesClientException); verify(watch).close(); return; } fail("The exception should have been rethrown"); } @Test public void testDeletePodThrowingAnyExceptionShouldCloseWatch() throws Exception { final String POD_NAME = "nonExistingPod"; doReturn(podResource).when(podResource).withPropagationPolicy(eq(BACKGROUND)); doThrow(RuntimeException.class).when(podResource).delete(); Watch watch = mock(Watch.class); doReturn(watch).when(podResource).watch(any()); try { new KubernetesDeployments("", "", clientFactory, executor) .doDeletePod(POD_NAME) .get(5, TimeUnit.SECONDS); fail("The exception should have been rethrown"); } catch (RuntimeException e) { verify(watch).close(); return; } } @Test( expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "testDeleteDeploymentThrowingAnyExceptionShouldCloseWatch msg") public void testDeleteDeploymentThrowingAnyExceptionShouldCloseWatch() throws Exception { final String DEPLOYMENT_NAME = "nonExistingPod"; when(deploymentResource.withPropagationPolicy(eq(BACKGROUND))) .thenReturn(gracePeriodConfigurable); doThrow(new RuntimeException("testDeleteDeploymentThrowingAnyExceptionShouldCloseWatch msg")) .when(gracePeriodConfigurable) .delete(); Watch watch = mock(Watch.class); doReturn(watch).when(podResource).watch(any()); try { new KubernetesDeployments("", "", clientFactory, executor) .doDeleteDeployment(DEPLOYMENT_NAME) .get(5, TimeUnit.SECONDS); fail("The exception should have been rethrown"); } catch (RuntimeException e) { verify(watch).close(); throw e; } } @Test public void shouldFallbackToFirstTimeStampIfLastTimeStampIsNull() throws InfrastructureException { // Given when(objectReference.getKind()).thenReturn(POD_OBJECT_KIND); kubernetesDeployments.watchEvents(podEventHandler); verify(eventNamespaceMixedOperation).watch(eventWatcherCaptor.capture()); Watcher watcher = eventWatcherCaptor.getValue(); Event event = mock(Event.class); when(event.getInvolvedObject()).thenReturn(objectReference); when(event.getMetadata()).thenReturn(new ObjectMeta()); Calendar cal = Calendar.getInstance(); cal.add(Calendar.YEAR, 1); Date nextYear = cal.getTime(); when(event.getFirstTimestamp()).thenReturn(PodEvents.convertDateToEventTimestamp(nextYear)); when(event.getLastTimestamp()).thenReturn(null); // When watcher.eventReceived(Watcher.Action.ADDED, event); // Then verify(event, times(1)).getLastTimestamp(); verify(event, times(1)).getFirstTimestamp(); ArgumentCaptor captor = ArgumentCaptor.forClass(PodEvent.class); verify(podEventHandler).handle(captor.capture()); PodEvent podEvent = captor.getValue(); assertEquals(podEvent.getLastTimestamp(), PodEvents.convertDateToEventTimestamp(nextYear)); } @Test public void shouldUseLastTimestampIfAvailable() throws InfrastructureException { // Given when(objectReference.getKind()).thenReturn(POD_OBJECT_KIND); kubernetesDeployments.watchEvents(podEventHandler); verify(eventNamespaceMixedOperation).watch(eventWatcherCaptor.capture()); Watcher watcher = eventWatcherCaptor.getValue(); Event event = mock(Event.class); when(event.getInvolvedObject()).thenReturn(objectReference); when(event.getMetadata()).thenReturn(new ObjectMeta()); Calendar cal = Calendar.getInstance(); cal.add(Calendar.YEAR, 2); Date nextYear = cal.getTime(); when(event.getLastTimestamp()).thenReturn(PodEvents.convertDateToEventTimestamp(nextYear)); // When watcher.eventReceived(Watcher.Action.ADDED, event); // Then verify(event, times(1)).getLastTimestamp(); verify(event, never()).getFirstTimestamp(); ArgumentCaptor captor = ArgumentCaptor.forClass(PodEvent.class); verify(podEventHandler).handle(captor.capture()); PodEvent podEvent = captor.getValue(); assertEquals(podEvent.getLastTimestamp(), PodEvents.convertDateToEventTimestamp(nextYear)); } @Test public void shouldHandleEventWithEmptyLastTimestampAndFirstTimestamp() throws Exception { // Given when(objectReference.getKind()).thenReturn(POD_OBJECT_KIND); kubernetesDeployments.watchEvents(podEventHandler); Calendar cal = Calendar.getInstance(); cal.add(Calendar.MINUTE, -1); Date minuteAgo = cal.getTime(); Field f = KubernetesDeployments.class.getDeclaredField("watcherInitializationDate"); f.setAccessible(true); f.set(kubernetesDeployments, minuteAgo); verify(eventNamespaceMixedOperation).watch(eventWatcherCaptor.capture()); Watcher watcher = eventWatcherCaptor.getValue(); Event event = mock(Event.class); when(event.getInvolvedObject()).thenReturn(objectReference); when(event.getMetadata()).thenReturn(new ObjectMeta()); when(event.getLastTimestamp()).thenReturn(null); when(event.getFirstTimestamp()).thenReturn(null); // When watcher.eventReceived(Watcher.Action.ADDED, event); // Then verify(event, times(1)).getLastTimestamp(); verify(event, times(1)).getFirstTimestamp(); ArgumentCaptor captor = ArgumentCaptor.forClass(PodEvent.class); verify(podEventHandler).handle(captor.capture()); PodEvent podEvent = captor.getValue(); assertNotNull(podEvent.getLastTimestamp()); } @Test public void deploymentShouldHaveImagePullSecretsOfSAAndSelf() throws InfrastructureException { final var client = kubernetesMockServer.createClient(); doReturn(client).when(clientFactory).create(anyString()); final String serviceAccountName = "workspace-sa"; LocalObjectReference pullSecretOfSA = new LocalObjectReferenceBuilder().withName("pullsecret-sa").build(); LocalObjectReference pullSecretOfPod = new LocalObjectReferenceBuilder().withName("pullsecret-pod").build(); List imagePullSecrets = new ArrayList<>(); imagePullSecrets.add(pullSecretOfPod); ServiceAccount serviceAccount = new ServiceAccountBuilder() .withMetadata( new ObjectMetaBuilder() .withName(serviceAccountName) .withNamespace(kubernetesDeployments.namespace) .build()) .withImagePullSecrets(pullSecretOfSA) .build(); client.serviceAccounts().inNamespace(kubernetesDeployments.namespace).create(serviceAccount); ObjectMeta objectMeta = new ObjectMetaBuilder() .withName("test-pod") .withNamespace(kubernetesDeployments.namespace) .build(); Deployment deployment = new DeploymentBuilder() .withMetadata(objectMeta) .withSpec( new DeploymentSpecBuilder() .withNewTemplate() .withMetadata(objectMeta) .withNewSpec() .withImagePullSecrets(imagePullSecrets) .withServiceAccountName(serviceAccountName) .endSpec() .endTemplate() .build()) .build(); kubernetesDeployments.addPullSecretsOfSA(deployment); imagePullSecrets.add(pullSecretOfSA); assertTrue( deployment .getSpec() .getTemplate() .getSpec() .getImagePullSecrets() .containsAll(imagePullSecrets)); } @Test public void deploymentShouldHavePullSecretsOnlyOfSelfWithNonexistentSA() throws InfrastructureException { doReturn(kubernetesMockServer.createClient()).when(clientFactory).create(anyString()); LocalObjectReference pullSecretOfPod = new LocalObjectReferenceBuilder().withName("pullsecret-pod").build(); ObjectMeta objectMeta = new ObjectMetaBuilder() .withName("test-pod") .withNamespace(kubernetesDeployments.namespace) .build(); Deployment deployment = new DeploymentBuilder() .withMetadata(objectMeta) .withSpec( new DeploymentSpecBuilder() .withNewTemplate() .withMetadata(objectMeta) .withNewSpec() .withImagePullSecrets(pullSecretOfPod) .withServiceAccountName("nonexistent-sa") .endSpec() .endTemplate() .build()) .build(); kubernetesDeployments.addPullSecretsOfSA(deployment); assertTrue( deployment .getSpec() .getTemplate() .getSpec() .getImagePullSecrets() .contains(pullSecretOfPod)); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceFactoryTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import static java.util.Collections.singletonList; import static java.util.Optional.empty; import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.DEFAULT_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.SECRETS_ROLE_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory.NAMESPACE_TEMPLATE_ATTRIBUTE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.core.Appender; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.NamespaceBuilder; import io.fabric8.kubernetes.api.model.NamespaceList; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.ServiceAccountList; import io.fabric8.kubernetes.api.model.Status; import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBuilder; import io.fabric8.kubernetes.api.model.rbac.PolicyRule; import io.fabric8.kubernetes.api.model.rbac.Role; import io.fabric8.kubernetes.api.model.rbac.RoleBindingList; import io.fabric8.kubernetes.api.model.rbac.RoleList; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.server.mock.KubernetesMixedDispatcher; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.mockwebserver.Context; import io.fabric8.mockwebserver.MockWebServer; import io.fabric8.mockwebserver.ServerRequest; import io.fabric8.mockwebserver.ServerResponse; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Queue; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl.WorkspaceImplBuilder; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationChecker; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.PreferencesConfigMapConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.WorkspaceServiceAccountConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.NamespaceProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import org.testng.collections.Sets; /** * Tests {@link KubernetesNamespaceFactory}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class KubernetesNamespaceFactoryTest { private static final String USER_ID = "userid"; private static final String USER_NAME = "username"; private static final String NAMESPACE_LABEL_NAME = "component"; private static final String NAMESPACE_LABELS = NAMESPACE_LABEL_NAME + "=workspace"; private static final String NAMESPACE_ANNOTATION_NAME = "owner"; private static final String NAMESPACE_ANNOTATIONS = NAMESPACE_ANNOTATION_NAME + "="; @Mock private KubernetesSharedPool pool; @Mock private CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private KubernetesClient k8sClient; @Mock private PreferenceManager preferenceManager; @Mock Appender mockedAppender; @Mock AuthorizationChecker authorizationChecker; @Mock PermissionsCleaner permissionsCleaner; @Mock private NonNamespaceOperation namespaceOperation; @Mock private Resource namespaceResource; private KubernetesMockServer kubernetesMockServer; private KubernetesNamespaceFactory namespaceFactory; @Mock private FilterWatchListDeletable> namespaceListResource; @Mock private NamespaceList namespaceList; @BeforeMethod public void setUp() throws Exception { final Map> responses = new HashMap<>(); kubernetesMockServer = new KubernetesMockServer( new Context(), new MockWebServer(), responses, new KubernetesMixedDispatcher(responses), true); kubernetesMockServer.init(); k8sClient = spy(kubernetesMockServer.createClient()); lenient().when(cheServerKubernetesClientFactory.create()).thenReturn(k8sClient); lenient().when(k8sClient.namespaces()).thenReturn(namespaceOperation); lenient().when(namespaceOperation.withName(any())).thenReturn(namespaceResource); lenient().when(namespaceResource.get()).thenReturn(mock(Namespace.class)); lenient().when(authorizationChecker.isAuthorized(any(Subject.class))).thenReturn(true); lenient().doReturn(namespaceListResource).when(namespaceOperation).withLabels(anyMap()); lenient().when(namespaceListResource.list()).thenReturn(namespaceList); lenient().when(namespaceList.getItems()).thenReturn(Collections.emptyList()); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "i123", null, false)); } @AfterMethod public void tearDown() { EnvironmentContext.reset(); kubernetesMockServer.destroy(); } @Test public void shouldNotThrowExceptionIfDefaultNamespaceIsSpecifiedOnCheckingIfNamespaceIsAllowed() throws Exception { namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); namespaceFactory.checkIfNamespaceIsAllowed("jondoe-che"); } @Test public void shouldLookAtStoredNamespacesOnCheckingIfNamespaceIsAllowed() throws Exception { Map prefs = new HashMap<>(); prefs.put(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "any-namespace"); prefs.put(NAMESPACE_TEMPLATE_ATTRIBUTE, "-che"); when(preferenceManager.find(anyString())).thenReturn(prefs); namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); namespaceFactory.checkIfNamespaceIsAllowed("any-namespace"); } @Test public void shouldNormaliseNamespaceWhenUserNameStartsWithKube() { namespaceFactory = new KubernetesNamespaceFactory( "che-", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); assertEquals("che-kube-admin", namespaceFactory.normalizeNamespaceName("kube:admin")); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "User defined namespaces are not allowed. Only the default namespace 'jondoe-che' is available.") public void shouldThrowExceptionIfNonDefaultNamespaceIsSpecifiedAndUserDefinedAreNotAllowedOnCheckingIfNamespaceIsAllowed() throws Exception { namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); namespaceFactory.checkIfNamespaceIsAllowed("any-namespace"); } @Test( expectedExceptions = ConfigurationException.class, expectedExceptionsMessageRegExp = "che.infra.kubernetes.namespace.default must be configured") public void shouldThrowExceptionIfNoDefaultNamespaceIsConfigured() { namespaceFactory = new KubernetesNamespaceFactory( null, true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); } @Test public void shouldReturnPreparedNamespacesWhenFound() throws InfrastructureException { // given List namespaces = Arrays.asList( new NamespaceBuilder() .withNewMetadata() .withName("ns1") .withAnnotations(Map.of(NAMESPACE_ANNOTATION_NAME, "jondoe")) .endMetadata() .withNewStatus() .withPhase("Active") .endStatus() .build(), new NamespaceBuilder() .withNewMetadata() .withName("ns2") .withAnnotations(Map.of(NAMESPACE_ANNOTATION_NAME, "jondoe")) .endMetadata() .withNewStatus() .withPhase("Active") .endStatus() .build(), new NamespaceBuilder() .withNewMetadata() .withName("ns3") .withAnnotations(Map.of(NAMESPACE_ANNOTATION_NAME, "some_other_user")) .endMetadata() .withNewStatus() .withPhase("Active") .endStatus() .build()); doReturn(namespaces).when(namespaceList).getItems(); namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "123", null, false)); // when List availableNamespaces = namespaceFactory.list(); // then assertEquals(availableNamespaces.size(), 2); verify(namespaceOperation).withLabels(Map.of(NAMESPACE_LABEL_NAME, "workspace")); assertEquals(availableNamespaces.get(0).getName(), "ns1"); assertEquals(availableNamespaces.get(1).getName(), "ns2"); } @Test public void shouldNotThrowAnExceptionWhenNotAllowedToListNamespaces() throws Exception { // given Namespace ns = new NamespaceBuilder() .withNewMetadata() .withName("ns1") .endMetadata() .withNewStatus() .withPhase("Active") .endStatus() .build(); doThrow(new KubernetesClientException("Not allowed.", 403, new Status())) .when(namespaceList) .getItems(); prepareNamespaceToBeFoundByName("jondoe-che", ns); namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "123", null, false)); // when List availableNamespaces = namespaceFactory.list(); // then assertEquals(availableNamespaces.get(0).getName(), "ns1"); } @Test(expectedExceptions = InfrastructureException.class) public void throwAnExceptionWhenErrorListingNamespaces() throws Exception { // given doThrow(new KubernetesClientException("Not allowed.", 500, new Status())) .when(namespaceList) .getItems(); namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); // when namespaceFactory.list(); // then throw } @Test public void shouldReturnDefaultNamespaceWhenItExists() throws Exception { prepareNamespaceToBeFoundByName( "jondoe-che", new NamespaceBuilder() .withNewMetadata() .withName("jondoe-che") .endMetadata() .withNewStatus() .withPhase("Active") .endStatus() .build()); namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); List availableNamespaces = namespaceFactory.list(); assertEquals(availableNamespaces.size(), 1); KubernetesNamespaceMeta defaultNamespace = availableNamespaces.get(0); assertEquals(defaultNamespace.getName(), "jondoe-che"); assertEquals(defaultNamespace.getAttributes().get(DEFAULT_ATTRIBUTE), "true"); assertEquals(defaultNamespace.getAttributes().get(PHASE_ATTRIBUTE), "Active"); } @Test public void shouldReturnDefaultNamespaceWhenItDoesNotExistAndUserDefinedIsNotAllowed() throws Exception { prepareNamespaceToBeFoundByName("jondoe-che", null); namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); List availableNamespaces = namespaceFactory.list(); assertEquals(availableNamespaces.size(), 1); KubernetesNamespaceMeta defaultNamespace = availableNamespaces.get(0); assertEquals(defaultNamespace.getName(), "jondoe-che"); assertEquals(defaultNamespace.getAttributes().get(DEFAULT_ATTRIBUTE), "true"); assertNull( defaultNamespace .getAttributes() .get(PHASE_ATTRIBUTE)); // no phase - means such namespace does not exist } @Test public void shouldCreatePreferencesConfigmapIfNotExists() throws Exception { // given namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, Set.of(new PreferencesConfigMapConfigurator(cheServerKubernetesClientFactory)), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); when(toReturnNamespace.getName()).thenReturn("namespaceName"); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); MixedOperation mixedOperation = mock(MixedOperation.class); when(k8sClient.configMaps()).thenReturn(mixedOperation); when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); when(namespaceResource.get()).thenReturn(null); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123"); namespaceFactory.getOrCreate(identity); // then ArgumentCaptor configMapCaptor = ArgumentCaptor.forClass(ConfigMap.class); verify(namespaceOperation).create(configMapCaptor.capture()); ConfigMap configmap = configMapCaptor.getValue(); Assert.assertEquals(configmap.getMetadata().getName(), PREFERENCES_CONFIGMAP_NAME); } @Test public void testAllConfiguratorsAreCalledWhenCreatingNamespace() throws InfrastructureException { // given String namespaceName = "testNamespaceName"; NamespaceConfigurator configurator1 = Mockito.mock(NamespaceConfigurator.class); NamespaceConfigurator configurator2 = Mockito.mock(NamespaceConfigurator.class); Set namespaceConfigurators = Set.of(configurator1, configurator2); namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, namespaceConfigurators, cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "123", null, false)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); when(toReturnNamespace.getName()).thenReturn(namespaceName); RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "old-che"); doReturn(toReturnNamespace).when(namespaceFactory).get(identity); // when KubernetesNamespace namespace = namespaceFactory.getOrCreate(identity); // then NamespaceResolutionContext resolutionCtx = new NamespaceResolutionContext("workspace123", "123", "jondoe"); verify(configurator1).configure(resolutionCtx, namespaceName); verify(configurator2).configure(resolutionCtx, namespaceName); assertEquals(namespace, toReturnNamespace); } @Test public void shouldNotCreateCredentialsSecretIfExists() throws Exception { // given namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); MixedOperation mixedOperation = mock(MixedOperation.class); lenient().when(k8sClient.secrets()).thenReturn(mixedOperation); lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123"); namespaceFactory.getOrCreate(identity); // then verify(namespaceOperation, never()).create(any()); } @Test public void shouldNotCreatePreferencesConfigmapIfExists() throws Exception { // given namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); MixedOperation mixedOperation = mock(MixedOperation.class); lenient().when(k8sClient.configMaps()).thenReturn(mixedOperation); lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123"); namespaceFactory.getOrCreate(identity); // then verify(namespaceOperation, never()).create(any()); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Error occurred when tried to fetch default namespace. Cause: connection refused") public void shouldThrowExceptionWhenFailedToGetInfoAboutDefaultNamespace() throws Exception { namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); throwOnTryToGetNamespaceByName( "jondoe-che", new KubernetesClientException("connection refused")); namespaceFactory.list(); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Error occurred when tried to list all available namespaces. Cause: connection refused") public void shouldThrowExceptionWhenFailedToGetNamespaces() throws Exception { namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); throwOnTryToGetNamespacesList(new KubernetesClientException("connection refused")); namespaceFactory.list(); } @Test public void shouldRequireNamespacePriorExistenceIfDifferentFromDefaultAndUserDefinedIsNotAllowed() throws Exception { // There is only one scenario where this can happen. The workspace was created and started in // some default namespace. Then server was reconfigured to use a different default namespace // AND the namespace of the workspace was MANUALLY deleted in the cluster. In this case, we // should NOT try to re-create the namespace because it would be created in a namespace that // is not configured. We DO allow it to start if the namespace still exists though. // given namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "old-che"); KubernetesNamespace namespace = namespaceFactory.getOrCreate(identity); // then assertEquals(toReturnNamespace, namespace); verify(toReturnNamespace).prepare(eq(true), any(), any()); } @Test public void shouldReturnDefaultNamespaceWhenCreatingIsNotIsNotAllowed() throws Exception { // given namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", false, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "old-default"); KubernetesNamespace namespace = namespaceFactory.getOrCreate(identity); // then assertEquals(toReturnNamespace, namespace); verify(toReturnNamespace).prepare(eq(false), any(), any()); } @Test public void shouldPrepareWorkspaceServiceAccountIfItIsConfiguredAndNamespaceIsNotPredefined() throws Exception { // given var serviceAccountCfg = spy( new WorkspaceServiceAccountConfigurator( "serviceAccount", "", cheServerKubernetesClientFactory)); namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, Set.of(serviceAccountCfg), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); when(toReturnNamespace.getName()).thenReturn("workspace123"); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); KubernetesWorkspaceServiceAccount serviceAccount = mock(KubernetesWorkspaceServiceAccount.class); doReturn(serviceAccount).when(serviceAccountCfg).doCreateServiceAccount(any(), any()); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123"); namespaceFactory.getOrCreate(identity); // then verify(serviceAccountCfg).doCreateServiceAccount("workspace123", "workspace123"); verify(serviceAccount).prepare(); } @Test public void shouldBindToAllConfiguredClusterRoles() throws Exception { // given var serviceAccountConfigurator = new WorkspaceServiceAccountConfigurator( "serviceAccount", "cr2, cr3", cheServerKubernetesClientFactory); namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, Set.of(serviceAccountConfigurator), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); when(toReturnNamespace.getName()).thenReturn("workspace123"); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); when(k8sClient.supportsApiPath(eq("/apis/metrics.k8s.io"))).thenReturn(true); when(cheServerKubernetesClientFactory.create(any())).thenReturn(k8sClient); // pre-create the cluster roles Stream.of("cr1", "cr2", "cr3") .forEach( cr -> k8sClient .rbac() .clusterRoles() .createOrReplace( new ClusterRoleBuilder() .withNewMetadata() .withName(cr) .endMetadata() .build())); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123"); namespaceFactory.getOrCreate(identity); // then ServiceAccountList sas = k8sClient.serviceAccounts().inNamespace("workspace123").list(); assertEquals(sas.getItems().size(), 1); assertEquals(sas.getItems().get(0).getMetadata().getName(), "serviceAccount"); RoleList roles = k8sClient.rbac().roles().inNamespace("workspace123").list(); assertEquals( roles.getItems().stream().map(r -> r.getMetadata().getName()).collect(Collectors.toSet()), Sets.newHashSet( "workspace-configmaps", "workspace-view", "workspace-metrics", "workspace-secrets", "exec")); RoleBindingList bindings = k8sClient.rbac().roleBindings().inNamespace("workspace123").list(); assertEquals( bindings.getItems().stream() .map(r -> r.getMetadata().getName()) .collect(Collectors.toSet()), Sets.newHashSet( "serviceAccount-metrics", "serviceAccount-cluster0", "serviceAccount-cluster1", "serviceAccount-configmaps", "serviceAccount-view", "serviceAccount-exec", "serviceAccount-secrets")); } @Test public void shouldCreateAndBindCredentialsSecretRole() throws Exception { // given var serviceAccountConfigurator = new WorkspaceServiceAccountConfigurator( "serviceAccount", "cr2, cr3", cheServerKubernetesClientFactory); namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, Set.of(serviceAccountConfigurator), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); when(toReturnNamespace.getName()).thenReturn("workspace123"); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); when(cheServerKubernetesClientFactory.create(any())).thenReturn(k8sClient); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123"); namespaceFactory.getOrCreate(identity); // then Optional roleOptional = k8sClient.rbac().roles().inNamespace("workspace123").list().getItems().stream() .filter(r -> r.getMetadata().getName().equals(SECRETS_ROLE_NAME)) .findAny(); assertTrue(roleOptional.isPresent()); PolicyRule rule = roleOptional.get().getRules().get(0); assertEquals(rule.getResources(), singletonList("secrets")); assertEquals(rule.getResourceNames(), singletonList(CREDENTIALS_SECRET_NAME)); assertEquals(rule.getApiGroups(), singletonList("")); assertEquals(rule.getVerbs(), Arrays.asList("get", "patch")); assertTrue( k8sClient.rbac().roleBindings().inNamespace("workspace123").list().getItems().stream() .anyMatch(rb -> rb.getMetadata().getName().equals("serviceAccount-secrets"))); } @Test public void shouldCreateExecAndViewRolesAndBindings() throws Exception { // given namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, Set.of( new WorkspaceServiceAccountConfigurator( "serviceAccount", "", cheServerKubernetesClientFactory)), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); when(toReturnNamespace.getName()).thenReturn("workspace123"); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); when(k8sClient.supportsApiPath(eq("/apis/metrics.k8s.io"))).thenReturn(true); when(cheServerKubernetesClientFactory.create(any())).thenReturn(k8sClient); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123"); namespaceFactory.getOrCreate(identity); // then ServiceAccountList sas = k8sClient.serviceAccounts().inNamespace("workspace123").list(); assertEquals(sas.getItems().size(), 1); assertEquals(sas.getItems().get(0).getMetadata().getName(), "serviceAccount"); RoleList roles = k8sClient.rbac().roles().inNamespace("workspace123").list(); assertEquals( roles.getItems().stream().map(r -> r.getMetadata().getName()).collect(Collectors.toSet()), Sets.newHashSet( "workspace-configmaps", "workspace-view", "workspace-metrics", "workspace-secrets", "exec")); Role role1 = roles.getItems().get(0); Role role2 = roles.getItems().get(1); assertFalse( role1.getRules().containsAll(role2.getRules()) && role2.getRules().containsAll(role1.getRules()), "exec and view roles should not be the same"); RoleBindingList bindings = k8sClient.rbac().roleBindings().inNamespace("workspace123").list(); assertEquals( bindings.getItems().stream() .map(r -> r.getMetadata().getName()) .collect(Collectors.toSet()), Sets.newHashSet( "serviceAccount-metrics", "serviceAccount-view", "serviceAccount-exec", "serviceAccount-configmaps", "serviceAccount-secrets")); } @Test public void testEvalNamespaceUsesNamespaceDefaultIfWorkspaceDoesntRecordNamespaceAndLegacyNamespaceDoesntExist() throws Exception { namespaceFactory = new KubernetesNamespaceFactory( "che-", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); WorkspaceImpl workspace = new WorkspaceImplBuilder().setId("workspace123").setAttributes(emptyMap()).build(); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "123", null, false)); String namespace = namespaceFactory.getNamespaceName(workspace); assertEquals(namespace, "che-123"); } @Test public void testEvalNamespaceUsesNamespaceFromUserPreferencesIfExist() throws Exception { namespaceFactory = new KubernetesNamespaceFactory( "che-", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); Map prefs = new HashMap<>(); prefs.put(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "che-123"); prefs.put(NAMESPACE_TEMPLATE_ATTRIBUTE, "che-"); when(preferenceManager.find(anyString())).thenReturn(prefs); String namespace = namespaceFactory.evaluateNamespaceName( new NamespaceResolutionContext("workspace123", "user123", "jondoe")); assertEquals(namespace, "che-123"); } @Test public void testEvalNamespaceSkipsNamespaceFromUserPreferencesIfTemplateChanged() throws Exception { namespaceFactory = new KubernetesNamespaceFactory( "che--", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); Map prefs = new HashMap<>(); // returned but ignored prefs.put(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "che-123"); prefs.put(NAMESPACE_TEMPLATE_ATTRIBUTE, "che-"); when(preferenceManager.find(anyString())).thenReturn(prefs); String namespace = namespaceFactory.evaluateNamespaceName( new NamespaceResolutionContext("workspace123", "user123", "jondoe")); assertEquals(namespace, "che-user123-jondoe"); } @Test public void testEvalNamespaceSkipsNamespaceFromUserPreferencesIfUserAllowedPropertySetFalse() throws Exception { namespaceFactory = new KubernetesNamespaceFactory( "che--", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); Map prefs = new HashMap<>(); // returned but ignored prefs.put(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "che-123"); prefs.put(NAMESPACE_TEMPLATE_ATTRIBUTE, "che-"); when(preferenceManager.find(anyString())).thenReturn(prefs); String namespace = namespaceFactory.evaluateNamespaceName( new NamespaceResolutionContext("workspace123", "user123", "jondoe")); assertEquals(namespace, "che-user123-jondoe"); } @Test public void testEvalNamespaceKubeAdmin() throws Exception { namespaceFactory = spy( new KubernetesNamespaceFactory( "che-", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); doReturn(empty()).when(namespaceFactory).fetchNamespace(anyString()); String namespace = namespaceFactory.evaluateNamespaceName( new NamespaceResolutionContext("workspace123", "kube:admin", "kube:admin")); assertTrue(namespace.startsWith("che-kube-admin-")); assertEquals(namespace.length(), 21); } @Test public void testEvalNamespaceUsesWorkspaceRecordedNamespaceIfWorkspaceRecordsIt() throws Exception { namespaceFactory = new KubernetesNamespaceFactory( "che-", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); WorkspaceImpl workspace = new WorkspaceImplBuilder() .setAttributes( ImmutableMap.of( Constants.WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "wkspcnmspc")) .build(); String namespace = namespaceFactory.getNamespaceName(workspace); assertEquals(namespace, "wkspcnmspc"); } @Test public void testEvalNamespaceTreatsWorkspaceRecordedNamespaceLiterally() throws Exception { namespaceFactory = new KubernetesNamespaceFactory( "che-", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); WorkspaceImpl workspace = new WorkspaceImplBuilder() .setAttributes( ImmutableMap.of(Constants.WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "ns34345")) .build(); String namespace = namespaceFactory.getNamespaceName(workspace); assertEquals(namespace, "ns34345"); } @Test public void testEvalNamespaceNameWhenPreparedNamespacesFound() throws InfrastructureException { List namespaces = Arrays.asList( new NamespaceBuilder() .withNewMetadata() .withName("ns1") .withAnnotations(Map.of(NAMESPACE_ANNOTATION_NAME, "jondoe")) .endMetadata() .withNewStatus() .withPhase("Active") .endStatus() .build(), new NamespaceBuilder() .withNewMetadata() .withName("ns2") .withAnnotations(Map.of(NAMESPACE_ANNOTATION_NAME, "jondoe")) .endMetadata() .withNewStatus() .withPhase("Active") .endStatus() .build()); doReturn(namespaces).when(namespaceList).getItems(); namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); String namespace = namespaceFactory.evaluateNamespaceName( new NamespaceResolutionContext("workspace123", "user123", "jondoe")); assertEquals(namespace, "ns1"); } @Test public void shouldHandleProvision() throws InfrastructureException { // given namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", false, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); when(toReturnNamespace.getName()).thenReturn("jondoe-che"); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); KubernetesNamespaceMetaImpl namespaceMeta = new KubernetesNamespaceMetaImpl( "jondoe-che", ImmutableMap.of("phase", "active", "default", "true")); doReturn(Optional.of(namespaceMeta)).when(namespaceFactory).fetchNamespace(eq("jondoe-che")); // when NamespaceResolutionContext context = new NamespaceResolutionContext("workspace123", "user123", "jondoe"); KubernetesNamespaceMeta actual = testProvisioning(context); // then assertEquals(actual.getName(), "jondoe-che"); assertEquals(actual.getAttributes(), ImmutableMap.of("phase", "active", "default", "true")); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Not able to find namespace jondoe-cha-cha-cha") public void shouldFailToProvisionIfNotAbleToFindNamespace() throws InfrastructureException { // given namespaceFactory = spy( new KubernetesNamespaceFactory( "-cha-cha-cha", false, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); when(toReturnNamespace.getName()).thenReturn("jondoe-cha-cha-cha"); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); KubernetesNamespaceMetaImpl namespaceMeta = new KubernetesNamespaceMetaImpl( "jondoe-cha-cha-cha", ImmutableMap.of("phase", "active", "default", "true")); doReturn(empty()).when(namespaceFactory).fetchNamespace(eq("jondoe-cha-cha-cha")); // when NamespaceResolutionContext context = new NamespaceResolutionContext("workspace123", "user123", "jondoe"); testProvisioning(context); // then fail("should not reach this point since exception has to be thrown"); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Error occurred when tried to fetch default namespace") public void shouldFail2ProvisionIfNotAbleToFindNamespace() throws InfrastructureException { // given namespaceFactory = spy( new KubernetesNamespaceFactory( "-cha-cha-cha", false, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); when(toReturnNamespace.getName()).thenReturn("jondoe-cha-cha-cha"); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); KubernetesNamespaceMetaImpl namespaceMeta = new KubernetesNamespaceMetaImpl( "jondoe-cha-cha-cha", ImmutableMap.of("phase", "active", "default", "true")); doThrow(new InfrastructureException("Error occurred when tried to fetch default namespace")) .when(namespaceFactory) .fetchNamespace(eq("jondoe-cha-cha-cha")); // when NamespaceResolutionContext context = new NamespaceResolutionContext("workspace123", "user123", "jondoe"); testProvisioning(context); // then fail("should not reach this point since exception has to be thrown"); } @Test public void testUsernamePlaceholderInLabelsIsNotEvaluated() throws InfrastructureException { List namespaces = singletonList( new NamespaceBuilder() .withNewMetadata() .withName("ns1") .withAnnotations(Map.of(NAMESPACE_ANNOTATION_NAME, "jondoe")) .endMetadata() .withNewStatus() .withPhase("Active") .endStatus() .build()); doReturn(namespaces).when(namespaceList).getItems(); namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, "try_placeholder_here=", NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "123", null, false)); namespaceFactory.list(); verify(namespaceOperation).withLabels(Map.of("try_placeholder_here", "")); } @Test public void testUsernamePlaceholderInAnnotationsIsEvaluated() throws InfrastructureException { // given namespaceFactory = spy( new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, "try_placeholder_here=", emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "123", null, false)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); prepareNamespace(toReturnNamespace); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "old-che"); KubernetesNamespace namespace = namespaceFactory.getOrCreate(identity); // then assertEquals(toReturnNamespace, namespace); verify(toReturnNamespace) .prepare(eq(true), any(), eq(Map.of("try_placeholder_here", "jondoe"))); } @Test(dataProvider = "invalidUsernames") public void normalizeTest(String raw, String expected) { namespaceFactory = new KubernetesNamespaceFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); assertEquals(expected, namespaceFactory.normalizeNamespaceName(raw)); } @Test public void normalizeLengthTest() { namespaceFactory = new KubernetesNamespaceFactory( "che-", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, emptySet(), cheServerKubernetesClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); assertEquals( 63, namespaceFactory .normalizeNamespaceName( "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong") .length()); } public void assertEqualsMessage(String message) { // verify using ArgumentCaptor ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Appender.class); Mockito.verify(mockedAppender).doAppend(argumentCaptor.capture()); // assert against argumentCaptor.getAllValues() Assert.assertEquals(1, argumentCaptor.getAllValues().size()); Assert.assertEquals( ((LoggingEvent) argumentCaptor.getAllValues().get(0)).getMessage(), message); } @BeforeMethod public void addMockedAppender() { ((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(KubernetesNamespaceFactory.class)) .addAppender(mockedAppender); } @AfterMethod public void detachMockedAppender() { // remove the mock appender from static context ((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)) .detachAppender(mockedAppender); } @DataProvider public static Object[][] invalidUsernames() { return new Object[][] { new Object[] {"gmail@foo.bar", "gmail-foo-bar"}, new Object[] {"_fef_123-ah_*zz**", "fef-123-ah-zz"}, new Object[] {"a-b#-hello", "a-b-hello"}, new Object[] {"a---------b", "a-b"}, new Object[] {"--ab--", "ab"} }; } private void prepareNamespaceToBeFoundByName(String name, Namespace namespace) throws Exception { @SuppressWarnings("unchecked") Resource getNamespaceByNameOperation = mock(Resource.class); when(namespaceOperation.withName(name)).thenReturn(getNamespaceByNameOperation); when(getNamespaceByNameOperation.get()).thenReturn(namespace); } private void throwOnTryToGetNamespaceByName(String namespaceName, Throwable e) throws Exception { @SuppressWarnings("unchecked") Resource getNamespaceByNameOperation = mock(Resource.class); when(namespaceOperation.withName(namespaceName)).thenReturn(getNamespaceByNameOperation); when(getNamespaceByNameOperation.get()).thenThrow(e); } private void prepareListedNamespaces(List namespaces) throws Exception { @SuppressWarnings("unchecked") NamespaceList namespaceList = mock(NamespaceList.class); when(namespaceList.getItems()).thenReturn(namespaces); when(namespaceListResource.list()).thenReturn(namespaceList); } private void throwOnTryToGetNamespacesList(Throwable e) throws Exception { when(namespaceList.getItems()).thenThrow(e); } private void prepareNamespace(KubernetesNamespace namespace) throws InfrastructureException { KubernetesSecrets secrets = mock(KubernetesSecrets.class); lenient().when(namespace.secrets()).thenReturn(secrets); KubernetesConfigsMaps configmaps = mock(KubernetesConfigsMaps.class); lenient().when(namespace.secrets()).thenReturn(secrets); lenient().when(namespace.configMaps()).thenReturn(configmaps); Secret secretMock = mock(Secret.class); ObjectMeta objectMeta = mock(ObjectMeta.class); lenient().when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME); lenient().when(secretMock.getMetadata()).thenReturn(objectMeta); lenient().when(secrets.get()).thenReturn(Collections.singletonList(secretMock)); } private Namespace createNamespace(String name, String phase) { return new NamespaceBuilder() .withNewMetadata() .withName(name) .endMetadata() .withNewStatus() .withPhase(phase) .endStatus() .build(); } private KubernetesNamespaceMeta testProvisioning(NamespaceResolutionContext context) throws InfrastructureException { return new NamespaceProvisioner(namespaceFactory).provision(context); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static io.fabric8.kubernetes.api.model.DeletionPropagation.BACKGROUND; import static java.util.Collections.emptyMap; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.NamespaceBuilder; import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.Status; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.Watcher.Action; import io.fabric8.kubernetes.client.WatcherException; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import java.util.Map; import java.util.concurrent.Executor; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.stubbing.Answer; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link KubernetesNamespace} * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class KubernetesNamespaceTest { public static final String NAMESPACE = "testNamespace"; public static final String WORKSPACE_ID = "workspace123"; @Mock private KubernetesDeployments deployments; @Mock private KubernetesServices services; @Mock private KubernetesIngresses ingresses; @Mock private KubernetesPersistentVolumeClaims pvcs; @Mock private KubernetesSecrets secrets; @Mock private KubernetesConfigsMaps configMaps; @Mock private CheServerKubernetesClientFactory cheClientFactory; @Mock private Executor executor; @Mock private KubernetesClient kubernetesClient; @Mock private NonNamespaceOperation namespaceOperation; @Mock private Resource serviceAccountResource; private KubernetesNamespace k8sNamespace; @BeforeMethod public void setUp() throws Exception { lenient().when(cheClientFactory.create(anyString())).thenReturn(kubernetesClient); lenient().doReturn(namespaceOperation).when(kubernetesClient).namespaces(); final MixedOperation mixedOperation = mock(MixedOperation.class); final NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class); lenient().doReturn(mixedOperation).when(kubernetesClient).serviceAccounts(); lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); lenient().when(namespaceOperation.withName(anyString())).thenReturn(serviceAccountResource); lenient().when(serviceAccountResource.get()).thenReturn(mock(ServiceAccount.class)); k8sNamespace = new KubernetesNamespace( cheClientFactory, WORKSPACE_ID, NAMESPACE, deployments, services, pvcs, ingresses, secrets, configMaps); } @Test public void testKubernetesNamespacePreparingWhenNamespaceExists() throws Exception { // given prepareNamespace(NAMESPACE); KubernetesNamespace namespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); // when namespace.prepare(true, Map.of(), Map.of()); // then verify(namespaceOperation, never()).create(any(Namespace.class)); } @Test public void testKubernetesNamespacePreparingCreationWhenNamespaceDoesNotExist() throws Exception { // given doThrow(new KubernetesClientException("error", 403, null)) .doReturn(namespaceOperation) .doReturn(namespaceOperation) .when(kubernetesClient) .namespaces(); Resource namespaceResource = mock(Resource.class); doReturn(namespaceResource).when(namespaceOperation).withName(anyString()); KubernetesNamespace namespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); // when namespace.prepare(true, Map.of(), Map.of()); // then ArgumentCaptor captor = ArgumentCaptor.forClass(Namespace.class); verify(namespaceOperation).create(captor.capture()); Assert.assertEquals(captor.getValue().getMetadata().getName(), NAMESPACE); } @Test(expectedExceptions = InfrastructureException.class) public void throwsExceptionIfNamespaceDoesntExistAndNotAllowedToCreateIt() throws Exception { // given Resource resource = prepareNamespaceResource(NAMESPACE); doThrow(new KubernetesClientException("error", 403, null)).when(resource).get(); KubernetesNamespace namespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); // when namespace.prepare(false, Map.of(), Map.of()); // then // exception is thrown } @Test public void testKubernetesNamespaceCleaningUp() throws Exception { // when k8sNamespace.cleanUp(); verify(ingresses).delete(); verify(services).delete(); verify(deployments).delete(); verify(secrets).delete(); verify(configMaps).delete(); } @Test public void testKubernetesNamespaceCleaningUpIfExceptionsOccurs() throws Exception { doThrow(new InfrastructureException("err1.")).when(services).delete(); doThrow(new InfrastructureException("err2.")).when(deployments).delete(); InfrastructureException error = null; // when try { k8sNamespace.cleanUp(); } catch (InfrastructureException e) { error = e; } // then assertNotNull(error); String message = error.getMessage(); assertEquals(message, "Error(s) occurs while cleaning up the namespace. err1. err2."); verify(ingresses).delete(); } @Test(expectedExceptions = InfrastructureException.class) public void testThrowsInfrastructureExceptionWhenFailedToGetNamespaceServiceAccounts() throws Exception { final Resource resource = prepareNamespaceResource(NAMESPACE); doThrow(new KubernetesClientException("error", 403, null)).when(resource).get(); doThrow(KubernetesClientException.class).when(kubernetesClient).serviceAccounts(); new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID) .prepare(false, Map.of(), Map.of()); } @Test(expectedExceptions = InfrastructureException.class) public void testThrowsInfrastructureExceptionWhenServiceAccountEventNotPublished() throws Exception { final Resource resource = prepareNamespaceResource(NAMESPACE); doThrow(new KubernetesClientException("error", 403, null)).when(resource).get(); when(serviceAccountResource.get()).thenReturn(null); new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID) .prepare(false, Map.of(), Map.of()); } @Test(expectedExceptions = InfrastructureException.class) public void testThrowsInfrastructureExceptionWhenWatcherClosed() throws Exception { final Resource resource = prepareNamespaceResource(NAMESPACE); doThrow(new KubernetesClientException("error", 403, null)).when(resource).get(); when(serviceAccountResource.get()).thenReturn(null); doAnswer( (Answer) invocation -> { final Watcher watcher = invocation.getArgument(0); watcher.onClose(mock(WatcherException.class)); return mock(Watch.class); }) .when(serviceAccountResource) .watch(any()); new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID) .prepare(false, Map.of(), Map.of()); } @Test public void testStopsWaitingServiceAccountEventJustAfterEventReceived() throws Exception { // given doThrow(new KubernetesClientException("error", 403, null)) .doReturn(namespaceOperation) .doReturn(namespaceOperation) .when(kubernetesClient) .namespaces(); Resource namespaceResource = mock(Resource.class); doReturn(namespaceResource).when(namespaceOperation).withName(anyString()); when(serviceAccountResource.get()).thenReturn(null); doAnswer( invocation -> { final Watcher watcher = invocation.getArgument(0); watcher.eventReceived(Action.ADDED, mock(ServiceAccount.class)); return mock(Watch.class); }) .when(serviceAccountResource) .watch(any()); KubernetesNamespace namespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); // when namespace.prepare(true, Map.of(), Map.of()); verify(serviceAccountResource).get(); verify(serviceAccountResource).watch(any()); } @Test public void testDeletesExistingNamespace() throws Exception { // given KubernetesNamespace namespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); Resource resource = prepareNamespaceResource(NAMESPACE); // when namespace.delete(); // then verify(resource).delete(); } @Test public void testDoesntFailIfDeletedNamespaceDoesntExist() throws Exception { // given KubernetesNamespace namespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); Resource resource = prepareNamespaceResource(NAMESPACE); when(resource.delete()).thenThrow(new KubernetesClientException("err", 404, null)); // when namespace.delete(); // then verify(resource).delete(); // and no exception is thrown } @Test public void testDoesntFailIfDeletedNamespaceIsBeingDeleted() throws Exception { // given KubernetesNamespace namespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); Resource resource = prepareNamespaceResource(NAMESPACE); when(resource.delete()).thenThrow(new KubernetesClientException("err", 409, null)); // when namespace.delete(); // then verify(resource).delete(); // and no exception is thrown } @Test public void testLabelNamespace() throws InfrastructureException { // given prepareNamespace(NAMESPACE); KubernetesNamespace kubernetesNamespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); KubernetesClient cheKubeClient = mock(KubernetesClient.class); doReturn(cheKubeClient).when(cheClientFactory).create(); NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces(); ArgumentCaptor namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class); doAnswer(a -> a.getArgument(0)) .when(nonNamespaceOperation) .createOrReplace(namespaceArgumentCaptor.capture()); Map labels = Map.of("label.with.this", "yes", "and.this", "of courese"); // when kubernetesNamespace.prepare(true, labels, emptyMap()); // then Namespace updatedNamespace = namespaceArgumentCaptor.getValue(); assertTrue( updatedNamespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet())); assertEquals(updatedNamespace.getMetadata().getName(), NAMESPACE); } @Test public void testDontTryToLabelNamespaceIfAlreadyLabeled() throws InfrastructureException { // given Map labels = Map.of("label.with.this", "yes", "and.this", "of courese"); Namespace namespace = prepareNamespace(NAMESPACE); namespace.getMetadata().setLabels(labels); KubernetesNamespace kubernetesNamespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); KubernetesClient cheKubeClient = mock(KubernetesClient.class); lenient().doReturn(cheKubeClient).when(cheClientFactory).create(); NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces(); lenient() .doAnswer(a -> a.getArgument(0)) .when(nonNamespaceOperation) .createOrReplace(any(Namespace.class)); // when kubernetesNamespace.prepare(true, labels, emptyMap()); // then assertTrue(namespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet())); verify(nonNamespaceOperation, never()).createOrReplace(any()); } @Test public void testDontTryToLabelNamespaceIfNoNamespacesProvided() throws InfrastructureException { // given Map existingLabels = Map.of("some", "labels"); Namespace namespace = prepareNamespace(NAMESPACE); namespace.getMetadata().setLabels(existingLabels); KubernetesNamespace kubernetesNamespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); KubernetesClient cheKubeClient = mock(KubernetesClient.class); lenient().doReturn(cheKubeClient).when(cheClientFactory).create(); NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces(); lenient() .doAnswer(a -> a.getArgument(0)) .when(nonNamespaceOperation) .createOrReplace(any(Namespace.class)); // when kubernetesNamespace.prepare(true, emptyMap(), emptyMap()); // then assertTrue( namespace.getMetadata().getLabels().entrySet().containsAll(existingLabels.entrySet())); verify(nonNamespaceOperation, never()).createOrReplace(any()); } public void testAnnotateNamespace() throws InfrastructureException { // given prepareNamespace(NAMESPACE); KubernetesNamespace kubernetesNamespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); KubernetesClient cheKubeClient = mock(KubernetesClient.class); doReturn(cheKubeClient).when(cheClientFactory).create(); NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces(); ArgumentCaptor namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class); doAnswer(a -> a.getArgument(0)) .when(nonNamespaceOperation) .createOrReplace(namespaceArgumentCaptor.capture()); Map annotations = Map.of("annotate.with.this", "yes", "and.this", "of courese"); // when kubernetesNamespace.prepare(true, emptyMap(), annotations); // then Namespace updatedNamespace = namespaceArgumentCaptor.getValue(); assertTrue( updatedNamespace .getMetadata() .getAnnotations() .entrySet() .containsAll(annotations.entrySet())); assertEquals(updatedNamespace.getMetadata().getName(), NAMESPACE); } @Test public void testDontTryToAnnotateNamespaceIfAlreadyAnnoted() throws InfrastructureException { // given Map annotations = Map.of("annotation.with.this", "yes", "and.this", "of courese"); Namespace namespace = prepareNamespace(NAMESPACE); namespace.getMetadata().setAnnotations(annotations); KubernetesNamespace kubernetesNamespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); // when kubernetesNamespace.prepare(true, emptyMap(), annotations); // then assertTrue( namespace.getMetadata().getAnnotations().entrySet().containsAll(annotations.entrySet())); } @Test public void testDontTryToAnnotateNamespaceIfNoNamespacesProvided() throws InfrastructureException { // given Map existingAnnotations = Map.of("some", "annotations"); Namespace namespace = prepareNamespace(NAMESPACE); namespace.getMetadata().setAnnotations(existingAnnotations); KubernetesNamespace kubernetesNamespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); // when kubernetesNamespace.prepare(true, emptyMap(), emptyMap()); // then assertTrue( namespace .getMetadata() .getAnnotations() .entrySet() .containsAll(existingAnnotations.entrySet())); } @Test public void testDoNotFailWhenNoPermissionsToUpdateNamespace() throws InfrastructureException { // given Map labels = Map.of("label.with.this", "yes", "and.this", "of courese"); prepareNamespace(NAMESPACE); KubernetesNamespace kubernetesNamespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); KubernetesClient cheKubeClient = mock(KubernetesClient.class); lenient().doReturn(cheKubeClient).when(cheClientFactory).create(); NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces(); ArgumentCaptor namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class); lenient() .doThrow(new KubernetesClientException("No permissions.", 403, new Status())) .when(nonNamespaceOperation) .createOrReplace(namespaceArgumentCaptor.capture()); // when kubernetesNamespace.prepare(true, labels, emptyMap()); // then Namespace updatedNamespace = namespaceArgumentCaptor.getValue(); assertTrue( updatedNamespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet())); assertEquals(updatedNamespace.getMetadata().getName(), NAMESPACE); } @Test(expectedExceptions = InfrastructureException.class) public void testFailWhenFailToUpdateNamespace() throws InfrastructureException { // given Map labels = Map.of("label.with.this", "yes", "and.this", "of courese"); Namespace namespace = prepareNamespace(NAMESPACE); KubernetesNamespace kubernetesNamespace = new KubernetesNamespace(cheClientFactory, executor, NAMESPACE, WORKSPACE_ID); KubernetesClient cheKubeClient = mock(KubernetesClient.class); lenient().doReturn(cheKubeClient).when(cheClientFactory).create(); NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces(); lenient() .doThrow(new KubernetesClientException("Some other error", 500, new Status())) .when(nonNamespaceOperation) .createOrReplace(any(Namespace.class)); // when kubernetesNamespace.prepare(true, labels, emptyMap()); // then verify(nonNamespaceOperation).createOrReplace(namespace); } private Resource prepareNamespaceResource(String namespaceName) { Resource namespaceResource = mock(Resource.class); doReturn(namespaceResource).when(namespaceOperation).withName(namespaceName); lenient() .doReturn(namespaceResource) .when(namespaceResource) .withPropagationPolicy(eq(BACKGROUND)); when(namespaceResource.get()) .thenReturn( new NamespaceBuilder().withNewMetadata().withName(namespaceName).endMetadata().build()); kubernetesClient.namespaces().withName(namespaceName).get(); return namespaceResource; } private Namespace prepareNamespace(String namespaceName) { Namespace namespace = new NamespaceBuilder().withNewMetadata().withName(namespaceName).endMetadata().build(); Resource namespaceResource = prepareNamespaceResource(namespaceName); doReturn(namespace).when(namespaceResource).get(); return namespace; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesObjectUtilTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static org.testng.Assert.assertEquals; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class KubernetesObjectUtilTest { @Test(dataProvider = "testNames") public void shouldTestisValidConfigMapKeyName(String nameToTest, boolean isValid) { assertEquals(KubernetesObjectUtil.isValidConfigMapKeyName(nameToTest), isValid); } @DataProvider public static Object[][] testNames() { return new Object[][] { new Object[] {"foo.bar", true}, new Object[] {"https://foo.bar", false}, new Object[] {"_fef_123-ah_*zz**", false}, new Object[] {"_fef_123-ah_z.z", true}, new Object[] {"a-b#-hello", false}, new Object[] {"a---------b", true}, new Object[] {"--ab--", true} }; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesSecretsTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static io.fabric8.kubernetes.api.model.DeletionPropagation.BACKGROUND; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_WORKSPACE_ID_LABEL; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretList; import io.fabric8.kubernetes.client.GracePeriodConfigurable; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link KubernetesSecrets}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class KubernetesSecretsTest { private static final String NAMESPACE = "namespace"; private static final String WORKSPACE_ID = "workspace123"; @Mock private KubernetesClient client; @Mock private KubernetesClientFactory clientFactory; @Mock private MixedOperation> secretsMixedOperation; @Mock private NonNamespaceOperation> nonNamespaceOperation; @Mock private FilterWatchListDeletable> deletableList; @Mock private GracePeriodConfigurable deletableSecret; private KubernetesSecrets kubernetesSecrets; @BeforeMethod public void setUp() throws Exception { kubernetesSecrets = new KubernetesSecrets("namespace", "workspace123", clientFactory); when(clientFactory.create("workspace123")).thenReturn(client); when(client.secrets()).thenReturn(secretsMixedOperation); lenient().when(secretsMixedOperation.inNamespace(any())).thenReturn(nonNamespaceOperation); lenient().when(nonNamespaceOperation.withLabel(any(), any())).thenReturn(deletableList); lenient().doReturn(deletableSecret).when(deletableList).withPropagationPolicy(eq(BACKGROUND)); } @Test public void testSecretCreation() throws Exception { Secret secret = new Secret(); kubernetesSecrets.create(secret); assertEquals(secret.getMetadata().getLabels().get(CHE_WORKSPACE_ID_LABEL), WORKSPACE_ID); verify(secretsMixedOperation).inNamespace(NAMESPACE); verify(nonNamespaceOperation).create(secret); } @Test public void testSecretsRemoving() throws Exception { kubernetesSecrets.delete(); verify(secretsMixedOperation).inNamespace(NAMESPACE); verify(nonNamespaceOperation).withLabel(CHE_WORKSPACE_ID_LABEL, WORKSPACE_ID); verify(deletableList).withPropagationPolicy(eq(BACKGROUND)); verify(deletableSecret).delete(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesWorkspaceServiceAccountTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static java.util.Collections.singletonList; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CONFIGMAPS_ROLE_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.METRICS_ROLE_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.SECRETS_ROLE_NAME; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.ServiceAccountBuilder; import io.fabric8.kubernetes.api.model.rbac.PolicyRule; import io.fabric8.kubernetes.api.model.rbac.Role; import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder; import io.fabric8.kubernetes.api.model.rbac.RoleBindingList; import io.fabric8.kubernetes.api.model.rbac.RoleBuilder; import io.fabric8.kubernetes.api.model.rbac.RoleList; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMixedDispatcher; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.mockwebserver.Context; import io.fabric8.mockwebserver.MockWebServer; import io.fabric8.mockwebserver.ServerRequest; import io.fabric8.mockwebserver.ServerResponse; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Queue; import java.util.Set; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class KubernetesWorkspaceServiceAccountTest { public static final String NAMESPACE = "testNamespace"; public static final String WORKSPACE_ID = "workspace123"; public static final String SA_NAME = "workspace-sa"; public static final Set ROLE_NAMES = Collections.singleton("role-foo"); @Mock private KubernetesClientFactory clientFactory; private KubernetesClient k8sClient; private KubernetesMockServer kubernetesMockServer; private KubernetesWorkspaceServiceAccount serviceAccount; @BeforeMethod public void setUp() throws Exception { this.serviceAccount = new KubernetesWorkspaceServiceAccount( WORKSPACE_ID, NAMESPACE, SA_NAME, ROLE_NAMES, clientFactory); final Map> responses = new HashMap<>(); kubernetesMockServer = new KubernetesMockServer( new Context(), new MockWebServer(), responses, new KubernetesMixedDispatcher(responses), true); kubernetesMockServer.init(); k8sClient = kubernetesMockServer.createClient(); when(clientFactory.create(anyString())).thenReturn(k8sClient); } @AfterMethod public void tearDown() { kubernetesMockServer.destroy(); } @Test public void shouldProvisionSARolesEvenIfItAlreadyExists() throws Exception { ServiceAccountBuilder serviceAccountBuilder = new ServiceAccountBuilder().withNewMetadata().withName(SA_NAME).endMetadata(); RoleBuilder roleBuilder = new RoleBuilder().withNewMetadata().withName("foo").endMetadata(); RoleBindingBuilder roleBindingBuilder = new RoleBindingBuilder().withNewMetadata().withName("foo-builder").endMetadata(); // pre-create SA and some roles k8sClient .serviceAccounts() .inNamespace(NAMESPACE) .createOrReplace(serviceAccountBuilder.build()); k8sClient.rbac().roles().inNamespace(NAMESPACE).create(roleBuilder.build()); k8sClient.rbac().roleBindings().inNamespace(NAMESPACE).create(roleBindingBuilder.build()); // when serviceAccount.prepare(); // then // make sure more roles added RoleList rl = k8sClient.rbac().roles().inNamespace(NAMESPACE).list(); assertTrue(rl.getItems().size() > 1); RoleBindingList rbl = k8sClient.rbac().roleBindings().inNamespace(NAMESPACE).list(); assertTrue(rbl.getItems().size() > 1); } @Test public void shouldCreateMetricsRoleIfAPIEnabledOnServer() throws Exception { KubernetesClient localK8sClient = spy(k8sClient); when(localK8sClient.supportsApiPath(eq("/apis/metrics.k8s.io"))).thenReturn(true); when(clientFactory.create(anyString())).thenReturn(localK8sClient); // when serviceAccount.prepare(); // then // make sure metrics role & rb added RoleList rl = k8sClient.rbac().roles().inNamespace(NAMESPACE).list(); assertTrue( rl.getItems().stream().anyMatch(r -> r.getMetadata().getName().equals(METRICS_ROLE_NAME))); RoleBindingList rbl = k8sClient.rbac().roleBindings().inNamespace(NAMESPACE).list(); assertTrue( rbl.getItems().stream() .anyMatch(rb -> rb.getMetadata().getName().equals(SA_NAME + "-metrics"))); } @Test public void shouldNotCreateMetricsRoleIfAPINotEnabledOnServer() throws Exception { KubernetesClient localK8sClient = spy(k8sClient); when(localK8sClient.supportsApiPath(eq("/apis/metrics.k8s.io"))).thenReturn(false); when(clientFactory.create(anyString())).thenReturn(localK8sClient); // when serviceAccount.prepare(); // then // make sure metrics role & rb not added RoleList rl = k8sClient.rbac().roles().inNamespace(NAMESPACE).list(); assertTrue( rl.getItems().stream().noneMatch(r -> r.getMetadata().getName().equals(METRICS_ROLE_NAME))); RoleBindingList rbl = k8sClient.rbac().roleBindings().inNamespace(NAMESPACE).list(); assertTrue( rbl.getItems().stream() .noneMatch(rb -> rb.getMetadata().getName().equals(SA_NAME + "-metrics"))); } @Test public void shouldCreateCredentialsSecretRole() throws Exception { KubernetesClient localK8sClient = spy(k8sClient); when(clientFactory.create(anyString())).thenReturn(localK8sClient); // when serviceAccount.prepare(); // then RoleList rl = k8sClient.rbac().roles().inNamespace(NAMESPACE).list(); Optional roleOptional = rl.getItems().stream() .filter(r -> r.getMetadata().getName().equals(SECRETS_ROLE_NAME)) .findFirst(); assertTrue(roleOptional.isPresent()); PolicyRule rule = roleOptional.get().getRules().get(0); assertEquals(rule.getResources(), singletonList("secrets")); assertEquals(rule.getResourceNames(), singletonList(CREDENTIALS_SECRET_NAME)); assertEquals(rule.getApiGroups(), singletonList("")); assertEquals(rule.getVerbs(), Arrays.asList("get", "patch")); RoleBindingList rbl = k8sClient.rbac().roleBindings().inNamespace(NAMESPACE).list(); assertTrue( rbl.getItems().stream() .anyMatch(rb -> rb.getMetadata().getName().equals(SA_NAME + "-secrets"))); } @Test public void shouldCreatePreferencesConfigmapRole() throws Exception { KubernetesClient localK8sClient = spy(k8sClient); when(clientFactory.create(anyString())).thenReturn(localK8sClient); // when serviceAccount.prepare(); // then RoleList rl = k8sClient.rbac().roles().inNamespace(NAMESPACE).list(); Optional roleOptional = rl.getItems().stream() .filter(r -> r.getMetadata().getName().equals(CONFIGMAPS_ROLE_NAME)) .findFirst(); assertTrue(roleOptional.isPresent()); PolicyRule rule = roleOptional.get().getRules().get(0); assertEquals(rule.getResources(), singletonList("configmaps")); assertEquals(rule.getResourceNames(), singletonList(PREFERENCES_CONFIGMAP_NAME)); assertEquals(rule.getApiGroups(), singletonList("")); assertEquals(rule.getVerbs(), Arrays.asList("get", "patch")); RoleBindingList rbl = k8sClient.rbac().roleBindings().inNamespace(NAMESPACE).list(); assertTrue( rbl.getItems().stream() .anyMatch(rb -> rb.getMetadata().getName().equals(SA_NAME + "-configmaps"))); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/NamespaceNameValidatorTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.NamespaceNameValidator.ValidationResult.INVALID; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.NamespaceNameValidator.ValidationResult.NULL_OR_EMPTY; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.NamespaceNameValidator.ValidationResult.TOO_LONG; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.NamespaceNameValidator.validateInternal; import static org.testng.Assert.assertEquals; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class NamespaceNameValidatorTest { @Test public void testFailsOnNull() { assertEquals(validateInternal(null), NULL_OR_EMPTY); } @Test public void testFailsOnEmpty() { assertEquals(validateInternal(""), NULL_OR_EMPTY); } @Test public void testFailsOnTooLong() { assertEquals( validateInternal("0123456789012345678901234567890123456789012345678901234567890123"), TOO_LONG); } @Test(dataProvider = "invalidDnsNames") public void testFailsOnInvalidChars(String invalidName) { assertEquals(validateInternal(invalidName), INVALID); } @DataProvider public static Object[][] invalidDnsNames() { return new Object[][] { new Object[] {" "}, new Object[] {"-a"}, new Object[] {"a-"}, new Object[] {"A"}, new Object[] {"<"}, new Object[] {"@"}, new Object[] {"*"}, new Object[] {";"}, }; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/RemoveNamespaceOnWorkspaceRemoveTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.event.WorkspaceRemovedEvent; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Test for {@link RemoveNamespaceOnWorkspaceRemove}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class RemoveNamespaceOnWorkspaceRemoveTest { private static final String WORKSPACE_ID = "workspace123"; @Mock private Workspace workspace; @Mock private KubernetesNamespaceFactory namespaceFactory; private RemoveNamespaceOnWorkspaceRemove removeNamespaceOnWorkspaceRemove; @BeforeMethod public void setUp() throws Exception { removeNamespaceOnWorkspaceRemove = spy(new RemoveNamespaceOnWorkspaceRemove(namespaceFactory)); lenient().doNothing().when(namespaceFactory).deleteIfManaged(any()); lenient().when(workspace.getId()).thenReturn(WORKSPACE_ID); } @Test public void shouldSubscribeListenerToEventService() { EventService eventService = mock(EventService.class); removeNamespaceOnWorkspaceRemove.subscribe(eventService); verify(eventService).subscribe(removeNamespaceOnWorkspaceRemove); } @Test public void shouldRemoveNamespaceIfManagedOnWorkspaceRemovedEventIfNamespaceIsManaged() throws Exception { removeNamespaceOnWorkspaceRemove.onEvent(new WorkspaceRemovedEvent(workspace)); verify(namespaceFactory).deleteIfManaged(eq(workspace)); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/CredentialsSecretConfiguratorTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMixedDispatcher; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.mockwebserver.Context; import io.fabric8.mockwebserver.MockWebServer; import io.fabric8.mockwebserver.ServerRequest; import io.fabric8.mockwebserver.ServerResponse; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Queue; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class CredentialsSecretConfiguratorTest { private NamespaceConfigurator configurator; @Mock private CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Mock private PersonalAccessTokenManager personalAccessTokenManager; private KubernetesMockServer kubernetesMockServer; private KubernetesClient kubernetesClient; private NamespaceResolutionContext namespaceResolutionContext; private final String TEST_NAMESPACE_NAME = "namespace123"; private final String TEST_WORKSPACE_ID = "workspace123"; private final String TEST_USER_ID = "user123"; private final String TEST_USERNAME = "jondoe"; private final String PAT_SECRET_NAME = "personal-access-token-1"; private static final String MERGED_GIT_CREDENTIALS_SECRET_NAME = "devworkspace-merged-git-credentials"; private static final Map SEARCH_LABELS = ImmutableMap.of( "app.kubernetes.io/part-of", "che.eclipse.org", "app.kubernetes.io/component", "scm-personal-access-token"); @BeforeMethod public void setUp() throws InfrastructureException { configurator = new CredentialsSecretConfigurator( cheServerKubernetesClientFactory, personalAccessTokenManager); final Map> responses = new HashMap<>(); kubernetesMockServer = new KubernetesMockServer( new Context(), new MockWebServer(), responses, new KubernetesMixedDispatcher(responses), true); kubernetesMockServer.init(); kubernetesClient = kubernetesMockServer.createClient(); when(cheServerKubernetesClientFactory.create()).thenReturn(kubernetesClient); namespaceResolutionContext = new NamespaceResolutionContext(TEST_WORKSPACE_ID, TEST_USER_ID, TEST_USERNAME); } @AfterMethod public void cleanUp() { kubernetesMockServer.destroy(); } @Test public void shouldStorePersonalAccessToken() throws Exception { // given kubernetesClient .secrets() .inNamespace(TEST_NAMESPACE_NAME) .create( new SecretBuilder() .withNewMetadata() .withName(PAT_SECRET_NAME) .withLabels(SEARCH_LABELS) .withAnnotations(Map.of("che.eclipse.org/scm-url", "test-url")) .endMetadata() .build()); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then verify(personalAccessTokenManager).storeGitCredentials(eq("test-url")); } @Test public void shouldRemovePersonalAccessToken() throws Exception { // given doThrow(new ScmCommunicationException("test error", 495)) .when(personalAccessTokenManager) .storeGitCredentials(eq("test-url")); kubernetesClient .secrets() .inNamespace(TEST_NAMESPACE_NAME) .create( new SecretBuilder() .withNewMetadata() .withName(PAT_SECRET_NAME) .withLabels(SEARCH_LABELS) .withAnnotations(Map.of("che.eclipse.org/scm-url", "test-url")) .endMetadata() .build()); // when try { configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); } catch (Exception e) { assertTrue(e instanceof InfrastructureException); assertEquals(e.getMessage(), "test error"); } // then verify(personalAccessTokenManager).remove(eq("test-url")); } @Test public void doNothingWhenSecretAlreadyStored() throws Exception { // given kubernetesClient .secrets() .inNamespace(TEST_NAMESPACE_NAME) .create( new SecretBuilder() .withNewMetadata() .withName(MERGED_GIT_CREDENTIALS_SECRET_NAME) .endMetadata() .withData( Map.of( "credentials", Base64.getEncoder().encodeToString("test-token".getBytes()))) .build()); kubernetesClient .secrets() .inNamespace(TEST_NAMESPACE_NAME) .create( new SecretBuilder() .withNewMetadata() .withName(PAT_SECRET_NAME) .withLabels(SEARCH_LABELS) .withAnnotations(Map.of("che.eclipse.org/scm-url", "test-url")) .endMetadata() .withData( Map.of("token", Base64.getEncoder().encodeToString("test-token".getBytes()))) .build()); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then verify(personalAccessTokenManager, never()).storeGitCredentials(anyString()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigConfiguratorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMixedDispatcher; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.mockwebserver.Context; import io.fabric8.mockwebserver.MockWebServer; import io.fabric8.mockwebserver.ServerRequest; import io.fabric8.mockwebserver.ServerResponse; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import org.eclipse.che.api.factory.server.scm.GitUserData; import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.exception.*; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitconfigConfiguratorTest { private NamespaceConfigurator configurator; @Mock private CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Mock private GitUserDataFetcher gitUserDataFetcher; private KubernetesMockServer kubernetesMockServer; private KubernetesClient kubernetesClient; private NamespaceResolutionContext namespaceResolutionContext; private final String TEST_NAMESPACE_NAME = "namespace123"; private final String TEST_WORKSPACE_ID = "workspace123"; private final String TEST_USER_ID = "user123"; private final String TEST_USERNAME = "username123"; private static final String GITCONFIG_CONFIGMAP_NAME = "workspace-userdata-gitconfig-configmap"; private static final Map GITCONFIG_CONFIGMAP_LABELS = ImmutableMap.of( "controller.devfile.io/mount-to-devworkspace", "true", "controller.devfile.io/watch-configmap", "true"); private static final Map GITCONFIG_CONFIGMAP_ANNOTATIONS = ImmutableMap.of( "controller.devfile.io/mount-as", "subpath", "controller.devfile.io/mount-path", "/etc"); @BeforeMethod public void setUp() throws InfrastructureException, ScmCommunicationException, ScmUnauthorizedException { configurator = new GitconfigConfigurator(cheServerKubernetesClientFactory, Collections.emptySet()); final Map> responses = new HashMap<>(); kubernetesMockServer = new KubernetesMockServer( new Context(), new MockWebServer(), responses, new KubernetesMixedDispatcher(responses), true); kubernetesMockServer.init(); kubernetesClient = spy(kubernetesMockServer.createClient()); when(cheServerKubernetesClientFactory.create()).thenReturn(kubernetesClient); namespaceResolutionContext = new NamespaceResolutionContext(TEST_WORKSPACE_ID, TEST_USER_ID, TEST_USERNAME); } @AfterMethod public void cleanUp() { kubernetesMockServer.destroy(); } @Test public void shouldCreateGitconfigConfigmapWithUserSection() throws Exception { // given when(gitUserDataFetcher.fetchGitUserData(anyString())) .thenReturn(new GitUserData("username", "userEmail")); configurator = new GitconfigConfigurator(cheServerKubernetesClientFactory, Set.of(gitUserDataFetcher)); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then Assert.assertEquals(kubernetesMockServer.getLastRequest().getMethod(), "POST"); List configMaps = kubernetesClient.configMaps().inNamespace(TEST_NAMESPACE_NAME).list().getItems(); Assert.assertEquals(configMaps.size(), 1); String expected = "[user]\n\tname = username\n\temail = userEmail"; Assert.assertEquals(configMaps.get(0).getData().get("gitconfig"), expected); } @Test public void shouldNotCreateGitconfigConfigmap() throws Exception { // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then Assert.assertEquals(kubernetesMockServer.getLastRequest().getMethod(), "GET"); List configMaps = kubernetesClient.configMaps().inNamespace(TEST_NAMESPACE_NAME).list().getItems(); Assert.assertEquals(configMaps.size(), 0); } @Test public void shouldUpdateGitconfigConfigmapWithUserSection() throws InfrastructureException, InterruptedException, ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException, ScmConfigurationPersistenceException, ScmBadRequestException { // given ConfigMap gitconfigConfigmap = new ConfigMapBuilder() .withNewMetadata() .withName(GITCONFIG_CONFIGMAP_NAME) .withLabels(GITCONFIG_CONFIGMAP_LABELS) .withAnnotations(GITCONFIG_CONFIGMAP_ANNOTATIONS) .endMetadata() .build(); gitconfigConfigmap.setData( Collections.singletonMap("gitconfig", "[user]\n\tname = \"\"\n\temail= \"\"")); kubernetesClient.configMaps().inNamespace(TEST_NAMESPACE_NAME).create(gitconfigConfigmap); configurator = new GitconfigConfigurator(cheServerKubernetesClientFactory, Set.of(gitUserDataFetcher)); when(gitUserDataFetcher.fetchGitUserData(anyString())) .thenReturn(new GitUserData("username", "userEmail")); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then Assert.assertEquals(kubernetesMockServer.getLastRequest().getMethod(), "PUT"); List configMaps = kubernetesClient.configMaps().inNamespace(TEST_NAMESPACE_NAME).list().getItems(); Assert.assertEquals(configMaps.size(), 1); String expected = "[user]\n\tname = username\n\temail = userEmail"; Assert.assertEquals(configMaps.get(0).getData().get("gitconfig"), expected); } @Test public void shouldUpdateGitconfigConfigmapWithStoredSectionsWithUserSection() throws InfrastructureException, InterruptedException, ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException, ScmConfigurationPersistenceException, ScmBadRequestException { // given ConfigMap gitconfigConfigmap = new ConfigMapBuilder() .withNewMetadata() .withName(GITCONFIG_CONFIGMAP_NAME) .withLabels(GITCONFIG_CONFIGMAP_LABELS) .withAnnotations(GITCONFIG_CONFIGMAP_ANNOTATIONS) .endMetadata() .build(); gitconfigConfigmap.setData( Collections.singletonMap( "gitconfig", "[other]\n\tkey = value\n[other1]\n\tkey = value\n[user]\n\tname = \"\"\n\temail= \"\"")); kubernetesClient.configMaps().inNamespace(TEST_NAMESPACE_NAME).create(gitconfigConfigmap); configurator = new GitconfigConfigurator(cheServerKubernetesClientFactory, Set.of(gitUserDataFetcher)); when(gitUserDataFetcher.fetchGitUserData(anyString())) .thenReturn(new GitUserData("username", "userEmail")); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then Assert.assertEquals(kubernetesMockServer.getLastRequest().getMethod(), "PUT"); List configMaps = kubernetesClient.configMaps().inNamespace(TEST_NAMESPACE_NAME).list().getItems(); Assert.assertEquals(configMaps.size(), 1); String expected = "[user]\n\tname = username\n\temail = userEmail\n[other]\n\tkey = value\n[other1]\n\tkey = value"; Assert.assertEquals(configMaps.get(0).getData().get("gitconfig"), expected); } @Test public void shouldNotUpdateGitconfigConfigmapWithUserSection() throws InfrastructureException, InterruptedException, ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException, ScmConfigurationPersistenceException, ScmBadRequestException { // given ConfigMap gitconfigConfigmap = new ConfigMapBuilder() .withNewMetadata() .withName(GITCONFIG_CONFIGMAP_NAME) .withLabels(GITCONFIG_CONFIGMAP_LABELS) .withAnnotations(GITCONFIG_CONFIGMAP_ANNOTATIONS) .endMetadata() .build(); gitconfigConfigmap.setData( Collections.singletonMap( "gitconfig", "[user]\n\tname = gitconfig-username\n\temail = gitconfig-email")); kubernetesClient.configMaps().inNamespace(TEST_NAMESPACE_NAME).create(gitconfigConfigmap); configurator = new GitconfigConfigurator(cheServerKubernetesClientFactory, Set.of(gitUserDataFetcher)); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then Assert.assertEquals(kubernetesMockServer.getLastRequest().getMethod(), "GET"); List configMaps = kubernetesClient.configMaps().inNamespace(TEST_NAMESPACE_NAME).list().getItems(); Assert.assertEquals(configMaps.size(), 1); String expected = "[user]\n\tname = gitconfig-username\n\temail = gitconfig-email"; Assert.assertEquals(configMaps.get(0).getData().get("gitconfig"), expected); // Check that fetchGitUserData was not called. verify(gitUserDataFetcher, never()).fetchGitUserData(anyString()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/PreferencesConfigMapConfiguratorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMixedDispatcher; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.mockwebserver.Context; import io.fabric8.mockwebserver.MockWebServer; import io.fabric8.mockwebserver.ServerRequest; import io.fabric8.mockwebserver.ServerResponse; import java.util.HashMap; import java.util.Map; import java.util.Queue; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class PreferencesConfigMapConfiguratorTest { private NamespaceConfigurator configurator; @Mock private CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private KubernetesMockServer kubernetesMockServer; private KubernetesClient kubernetesClient; private NamespaceResolutionContext namespaceResolutionContext; private final String TEST_NAMESPACE_NAME = "namespace123"; private final String TEST_WORKSPACE_ID = "workspace123"; private final String TEST_USER_ID = "user123"; private final String TEST_USERNAME = "jondoe"; @BeforeMethod public void setUp() throws InfrastructureException { configurator = new PreferencesConfigMapConfigurator(cheServerKubernetesClientFactory); final Map> responses = new HashMap<>(); kubernetesMockServer = new KubernetesMockServer( new Context(), new MockWebServer(), responses, new KubernetesMixedDispatcher(responses), true); kubernetesMockServer.init(); kubernetesClient = spy(kubernetesMockServer.createClient()); when(cheServerKubernetesClientFactory.create()).thenReturn(kubernetesClient); namespaceResolutionContext = new NamespaceResolutionContext(TEST_WORKSPACE_ID, TEST_USER_ID, TEST_USERNAME); } @AfterMethod public void tearDown() { kubernetesMockServer.destroy(); } @Test public void createConfigmapWhenDoesntExist() throws InfrastructureException, InterruptedException { // given - clean env // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then configmap created Assert.assertEquals(kubernetesMockServer.getLastRequest().getMethod(), "POST"); Assert.assertNotNull( kubernetesClient .configMaps() .inNamespace(TEST_NAMESPACE_NAME) .withName(PREFERENCES_CONFIGMAP_NAME) .get()); verify(cheServerKubernetesClientFactory, times(1)).create(); } @Test public void doNothingWhenConfigmapExists() throws InfrastructureException, InterruptedException { // given - configmap already exists kubernetesClient .configMaps() .inNamespace(TEST_NAMESPACE_NAME) .create( new ConfigMapBuilder() .withNewMetadata() .withName(PREFERENCES_CONFIGMAP_NAME) .withAnnotations(Map.of("already", "created")) .endMetadata() .build()); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then - don't create the configmap Assert.assertEquals(kubernetesMockServer.getLastRequest().getMethod(), "GET"); var configmaps = kubernetesClient.configMaps().inNamespace(TEST_NAMESPACE_NAME).list().getItems(); Assert.assertEquals(configmaps.size(), 1); Assert.assertEquals(configmaps.get(0).getMetadata().getAnnotations().get("already"), "created"); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/SshKeysConfiguratorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_WATCH_SECRET_LABEL; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMixedDispatcher; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.mockwebserver.Context; import io.fabric8.mockwebserver.MockWebServer; import io.fabric8.mockwebserver.ServerRequest; import io.fabric8.mockwebserver.ServerResponse; import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.ssh.server.SshManager; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class SshKeysConfiguratorTest { private static final String USER_ID = "user-id"; private static final String USER_NAME = "user-name"; private static final String USER_NAMESPACE = "user-namespace"; @Mock private CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Mock private SshManager sshManager; @InjectMocks private SshKeysConfigurator sshKeysConfigurator; private KubernetesMockServer kubernetesMockServer; private KubernetesClient kubernetesClient; private NamespaceResolutionContext context; private final SshPairImpl sshPair = new SshPairImpl(USER_ID, "vcs", "github.com", "public-key", "private-key"); @BeforeMethod public void setUp() throws InfrastructureException, NotFoundException, ServerException { context = new NamespaceResolutionContext(null, USER_ID, USER_NAME); final Map> responses = new HashMap<>(); kubernetesMockServer = new KubernetesMockServer( new Context(), new MockWebServer(), responses, new KubernetesMixedDispatcher(responses), true); kubernetesMockServer.init(); kubernetesClient = kubernetesMockServer.createClient(); when(sshManager.getPairs(USER_ID, "vcs")).thenReturn(Collections.singletonList(sshPair)); when(cheServerKubernetesClientFactory.create()).thenReturn(kubernetesClient); } @AfterMethod public void cleanUp() { kubernetesClient.secrets().inNamespace(USER_NAMESPACE).delete(); kubernetesMockServer.destroy(); } @Test public void shouldCreateSSHKeysSecret() throws InfrastructureException { sshKeysConfigurator.configure(context, USER_NAMESPACE); List secrets = kubernetesClient .secrets() .inNamespace(USER_NAMESPACE) .withLabels( Map.of( DEV_WORKSPACE_MOUNT_LABEL, "true", DEV_WORKSPACE_WATCH_SECRET_LABEL, "true")) .list() .getItems(); assertEquals(secrets.size(), 1); assertEquals(secrets.get(0).getMetadata().getName(), "che-git-ssh-key"); assertEquals(secrets.get(0).getData().size(), 3); assertEquals( new String(Base64.getDecoder().decode(secrets.get(0).getData().get("github.com"))), "private-key"); assertEquals( new String(Base64.getDecoder().decode(secrets.get(0).getData().get("github.com.pub"))), "public-key"); assertEquals( new String(Base64.getDecoder().decode(secrets.get(0).getData().get("ssh_config"))), "host github.com\n" + "IdentityFile /etc/ssh/github.com\n" + "StrictHostKeyChecking = no\n\n"); } @Test public void shouldNotCreateSSHKeysSecretFromBadSecret() throws Exception { SshPairImpl sshPairLocal = new SshPairImpl(USER_ID, "vcs", "%sd$$$", "public-key", "private-key"); when(sshManager.getPairs(USER_ID, "vcs")).thenReturn(List.of(sshPairLocal)); sshKeysConfigurator.configure(context, USER_NAMESPACE); List secrets = kubernetesClient .secrets() .inNamespace(USER_NAMESPACE) .withLabels( Map.of( DEV_WORKSPACE_MOUNT_LABEL, "true", DEV_WORKSPACE_WATCH_SECRET_LABEL, "true")) .list() .getItems(); assertEquals(secrets.size(), 0); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/UserPermissionConfiguratorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder; import io.fabric8.kubernetes.api.model.rbac.Subject; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMixedDispatcher; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.mockwebserver.Context; import io.fabric8.mockwebserver.MockWebServer; import io.fabric8.mockwebserver.ServerRequest; import io.fabric8.mockwebserver.ServerResponse; import java.util.HashMap; import java.util.Map; import java.util.Queue; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class UserPermissionConfiguratorTest { private NamespaceConfigurator configurator; @Mock private CheServerKubernetesClientFactory clientFactory; private KubernetesClient client; private KubernetesMockServer kubernetesMockServer; private NamespaceResolutionContext namespaceResolutionContext; private final String TEST_NAMESPACE_NAME = "namespace123"; private final String TEST_WORKSPACE_ID = "workspace123"; private final String TEST_USER_ID = "user123"; private final String TEST_USERNAME = "jondoe"; private final String TEST_CLUSTER_ROLES = "cr1,cr2"; @BeforeMethod public void setUp() throws InfrastructureException { configurator = new UserPermissionConfigurator(TEST_CLUSTER_ROLES, clientFactory); final Map> responses = new HashMap<>(); kubernetesMockServer = new KubernetesMockServer( new Context(), new MockWebServer(), responses, new KubernetesMixedDispatcher(responses), true); kubernetesMockServer.init(); client = spy(kubernetesMockServer.createClient()); lenient().when(clientFactory.create()).thenReturn(client); namespaceResolutionContext = new NamespaceResolutionContext(TEST_WORKSPACE_ID, TEST_USER_ID, TEST_USERNAME); } @AfterMethod public void tearDown() { kubernetesMockServer.destroy(); } @Test public void doNothingWhenNoClusterRolesSet() throws InfrastructureException, InterruptedException { // given - no cluster roles set configurator = new UserPermissionConfigurator("", clientFactory); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then - do nothing Assert.assertNull(kubernetesMockServer.getLastRequest()); verify(clientFactory, never()).create(); } @Test public void bindAllClusterRolesWhenEmptyEnv() throws InfrastructureException { // given - clean env // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then - create all role bindings var roleBindings = kubernetesMockServer.createClient().rbac().roleBindings().inNamespace(TEST_NAMESPACE_NAME); Assert.assertEquals(roleBindings.list().getItems().size(), 2); var cr1 = roleBindings.withName("cr1").get(); Assert.assertNotNull(cr1); Assert.assertEquals(cr1.getSubjects().get(0).getName(), TEST_USERNAME); Assert.assertEquals(cr1.getSubjects().get(0).getNamespace(), TEST_NAMESPACE_NAME); Assert.assertEquals(cr1.getRoleRef().getName(), "cr1"); var cr2 = roleBindings.withName("cr2").get(); Assert.assertNotNull(cr2); Assert.assertEquals(cr2.getSubjects().get(0).getName(), TEST_USERNAME); Assert.assertEquals(cr2.getSubjects().get(0).getNamespace(), TEST_NAMESPACE_NAME); Assert.assertEquals(cr2.getRoleRef().getName(), "cr2"); verify(client, times(2)).rbac(); } @Test public void replaceExistingBindingsWithSameName() throws InfrastructureException { // given - cr1 binding already exists client .rbac() .roleBindings() .inNamespace(TEST_NAMESPACE_NAME) .create( new RoleBindingBuilder() .withNewMetadata() .withName("cr1") .endMetadata() .withSubjects(new Subject("blabol", "blabol", "blabol", "blabol")) .withNewRoleRef() .withName("blabol") .endRoleRef() .build()); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then var roleBindings = client.rbac().roleBindings().inNamespace(TEST_NAMESPACE_NAME); Assert.assertEquals(roleBindings.list().getItems().size(), 2); var cr1 = roleBindings.withName("cr1").get(); Assert.assertEquals(cr1.getRoleRef().getName(), "cr1"); Assert.assertEquals(cr1.getSubjects().size(), 1); Assert.assertEquals(cr1.getSubjects().get(0).getName(), TEST_USERNAME); Assert.assertEquals(cr1.getSubjects().get(0).getNamespace(), TEST_NAMESPACE_NAME); } @Test public void keepOtherClusterRoles() throws InfrastructureException { // given - some other binding in place client .rbac() .roleBindings() .inNamespace(TEST_NAMESPACE_NAME) .create( new RoleBindingBuilder() .withNewMetadata() .withName("othercr") .endMetadata() .withSubjects(new Subject("blabol", "blabol", "blabol", "blabol")) .withNewRoleRef() .withName("blabol") .endRoleRef() .build()); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then var roleBindings = client.rbac().roleBindings().inNamespace(TEST_NAMESPACE_NAME); Assert.assertEquals(roleBindings.list().getItems().size(), 3); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/UserPreferencesConfiguratorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.testng.Assert.assertEquals; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests for {@link UserPreferencesConfigurator}. * * @author Pavol Baran */ @Listeners(MockitoTestNGListener.class) public class UserPreferencesConfiguratorTest { private static final String USER_ID = "user-id"; private static final String USER_NAME = "user-name"; private static final String USER_EMAIL = "user-email"; private static final String USER_NAMESPACE = "user-namespace"; @Mock private KubernetesNamespaceFactory namespaceFactory; @Mock private PreferenceManager preferenceManager; @InjectMocks private UserPreferencesConfigurator userPreferencesConfigurator; private NamespaceResolutionContext context; @BeforeMethod public void setUp() throws InfrastructureException, NotFoundException, ServerException { context = new NamespaceResolutionContext(null, USER_ID, USER_NAME); Map preferences = new HashMap<>(); preferences.put("preference-name", "preference"); lenient().when(namespaceFactory.evaluateNamespaceName(any())).thenReturn(USER_NAMESPACE); lenient().when(preferenceManager.find(USER_ID)).thenReturn(preferences); } @Test public void shouldNormalizePreferenceName() { assertEquals( userPreferencesConfigurator.normalizePreferenceName("codename:bond"), "codename-bond"); assertEquals(userPreferencesConfigurator.normalizePreferenceName("some--:pref"), "some-pref"); assertEquals( userPreferencesConfigurator.normalizePreferenceName("pref[name].sub"), "pref-name-.sub"); } @Test public void shouldKeepPreferenceName() { assertEquals( userPreferencesConfigurator.normalizePreferenceName("codename.bond"), "codename.bond"); assertEquals(userPreferencesConfigurator.normalizePreferenceName("pref_name"), "pref_name"); assertEquals( userPreferencesConfigurator.normalizePreferenceName("some-name.over_rainbow"), "some-name.over_rainbow"); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/WorkspaceServiceAccountConfiguratorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; import static java.util.stream.Collectors.joining; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.spy; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMixedDispatcher; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.mockwebserver.Context; import io.fabric8.mockwebserver.MockWebServer; import io.fabric8.mockwebserver.ServerRequest; import io.fabric8.mockwebserver.ServerResponse; import java.util.HashMap; import java.util.Map; import java.util.Queue; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesWorkspaceServiceAccount; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class WorkspaceServiceAccountConfiguratorTest { private WorkspaceServiceAccountConfigurator configurator; @Mock private CheServerKubernetesClientFactory clientFactory; private KubernetesClient client; private KubernetesMockServer kubernetesMockServer; private NamespaceResolutionContext namespaceResolutionContext; @Mock private KubernetesWorkspaceServiceAccount kubeWSA; private final String TEST_NAMESPACE_NAME = "namespace123"; private final String TEST_WORKSPACE_ID = "workspace123"; private final String TEST_USER_ID = "user123"; private final String TEST_USERNAME = "jondoe"; private final String TEST_SERVICE_ACCOUNT = "serviceAccount123"; private final String TEST_CLUSTER_ROLES = "cr1, cr2"; @BeforeMethod public void setUp() throws InfrastructureException { configurator = spy( new WorkspaceServiceAccountConfigurator( TEST_SERVICE_ACCOUNT, TEST_CLUSTER_ROLES, clientFactory)); // when(configurator.doCreateServiceAccount(TEST_WORKSPACE_ID, // TEST_NAMESPACE_NAME)).thenReturn(kubeWSA); final Map> responses = new HashMap<>(); kubernetesMockServer = new KubernetesMockServer( new Context(), new MockWebServer(), responses, new KubernetesMixedDispatcher(responses), true); kubernetesMockServer.init(); client = spy(kubernetesMockServer.createClient()); lenient().when(clientFactory.create(TEST_WORKSPACE_ID)).thenReturn(client); namespaceResolutionContext = new NamespaceResolutionContext(TEST_WORKSPACE_ID, TEST_USER_ID, TEST_USERNAME); } @AfterMethod public void tearDown() { kubernetesMockServer.destroy(); } @Test public void createWorkspaceServiceAccountWithBindings() throws InfrastructureException { // given - cluster roles exists in cluster configurator = new WorkspaceServiceAccountConfigurator( TEST_SERVICE_ACCOUNT, TEST_CLUSTER_ROLES, clientFactory); client .rbac() .clusterRoles() .create(new ClusterRoleBuilder().withNewMetadata().withName("cr1").endMetadata().build()); client .rbac() .clusterRoles() .create(new ClusterRoleBuilder().withNewMetadata().withName("cr2").endMetadata().build()); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then - create service account with all the bindings var serviceAccount = client .serviceAccounts() .inNamespace(TEST_NAMESPACE_NAME) .withName(TEST_SERVICE_ACCOUNT) .get(); assertNotNull(serviceAccount); var roleBindings = client.rbac().roleBindings().inNamespace(TEST_NAMESPACE_NAME).list().getItems(); assertEquals( roleBindings.size(), 6, roleBindings.stream() .map(r -> r.getMetadata().getName()) .collect(joining(", "))); // exec, secrets, configmaps, view bindings + cr1, cr2 } @Test public void dontCreateBindingsWhenClusterRolesDontExists() throws InfrastructureException { // given - cluster roles exists in cluster configurator = new WorkspaceServiceAccountConfigurator( TEST_SERVICE_ACCOUNT, TEST_CLUSTER_ROLES, clientFactory); // when configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); // then - create service account with default bindings var serviceAccount = client .serviceAccounts() .inNamespace(TEST_NAMESPACE_NAME) .withName(TEST_SERVICE_ACCOUNT) .get(); assertNotNull(serviceAccount); var roleBindings = client.rbac().roleBindings().inNamespace(TEST_NAMESPACE_NAME).list().getItems(); assertEquals( roleBindings.size(), 4, roleBindings.stream() .map(r -> r.getMetadata().getName()) .collect(joining(", "))); // exec, secrets, configmaps, view bindings } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/log/ContainerLogWatchTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodList; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.ContainerResource; import io.fabric8.kubernetes.client.dsl.LogWatch; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.PodResource; import java.io.IOException; import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.concurrent.CompletionStage; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class ContainerLogWatchTest { private final String namespace = "namespace123"; private final String podname = "pod123"; private final String container = "containre123"; private final LogWatchTimeouts TIMEOUTS = new LogWatchTimeouts(100, 0, 0); private final long LOG_LIMIT_BYTES = 1024; @Mock KubernetesClient client; @Mock RuntimeEventsPublisher eventsPublisher; @Mock PodLogHandler podLogHandler; @Mock MixedOperation pods; @Mock PodResource podResource; @Mock ContainerResource containerResource; LogWatchMock logWatch; @BeforeMethod public void setUp() throws IOException { logWatch = new LogWatchMock(); when(client.pods()).thenReturn(pods); when(pods.inNamespace(namespace)).thenReturn(pods); when(pods.withName(podname)).thenReturn(podResource); when(podResource.inContainer(container)).thenReturn(containerResource); when(containerResource.watchLog()).thenReturn(logWatch); } @Test public void testSuccessfulFinishedContainerLogWatch() throws IOException { PipedInputStream inputStream = new PipedInputStream(); PipedOutputStream outputStream = new PipedOutputStream(inputStream); outputStream.write("first\nsecond".getBytes()); outputStream.write("\nthird".getBytes()); outputStream.close(); logWatch.setInputStream(inputStream); ContainerLogWatch clw = new ContainerLogWatch( client, eventsPublisher, namespace, podname, container, podLogHandler, TIMEOUTS, LOG_LIMIT_BYTES); clw.run(); verify(podLogHandler).handle("first", container); verify(podLogHandler).handle("second", container); verify(podLogHandler).handle("third", container); assertTrue(logWatch.isClosed); // verify events were properly fired verify(eventsPublisher, times(1)).sendWatchLogStartedEvent(any(String.class)); verify(eventsPublisher, times(1)).sendWatchLogStoppedEvent(any(String.class)); } @Test public void testLimitInputStreamBytes() throws IOException { PipedInputStream inputStream = new PipedInputStream(); PipedOutputStream outputStream = new PipedOutputStream(inputStream); outputStream.write("This is long message that won't fit into the limit.".getBytes()); outputStream.close(); logWatch.setInputStream(inputStream); ContainerLogWatch clw = new ContainerLogWatch( client, eventsPublisher, namespace, podname, container, podLogHandler, TIMEOUTS, 4); clw.run(); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor containerCaptor = ArgumentCaptor.forClass(String.class); verify(podLogHandler, times(1)).handle(messageCaptor.capture(), containerCaptor.capture()); assertEquals(messageCaptor.getValue(), "This"); assertEquals(containerCaptor.getValue(), container); assertTrue(logWatch.isClosed); // verify events were properly fired verify(eventsPublisher, times(1)).sendWatchLogStartedEvent(any(String.class)); verify(eventsPublisher, times(1)).sendWatchLogStoppedEvent(any(String.class)); } @Test public void testCloseFromOutside() throws IOException, InterruptedException { PipedInputStream inputStream = new PipedInputStream(); PipedOutputStream outputStream = new PipedOutputStream(inputStream); outputStream.write("message\n".getBytes()); logWatch.setInputStream(inputStream); CountDownLatch latch = new CountDownLatch(1); doAnswer( (a) -> { outputStream.write("nextMessage\n".getBytes()); latch.countDown(); return null; }) .when(podLogHandler) .handle(any(), any()); ContainerLogWatch clw = new ContainerLogWatch( client, eventsPublisher, namespace, podname, container, podLogHandler, TIMEOUTS, LOG_LIMIT_BYTES); new Thread(clw).start(); latch.await(1, TimeUnit.SECONDS); clw.close(); assertTrue(logWatch.isClosed); // verify events were properly fired verify(eventsPublisher, times(1)).sendWatchLogStartedEvent(any(String.class)); verify(eventsPublisher, timeout(1000).times(1)).sendWatchLogStoppedEvent(any(String.class)); } @Test public void testCloseOfOutputStream() throws IOException, InterruptedException { PipedInputStream inputStream = new PipedInputStream(); PipedOutputStream outputStream = new PipedOutputStream(inputStream); outputStream.write("message\n".getBytes()); logWatch.setInputStream(inputStream); CountDownLatch messageHandleLatch = new CountDownLatch(1); doAnswer( (a) -> { messageHandleLatch.countDown(); return null; }) .when(podLogHandler) .handle("message", container); ContainerLogWatch clw = new ContainerLogWatch( client, eventsPublisher, namespace, podname, container, podLogHandler, TIMEOUTS, LOG_LIMIT_BYTES); new Thread(clw).start(); messageHandleLatch.await(1, TimeUnit.SECONDS); outputStream.close(); CountDownLatch closeLatch = new CountDownLatch(1); logWatch.setLatch(closeLatch); closeLatch.await(1, TimeUnit.SECONDS); assertTrue(logWatch.isClosed); // verify events were properly fired verify(eventsPublisher, times(1)).sendWatchLogStartedEvent(any(String.class)); verify(eventsPublisher, timeout(1000).times(1)).sendWatchLogStoppedEvent(any(String.class)); } @Test public void shouldRetryWhenErrorMessageReceived() throws IOException, InterruptedException { // prepare error message logWatch String podInitializingMessage = "{\"kind\":\"Status\"," + "\"apiVersion\":\"v1\"," + "\"metadata\":{}," + "\"status\":\"Failure\"," + "\"message\":\"container \\\"" + container + "\\\" in pod \\\"" + podname + "\\\" is waiting to start: ContainerCreating\"," + "\"reason\":\"BadRequest\"," + "\"code\":400}"; PipedInputStream inputStream = new PipedInputStream(); PipedOutputStream outputStream = new PipedOutputStream(inputStream); outputStream.write((podInitializingMessage + "\n").getBytes()); outputStream.close(); logWatch.setInputStream(inputStream); LogWatchMock logWatchRegularMessage = new LogWatchMock(); // prepare regular message logwatch inputStream = new PipedInputStream(); outputStream = new PipedOutputStream(inputStream); outputStream.write("message\n".getBytes()); outputStream.close(); logWatchRegularMessage.setInputStream(inputStream); CountDownLatch messageHandleLatch = new CountDownLatch(2); logWatchRegularMessage.setLatch(messageHandleLatch); doAnswer( (a) -> { messageHandleLatch.countDown(); return null; }) .when(podLogHandler) .handle("message", container); // return error message logwatch first and regular message logwatch on second call when(containerResource.watchLog()).thenReturn(logWatch).thenReturn(logWatchRegularMessage); ContainerLogWatch clw = new ContainerLogWatch( client, eventsPublisher, namespace, podname, container, podLogHandler, TIMEOUTS, LOG_LIMIT_BYTES); new Thread(clw).start(); // wait for logwatch close, it means that error message was processed CountDownLatch closeLatch = new CountDownLatch(1); logWatch.setLatch(closeLatch); closeLatch.await(1, TimeUnit.SECONDS); assertTrue(logWatch.isClosed); // wait for regular message messageHandleLatch.await(1, TimeUnit.SECONDS); // message was processed verify(podLogHandler).handle("message", container); assertTrue(logWatchRegularMessage.isClosed); // verify events were properly fired verify(eventsPublisher, times(2)).sendWatchLogStartedEvent(any(String.class)); verify(eventsPublisher, timeout(1000).times(2)).sendWatchLogStoppedEvent(any(String.class)); } @Test public void shouldRetryWhenOutputIsNullFirst() throws IOException, InterruptedException { logWatch.setInputStream(null); LogWatchMock logWatchRegularMessage = new LogWatchMock(); // prepare regular message logwatch PipedInputStream inputStream = new PipedInputStream(); PipedOutputStream outputStream = new PipedOutputStream(inputStream); outputStream.write("message\n".getBytes()); outputStream.close(); logWatchRegularMessage.setInputStream(inputStream); CountDownLatch messageHandleLatch = new CountDownLatch(2); logWatchRegularMessage.setLatch(messageHandleLatch); doAnswer( (a) -> { messageHandleLatch.countDown(); return null; }) .when(podLogHandler) .handle("message", container); // return null stream first and regular message stream on second call when(containerResource.watchLog()).thenReturn(logWatch).thenReturn(logWatchRegularMessage); ContainerLogWatch clw = new ContainerLogWatch( client, eventsPublisher, namespace, podname, container, podLogHandler, TIMEOUTS, LOG_LIMIT_BYTES); new Thread(clw).start(); // wait for logwatch close, it means that error message was processed CountDownLatch closeLatch = new CountDownLatch(1); logWatch.setLatch(closeLatch); closeLatch.await(1, TimeUnit.SECONDS); assertTrue(logWatch.isClosed); // wait for regular message messageHandleLatch.await(1, TimeUnit.SECONDS); // message was processed verify(podLogHandler).handle("message", container); assertTrue(logWatchRegularMessage.isClosed); // verify events were properly fired verify(eventsPublisher, times(2)).sendWatchLogStartedEvent(any(String.class)); verify(eventsPublisher, timeout(1000).times(2)).sendWatchLogStoppedEvent(any(String.class)); } private class LogWatchMock implements LogWatch { private InputStream inputStream; private CountDownLatch latch; private boolean isClosed = false; private LogWatchMock() {} public void setLatch(CountDownLatch latch) { this.latch = latch; } public void setInputStream(InputStream inputStream) { this.inputStream = inputStream; } @Override public InputStream getOutput() { return inputStream; } @Override public CompletionStage onClose() { return mock(CompletionStage.class); } @Override public void close() { isClosed = true; if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (latch != null) { latch.countDown(); } } } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/log/LogWatcherTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.workspace.shared.Constants.DEBUG_WORKSPACE_START; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.LogWatcher.DEFAULT_LOG_LIMIT_BYTES; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEvent; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class LogWatcherTest { private final String POD = "pod123"; private final Set PODNAMES = Collections.singleton(POD); private final String WORKSPACE_ID = "workspace123"; private final String NAMESPACE = "namespace123"; private final LogWatchTimeouts TIMEOUTS = new LogWatchTimeouts(100, 0, 0); private final long LIMIT_BYTES = 64; @Mock private PodLogHandler handler; @Mock private KubernetesClientFactory clientFactory; @Mock private RuntimeEventsPublisher eventsPublisher; @Mock private Executor executor; @BeforeMethod public void setUp() {} @Test public void executorIsNotCalledWhenContainerIsNull() throws InfrastructureException { // given LogWatcher logWatcher = new LogWatcher( clientFactory, eventsPublisher, WORKSPACE_ID, NAMESPACE, PODNAMES, executor, TIMEOUTS, LIMIT_BYTES); logWatcher.addLogHandler(handler); PodEvent podEvent = new PodEvent( POD, null, "somereallygoodreason", "someevenbettermessage", "123456789", "987654321"); // when logWatcher.handle(podEvent); // then verify(executor, times(0)).execute(any()); } @Test public void executorIsNotCalledWhenPodNameDontMatch() throws InfrastructureException { // given LogWatcher logWatcher = new LogWatcher( clientFactory, eventsPublisher, WORKSPACE_ID, NAMESPACE, PODNAMES, executor, TIMEOUTS, LIMIT_BYTES); logWatcher.addLogHandler(handler); PodEvent podEvent = new PodEvent( "someOtherPod", "container123", "Started", "someevenbettermessage", "123456789", "987654321"); // when logWatcher.handle(podEvent); // then verify(executor, times(0)).execute(any()); } @Test public void executorIsNotCalledWhenReasonIsNotStarted() throws InfrastructureException { // given LogWatcher logWatcher = new LogWatcher( clientFactory, eventsPublisher, WORKSPACE_ID, NAMESPACE, PODNAMES, executor, TIMEOUTS, LIMIT_BYTES); logWatcher.addLogHandler(handler); PodEvent podEvent = new PodEvent( POD, "container123", "NotStarted", "someevenbettermessage", "123456789", "987654321"); // when logWatcher.handle(podEvent); // then verify(executor, times(0)).execute(any()); } @Test public void executorIsCalledWhenAllIsSet() throws InfrastructureException { // given LogWatcher logWatcher = new LogWatcher( clientFactory, eventsPublisher, WORKSPACE_ID, NAMESPACE, PODNAMES, executor, TIMEOUTS, LIMIT_BYTES); logWatcher.addLogHandler(handler); PodEvent podEvent = new PodEvent( POD, "container123", "Started", "someevenbettermessage", "123456789", "987654321"); // when logWatcher.handle(podEvent); // then verify(executor, times(1)).execute(any()); } @Test public void executorIsCalledJustOnceWhenSameEventArriveAgain() throws InfrastructureException { // given LogWatcher logWatcher = new LogWatcher( clientFactory, eventsPublisher, WORKSPACE_ID, NAMESPACE, PODNAMES, executor, TIMEOUTS, LIMIT_BYTES); logWatcher.addLogHandler(handler); PodEvent podEvent = new PodEvent( POD, "container123", "Started", "someevenbettermessage", "123456789", "987654321"); // when logWatcher.handle(podEvent); logWatcher.handle(podEvent); // then verify(executor, times(1)).execute(any()); } @Test public void executorIsNotCalledAgainAfterCleanup() throws InfrastructureException { // given LogWatcher logWatcher = new LogWatcher( clientFactory, eventsPublisher, WORKSPACE_ID, NAMESPACE, PODNAMES, executor, TIMEOUTS, LIMIT_BYTES); logWatcher.addLogHandler(handler); PodEvent podEvent = new PodEvent( POD, "container123", "Started", "someevenbettermessage", "123456789", "987654321"); // when logWatcher.handle(podEvent); logWatcher.close(); logWatcher.handle(podEvent); // then verify(executor, times(1)).execute(any()); } @DataProvider public Object[][] logLimitData() { return new Object[][] { {null, DEFAULT_LOG_LIMIT_BYTES}, {emptyMap(), DEFAULT_LOG_LIMIT_BYTES}, {singletonMap("bla", "bol"), DEFAULT_LOG_LIMIT_BYTES}, { ImmutableMap.of(Constants.DEBUG_WORKSPACE_START_LOG_LIMIT_BYTES, "123", "other", "value"), 123 } }; } @Test(dataProvider = "logLimitData") public void testGettingLogLimitBytes(Map startOptions, long expectedLimit) { assertEquals(LogWatcher.getLogLimitBytes(startOptions), expectedLimit); } @DataProvider public Object[][] shouldWatchLogsData() { return new Object[][] { {null, false}, {emptyMap(), false}, {singletonMap("bla", "bol"), false}, {singletonMap(DEBUG_WORKSPACE_START, "blbost"), false}, {singletonMap(DEBUG_WORKSPACE_START, "false"), false}, {singletonMap(DEBUG_WORKSPACE_START, "true"), true}, {ImmutableMap.of(DEBUG_WORKSPACE_START, "true", "bla", "bol"), true}, {ImmutableMap.of(DEBUG_WORKSPACE_START, "tttt", "bla", "bol"), false}, }; } @Test(dataProvider = "shouldWatchLogsData") public void testShouldWatchLogsFromStartOptions( Map startOptions, boolean expected) { assertEquals(LogWatcher.shouldWatchLogs(startOptions), expected); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/log/PodLogToEventPublisherTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.testng.Assert.*; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class PodLogToEventPublisherTest { @Mock RuntimeEventsPublisher eventsPublisher; @Mock RuntimeIdentity identity; @Test public void sendMessageToPublisher() { PodLogHandler handler = new PodLogToEventPublisher(eventsPublisher, identity); handler.handle("message", "containerName"); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); verify(eventsPublisher) .sendRuntimeLogEvent( messageCaptor.capture(), any(String.class), any(RuntimeIdentity.class)); String capturedMessage = messageCaptor.getValue(); assertTrue(capturedMessage.contains("message")); assertTrue(capturedMessage.contains("containerName")); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/pvc/TestObjects.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.VolumeMount; import io.fabric8.kubernetes.api.model.VolumeMountBuilder; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; /** * Helps prepare objects for PVC strategy tests. * * @author Sergii Leshchenko */ public class TestObjects { public static InternalMachineConfigBuilder newMachineConfig() { return new InternalMachineConfigBuilder(); } public static TestPodBuilder newPod(String podName) { return new TestPodBuilder(podName); } public static TestContainerBuilder newContainer(String containerName) { return new TestContainerBuilder(containerName); } public static class TestPodBuilder { private String name; private List initContainers = new ArrayList<>(); private List containers = new ArrayList<>(); private List volumes = new ArrayList<>(); public TestPodBuilder(String podName) { this.name = podName; } public TestPodBuilder withInitContainers(Container... containers) { this.initContainers.addAll(Arrays.asList(containers)); return this; } public TestPodBuilder withContainers(Container... containers) { this.containers.addAll(Arrays.asList(containers)); return this; } public TestPodBuilder withPVCVolume(String volumeName, String pvcName) { this.volumes.add( new VolumeBuilder() .withName(volumeName) .withNewPersistentVolumeClaim() .withClaimName(pvcName) .endPersistentVolumeClaim() .build()); return this; } public Pod build() { return new PodBuilder() .withNewMetadata() .withName(name) .endMetadata() .withNewSpec() .withInitContainers(initContainers) .withContainers(containers) .withVolumes(volumes) .endSpec() .build(); } } public static class TestContainerBuilder { private String name; private List volumeMounts = new ArrayList<>(); public TestContainerBuilder(String containerName) { this.name = containerName; } public TestContainerBuilder withVolumeMount( String volumeName, String mountPath, String subpath) { this.volumeMounts.add( new VolumeMountBuilder() .withName(volumeName) .withMountPath(mountPath) .withSubPath(subpath) .build()); return this; } public Container build() { return new ContainerBuilder().withName(name).withVolumeMounts(volumeMounts).build(); } } public static class InternalMachineConfigBuilder { private Map volumes = new HashMap<>(); public InternalMachineConfigBuilder withVolume(String volumeName, String path) { volumes.put(volumeName, new VolumeImpl().withPath(path)); return this; } public InternalMachineConfig build() { return new InternalMachineConfig(new HashMap<>(), new HashMap<>(), new HashMap<>(), volumes); } } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/CertificateProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static java.util.Collections.emptyMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner.CA_CERT_FILE; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner.CERT_MOUNT_PATH; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner.CHE_SELF_SIGNED_CERT_SECRET_SUFFIX; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner.CHE_SELF_SIGNED_CERT_VOLUME; import static org.mockito.Mockito.lenient; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretVolumeSource; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeMount; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link CertificateProvisioner}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class CertificateProvisionerTest { private static final String WORKSPACE_ID = "workspace123"; private static final String EXPECTED_CERT_NAME = WORKSPACE_ID + CHE_SELF_SIGNED_CERT_SECRET_SUFFIX; public static final String CERT_CONTENT = "--BEGIN FJASBNDF END"; @Mock private RuntimeIdentity runtimeId; private CertificateProvisioner provisioner; private KubernetesEnvironment k8sEnv; @BeforeMethod public void setUp() { lenient().when(runtimeId.getWorkspaceId()).thenReturn(WORKSPACE_ID); provisioner = new CertificateProvisioner("--BEGIN FJASBNDF END"); k8sEnv = KubernetesEnvironment.builder().build(); } @Test public void shouldReturnFalseIfCertificateIsNotSpecifiedOnIsConfiguredInvoking() { // given provisioner = new CertificateProvisioner(""); // when boolean configured = provisioner.isConfigured(); // then assertFalse(configured); } @Test public void shouldReturnTrueIfCertificateIsSpecifiedOnIsConfiguredInvoking() { // given provisioner = new CertificateProvisioner(CERT_CONTENT); // when boolean configured = provisioner.isConfigured(); // then assertTrue(configured); } @Test public void shouldReturnCertPathFile() { // when String certPath = provisioner.getCertPath(); // then assertEquals(certPath, "/tmp/che/secret/ca.crt"); } @Test public void shouldAddSecretWithCertificateIntoEnvironment() throws Exception { // when provisioner.provision(k8sEnv, runtimeId); // then Map secrets = k8sEnv.getSecrets(); assertEquals(secrets.size(), 1); Secret certSecret = secrets.get(EXPECTED_CERT_NAME); assertNotNull(certSecret); assertEquals(certSecret.getMetadata().getName(), EXPECTED_CERT_NAME); assertEquals(certSecret.getStringData().get(CA_CERT_FILE), CERT_CONTENT); } @Test public void shouldAddVolumeAndVolumeMountsToPodsAndContainersInEnvironment() throws Exception { // given k8sEnv.addPod(createPod("pod")); k8sEnv.addPod(createPod("pod2")); // when provisioner.provision(k8sEnv, runtimeId); // then for (Pod pod : k8sEnv.getPodsCopy().values()) { verifyVolumeIsPresent(pod); for (Container container : pod.getSpec().getInitContainers()) { verifyVolumeMountIsPresent(container); } for (Container container : pod.getSpec().getContainers()) { verifyVolumeMountIsPresent(container); } } } @Test public void shouldNotAddVolumeAndVolumeMountsToPodsAndContainersInEnvironmentIfCertIsNotConfigured() throws Exception { // given provisioner = new CertificateProvisioner(""); k8sEnv.addPod(createPod("pod")); k8sEnv.addPod(createPod("pod2")); // when provisioner.provision(k8sEnv, runtimeId); // then for (Pod pod : k8sEnv.getPodsCopy().values()) { assertTrue(pod.getSpec().getVolumes().isEmpty()); for (Container container : pod.getSpec().getContainers()) { assertTrue(container.getVolumeMounts().isEmpty()); } } } @Test public void shouldNotAddVolumeButAddMountToInjectablePod() throws Exception { // given provisioner = new CertificateProvisioner(CERT_CONTENT); k8sEnv.addPod(createPod("pod")); k8sEnv.addInjectablePod("r", "i", createPod("injected")); // when provisioner.provision(k8sEnv, runtimeId); // then Pod pod = k8sEnv.getPodsCopy().get("pod"); Pod injected = k8sEnv.getInjectablePodsCopy().getOrDefault("r", emptyMap()).get("i"); assertNotNull(pod); assertNotNull(injected); verifyVolumeIsPresent(pod); pod.getSpec().getContainers().forEach(this::verifyVolumeMountIsPresent); assertTrue(injected.getSpec().getVolumes().isEmpty()); injected.getSpec().getContainers().forEach(this::verifyVolumeMountIsPresent); } private void verifyVolumeIsPresent(Pod pod) { List podVolumes = pod.getSpec().getVolumes(); assertEquals(podVolumes.size(), 1); Volume certVolume = podVolumes.get(0); assertEquals(certVolume.getName(), CHE_SELF_SIGNED_CERT_VOLUME); SecretVolumeSource volumeSecret = certVolume.getSecret(); assertNotNull(volumeSecret); assertEquals(volumeSecret.getSecretName(), EXPECTED_CERT_NAME); } private void verifyVolumeMountIsPresent(Container container) { List volumeMounts = container.getVolumeMounts(); assertEquals(volumeMounts.size(), 1); VolumeMount volumeMount = volumeMounts.get(0); assertEquals(volumeMount.getName(), CHE_SELF_SIGNED_CERT_VOLUME); assertTrue(volumeMount.getReadOnly()); assertEquals(volumeMount.getMountPath(), CERT_MOUNT_PATH); } private Pod createPod(String podName) { return new PodBuilder() .withNewMetadata() .withName(podName) .endMetadata() .withNewSpec() .withInitContainers(new ContainerBuilder().build()) .withContainers(new ContainerBuilder().build(), new ContainerBuilder().build()) .endSpec() .build(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/GatewayRouterProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static java.util.Collections.emptyMap; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVICE_NAME_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVICE_PORT_ATTRIBUTE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import java.util.Collections; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.GatewayRouteConfigGenerator; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.GatewayRouteConfigGeneratorFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.util.GatewayConfigmapLabels; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GatewayRouterProvisionerTest { private final String NAMESPACE = "nejmspejs"; @Mock private GatewayRouteConfigGeneratorFactory configGeneratorFactory; @Mock private GatewayRouteConfigGenerator gatewayRouteConfigGenerator; @Mock private GatewayConfigmapLabels gatewayConfigmapLabels; @Mock private KubernetesEnvironment env; @Mock private RuntimeIdentity identity; private GatewayRouterProvisioner gatewayRouterProvisioner; private final ServerConfigImpl serverConfigWithoutAttributes = new ServerConfigImpl("1234", "http", "/hello/there", emptyMap()); private final ServerConfigImpl serverConfig = new ServerConfigImpl( "1234", "http", "/hello/there", ImmutableMap.of(SERVICE_NAME_ATTRIBUTE, "serviceName", SERVICE_PORT_ATTRIBUTE, "1111")); @BeforeMethod public void setUp() { lenient().when(configGeneratorFactory.create()).thenReturn(gatewayRouteConfigGenerator); lenient().when(identity.getInfrastructureNamespace()).thenReturn(NAMESPACE); when(gatewayConfigmapLabels.isGatewayConfig(any(ConfigMap.class))).thenReturn(true); gatewayRouterProvisioner = new GatewayRouterProvisioner(configGeneratorFactory, gatewayConfigmapLabels); } @Test(expectedExceptions = InfrastructureException.class) public void testFailWhenNoServersInConfigmapAnnotations() throws InfrastructureException { // given ConfigMap gatewayRouteConfigMap = new ConfigMapBuilder().withNewMetadata().withName("route").endMetadata().build(); when(env.getConfigMaps()).thenReturn(Collections.singletonMap("route", gatewayRouteConfigMap)); // when gatewayRouterProvisioner.provision(env, identity); // then exception } @Test(expectedExceptions = InfrastructureException.class) public void testFailWhenMoreThanOneServerInConfigmapAnnotations() throws InfrastructureException { // given Map annotationsWith2Servers = new Annotations.Serializer() .server("s1", serverConfigWithoutAttributes) .server("s2", serverConfigWithoutAttributes) .annotations(); ConfigMap gatewayRouteConfigMap = new ConfigMapBuilder() .withNewMetadata() .withName("route") .withAnnotations(annotationsWith2Servers) .endMetadata() .build(); when(env.getConfigMaps()).thenReturn(Collections.singletonMap("route", gatewayRouteConfigMap)); // when gatewayRouterProvisioner.provision(env, identity); // then exception } @Test(expectedExceptions = InfrastructureException.class) public void testFailWhenServerHasNotAllNeededAttributes() throws InfrastructureException { // given Map annotationsWith2Servers = new Annotations.Serializer().server("s1", serverConfigWithoutAttributes).annotations(); ConfigMap gatewayRouteConfigMap = new ConfigMapBuilder() .withNewMetadata() .withName("route") .withAnnotations(annotationsWith2Servers) .endMetadata() .build(); when(env.getConfigMaps()).thenReturn(Collections.singletonMap("route", gatewayRouteConfigMap)); // when gatewayRouterProvisioner.provision(env, identity); // then exception } @Test public void testNoProvisionWhenNoMatchingLabels() throws InfrastructureException { // given Map annotationsWith2Servers = new Annotations.Serializer().server("s1", serverConfig).annotations(); ConfigMap gatewayRouteConfigMap = new ConfigMapBuilder() .withNewMetadata() .withName("route") .withAnnotations(annotationsWith2Servers) .endMetadata() .build(); when(env.getConfigMaps()).thenReturn(Collections.singletonMap("route", gatewayRouteConfigMap)); when(gatewayConfigmapLabels.isGatewayConfig(gatewayRouteConfigMap)).thenReturn(false); // when gatewayRouterProvisioner.provision(env, identity); // then verify(configGeneratorFactory, never()).create(); } @Test public void testProvision() throws InfrastructureException { // given Map annotationsWith2Servers = new Annotations.Serializer().server("s1", serverConfig).annotations(); ConfigMap gatewayRouteConfigMap = new ConfigMapBuilder() .withNewMetadata() .withName("route") .withAnnotations(annotationsWith2Servers) .endMetadata() .build(); when(env.getConfigMaps()).thenReturn(Collections.singletonMap("route", gatewayRouteConfigMap)); Map expectedData = Collections.singletonMap("data.yml", "this is for sure generated configuration"); when(gatewayRouteConfigGenerator.generate(NAMESPACE)).thenReturn(expectedData); // when gatewayRouterProvisioner.provision(env, identity); // then verify(configGeneratorFactory).create(); verify(gatewayRouteConfigGenerator).addRouteConfig("route", gatewayRouteConfigMap); verify(gatewayRouteConfigGenerator).generate(NAMESPACE); Map serverConfigsAfterProvisioning = new Annotations.Deserializer(gatewayRouteConfigMap.getMetadata().getAnnotations()) .servers(); assertEquals(serverConfigsAfterProvisioning.size(), 1); ServerConfigImpl server = serverConfigsAfterProvisioning.get( serverConfigsAfterProvisioning.keySet().iterator().next()); // verify that provisioner removes the internal attributes assertFalse(server.getAttributes().containsKey(SERVICE_NAME_ATTRIBUTE)); assertFalse(server.getAttributes().containsKey(SERVICE_PORT_ATTRIBUTE)); // verify that provisioner included the data info configmap Map actualData = gatewayRouteConfigMap.getData(); assertEquals(actualData, expectedData); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/GatewayTlsProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType; import org.eclipse.che.workspace.infrastructure.kubernetes.util.GatewayConfigmapLabels; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GatewayTlsProvisionerTest { public static final String WORKSPACE_ID = "workspace123"; @Mock private KubernetesEnvironment k8sEnv; @Mock private RuntimeIdentity runtimeIdentity; @Mock private GatewayConfigmapLabels gatewayConfigmapLabels; @Mock private TlsProvisionerProvider tlsProvisionerProvider; @Mock private TlsProvisioner nativeTlsProvisioner; private final ServerConfigImpl httpServer = new ServerConfigImpl("8080/tpc", "http", "/api", emptyMap()); private final ServerConfigImpl wsServer = new ServerConfigImpl("8080/tpc", "ws", "/ws", emptyMap()); private final Map annotations = singletonMap("annotation-key", "annotation-value"); private final String machine = "machine"; @BeforeMethod public void setUp() { lenient().when(gatewayConfigmapLabels.isGatewayConfig(any(ConfigMap.class))).thenReturn(true); when(tlsProvisionerProvider.get(eq(WorkspaceExposureType.NATIVE))) .thenReturn(nativeTlsProvisioner); } @Test(dataProvider = "tlsProvisionData") public void provisionTlsForGatewayRouteConfigmaps( ServerConfigImpl server, boolean tlsEnabled, String expectedProtocol) throws Exception { // given Map composedAnnotations = new HashMap<>(annotations); composedAnnotations.putAll( Annotations.newSerializer().server("server", server).machineName(machine).annotations()); ConfigMap routeConfigMap = new ConfigMapBuilder() .withNewMetadata() .withName("route") .withAnnotations(composedAnnotations) .endMetadata() .build(); GatewayTlsProvisioner gatewayTlsProvisioner = new GatewayTlsProvisioner<>(tlsEnabled, gatewayConfigmapLabels, tlsProvisionerProvider); lenient().when(k8sEnv.getConfigMaps()).thenReturn(singletonMap("route", routeConfigMap)); // when gatewayTlsProvisioner.provision(k8sEnv, runtimeIdentity); // then Map servers = Annotations.newDeserializer(routeConfigMap.getMetadata().getAnnotations()).servers(); assertEquals(servers.get("server").getProtocol(), expectedProtocol); } @DataProvider public Object[][] tlsProvisionData() { return new Object[][] { {httpServer, true, "https"}, {httpServer, false, "http"}, {wsServer, true, "wss"}, {wsServer, false, "ws"}, }; } @Test(expectedExceptions = InfrastructureException.class) public void throwExceptionWhenMultipleServersInGatewayRouteConfigAnnotations() throws InfrastructureException { // given Map composedAnnotations = new HashMap<>(annotations); composedAnnotations.putAll( Annotations.newSerializer() .server("server1", httpServer) .server("server2", wsServer) .machineName(machine) .annotations()); ConfigMap routeConfigMap = new ConfigMapBuilder() .withNewMetadata() .withName("route") .withAnnotations(composedAnnotations) .endMetadata() .build(); when(k8sEnv.getConfigMaps()).thenReturn(singletonMap("route", routeConfigMap)); GatewayTlsProvisioner gatewayTlsProvisioner = new GatewayTlsProvisioner<>(true, gatewayConfigmapLabels, tlsProvisionerProvider); // when gatewayTlsProvisioner.provision(k8sEnv, runtimeIdentity); // then exception } @Test public void nativeRoutesProvisioned() throws Exception { // given GatewayTlsProvisioner gatewayTlsProvisioner = new GatewayTlsProvisioner<>(true, gatewayConfigmapLabels, tlsProvisionerProvider); // when gatewayTlsProvisioner.provision(k8sEnv, runtimeIdentity); // then verify(nativeTlsProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/GitConfigProvisionerTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static java.lang.String.format; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.VolumeMount; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.api.workspace.server.model.impl.WarningImpl; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitConfigProvisionerTest { private KubernetesEnvironment k8sEnv; @Mock private RuntimeIdentity runtimeIdentity; @Mock private Pod pod; @Mock private PodSpec podSpec; @Mock private Container container; @Mock private PreferenceManager preferenceManager; @Mock private UserManager userManager; @Mock private VcsSslCertificateProvisioner vcsSslCertificateProvisioner; private GitConfigProvisioner gitConfigProvisioner; @BeforeMethod public void setup() { k8sEnv = KubernetesEnvironment.builder().build(); ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build(); when(pod.getMetadata()).thenReturn(podMeta); when(pod.getSpec()).thenReturn(podSpec); k8sEnv.addPod(pod); gitConfigProvisioner = new GitConfigProvisioner(preferenceManager, userManager, vcsSslCertificateProvisioner); Subject subject = new SubjectImpl(null, Collections.emptyList(), "id", null, false); EnvironmentContext environmentContext = new EnvironmentContext(); environmentContext.setSubject(subject); EnvironmentContext.setCurrent(environmentContext); } @Test public void testShouldDoNothingWhenGitPreferencesAndUserManagerAreEmpty() throws Exception { Map preferences = singletonMap("theia-user-preferences", "{}"); when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))).thenReturn(preferences); User user = mock(User.class); when(userManager.getById(eq("id"))).thenReturn(user); when(user.getName()).thenReturn(null); when(user.getEmail()).thenReturn(null); gitConfigProvisioner.provision(k8sEnv, runtimeIdentity); verifyNoMoreInteractions(runtimeIdentity); } @Test public void testShouldExpectWarningWhenPreferenceManagerThrowsServerException() throws Exception { when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))) .thenThrow(new ServerException("message")); gitConfigProvisioner.provision(k8sEnv, runtimeIdentity); verifyNoMoreInteractions(runtimeIdentity); List warnings = k8sEnv.getWarnings(); assertEquals(warnings.size(), 1); Warning actualWarning = warnings.get(0); String warnMsg = format(Warnings.EXCEPTION_IN_USER_MANAGEMENT_DURING_GIT_PROVISION_MESSAGE_FMT, "message"); Warning expectedWarning = new WarningImpl( Warnings.EXCEPTION_IN_USER_MANAGEMENT_DURING_GIT_PROVISION_WARNING_CODE, warnMsg); assertEquals(expectedWarning, actualWarning); } @Test public void testShouldExpectWarningWhenJsonObjectInPreferencesIsNotValid() throws Exception { Map preferences = singletonMap("theia-user-preferences", "{#$%}"); when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))).thenReturn(preferences); gitConfigProvisioner.provision(k8sEnv, runtimeIdentity); verifyNoMoreInteractions(runtimeIdentity); List warnings = k8sEnv.getWarnings(); assertEquals(warnings.size(), 1); Warning actualWarning = warnings.get(0); String warnMsg = format( Warnings.JSON_IS_NOT_A_VALID_REPRESENTATION_FOR_AN_OBJECT_OF_TYPE_MESSAGE_FMT, "java.io.EOFException: End of input at line 1 column 6 path $."); Warning expectedWarning = new WarningImpl( Warnings.JSON_IS_NOT_A_VALID_REPRESENTATION_FOR_AN_OBJECT_OF_TYPE_WARNING_CODE, warnMsg); assertEquals(expectedWarning, actualWarning); } @Test public void testShouldExpectWarningWhenUserManagerThrowsServerException() throws Exception { when(userManager.getById(eq("id"))).thenThrow(new ServerException("message")); gitConfigProvisioner.provision(k8sEnv, runtimeIdentity); verifyNoMoreInteractions(runtimeIdentity); List warnings = k8sEnv.getWarnings(); assertEquals(warnings.size(), 1); Warning actualWarning = warnings.get(0); String warnMsg = format(Warnings.EXCEPTION_IN_USER_MANAGEMENT_DURING_GIT_PROVISION_MESSAGE_FMT, "message"); Warning expectedWarning = new WarningImpl( Warnings.EXCEPTION_IN_USER_MANAGEMENT_DURING_GIT_PROVISION_WARNING_CODE, warnMsg); assertEquals(expectedWarning, actualWarning); } @Test public void testShouldCheckIfPodHasMountAndK8HasConfigMapForGitConfig() throws Exception { String json = "{\"git.user.name\":\"user\",\"git.user.email\":\"email\"}"; Map preferences = singletonMap("theia-user-preferences", json); when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))).thenReturn(preferences); ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build(); when(pod.getMetadata()).thenReturn(podMeta); when(pod.getSpec()).thenReturn(podSpec); when(podSpec.getContainers()).thenReturn(singletonList(container)); List volumeMounts = new ArrayList<>(); when(container.getVolumeMounts()).thenReturn(volumeMounts); k8sEnv.addPod(pod); gitConfigProvisioner.provision(k8sEnv, runtimeIdentity); assertEquals(volumeMounts.size(), 1); VolumeMount mount = volumeMounts.get(0); assertEquals(mount.getMountPath(), "/etc/gitconfig"); assertEquals(mount.getName(), "gitconfigvolume"); assertFalse(mount.getReadOnly()); assertEquals(mount.getSubPath(), "gitconfig"); assertEquals(k8sEnv.getConfigMaps().size(), 1); assertTrue(k8sEnv.getConfigMaps().containsKey("gitconfig")); ConfigMap configMap = k8sEnv.getConfigMaps().get("gitconfig"); assertEquals(configMap.getData().size(), 1); assertTrue(configMap.getData().containsKey("gitconfig")); String gitconfig = configMap.getData().get("gitconfig"); String expectedGitconfig = "[user]\n\tname = user\n\temail = email\n"; assertEquals(gitconfig, expectedGitconfig); } @DataProvider(name = "invalidEmailValues") public Object[][] invalidEmailValues() { return new Object[][] { {"{\"git.user.name\":\"user\",\"git.user.email\":{}}"}, {"{\"git.user.name\":\"user\",\"git.user.email\":true}"}, {"{\"git.user.name\":\"user\",\"git.user.email\":1}"}, {"{\"git.user.name\":\"user\",\"git.user.email\":[]}"}, {"{\"git.user.name\":\"user\",\"git.user.email\":null}"} }; } @Test(dataProvider = "invalidEmailValues") public void testShouldParseOnlyNameWhenEmailIsNotAString(String json) throws Exception { Map preferences = singletonMap("theia-user-preferences", json); when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))).thenReturn(preferences); ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build(); when(pod.getMetadata()).thenReturn(podMeta); when(pod.getSpec()).thenReturn(podSpec); when(podSpec.getContainers()).thenReturn(singletonList(container)); List volumeMounts = new ArrayList<>(); when(container.getVolumeMounts()).thenReturn(volumeMounts); k8sEnv.addPod(pod); gitConfigProvisioner.provision(k8sEnv, runtimeIdentity); assertEquals(volumeMounts.size(), 1); VolumeMount mount = volumeMounts.get(0); assertEquals(mount.getMountPath(), "/etc/gitconfig"); assertEquals(mount.getName(), "gitconfigvolume"); assertFalse(mount.getReadOnly()); assertEquals(mount.getSubPath(), "gitconfig"); assertEquals(k8sEnv.getConfigMaps().size(), 1); assertTrue(k8sEnv.getConfigMaps().containsKey("gitconfig")); ConfigMap configMap = k8sEnv.getConfigMaps().get("gitconfig"); assertEquals(configMap.getData().size(), 1); assertTrue(configMap.getData().containsKey("gitconfig")); String gitconfig = configMap.getData().get("gitconfig"); String expectedGitconfig = "[user]\n\tname = user\n"; assertEquals(gitconfig, expectedGitconfig); } @DataProvider(name = "invalidNameValues") public Object[][] invalidNameValues() { return new Object[][] { {"{\"git.user.name\":{},\"git.user.email\":\"email\"}"}, {"{\"git.user.name\":true,\"git.user.email\":\"email\"}"}, {"{\"git.user.name\":1,\"git.user.email\":\"email\"}"}, {"{\"git.user.name\":[],\"git.user.email\":\"email\"}"}, {"{\"git.user.name\":null,\"git.user.email\":\"email\"}"} }; } @Test(dataProvider = "invalidNameValues") public void testShouldParseOnlyEmailWhenNameIsNotAString(String json) throws Exception { Map preferences = singletonMap("theia-user-preferences", json); when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))).thenReturn(preferences); ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build(); when(pod.getMetadata()).thenReturn(podMeta); when(pod.getSpec()).thenReturn(podSpec); when(podSpec.getContainers()).thenReturn(singletonList(container)); List volumeMounts = new ArrayList<>(); when(container.getVolumeMounts()).thenReturn(volumeMounts); k8sEnv.addPod(pod); gitConfigProvisioner.provision(k8sEnv, runtimeIdentity); assertEquals(volumeMounts.size(), 1); VolumeMount mount = volumeMounts.get(0); assertEquals(mount.getMountPath(), "/etc/gitconfig"); assertEquals(mount.getName(), "gitconfigvolume"); assertFalse(mount.getReadOnly()); assertEquals(mount.getSubPath(), "gitconfig"); assertEquals(k8sEnv.getConfigMaps().size(), 1); assertTrue(k8sEnv.getConfigMaps().containsKey("gitconfig")); ConfigMap configMap = k8sEnv.getConfigMaps().get("gitconfig"); assertEquals(configMap.getData().size(), 1); assertTrue(configMap.getData().containsKey("gitconfig")); String gitconfig = configMap.getData().get("gitconfig"); String expectedGitconfig = "[user]\n\temail = email\n"; assertEquals(gitconfig, expectedGitconfig); } @Test public void testShouldCheckIfPodHasMountAndK8HasConfigMapForGitConfigForDifferentTypeOfPreference() throws Exception { String json = "{\"chePlugins.repositories\":{\"Plugins\":\"http://url/\",\"my\":\"https://url/plugins.json\"}, \"git.user.name\":\"user\",\"git.user.email\":\"email\"}"; Map preferences = singletonMap("theia-user-preferences", json); when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))).thenReturn(preferences); ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build(); when(pod.getMetadata()).thenReturn(podMeta); when(pod.getSpec()).thenReturn(podSpec); when(podSpec.getContainers()).thenReturn(singletonList(container)); List volumeMounts = new ArrayList<>(); when(container.getVolumeMounts()).thenReturn(volumeMounts); k8sEnv.addPod(pod); gitConfigProvisioner.provision(k8sEnv, runtimeIdentity); assertEquals(volumeMounts.size(), 1); VolumeMount mount = volumeMounts.get(0); assertEquals(mount.getMountPath(), "/etc/gitconfig"); assertEquals(mount.getName(), "gitconfigvolume"); assertFalse(mount.getReadOnly()); assertEquals(mount.getSubPath(), "gitconfig"); assertEquals(k8sEnv.getConfigMaps().size(), 1); assertTrue(k8sEnv.getConfigMaps().containsKey("gitconfig")); ConfigMap configMap = k8sEnv.getConfigMaps().get("gitconfig"); assertEquals(configMap.getData().size(), 1); assertTrue(configMap.getData().containsKey("gitconfig")); String gitconfig = configMap.getData().get("gitconfig"); String expectedGitconfig = "[user]\n\tname = user\n\temail = email\n"; assertEquals(gitconfig, expectedGitconfig); } @Test public void testShouldProvisionNameAndEmailFromUserManagerWhenUserPreferencesEmpty() throws Exception { String json = "{}"; Map preferences = singletonMap("theia-user-preferences", json); when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))).thenReturn(preferences); ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build(); when(pod.getMetadata()).thenReturn(podMeta); when(pod.getSpec()).thenReturn(podSpec); when(podSpec.getContainers()).thenReturn(singletonList(container)); User userMock = mock(User.class); when(userMock.getName()).thenReturn("userMockName"); when(userMock.getEmail()).thenReturn("userMockEmail"); when(userManager.getById(eq("id"))).thenReturn(userMock); List volumeMounts = new ArrayList<>(); when(container.getVolumeMounts()).thenReturn(volumeMounts); k8sEnv.addPod(pod); gitConfigProvisioner.provision(k8sEnv, runtimeIdentity); assertEquals(volumeMounts.size(), 1); VolumeMount mount = volumeMounts.get(0); assertEquals(mount.getMountPath(), "/etc/gitconfig"); assertEquals(mount.getName(), "gitconfigvolume"); assertFalse(mount.getReadOnly()); assertEquals(mount.getSubPath(), "gitconfig"); assertEquals(k8sEnv.getConfigMaps().size(), 1); assertTrue(k8sEnv.getConfigMaps().containsKey("gitconfig")); ConfigMap configMap = k8sEnv.getConfigMaps().get("gitconfig"); assertEquals(configMap.getData().size(), 1); assertTrue(configMap.getData().containsKey("gitconfig")); String gitconfig = configMap.getData().get("gitconfig"); String expectedGitconfig = "[user]\n\tname = userMockName\n\temail = userMockEmail\n"; assertEquals(gitconfig, expectedGitconfig); } @Test public void testShouldProvisionConfigForHttpsServer() throws Exception { when(vcsSslCertificateProvisioner.isConfigured()).thenReturn(true); when(vcsSslCertificateProvisioner.getGitServerHost()).thenReturn("https://localhost"); when(vcsSslCertificateProvisioner.getCertPath()).thenReturn("/some/path"); ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build(); when(pod.getMetadata()).thenReturn(podMeta); when(pod.getSpec()).thenReturn(podSpec); when(podSpec.getContainers()).thenReturn(singletonList(container)); User userMock = mock(User.class); when(userMock.getName()).thenReturn("userMockName"); when(userMock.getEmail()).thenReturn("userMockEmail"); when(userManager.getById(eq("id"))).thenReturn(userMock); List volumeMounts = new ArrayList<>(); when(container.getVolumeMounts()).thenReturn(volumeMounts); k8sEnv.addPod(pod); gitConfigProvisioner.provision(k8sEnv, runtimeIdentity); assertEquals(volumeMounts.size(), 1); VolumeMount mount = volumeMounts.get(0); assertEquals(mount.getMountPath(), "/etc/gitconfig"); assertEquals(mount.getName(), "gitconfigvolume"); assertFalse(mount.getReadOnly()); assertEquals(mount.getSubPath(), "gitconfig"); assertEquals(k8sEnv.getConfigMaps().size(), 1); assertTrue(k8sEnv.getConfigMaps().containsKey("gitconfig")); ConfigMap configMap = k8sEnv.getConfigMaps().get("gitconfig"); assertEquals(configMap.getData().size(), 1); assertTrue(configMap.getData().containsKey("gitconfig")); String gitconfig = configMap.getData().get("gitconfig"); String expectedGitconfig = "[user]\n\tname = userMockName\n\temail = userMockEmail\n[http \"https://localhost\"]\n\tsslCAInfo = /some/path"; assertEquals(gitconfig, expectedGitconfig); } @Test public void shouldNotProvisionVolumeButShouldMountInInjectablePods() throws Exception { // given Map preferences = singletonMap( "theia-user-preferences", "{\"git.user.name\":\"user\",\"git.user.email\":\"email\"}"); when(preferenceManager.find(eq("id"), eq("theia-user-preferences"))).thenReturn(preferences); Pod pod = new PodBuilder() .withNewMetadata() .withName("wkspc") .and() .withNewSpec() .withContainers(new ContainerBuilder().withImage("image").build()) .and() .build(); // we want to replace everything in the env that the setup() put there, so let's just re-init. k8sEnv = KubernetesEnvironment.builder().build(); k8sEnv.addPod(pod); Pod injectedPod = new PodBuilder() .withNewMetadata() .withName("injected") .and() .withNewSpec() .withContainers(new ContainerBuilder().withImage("image").build()) .and() .build(); k8sEnv.addInjectablePod("r", "i", injectedPod); // when gitConfigProvisioner.provision(k8sEnv, runtimeIdentity); // then assertEquals(pod.getSpec().getVolumes().size(), 1); assertEquals(injectedPod.getSpec().getVolumes().size(), 0); Container podContainer = pod.getSpec().getContainers().get(0); Container injectedPodContainer = injectedPod.getSpec().getContainers().get(0); assertEquals(podContainer.getVolumeMounts().size(), 1); assertEquals(injectedPodContainer.getVolumeMounts().size(), 1); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/ImagePullSecretProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.ImagePullSecretProvisioner.SECRET_NAME_SUFFIX; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import io.fabric8.kubernetes.api.model.LocalObjectReference; import io.fabric8.kubernetes.api.model.LocalObjectReferenceBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.Secret; import java.util.Base64; import java.util.Collections; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.DockerAuthConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.DockerAuthConfigs; import org.eclipse.che.workspace.infrastructure.kubernetes.docker.auth.UserSpecificDockerRegistryCredentialsProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link ImagePullSecretProvisioner}. * * @author David Festal */ @Listeners(MockitoTestNGListener.class) public class ImagePullSecretProvisionerTest { private static final String WORKSPACE_ID = "workspace123"; private KubernetesEnvironment k8sEnv; @Mock private RuntimeIdentity runtimeIdentity; @Mock private UserSpecificDockerRegistryCredentialsProvider credentialsProvider; @Mock private DockerAuthConfigs dockerAuthConfigs; @Mock private Pod pod; @Mock private PodSpec podSpec; private LocalObjectReference existingImagePullSecretRef = new LocalObjectReferenceBuilder().withName("existing").build(); private LocalObjectReference newImagePullSecretRef = new LocalObjectReferenceBuilder().withName(WORKSPACE_ID + SECRET_NAME_SUFFIX).build(); private ImagePullSecretProvisioner imagePullSecretProvisioner; @BeforeMethod public void setup() { lenient().when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); k8sEnv = KubernetesEnvironment.builder().build(); ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build(); when(pod.getMetadata()).thenReturn(podMeta); lenient().when(pod.getSpec()).thenReturn(podSpec); lenient() .when(podSpec.getImagePullSecrets()) .thenReturn(ImmutableList.of(existingImagePullSecretRef)); k8sEnv.addPod(pod); when(credentialsProvider.getCredentials()).thenReturn(dockerAuthConfigs); imagePullSecretProvisioner = new ImagePullSecretProvisioner(credentialsProvider); } @Test public void doNotDoAnythingIfNoPrivateRegistries() throws Exception { when(dockerAuthConfigs.getConfigs()).thenReturn(Collections.emptyMap()); imagePullSecretProvisioner.provision(k8sEnv, runtimeIdentity); assertTrue(k8sEnv.getSecrets().isEmpty()); verifyNoMoreInteractions(podSpec); } @Test public void addSecretAndReferenceInPod() throws Exception { when(dockerAuthConfigs.getConfigs()) .thenReturn( ImmutableMap.of( "reg1", new TestDockerAuthConfig().withUsername("username1").withPassword("password1"), "reg2", new TestDockerAuthConfig().withUsername("username2").withPassword("password2"))); imagePullSecretProvisioner.provision(k8sEnv, runtimeIdentity); verify(podSpec) .setImagePullSecrets(ImmutableList.of(newImagePullSecretRef, existingImagePullSecretRef)); Secret secret = k8sEnv.getSecrets().get(WORKSPACE_ID + SECRET_NAME_SUFFIX); assertNotNull(secret); assertEquals(secret.getType(), "kubernetes.io/dockercfg"); String dockerCfgData = secret.getData().get(".dockercfg"); assertNotNull(dockerCfgData); Gson gson = new Gson(); assertEquals( gson.toJson( gson.fromJson(new String(Base64.getDecoder().decode(dockerCfgData)), Map.class)), gson.toJson( gson.fromJson( "" + "{ \"https://reg1\": { \"username\": \"username1\", \"password\": \"password1\", \"email\": \"email@email\", \"auth\": \"" + buildAuth("username1", "password1") + "\" }" + ", \"https://reg2\": { \"username\": \"username2\", \"password\": \"password2\", \"email\": \"email@email\", \"auth\": \"" + buildAuth("username2", "password2") + "\" }" + "}", Map.class))); } private static class TestDockerAuthConfig implements DockerAuthConfig { private String username; private String password; @Override public String getUsername() { return username; } @Override public void setUsername(String username) { this.username = username; } @Override public DockerAuthConfig withUsername(String username) { setUsername(username); return this; } @Override public String getPassword() { return password; } @Override public void setPassword(String password) { this.password = password; } @Override public DockerAuthConfig withPassword(String password) { setPassword(password); return this; } } private String buildAuth(String username, String password) { return Base64.getEncoder().encodeToString((username + ':' + password).getBytes()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/IngressTlsProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.IngressTlsProvisioner.TLS_SECRET_TYPE; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerIngressBuilder; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link IngressTlsProvisioner}. * * @author Ilya Buziuk * @author Sergii Leshchenko * @author Guy Daich */ @Listeners(MockitoTestNGListener.class) public class IngressTlsProvisionerTest { public static final String WORKSPACE_ID = "workspace123"; @Mock private KubernetesEnvironment k8sEnv; @Mock private RuntimeIdentity runtimeIdentity; private final ServerConfigImpl httpServer = new ServerConfigImpl("8080/tpc", "http", "/api", emptyMap()); private final ServerConfigImpl wsServer = new ServerConfigImpl("8080/tpc", "ws", "/ws", emptyMap()); private final Map servers = ImmutableMap.of("http-server", httpServer, "ws-server", wsServer); private final Map annotations = singletonMap("annotation-key", "annotation-value"); private final String machine = "machine"; private final String name = "IngressName"; private final String serviceName = "ServiceName"; private final String servicePortName = "server-port"; private final Integer servicePort = 7777; private final String host = "server-host"; private final ExternalServerIngressBuilder externalServerIngressBuilder = new ExternalServerIngressBuilder(); private final Ingress ingress = externalServerIngressBuilder .withHost(host) .withAnnotations(annotations) .withMachineName(machine) .withName(name) .withServers(servers) .withServiceName(serviceName) .withServicePortName(servicePortName) .withServicePort(servicePort) .build(); @BeforeMethod public void setUp() { lenient().when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); } @Test public void doNothingWhenTlsDisabled() throws Exception { // given IngressTlsProvisioner ingressTlsProvisioner = new IngressTlsProvisioner(false, "", "", ""); // when ingressTlsProvisioner.provision(k8sEnv, runtimeIdentity); // then verify(k8sEnv, never()).getIngresses(); } @Test public void provisionTlsForIngressesWhenTlsEnabledAndSecretProvided() throws Exception { // given IngressTlsProvisioner ingressTlsProvisioner = new IngressTlsProvisioner(true, "secretname", "", ""); Map ingresses = new HashMap<>(); ingresses.put("ingress", ingress); when(k8sEnv.getIngresses()).thenReturn(ingresses); // when ingressTlsProvisioner.provision(k8sEnv, runtimeIdentity); // then assertEquals(ingress.getSpec().getTls().size(), 1); assertEquals(ingress.getSpec().getTls().get(0).getHosts().size(), 1); assertEquals(ingress.getSpec().getTls().get(0).getHosts().get(0), host); assertEquals(ingress.getSpec().getTls().get(0).getSecretName(), "secretname"); verifyServersTLS(ingress.getMetadata().getAnnotations()); } @Test public void provisionTlsForIngressesWhenTlsEnabledAndSecretEmpty() throws Exception { // given IngressTlsProvisioner ingressTlsProvisioner = new IngressTlsProvisioner(true, "", "", ""); Map ingresses = new HashMap<>(); ingresses.put("ingress", ingress); when(k8sEnv.getIngresses()).thenReturn(ingresses); // when ingressTlsProvisioner.provision(k8sEnv, runtimeIdentity); // then assertEquals(ingress.getSpec().getTls().size(), 1); assertEquals(ingress.getSpec().getTls().get(0).getHosts().size(), 1); assertEquals(ingress.getSpec().getTls().get(0).getHosts().get(0), host); assertNull(ingress.getSpec().getTls().get(0).getSecretName()); verifyServersTLS(ingress.getMetadata().getAnnotations()); } @Test public void provisionTlsForIngressesWhenTlsEnabledAndSecretNull() throws Exception { // given IngressTlsProvisioner ingressTlsProvisioner = new IngressTlsProvisioner(true, null, "", ""); Map ingresses = new HashMap<>(); ingresses.put("ingress", ingress); when(k8sEnv.getIngresses()).thenReturn(ingresses); // when ingressTlsProvisioner.provision(k8sEnv, runtimeIdentity); // then assertEquals(ingress.getSpec().getTls().size(), 1); assertEquals(ingress.getSpec().getTls().get(0).getHosts().size(), 1); assertEquals(ingress.getSpec().getTls().get(0).getHosts().get(0), host); assertEquals(ingress.getSpec().getTls().get(0).getSecretName(), null); verifyServersTLS(ingress.getMetadata().getAnnotations()); } @Test public void provisionTlsSecretWhenTlsCertAndKeyAreSpecified() throws Exception { // given IngressTlsProvisioner ingressTlsProvisioner = new IngressTlsProvisioner(true, "ws-tls-secret", "cert", "key"); Map secrets = new HashMap<>(); when(k8sEnv.getSecrets()).thenReturn(secrets); // when ingressTlsProvisioner.provision(k8sEnv, runtimeIdentity); // then assertEquals(k8sEnv.getSecrets().size(), 1); Secret tlsSecret = k8sEnv.getSecrets().get(runtimeIdentity.getWorkspaceId() + "-ws-tls-secret"); assertNotNull(tlsSecret); assertEquals(tlsSecret.getStringData().get("tls.crt"), "cert"); assertEquals(tlsSecret.getStringData().get("tls.key"), "key"); assertEquals(tlsSecret.getType(), TLS_SECRET_TYPE); verifyServersTLS(ingress.getMetadata().getAnnotations()); } @Test( expectedExceptions = ConfigurationException.class, expectedExceptionsMessageRegExp = "None or both of `che.infra.kubernetes.tls_cert` and " + "`che.infra.kubernetes.tls_key` must be configured with non-null value\\.") public void shouldThrowAnExceptionIfOnlyOneIfTlsCertOrKeyIsConfigured() { // given new IngressTlsProvisioner(true, "secret", "test", ""); } private void verifyServersTLS(Map annotations) { Map servers = Annotations.newDeserializer(annotations).servers(); assertEquals(servers.get("http-server").getProtocol(), "https"); assertEquals(servers.get("ws-server").getProtocol(), "wss"); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/KubernetesPreviewUrlCommandProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServiceSpec; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPath; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressRuleValue; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; import io.fabric8.kubernetes.api.model.networking.v1.IngressServiceBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressSpec; import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPort; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.PreviewUrlImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesIngresses; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesServices; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class KubernetesPreviewUrlCommandProvisionerTest { private KubernetesPreviewUrlCommandProvisioner previewUrlCommandProvisioner; @Mock private KubernetesEnvironment mockEnvironment; @Mock private KubernetesNamespace mockNamespace; @Mock private KubernetesServices mockServices; @Mock private KubernetesIngresses mockIngresses; @BeforeMethod public void setUp() { previewUrlCommandProvisioner = new KubernetesPreviewUrlCommandProvisioner(); } @Test public void shouldDoNothingWhenGetCommandsIsNull() throws InfrastructureException { Mockito.when(mockEnvironment.getCommands()).thenReturn(null); previewUrlCommandProvisioner.provision(mockEnvironment, mockNamespace); } @Test public void shouldDoNothingWhenNoCommandsDefined() throws InfrastructureException { Mockito.when(mockEnvironment.getCommands()).thenReturn(Collections.emptyList()); Mockito.when(mockNamespace.ingresses()).thenReturn(mockIngresses); Mockito.when(mockNamespace.services()).thenReturn(mockServices); previewUrlCommandProvisioner.provision(mockEnvironment, mockNamespace); } @Test public void shouldDoNothingWhenCommandsWithoutPreviewUrlDefined() throws InfrastructureException { List commands = Arrays.asList(new CommandImpl("a", "a", "a"), new CommandImpl("b", "b", "b")); KubernetesEnvironment env = KubernetesEnvironment.builder().setCommands(new ArrayList<>(commands)).build(); Mockito.when(mockNamespace.ingresses()).thenReturn(mockIngresses); Mockito.when(mockNamespace.services()).thenReturn(mockServices); previewUrlCommandProvisioner.provision(env, mockNamespace); assertTrue(commands.containsAll(env.getCommands())); assertTrue(env.getCommands().containsAll(commands)); assertTrue(env.getWarnings().isEmpty()); } @Test public void shouldDoNothingWhenCantFindServiceForPreviewurl() throws InfrastructureException { List commands = Collections.singletonList( new CommandImpl("a", "a", "a", new PreviewUrlImpl(8080, null), Collections.emptyMap())); KubernetesEnvironment env = KubernetesEnvironment.builder().setCommands(new ArrayList<>(commands)).build(); Mockito.when(mockNamespace.ingresses()).thenReturn(mockIngresses); Mockito.when(mockNamespace.services()).thenReturn(mockServices); Mockito.when(mockServices.get()).thenReturn(Collections.emptyList()); previewUrlCommandProvisioner.provision(env, mockNamespace); assertTrue(commands.containsAll(env.getCommands())); assertTrue(env.getCommands().containsAll(commands)); assertEquals( env.getWarnings().get(0).getCode(), Warnings.NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL); } @Test public void shouldDoNothingWhenCantFindIngressForPreviewUrl() throws InfrastructureException { int port = 8080; List commands = Collections.singletonList( new CommandImpl("a", "a", "a", new PreviewUrlImpl(port, null), Collections.emptyMap())); KubernetesEnvironment env = KubernetesEnvironment.builder().setCommands(new ArrayList<>(commands)).build(); Mockito.when(mockNamespace.services()).thenReturn(mockServices); Service service = new Service(); ServiceSpec spec = new ServiceSpec(); spec.setPorts( Collections.singletonList( new ServicePort(null, "a", null, port, "TCP", new IntOrString(port)))); service.setSpec(spec); Mockito.when(mockServices.get()).thenReturn(Collections.singletonList(service)); Mockito.when(mockNamespace.ingresses()).thenReturn(mockIngresses); Mockito.when(mockIngresses.get()).thenReturn(Collections.emptyList()); previewUrlCommandProvisioner.provision(env, mockNamespace); assertTrue(commands.containsAll(env.getCommands())); assertTrue(env.getCommands().containsAll(commands)); assertEquals( env.getWarnings().get(0).getCode(), Warnings.NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL); } @Test public void shouldUpdateCommandWhenServiceAndIngressFound() throws InfrastructureException { final int PORT = 8080; final String SERVICE_PORT_NAME = "service-" + PORT; List commands = Collections.singletonList( new CommandImpl("a", "a", "a", new PreviewUrlImpl(PORT, null), Collections.emptyMap())); KubernetesEnvironment env = KubernetesEnvironment.builder().setCommands(new ArrayList<>(commands)).build(); Mockito.when(mockNamespace.services()).thenReturn(mockServices); Service service = new Service(); ObjectMeta metadata = new ObjectMeta(); metadata.setName("servicename"); service.setMetadata(metadata); ServiceSpec spec = new ServiceSpec(); spec.setPorts( Collections.singletonList( new ServicePort(null, SERVICE_PORT_NAME, null, PORT, "TCP", new IntOrString(PORT)))); service.setSpec(spec); Mockito.when(mockServices.get()).thenReturn(Collections.singletonList(service)); Ingress ingress = new Ingress(); IngressSpec ingressSpec = new IngressSpec(); IngressRule rule = new IngressRule( "testhost", new HTTPIngressRuleValue( Collections.singletonList( new HTTPIngressPath( new IngressBackend( null, new IngressServiceBackend( "servicename", new ServiceBackendPort(SERVICE_PORT_NAME, PORT))), null, null)))); ingressSpec.setRules(Collections.singletonList(rule)); ingress.setSpec(ingressSpec); Mockito.when(mockNamespace.ingresses()).thenReturn(mockIngresses); Mockito.when(mockIngresses.get()).thenReturn(Collections.singletonList(ingress)); previewUrlCommandProvisioner.provision(env, mockNamespace); assertTrue(env.getCommands().get(0).getAttributes().containsKey("previewUrl")); assertEquals(env.getCommands().get(0).getAttributes().get("previewUrl"), "testhost"); assertTrue(env.getWarnings().isEmpty()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/SecurityContextProvisionerTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.PodSecurityContext; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Test {@link SecurityContextProvisioner}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class SecurityContextProvisionerTest { @Mock private RuntimeIdentity runtimeIdentity; private KubernetesEnvironment kubernetesEnvironment; private Pod pod; private SecurityContextProvisioner securityContextProvisioner; @BeforeMethod public void setUp() throws Exception { pod = new PodBuilder().withNewSpec().endSpec().build(); kubernetesEnvironment = KubernetesEnvironment.builder().setPods(ImmutableMap.of("pod", pod)).build(); } @Test public void shouldNotProvisionSecurityContextIfItIsNotConfigured() throws Exception { // given securityContextProvisioner = new SecurityContextProvisioner(null, null); // when securityContextProvisioner.provision(kubernetesEnvironment, runtimeIdentity); // then assertNull(pod.getSpec().getSecurityContext()); } @Test public void shouldProvisionSecurityContextIfItIsConfigured() throws Exception { // given securityContextProvisioner = new SecurityContextProvisioner("1", "2"); // when securityContextProvisioner.provision(kubernetesEnvironment, runtimeIdentity); // then PodSecurityContext securityContext = pod.getSpec().getSecurityContext(); assertNotNull(securityContext); assertEquals(securityContext.getRunAsUser(), new Long(1)); assertEquals(securityContext.getFsGroup(), new Long(2)); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/SshKeySecretProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableList; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.Secret; import java.util.ArrayList; import java.util.Base64; import java.util.Collections; import java.util.Map; import java.util.UUID; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.ssh.server.SshManager; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link SshKeysProvisioner}. * * @author Vitalii Parfonov * @author Vlad Zhukovskyi */ @Listeners(MockitoTestNGListener.class) public class SshKeySecretProvisionerTest { private KubernetesEnvironment k8sEnv; @Mock private RuntimeIdentity runtimeIdentity; @Mock private SshManager sshManager; @Mock private Pod pod; @Mock private PodSpec podSpec; @Mock private Container container; @Mock RuntimeEventsPublisher runtimeEventsPublisher; private final String someUser = "someuser"; private SshKeysProvisioner sshKeysProvisioner; @BeforeMethod public void setup() { lenient().when(runtimeIdentity.getOwnerId()).thenReturn(someUser); lenient().when(runtimeIdentity.getWorkspaceId()).thenReturn("wksp"); k8sEnv = KubernetesEnvironment.builder().build(); ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build(); lenient().when(pod.getMetadata()).thenReturn(podMeta); lenient().when(pod.getSpec()).thenReturn(podSpec); lenient().when(podSpec.getVolumes()).thenReturn(new ArrayList<>()); lenient().when(podSpec.getContainers()).thenReturn(Collections.singletonList(container)); lenient().when(container.getVolumeMounts()).thenReturn(new ArrayList<>()); k8sEnv.addPod(pod); sshKeysProvisioner = new SshKeysProvisioner(sshManager, runtimeEventsPublisher); } @Test public void generateSshKeyIfNoSshKeys() throws Exception { when(sshManager.getPairs(someUser, "vcs")).thenReturn(Collections.emptyList()); when(sshManager.generatePair(eq(someUser), eq("vcs"), anyString())) .thenReturn( new SshPairImpl( someUser, "vcs", "default-" + UUID.randomUUID().toString(), "public", "private")); sshKeysProvisioner.provision(k8sEnv, runtimeIdentity); assertEquals(k8sEnv.getSecrets().size(), 1); } @Test(dataProvider = "invalidNames") public void shouldNotMountKeysWithInvalidKeyNames(String invalidName) throws Exception { // given when(sshManager.getPairs(someUser, "vcs")) .thenReturn( ImmutableList.of( new SshPairImpl(someUser, "vcs", invalidName, "public", "private"), new SshPairImpl(someUser, "vcs", "gothub.mk", "public", "private"), new SshPairImpl(someUser, "vcs", "boombaket.barabaket.com", "public", "private"))); // when sshKeysProvisioner.provision(k8sEnv, runtimeIdentity); // then Secret secret = k8sEnv.getSecrets().get("wksp-sshprivatekeys"); assertNotNull(secret); assertEquals(secret.getData().size(), 2); assertTrue(secret.getData().containsKey("gothub.mk")); assertTrue(secret.getData().containsKey("boombaket.barabaket.com")); assertFalse(secret.getData().containsKey(invalidName)); verify(runtimeEventsPublisher, times(1)) .sendRuntimeLogEvent(anyString(), anyString(), eq(runtimeIdentity)); } @Test(dataProvider = "invalidNames") public void shouldIgnoreInvalidKeyNames(String invalidName) throws Exception { assertFalse( sshKeysProvisioner.isValidSshKeyPair( new SshPairImpl(someUser, "vcs", invalidName, "public", "private"))); } @Test(dataProvider = "validNames") public void shouldAcceptValidKeyNames(String validName) throws Exception { assertTrue( sshKeysProvisioner.isValidSshKeyPair( new SshPairImpl(someUser, "vcs", validName, "public", "private"))); } @DataProvider public static Object[][] invalidNames() { return new Object[][] { new Object[] {"https://foo.bar"}, new Object[] {"_fef_123-ah_*zz**"}, new Object[] {"_fef_123-ah_z.z"}, new Object[] {"a-b#-hello"}, new Object[] {"--ab--"} }; } @DataProvider public static Object[][] validNames() { return new Object[][] { new Object[] {"foo.bar"}, new Object[] {"fef-123-ahzz"}, new Object[] {"fef0123-ahz.z"}, new Object[] {"a-b.hello"}, new Object[] {"a--a----------b"} }; } @Test public void addSshKeysConfigInPod() throws Exception { String keyName1 = UUID.randomUUID().toString(); String keyName2 = "default-" + UUID.randomUUID().toString(); String keyName3 = "github.com"; String keyName4 = UUID.randomUUID().toString(); lenient() .when(sshManager.getPairs(someUser, "vcs")) .thenReturn( ImmutableList.of( new SshPairImpl(someUser, "vcs", keyName1, "public", "private"), new SshPairImpl(someUser, "vcs", keyName2, "public", "private"), new SshPairImpl(someUser, "vcs", keyName3, "public", "private"))); lenient() .when(sshManager.getPairs(someUser, "internal")) .thenReturn( ImmutableList.of(new SshPairImpl(someUser, "internal", keyName4, "public", "private"))); sshKeysProvisioner.provision(k8sEnv, runtimeIdentity); verify(podSpec, times(2)).getVolumes(); verify(podSpec, times(2)).getContainers(); Secret secret = k8sEnv.getSecrets().get("wksp-sshprivatekeys"); assertNotNull(secret); assertEquals(secret.getType(), "opaque"); String key1 = secret.getData().get(keyName1); assertNotNull(key1); assertEquals("private", new String(Base64.getDecoder().decode(key1))); String key2 = secret.getData().get(keyName2); assertNotNull(key2); assertEquals("private", new String(Base64.getDecoder().decode(key2))); String key3 = secret.getData().get(keyName3); assertNotNull(key3); assertEquals("private", new String(Base64.getDecoder().decode(key3))); String key4 = secret.getData().get(keyName3); assertNotNull(key3); assertEquals("private", new String(Base64.getDecoder().decode(key4))); Map configMaps = k8sEnv.getConfigMaps(); assertNotNull(configMaps); assertTrue(configMaps.containsKey("sshconfigmap")); ConfigMap sshConfigMap = configMaps.get("sshconfigmap"); assertNotNull(sshConfigMap); Map mapData = sshConfigMap.getData(); assertNotNull(mapData); assertTrue(mapData.containsKey("ssh_config")); String sshConfig = mapData.get("ssh_config"); assertTrue(sshConfig.contains("host " + keyName1)); assertTrue(sshConfig.contains("IdentityFile " + "/etc/ssh/private/" + keyName1)); assertTrue(sshConfig.contains("host *")); assertTrue(sshConfig.contains("IdentityFile " + "/etc/ssh/private/" + keyName2)); assertTrue(sshConfig.contains("host github.com")); assertTrue(sshConfig.contains("IdentityFile /etc/ssh/private/github.com")); } @Test public void shouldNotProvisionVolumeButShouldMountInInjectablePods() throws Exception { // given String keyName1 = UUID.randomUUID().toString(); String keyName2 = "default-" + UUID.randomUUID().toString(); String keyName3 = "github.com"; when(sshManager.getPairs(someUser, "vcs")) .thenReturn( ImmutableList.of( new SshPairImpl(someUser, "vcs", keyName1, "public", "private"), new SshPairImpl(someUser, "vcs", keyName2, "public", "private"), new SshPairImpl(someUser, "vcs", keyName3, "public", "private"))); Pod pod = new PodBuilder() .withNewMetadata() .withName("wkspc") .and() .withNewSpec() .withContainers(new ContainerBuilder().withImage("image").build()) .and() .build(); // we want to replace everything in the env that the setup() put there, so let's just re-init. k8sEnv = KubernetesEnvironment.builder().build(); k8sEnv.addPod(pod); Pod injectedPod = new PodBuilder() .withNewMetadata() .withName("injected") .and() .withNewSpec() .withContainers(new ContainerBuilder().withImage("image").build()) .and() .build(); k8sEnv.addInjectablePod("r", "i", injectedPod); // when sshKeysProvisioner.provision(k8sEnv, runtimeIdentity); // then assertEquals(pod.getSpec().getVolumes().size(), 2); assertEquals(injectedPod.getSpec().getVolumes().size(), 0); Container podContainer = pod.getSpec().getContainers().get(0); Container injectedPodContainer = injectedPod.getSpec().getContainers().get(0); assertEquals(podContainer.getVolumeMounts().size(), 2); assertEquals(injectedPodContainer.getVolumeMounts().size(), 2); } @Test public void addInternalSshKeysConfigInPod() throws Exception { String keyName = UUID.randomUUID().toString(); lenient() .when(sshManager.getPairs(someUser, "internal")) .thenReturn( ImmutableList.of(new SshPairImpl(someUser, "internal", keyName, "public", "private"))); // should exist at least one 'vcs' key by design when(sshManager.generatePair(eq(someUser), eq("vcs"), anyString())) .thenReturn( new SshPairImpl( someUser, "vcs", "default-" + UUID.randomUUID().toString(), "public", "private")); sshKeysProvisioner.provision(k8sEnv, runtimeIdentity); verify(podSpec, times(2)).getVolumes(); verify(podSpec, times(2)).getContainers(); Secret secret = k8sEnv.getSecrets().get("wksp-sshprivatekeys"); assertNotNull(secret); assertEquals(secret.getType(), "opaque"); String key = secret.getData().get(keyName); assertNotNull(key); assertEquals("private", new String(Base64.getDecoder().decode(key))); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/UniqueNamesProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotEquals; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.EnvFromSource; import io.fabric8.kubernetes.api.model.EnvFromSourceBuilder; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.EnvVarBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressBuilder; import java.util.HashMap; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link UniqueNamesProvisioner}. * * @author Anton Korneta */ @Listeners(MockitoTestNGListener.class) public class UniqueNamesProvisionerTest { private static final String WORKSPACE_ID = "workspace37"; private static final String POD_NAME = "testPod"; private static final String DEPLOYMENT_NAME = "testDeployment"; private static final String CONFIGMAP_NAME = "testConfigMap"; private static final String CONFIGMAP_KEY = "testConfigMapKey"; private static final String CONFIGMAP_VALUE = "testConfigMapValue"; private static final String INGRESS_NAME = "testIngress"; @Mock private KubernetesEnvironment k8sEnv; @Mock private RuntimeIdentity runtimeIdentity; private UniqueNamesProvisioner uniqueNamesProvisioner; @BeforeMethod public void setup() { uniqueNamesProvisioner = new UniqueNamesProvisioner<>(); } @Test public void provideUniquePodsNames() throws Exception { when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); Pod pod = newPod(); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); doReturn(ImmutableMap.of(POD_NAME, podData)).when(k8sEnv).getPodsData(); uniqueNamesProvisioner.provision(k8sEnv, runtimeIdentity); ObjectMeta podMetadata = pod.getMetadata(); assertNotEquals(podMetadata.getName(), POD_NAME); assertEquals(podMetadata.getLabels().get(CHE_ORIGINAL_NAME_LABEL), POD_NAME); } @Test public void provideUniqueDeploymentsName() throws Exception { when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); Deployment deployment = newDeployment(); doReturn(ImmutableMap.of(DEPLOYMENT_NAME, deployment)).when(k8sEnv).getDeploymentsCopy(); uniqueNamesProvisioner.provision(k8sEnv, runtimeIdentity); ObjectMeta deploymentMetadata = deployment.getMetadata(); assertNotEquals(deploymentMetadata.getName(), DEPLOYMENT_NAME); assertEquals(deploymentMetadata.getLabels().get(CHE_ORIGINAL_NAME_LABEL), DEPLOYMENT_NAME); } @Test public void provideUniqueConfigMapName() throws Exception { when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); ConfigMap configMap = newConfigMap(); doReturn(ImmutableMap.of(CONFIGMAP_NAME, configMap)).when(k8sEnv).getConfigMaps(); uniqueNamesProvisioner.provision(k8sEnv, runtimeIdentity); ObjectMeta configMapMetadata = configMap.getMetadata(); assertNotEquals(configMapMetadata.getName(), CONFIGMAP_NAME); assertEquals(configMapMetadata.getLabels().get(CHE_ORIGINAL_NAME_LABEL), CONFIGMAP_NAME); } @Test public void rewritePodConfigMapEnv() throws Exception { when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); ConfigMap configMap = newConfigMap(); doReturn(ImmutableMap.of(CONFIGMAP_NAME, configMap)).when(k8sEnv).getConfigMaps(); EnvVar envVar = new EnvVarBuilder() .withNewValueFrom() .withNewConfigMapKeyRef() .withName(CONFIGMAP_NAME) .withKey(CONFIGMAP_KEY) .endConfigMapKeyRef() .endValueFrom() .build(); Container container = new ContainerBuilder().withEnv(envVar).build(); Pod pod = newPod(); pod.getSpec().setContainers(ImmutableList.of(container)); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); doReturn(ImmutableMap.of(POD_NAME, podData)).when(k8sEnv).getPodsData(); uniqueNamesProvisioner.provision(k8sEnv, runtimeIdentity); String newConfigMapName = configMap.getMetadata().getName(); EnvVar newEnvVar = container.getEnv().iterator().next(); assertEquals(newEnvVar.getValueFrom().getConfigMapKeyRef().getName(), newConfigMapName); } @Test public void doesNotRewritePodConfigMapEnvWhenNoConfigMap() throws Exception { when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); EnvVar envVar = new EnvVarBuilder() .withNewValueFrom() .withNewConfigMapKeyRef() .withName(CONFIGMAP_NAME) .withKey(CONFIGMAP_KEY) .endConfigMapKeyRef() .endValueFrom() .build(); Container container = new ContainerBuilder().withEnv(envVar).build(); Pod pod = newPod(); pod.getSpec().setContainers(ImmutableList.of(container)); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); doReturn(ImmutableMap.of(POD_NAME, podData)).when(k8sEnv).getPodsData(); uniqueNamesProvisioner.provision(k8sEnv, runtimeIdentity); EnvVar newEnvVar = container.getEnv().iterator().next(); assertEquals(newEnvVar.getValueFrom().getConfigMapKeyRef().getName(), CONFIGMAP_NAME); } @Test public void rewritePodConfigMapEnvFrom() throws Exception { when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); ConfigMap configMap = newConfigMap(); doReturn(ImmutableMap.of(CONFIGMAP_NAME, configMap)).when(k8sEnv).getConfigMaps(); EnvFromSource envFrom = new EnvFromSourceBuilder() .withNewConfigMapRef() .withName(CONFIGMAP_NAME) .endConfigMapRef() .build(); Container container = new ContainerBuilder().withEnvFrom(envFrom).build(); Pod pod = newPod(); pod.getSpec().setContainers(ImmutableList.of(container)); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); doReturn(ImmutableMap.of(POD_NAME, podData)).when(k8sEnv).getPodsData(); uniqueNamesProvisioner.provision(k8sEnv, runtimeIdentity); String newConfigMapName = configMap.getMetadata().getName(); EnvFromSource newEnvFromSource = container.getEnvFrom().iterator().next(); assertEquals(newEnvFromSource.getConfigMapRef().getName(), newConfigMapName); } @Test public void doesNotRewritePodConfigMapEnvFromWhenNoConfigMap() throws Exception { when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); EnvFromSource envFrom = new EnvFromSourceBuilder() .withNewConfigMapRef() .withName(CONFIGMAP_NAME) .endConfigMapRef() .build(); Container container = new ContainerBuilder().withEnvFrom(envFrom).build(); Pod pod = newPod(); pod.getSpec().setContainers(ImmutableList.of(container)); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); doReturn(ImmutableMap.of(POD_NAME, podData)).when(k8sEnv).getPodsData(); uniqueNamesProvisioner.provision(k8sEnv, runtimeIdentity); EnvFromSource newEnvFromSource = container.getEnvFrom().iterator().next(); assertEquals(newEnvFromSource.getConfigMapRef().getName(), CONFIGMAP_NAME); } @Test public void rewritePodConfigMapVolumes() throws Exception { when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); ConfigMap configMap = newConfigMap(); doReturn(ImmutableMap.of(CONFIGMAP_NAME, configMap)).when(k8sEnv).getConfigMaps(); Volume volume = new VolumeBuilder().withNewConfigMap().withName(CONFIGMAP_NAME).endConfigMap().build(); Pod pod = newPod(); pod.getSpec().setVolumes(ImmutableList.of(volume)); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); doReturn(ImmutableMap.of(POD_NAME, podData)).when(k8sEnv).getPodsData(); uniqueNamesProvisioner.provision(k8sEnv, runtimeIdentity); String newConfigMapName = configMap.getMetadata().getName(); Volume newVolume = pod.getSpec().getVolumes().iterator().next(); assertEquals(newVolume.getConfigMap().getName(), newConfigMapName); } @Test public void doesNotRewritePodConfigMapVolumesWhenNoConfigMap() throws Exception { when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); Volume volume = new VolumeBuilder().withNewConfigMap().withName(CONFIGMAP_NAME).endConfigMap().build(); Pod pod = newPod(); pod.getSpec().setVolumes(ImmutableList.of(volume)); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); doReturn(ImmutableMap.of(POD_NAME, podData)).when(k8sEnv).getPodsData(); uniqueNamesProvisioner.provision(k8sEnv, runtimeIdentity); Volume newVolume = pod.getSpec().getVolumes().iterator().next(); assertEquals(newVolume.getConfigMap().getName(), CONFIGMAP_NAME); } @Test public void provideUniqueIngressesNames() throws Exception { final HashMap ingresses = new HashMap<>(); Ingress ingress = newIngress(); ingresses.put(POD_NAME, ingress); doReturn(ingresses).when(k8sEnv).getIngresses(); uniqueNamesProvisioner.provision(k8sEnv, runtimeIdentity); final ObjectMeta ingressMetadata = ingress.getMetadata(); assertNotEquals(ingressMetadata.getName(), INGRESS_NAME); assertEquals(ingressMetadata.getLabels().get(CHE_ORIGINAL_NAME_LABEL), INGRESS_NAME); } private static Pod newPod() { return new PodBuilder() .withMetadata(new ObjectMetaBuilder().withName(POD_NAME).build()) .withNewSpec() .endSpec() .build(); } private static Deployment newDeployment() { return new DeploymentBuilder() .withNewMetadata() .withName(DEPLOYMENT_NAME) .endMetadata() .withNewSpec() .withNewTemplate() .withNewMetadata() .withName(POD_NAME) .endMetadata() .endTemplate() .endSpec() .build(); } private static ConfigMap newConfigMap() { return new ConfigMapBuilder() .withNewMetadata() .withName(CONFIGMAP_NAME) .endMetadata() .withData(ImmutableMap.of(CONFIGMAP_KEY, CONFIGMAP_VALUE)) .build(); } private static Ingress newIngress() { return new IngressBuilder() .withMetadata(new ObjectMetaBuilder().withName(INGRESS_NAME).build()) .build(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/VcsSslCertificateProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSslCertificateProvisioner.CA_CERT_FILE; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSslCertificateProvisioner.CERT_MOUNT_PATH; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSslCertificateProvisioner.CHE_GIT_SELF_SIGNED_CERT_CONFIG_MAP_SUFFIX; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSslCertificateProvisioner.CHE_GIT_SELF_SIGNED_VOLUME; import static org.mockito.Mockito.lenient; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapVolumeSource; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeMount; import java.util.List; import java.util.Map; import java.util.UUID; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link VcsSslCertificateProvisioner}. * * @author Vitalii Parfonov */ @Listeners(MockitoTestNGListener.class) public class VcsSslCertificateProvisionerTest { private static final String WORKSPACE_ID = "workspace123"; private static final String EXPECTED_CERT_NAME = WORKSPACE_ID + CHE_GIT_SELF_SIGNED_CERT_CONFIG_MAP_SUFFIX; private static final String CERT_CONTENT = "-----BEGIN CERTIFICATE-----\n" + UUID.randomUUID().toString() + "\n-----END CERTIFICATE-----"; @Mock private RuntimeIdentity runtimeId; private VcsSslCertificateProvisioner provisioner; private KubernetesEnvironment k8sEnv; @BeforeMethod public void setUp() { lenient().when(runtimeId.getWorkspaceId()).thenReturn(WORKSPACE_ID); provisioner = new VcsSslCertificateProvisioner(CERT_CONTENT, ""); k8sEnv = KubernetesEnvironment.builder().build(); } @Test public void shouldReturnFalseIfCertificateIsNotConfigured() { provisioner = new VcsSslCertificateProvisioner(); assertFalse(provisioner.isConfigured()); } @Test public void shouldReturnTrueIfCertificateIsConfigured() { provisioner = new VcsSslCertificateProvisioner(CERT_CONTENT, "localhost"); assertTrue(provisioner.isConfigured()); } @Test public void shouldReturnCertPathFile() { String certPath = provisioner.getCertPath(); assertEquals(certPath, "/etc/che/git/cert/ca.crt"); } @Test public void shouldAddConfigMapWithCertificateIntoEnvironment() throws Exception { provisioner.provision(k8sEnv, runtimeId); Map configMaps = k8sEnv.getConfigMaps(); assertEquals(configMaps.size(), 1); ConfigMap configMap = configMaps.get(EXPECTED_CERT_NAME); assertNotNull(configMap); assertEquals(configMap.getMetadata().getName(), EXPECTED_CERT_NAME); assertEquals(configMap.getData().get(CA_CERT_FILE), CERT_CONTENT); } @Test public void shouldAddVolumeAndVolumeMountsToPodsAndContainersInEnvironment() throws Exception { k8sEnv.addPod(createPod("pod")); k8sEnv.addPod(createPod("pod2")); provisioner.provision(k8sEnv, runtimeId); for (Pod pod : k8sEnv.getPodsCopy().values()) { verifyVolumeIsPresent(pod); for (Container container : pod.getSpec().getInitContainers()) { verifyVolumeMountIsPresent(container); } for (Container container : pod.getSpec().getContainers()) { verifyVolumeMountIsPresent(container); } } } @Test public void shouldNotAddVolumeAndVolumeMountsToPodsAndContainersInEnvironmentIfCertIsNotConfigured() throws Exception { provisioner = new VcsSslCertificateProvisioner("", ""); k8sEnv.addPod(createPod("pod")); k8sEnv.addPod(createPod("pod2")); provisioner.provision(k8sEnv, runtimeId); for (Pod pod : k8sEnv.getPodsCopy().values()) { assertTrue(pod.getSpec().getVolumes().isEmpty()); for (Container container : pod.getSpec().getContainers()) { assertTrue(container.getVolumeMounts().isEmpty()); } } } @Test public void shouldNotAddVolumeButAddVolumeMountsToInjectablePods() throws Exception { k8sEnv.addPod(createPod("pod")); k8sEnv.addInjectablePod("r", "i", createPod("pod2")); provisioner.provision(k8sEnv, runtimeId); for (Pod pod : k8sEnv.getPodsCopy().values()) { verifyVolumeIsPresent(pod); for (Container container : pod.getSpec().getInitContainers()) { verifyVolumeMountIsPresent(container); } for (Container container : pod.getSpec().getContainers()) { verifyVolumeMountIsPresent(container); } } for (Pod pod : k8sEnv.getInjectablePodsCopy().values().stream() .flatMap(v -> v.values().stream()) .toArray(Pod[]::new)) { assertTrue(pod.getSpec().getVolumes().isEmpty()); for (Container container : pod.getSpec().getInitContainers()) { verifyVolumeMountIsPresent(container); } for (Container container : pod.getSpec().getContainers()) { verifyVolumeMountIsPresent(container); } } } private void verifyVolumeIsPresent(Pod pod) { List podVolumes = pod.getSpec().getVolumes(); assertEquals(podVolumes.size(), 1); Volume certVolume = podVolumes.get(0); assertEquals(certVolume.getName(), CHE_GIT_SELF_SIGNED_VOLUME); ConfigMapVolumeSource volumeConfigMap = certVolume.getConfigMap(); assertNotNull(volumeConfigMap); assertEquals(volumeConfigMap.getName(), EXPECTED_CERT_NAME); } private void verifyVolumeMountIsPresent(Container container) { List volumeMounts = container.getVolumeMounts(); assertEquals(volumeMounts.size(), 1); VolumeMount volumeMount = volumeMounts.get(0); assertEquals(volumeMount.getName(), CHE_GIT_SELF_SIGNED_VOLUME); assertTrue(volumeMount.getReadOnly()); assertEquals(volumeMount.getMountPath(), CERT_MOUNT_PATH); } private Pod createPod(String podName) { return new PodBuilder() .withNewMetadata() .withName(podName) .endMetadata() .withNewSpec() .withInitContainers(new ContainerBuilder().build()) .withContainers(new ContainerBuilder().build(), new ContainerBuilder().build()) .endSpec() .build(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/env/EnvVarsConverterTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.env; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.testng.Assert.assertEquals; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodSpec; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class EnvVarsConverterTest { private static final String PRE_EXISTING_VAR = "VAR_THAT_EXISTS"; private static final String PRE_EXISTING_VAR_VALUE = "jmenuju se VAR"; private static final String PRE_EXISTING_VAR_NEW_VALUE = "my name is VAR"; private static final String A_VAR = "A"; private static final String A_VAL = "$(C)"; private static final String B_VAR = "B"; private static final String B_VAL = "b"; private static final String C_VAR = "C"; private static final String C_VAL = "c"; private KubernetesEnvironment environment; private RuntimeIdentity identity; private Container testContainer; private InternalMachineConfig machine; @BeforeMethod public void setUp() { testContainer = new Container(); PodSpec podSpec = new PodSpec(); podSpec.setContainers(singletonList(testContainer)); ObjectMeta podMeta = new ObjectMeta(); podMeta.setName("pod"); Pod pod = new Pod(); pod.setSpec(podSpec); pod.setMetadata(podMeta); Map pods = new HashMap<>(); pods.put("pod", pod); environment = KubernetesEnvironment.builder().setPods(pods).build(); machine = new InternalMachineConfig(); environment.setMachines( Collections.singletonMap(Names.machineName(podMeta, testContainer), machine)); identity = new RuntimeIdentityImpl("wsId", "blah", "bleh", "infraNamespace"); } @Test public void shouldProvisionEnvironmentVariablesSorted() throws InfrastructureException { // given List preExistingEnvironment = new ArrayList<>(); preExistingEnvironment.add(new EnvVar(PRE_EXISTING_VAR, PRE_EXISTING_VAR_VALUE, null)); testContainer.setEnv(preExistingEnvironment); machine.getEnv().put(PRE_EXISTING_VAR, PRE_EXISTING_VAR_NEW_VALUE); machine.getEnv().put(A_VAR, A_VAL); machine.getEnv().put(B_VAR, B_VAL); machine.getEnv().put(C_VAR, C_VAL); // when EnvVarsConverter converter = new EnvVarsConverter(); converter.provision(environment, identity); // then EnvVar expectedA = new EnvVar(A_VAR, A_VAL, null); EnvVar expectedB = new EnvVar(B_VAR, B_VAL, null); EnvVar expectedC = new EnvVar(C_VAR, C_VAL, null); EnvVar expectedPreExisting = new EnvVar(PRE_EXISTING_VAR, PRE_EXISTING_VAR_NEW_VALUE, null); List expectedOrder = asList(expectedB, expectedC, expectedA, expectedPreExisting); assertEquals(4, testContainer.getEnv().size()); assertEquals(expectedOrder, testContainer.getEnv()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/limits/ram/ContainerResourceProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.limits.ram; import static com.google.common.collect.ImmutableMap.of; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_REQUEST_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_REQUEST_ATTRIBUTE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.Quantity; import io.fabric8.kubernetes.api.model.ResourceRequirements; import io.fabric8.kubernetes.api.model.ResourceRequirementsBuilder; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link ContainerResourceProvisioner}. * * @author Anton Korneta */ @Listeners(MockitoTestNGListener.class) public class ContainerResourceProvisionerTest { private static final String POD_NAME = "web"; private static final String CONTAINER_NAME = "app"; private static final String MACHINE_NAME = POD_NAME + '/' + CONTAINER_NAME; private static final String RAM_LIMIT_VALUE = "2147483648"; private static final String RAM_REQUEST_VALUE = "1234567890"; private static final String CPU_LIMIT_VALUE = "0.4"; private static final String CPU_REQUEST_VALUE = "0.15"; @Mock private KubernetesEnvironment k8sEnv; @Mock private RuntimeIdentity identity; @Mock private InternalMachineConfig internalMachineConfig; private Container container; private ContainerResourceProvisioner resourceProvisioner; @BeforeMethod public void setup() { resourceProvisioner = new ContainerResourceProvisioner(1024, 512, "500m", "100m"); container = new Container(); container.setName(CONTAINER_NAME); when(k8sEnv.getMachines()).thenReturn(of(MACHINE_NAME, internalMachineConfig)); when(internalMachineConfig.getAttributes()) .thenReturn( of( MEMORY_LIMIT_ATTRIBUTE, RAM_LIMIT_VALUE, MEMORY_REQUEST_ATTRIBUTE, RAM_REQUEST_VALUE, CPU_LIMIT_ATTRIBUTE, CPU_LIMIT_VALUE, CPU_REQUEST_ATTRIBUTE, CPU_REQUEST_VALUE)); final ObjectMeta podMetadata = mock(ObjectMeta.class); when(podMetadata.getName()).thenReturn(POD_NAME); final PodSpec podSpec = mock(PodSpec.class); when(podSpec.getContainers()).thenReturn(Collections.singletonList(container)); when(k8sEnv.getPodsData()).thenReturn(of(POD_NAME, new PodData(podSpec, podMetadata))); } @Test public void testProvisionResourcesLimitAndRequestAttributeToContainer() throws Exception { resourceProvisioner.provision(k8sEnv, identity); assertEquals(container.getResources().getLimits().get("memory").getAmount(), RAM_LIMIT_VALUE); assertEquals(container.getResources().getLimits().get("cpu").getAmount(), CPU_LIMIT_VALUE); assertEquals( container.getResources().getRequests().get("memory").getAmount(), RAM_REQUEST_VALUE); assertEquals(container.getResources().getRequests().get("cpu").getAmount(), CPU_REQUEST_VALUE); } @Test public void testIgnoreNegativeRAMResourcesLimitAndRequestAttributeToContainer() throws Exception { ContainerResourceProvisioner localResourceProvisioner = new ContainerResourceProvisioner(-1, -1, "-1", "-1"); Map attributes = new HashMap<>(); attributes.put(MEMORY_LIMIT_ATTRIBUTE, "-1"); attributes.put(MEMORY_REQUEST_ATTRIBUTE, "-1"); attributes.put(CPU_LIMIT_ATTRIBUTE, CPU_LIMIT_VALUE); attributes.put(CPU_REQUEST_ATTRIBUTE, CPU_REQUEST_VALUE); when(internalMachineConfig.getAttributes()).thenReturn(attributes); localResourceProvisioner.provision(k8sEnv, identity); assertNull(container.getResources().getLimits().get("memory")); assertEquals(container.getResources().getLimits().get("cpu").getAmount(), CPU_LIMIT_VALUE); assertNull(container.getResources().getRequests().get("memory")); assertEquals(container.getResources().getRequests().get("cpu").getAmount(), CPU_REQUEST_VALUE); } @Test public void testIgnoreNegativeCPUResourcesLimitAndRequestAttributeToContainer() throws Exception { ContainerResourceProvisioner localResourceProvisioner = new ContainerResourceProvisioner(-1, -1, "-1", "-1"); Map attributes = new HashMap<>(); attributes.put(MEMORY_LIMIT_ATTRIBUTE, RAM_LIMIT_VALUE); attributes.put(MEMORY_REQUEST_ATTRIBUTE, RAM_REQUEST_VALUE); attributes.put(CPU_LIMIT_ATTRIBUTE, "-1"); attributes.put(CPU_REQUEST_ATTRIBUTE, "-1"); when(internalMachineConfig.getAttributes()).thenReturn(attributes); localResourceProvisioner.provision(k8sEnv, identity); assertEquals(container.getResources().getLimits().get("memory").getAmount(), RAM_LIMIT_VALUE); assertNull(container.getResources().getLimits().get("cpu")); assertEquals( container.getResources().getRequests().get("memory").getAmount(), RAM_REQUEST_VALUE); assertNull(container.getResources().getRequests().get("cpu")); } @Test public void testOverridesContainerRamLimitAndRequestFromMachineAttribute() throws Exception { ResourceRequirements resourceRequirements = new ResourceRequirementsBuilder() .addToLimits(of("memory", new Quantity("3221225472"), "cpu", new Quantity("0.678"))) .addToRequests(of("memory", new Quantity("1231231423"), "cpu", new Quantity("0.333"))) .build(); container.setResources(resourceRequirements); resourceProvisioner.provision(k8sEnv, identity); assertEquals(container.getResources().getLimits().get("memory").getAmount(), RAM_LIMIT_VALUE); assertEquals(container.getResources().getLimits().get("cpu").getAmount(), CPU_LIMIT_VALUE); assertEquals( container.getResources().getRequests().get("memory").getAmount(), RAM_REQUEST_VALUE); assertEquals(container.getResources().getRequests().get("cpu").getAmount(), CPU_REQUEST_VALUE); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/restartpolicy/RestartPolicyRewriterTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy; import static java.lang.String.format; import static java.util.Collections.singletonMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.Warnings.RESTART_POLICY_SET_TO_NEVER_WARNING_CODE; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter.DEFAULT_RESTART_POLICY; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.fail; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.PodSpecBuilder; import java.util.Iterator; import java.util.List; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.WarningImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ @Listeners(MockitoTestNGListener.class) public class RestartPolicyRewriterTest { private static final String TEST_POD_NAME = "app"; private static final String ALWAYS_RESTART_POLICY = "Always"; @Mock private KubernetesEnvironment k8sEnv; @Mock private RuntimeIdentity runtimeIdentity; @InjectMocks private RestartPolicyRewriter restartPolicyRewriter; @Captor private ArgumentCaptor warningCaptor; @Test public void rewritesRestartPolicyWhenItsDifferentWithDefaultOne() throws Exception { Pod pod = newPod(TEST_POD_NAME, ALWAYS_RESTART_POLICY); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); when(k8sEnv.getPodsData()).thenReturn(singletonMap(TEST_POD_NAME, podData)); restartPolicyRewriter.provision(k8sEnv, runtimeIdentity); assertEquals(pod.getSpec().getRestartPolicy(), DEFAULT_RESTART_POLICY); verifyWarnings( new WarningImpl( RESTART_POLICY_SET_TO_NEVER_WARNING_CODE, format( "Restart policy '%s' for pod '%s' is rewritten with %s", ALWAYS_RESTART_POLICY, TEST_POD_NAME, DEFAULT_RESTART_POLICY))); } private static Pod newPod(String podName, String restartPolicy, Container... containers) { final ObjectMeta podMetadata = new ObjectMetaBuilder().withName(podName).build(); final PodSpec podSpec = new PodSpecBuilder().withRestartPolicy(restartPolicy).withContainers(containers).build(); return new PodBuilder().withMetadata(podMetadata).withSpec(podSpec).build(); } private void verifyWarnings(Warning... expectedWarnings) { final Iterator actualWarnings = captureWarnings().iterator(); for (Warning expected : expectedWarnings) { if (!actualWarnings.hasNext()) { fail("It is expected to receive environment warning"); } final Warning actual = actualWarnings.next(); assertEquals(actual, expected); } if (actualWarnings.hasNext()) { fail("No more warnings expected"); } } private List captureWarnings() { verify(k8sEnv, atLeastOnce()).addWarning(warningCaptor.capture()); return warningCaptor.getAllValues(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/secret/EnvironmentVariableSecretApplierTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_AUTOMOUNT; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_ENV_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_ENV_NAME_TEMPLATE; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_AS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.LabelSelector; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class EnvironmentVariableSecretApplierTest { @Mock private KubernetesEnvironment environment; @Mock private KubernetesSecrets secrets; @Mock private PodData podData; @Mock private PodSpec podSpec; @Mock private RuntimeIdentity runtimeIdentity; EnvironmentVariableSecretApplier secretApplier = new EnvironmentVariableSecretApplier(); @BeforeMethod public void setUp() throws Exception { when(environment.getPodsData()).thenReturn(singletonMap("pod1", podData)); when(podData.getRole()).thenReturn(PodRole.DEPLOYMENT); when(podData.getSpec()).thenReturn(podSpec); } @Test public void shouldProvisionSingleEnvVariable() throws Exception { Container container_match = new ContainerBuilder().withName("maven").build(); Container container_unmatch = spy(new ContainerBuilder().withName("other").build()); when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match, container_unmatch)); Secret secret = new SecretBuilder() .withData(singletonMap("foo", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_ENV_NAME, "MY_FOO", ANNOTATION_MOUNT_AS, "env", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // container has env set assertEquals(container_match.getEnv().size(), 1); EnvVar var = container_match.getEnv().get(0); assertEquals(var.getName(), "MY_FOO"); assertEquals(var.getValueFrom().getSecretKeyRef().getName(), "test_secret"); assertEquals(var.getValueFrom().getSecretKeyRef().getKey(), "foo"); } @Test public void shouldProvisionMultiEnvVariable() throws Exception { Container container_match = new ContainerBuilder().withName("maven").build(); when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match)); Secret secret = new SecretBuilder() .withData(ImmutableMap.of("foo", "random", "bar", "freedom")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( String.format(ANNOTATION_ENV_NAME_TEMPLATE, "foo"), "MY_FOO", String.format(ANNOTATION_ENV_NAME_TEMPLATE, "bar"), "MY_BAR", ANNOTATION_MOUNT_AS, "env", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // container has env set assertEquals(container_match.getEnv().size(), 2); EnvVar var = container_match.getEnv().get(0); assertEquals(var.getName(), "MY_FOO"); assertEquals(var.getValueFrom().getSecretKeyRef().getName(), "test_secret"); assertEquals(var.getValueFrom().getSecretKeyRef().getKey(), "foo"); EnvVar var2 = container_match.getEnv().get(1); assertEquals(var2.getName(), "MY_BAR"); assertEquals(var2.getValueFrom().getSecretKeyRef().getName(), "test_secret"); assertEquals(var2.getValueFrom().getSecretKeyRef().getKey(), "bar"); } @Test public void shouldProvisionAllContainersIfAutomountEnabled() throws Exception { Container container_match1 = new ContainerBuilder().withName("maven").build(); Container container_match2 = new ContainerBuilder().withName("other").build(); when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match1, container_match2)); Secret secret = new SecretBuilder() .withData(singletonMap("foo", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_ENV_NAME, "MY_FOO", ANNOTATION_MOUNT_AS, "env", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // both containers has env set assertEquals(container_match1.getEnv().size(), 1); EnvVar var = container_match1.getEnv().get(0); assertEquals(var.getName(), "MY_FOO"); assertEquals(var.getValueFrom().getSecretKeyRef().getName(), "test_secret"); assertEquals(var.getValueFrom().getSecretKeyRef().getKey(), "foo"); assertEquals(container_match2.getEnv().size(), 1); EnvVar var2 = container_match2.getEnv().get(0); assertEquals(var2.getName(), "MY_FOO"); assertEquals(var2.getValueFrom().getSecretKeyRef().getName(), "test_secret"); assertEquals(var2.getValueFrom().getSecretKeyRef().getKey(), "foo"); } @Test public void shouldProvisionContainersWithAutomountOverrideTrue() throws Exception { Container container_match1 = new ContainerBuilder().withName("maven").build(); Container container_match2 = new ContainerBuilder().withName("other").build(); DevfileImpl mock_defvile = mock(DevfileImpl.class); ComponentImpl component = new ComponentImpl(); component.setAlias("maven"); component.setAutomountWorkspaceSecrets(true); when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match1, container_match2)); InternalMachineConfig internalMachineConfig = new InternalMachineConfig(); internalMachineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, "maven"); when(environment.getMachines()).thenReturn(ImmutableMap.of("maven", internalMachineConfig)); when(environment.getDevfile()).thenReturn(mock_defvile); when(mock_defvile.getComponents()).thenReturn(singletonList(component)); Secret secret = new SecretBuilder() .withData(singletonMap("foo", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_ENV_NAME, "MY_FOO", ANNOTATION_MOUNT_AS, "env", ANNOTATION_AUTOMOUNT, "false")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // only first container has env set assertEquals(container_match1.getEnv().size(), 1); EnvVar var = container_match1.getEnv().get(0); assertEquals(var.getName(), "MY_FOO"); assertEquals(var.getValueFrom().getSecretKeyRef().getName(), "test_secret"); assertEquals(var.getValueFrom().getSecretKeyRef().getKey(), "foo"); assertEquals(container_match2.getEnv().size(), 0); } @Test public void shouldNotProvisionContainersWithAutomountOverrideFalse() throws Exception { Container container_match1 = new ContainerBuilder().withName("maven").build(); Container container_match2 = new ContainerBuilder().withName("other").build(); DevfileImpl mock_defvile = mock(DevfileImpl.class); ComponentImpl component = new ComponentImpl(); component.setAlias("maven"); component.setAutomountWorkspaceSecrets(false); when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match1, container_match2)); InternalMachineConfig internalMachineConfig = new InternalMachineConfig(); internalMachineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, "maven"); when(environment.getMachines()).thenReturn(ImmutableMap.of("maven", internalMachineConfig)); when(environment.getDevfile()).thenReturn(mock_defvile); when(mock_defvile.getComponents()).thenReturn(singletonList(component)); Secret secret = new SecretBuilder() .withData(singletonMap("foo", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_ENV_NAME, "MY_FOO", ANNOTATION_MOUNT_AS, "env", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // only second container has env set assertEquals(container_match1.getEnv().size(), 0); assertEquals(container_match2.getEnv().size(), 1); EnvVar var2 = container_match2.getEnv().get(0); assertEquals(var2.getName(), "MY_FOO"); assertEquals(var2.getValueFrom().getSecretKeyRef().getName(), "test_secret"); assertEquals(var2.getValueFrom().getSecretKeyRef().getKey(), "foo"); } @Test public void shouldNotProvisionAllContainersifAutomountDisabled() throws Exception { Container container_match1 = spy(new ContainerBuilder().withName("maven").build()); Container container_match2 = spy(new ContainerBuilder().withName("other").build()); when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match1, container_match2)); Secret secret = new SecretBuilder() .withData(singletonMap("foo", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_ENV_NAME, "MY_FOO", ANNOTATION_MOUNT_AS, "env", ANNOTATION_AUTOMOUNT, "false")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); verify(container_match1).getName(); verify(container_match2).getName(); // both containers no actions verifyNoMoreInteractions(container_match1, container_match2); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Unable to mount secret 'test_secret': It is configured to be mount as a environment variable, but its name was not specified. Please define the 'che.eclipse.org/env-name' annotation on the secret to specify it.") public void shouldThrowExceptionWhenNoEnvNameSpecifiedSingleValue() throws Exception { Container container_match = new ContainerBuilder().withName("maven").build(); when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match)); Secret secret = new SecretBuilder() .withData(singletonMap("foo", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of(ANNOTATION_MOUNT_AS, "env", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret)); secretApplier.applySecret(environment, runtimeIdentity, secret); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Unable to mount key 'foo' of secret 'test_secret': It is configured to be mount as a environment variable, but its name was not specified. Please define the 'che.eclipse.org/foo_env-name' annotation on the secret to specify it.") public void shouldThrowExceptionWhenNoEnvNameSpecifiedMultiValue() throws Exception { Container container_match = new ContainerBuilder().withName("maven").build(); when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match)); Secret secret = new SecretBuilder() .withData(ImmutableMap.of("foo", "random", "bar", "test")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of(ANNOTATION_MOUNT_AS, "env", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret)); secretApplier.applySecret(environment, runtimeIdentity, secret); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/secret/FileSecretApplierTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_AUTOMOUNT; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_AS; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_PATH; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.LabelSelector; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.PodSpecBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeMount; import io.fabric8.kubernetes.api.model.VolumeMountBuilder; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.K8sVersion; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class FileSecretApplierTest { @Mock private KubernetesEnvironment environment; @Mock private KubernetesSecrets secrets; @Mock private PodData podData; @Mock private PodSpec podSpec; @Mock private RuntimeIdentity runtimeIdentity; @Mock private K8sVersion kubernetesVersion; FileSecretApplier secretApplier; @BeforeMethod public void setUp() throws Exception { lenient().when(kubernetesVersion.newerOrEqualThan(1, 13)).thenReturn(true); lenient().when(kubernetesVersion.olderThan(1, 13)).thenReturn(false); secretApplier = new FileSecretApplier(kubernetesVersion); when(environment.getPodsData()).thenReturn(singletonMap("pod1", podData)); when(podData.getRole()).thenReturn(PodRole.DEPLOYMENT); when(podData.getSpec()).thenReturn(podSpec); } @Test public void shouldProvisionAsFiles() throws Exception { Container container_match1 = new ContainerBuilder().withName("maven").build(); Container container_match2 = new ContainerBuilder().withName("other").build(); PodSpec localSpec = new PodSpecBuilder() .withContainers(ImmutableList.of(container_match1, container_match2)) .build(); when(podData.getSpec()).thenReturn(localSpec); Secret secret = new SecretBuilder() .withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.m2", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // pod has volume created assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1); Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0); assertEquals(volume.getName(), "test_secret"); assertEquals(volume.getSecret().getSecretName(), "test_secret"); // both containers has mounts set assertEquals( environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .size(), 2); VolumeMount mount1 = environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .get(0); assertEquals(mount1.getName(), "test_secret"); assertEquals(mount1.getMountPath(), "/home/user/.m2/" + mount1.getSubPath()); assertFalse(mount1.getSubPath().isEmpty()); assertTrue(mount1.getReadOnly()); VolumeMount mount2 = environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .get(1); assertEquals(mount2.getName(), "test_secret"); assertEquals(mount2.getMountPath(), "/home/user/.m2/" + mount2.getSubPath()); assertFalse(mount2.getSubPath().isEmpty()); assertTrue(mount2.getReadOnly()); assertEquals( environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(1) .getVolumeMounts() .size(), 2); if ("settings.xml".equals(mount1.getSubPath())) { assertEquals(mount1.getSubPath(), "settings.xml"); assertEquals(mount2.getSubPath(), "another.xml"); } else { assertEquals(mount1.getSubPath(), "another.xml"); assertEquals(mount2.getSubPath(), "settings.xml"); } } @Test public void shouldProvisionAsFilesWithPathOverride() throws Exception { Container container = new ContainerBuilder().withName("maven").build(); DevfileImpl mock_defvile = mock(DevfileImpl.class); ComponentImpl component = new ComponentImpl(); component.setAlias("maven"); component.getVolumes().add(new VolumeImpl("test_secret", "/path/to/override")); InternalMachineConfig internalMachineConfig = new InternalMachineConfig(); internalMachineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, "maven"); when(environment.getMachines()).thenReturn(ImmutableMap.of("maven", internalMachineConfig)); when(environment.getDevfile()).thenReturn(mock_defvile); when(mock_defvile.getComponents()).thenReturn(singletonList(component)); PodSpec localSpec = new PodSpecBuilder().withContainers(ImmutableList.of(container)).build(); when(podData.getSpec()).thenReturn(localSpec); Secret secret = new SecretBuilder() .withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.m2", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // pod has volume created assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1); Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0); assertEquals(volume.getName(), "test_secret"); assertEquals(volume.getSecret().getSecretName(), "test_secret"); // both containers has mounts set assertEquals( environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .size(), 2); VolumeMount mount1 = environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .get(0); assertEquals(mount1.getName(), "test_secret"); assertEquals(mount1.getMountPath(), "/path/to/override/settings.xml"); assertTrue(mount1.getReadOnly()); } @Test public void shouldProvisionContainersWithAutomountOverrideTrue() throws Exception { Container container_match1 = new ContainerBuilder().withName("maven").build(); Container container_match2 = new ContainerBuilder().withName("other").build(); DevfileImpl mock_defvile = mock(DevfileImpl.class); ComponentImpl component = new ComponentImpl(); component.setAlias("maven"); component.setAutomountWorkspaceSecrets(true); InternalMachineConfig internalMachineConfig = new InternalMachineConfig(); internalMachineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, "maven"); when(environment.getMachines()).thenReturn(ImmutableMap.of("maven", internalMachineConfig)); when(environment.getDevfile()).thenReturn(mock_defvile); when(mock_defvile.getComponents()).thenReturn(singletonList(component)); PodSpec localSpec = new PodSpecBuilder() .withContainers(ImmutableList.of(container_match1, container_match2)) .build(); when(podData.getSpec()).thenReturn(localSpec); Secret secret = new SecretBuilder() .withData(singletonMap("foo", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.m2", ANNOTATION_AUTOMOUNT, "false")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // pod has volume created assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1); Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0); assertEquals(volume.getName(), "test_secret"); assertEquals(volume.getSecret().getSecretName(), "test_secret"); // first container has mount set assertEquals( environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .size(), 1); VolumeMount mount1 = environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .get(0); assertEquals(mount1.getName(), "test_secret"); assertEquals(mount1.getMountPath(), "/home/user/.m2/foo"); assertTrue(mount1.getReadOnly()); // second container has no mounts assertEquals( environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(1) .getVolumeMounts() .size(), 0); } @Test public void shouldNotProvisionContainersWithAutomountOverrideFalse() throws Exception { Container container_match1 = new ContainerBuilder().withName("maven").build(); Container container_match2 = new ContainerBuilder().withName("other").build(); DevfileImpl mock_defvile = mock(DevfileImpl.class); ComponentImpl component = new ComponentImpl(); component.setAlias("maven"); component.setAutomountWorkspaceSecrets(false); InternalMachineConfig internalMachineConfig = new InternalMachineConfig(); internalMachineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, "maven"); when(environment.getMachines()).thenReturn(ImmutableMap.of("maven", internalMachineConfig)); when(environment.getDevfile()).thenReturn(mock_defvile); when(mock_defvile.getComponents()).thenReturn(singletonList(component)); PodSpec localSpec = new PodSpecBuilder() .withContainers(ImmutableList.of(container_match1, container_match2)) .build(); when(podData.getSpec()).thenReturn(localSpec); Secret secret = new SecretBuilder() .withData(singletonMap("foo", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.m2", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // only second container has mounts assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1); Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0); assertEquals(volume.getName(), "test_secret"); assertEquals(volume.getSecret().getSecretName(), "test_secret"); assertEquals( environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .size(), 0); assertEquals( environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(1) .getVolumeMounts() .size(), 1); VolumeMount mount2 = environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(1) .getVolumeMounts() .get(0); assertEquals(mount2.getName(), "test_secret"); assertEquals(mount2.getMountPath(), "/home/user/.m2/foo"); assertTrue(mount2.getReadOnly()); } @Test public void shouldNotProvisionAllContainersifAutomountDisabled() throws Exception { Container container_match1 = spy(new ContainerBuilder().withName("maven").build()); Container container_match2 = spy(new ContainerBuilder().withName("other").build()); when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match1, container_match2)); Secret secret = new SecretBuilder() .withData(singletonMap("foo", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.m2", ANNOTATION_AUTOMOUNT, "false")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); verify(container_match1).getName(); verify(container_match2).getName(); // both containers no actions verifyNoMoreInteractions(container_match1, container_match2); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Unable to mount secret 'test_secret': It is configured to be mounted as a file but the mount path was not specified. Please define the 'che.eclipse.org/mount-path' annotation on the secret to specify it.") public void shouldThrowExceptionWhenNoMountPathSpecifiedForFiles() throws Exception { Container container_match = new ContainerBuilder().withName("maven").build(); PodSpec localSpec = new PodSpecBuilder().withContainers(ImmutableList.of(container_match)).build(); when(podData.getSpec()).thenReturn(localSpec); Secret secret = new SecretBuilder() .withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations(singletonMap(ANNOTATION_MOUNT_AS, "file")) .withLabels(emptyMap()) .build()) .build(); when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret)); secretApplier.applySecret(environment, runtimeIdentity, secret); } @Test public void shouldNotUseSubpathForOlderK8s() throws InfrastructureException { lenient().when(kubernetesVersion.newerOrEqualThan(1, 13)).thenReturn(false); lenient().when(kubernetesVersion.olderThan(1, 13)).thenReturn(true); Container container_match1 = new ContainerBuilder().withName("maven").build(); PodSpec localSpec = new PodSpecBuilder().withContainers(ImmutableList.of(container_match1)).build(); when(podData.getSpec()).thenReturn(localSpec); Secret secret = new SecretBuilder() .withData(ImmutableMap.of("settings.xml", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.m2", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // pod has volume created assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1); Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0); assertEquals(volume.getName(), "test_secret"); assertEquals(volume.getSecret().getSecretName(), "test_secret"); assertEquals( environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .size(), 1); VolumeMount mount1 = environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .get(0); assertEquals(mount1.getName(), "test_secret"); assertEquals(mount1.getMountPath(), "/home/user/.m2"); assertNull(mount1.getSubPath()); } @Test public void shouldNotOverrideExistingVolumeMounts() throws InfrastructureException { Container container_match1 = new ContainerBuilder() .withName("maven") .withVolumeMounts( new VolumeMountBuilder() .withName("existing-volume") .withMountPath("/home/user/.m2") .build()) .build(); PodSpec localSpec = new PodSpecBuilder().withContainers(ImmutableList.of(container_match1)).build(); when(podData.getSpec()).thenReturn(localSpec); Secret secret = new SecretBuilder() .withData(ImmutableMap.of("settings.xml", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.m2", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // pod has volume created assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1); Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0); assertEquals(volume.getName(), "test_secret"); assertEquals(volume.getSecret().getSecretName(), "test_secret"); assertEquals( environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .size(), 2); VolumeMount mount1 = environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .get(0); assertEquals(mount1.getName(), "existing-volume"); assertEquals(mount1.getMountPath(), "/home/user/.m2"); assertNull(mount1.getSubPath()); VolumeMount mount2 = environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .get(1); assertEquals(mount2.getName(), "test_secret"); assertEquals(mount2.getMountPath(), "/home/user/.m2/settings.xml"); assertEquals(mount2.getSubPath(), "settings.xml"); } @Test public void shouldOverrideExistingVolumeMountsOnOlderK8s() throws InfrastructureException { lenient().when(kubernetesVersion.newerOrEqualThan(1, 13)).thenReturn(false); lenient().when(kubernetesVersion.olderThan(1, 13)).thenReturn(true); Container container_match1 = new ContainerBuilder() .withName("maven") .withVolumeMounts( new VolumeMountBuilder() .withName("existing-volume") .withMountPath("/home/user/.m2") .build()) .build(); PodSpec localSpec = new PodSpecBuilder().withContainers(ImmutableList.of(container_match1)).build(); when(podData.getSpec()).thenReturn(localSpec); Secret secret = new SecretBuilder() .withData(ImmutableMap.of("settings.xml", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.m2", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); secretApplier.applySecret(environment, runtimeIdentity, secret); // pod has volume created assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1); Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0); assertEquals(volume.getName(), "test_secret"); assertEquals(volume.getSecret().getSecretName(), "test_secret"); assertEquals( environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .size(), 1); VolumeMount secretMount = environment .getPodsData() .get("pod1") .getSpec() .getContainers() .get(0) .getVolumeMounts() .get(0); assertEquals(secretMount.getName(), "test_secret"); assertEquals(secretMount.getMountPath(), "/home/user/.m2"); assertNull(secretMount.getSubPath()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/secret/GitCredentialStorageFileSecretApplierTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitConfigProvisioner.GIT_CONFIG_MAP_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_AUTOMOUNT; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_GIT_CREDENTIALS; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_AS; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_PATH; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_USER_NAME; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.K8sVersion; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitConfigProvisioner; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitCredentialStorageFileSecretApplierTest { @Mock private KubernetesEnvironment environment; @Mock private KubernetesEnvironment.PodData podData; @Mock private PodSpec podSpec; @Mock private RuntimeIdentity runtimeIdentity; @Mock private K8sVersion kubernetesVersion; GitCredentialStorageFileSecretApplier secretApplier; public static final String GIT_CONFIG_CONTENT = "[user]\n\tname = Michelangelo Merisi da Caravaggio\n\tmail = mcaravag@email.not.exists.com"; @BeforeMethod public void setUp() throws Exception { lenient().when(kubernetesVersion.newerOrEqualThan(1, 13)).thenReturn(true); lenient().when(kubernetesVersion.olderThan(1, 13)).thenReturn(false); secretApplier = new GitCredentialStorageFileSecretApplier(kubernetesVersion); when(environment.getPodsData()).thenReturn(singletonMap("pod1", podData)); when(podData.getRole()).thenReturn(KubernetesEnvironment.PodRole.DEPLOYMENT); when(podData.getSpec()).thenReturn(podSpec); lenient().when(runtimeIdentity.getWorkspaceId()).thenReturn("ws-1234598"); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Invalid git credential secret data. It should contain only 1 data item but it have 2") public void shouldThrowInfrastructureExceptionIfSecretsHasMoreOrLessWhen1Data() throws InfrastructureException { // given Secret secret = new SecretBuilder() .withData(ImmutableMap.of("credentials", "random", "credentials2", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.git", ANNOTATION_GIT_CREDENTIALS, "true", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); // when secretApplier.applySecret(environment, runtimeIdentity, secret); } @Test public void shouldBeAbleToAdjustGiConfigConfigMap() throws InfrastructureException { // given Secret secret = new SecretBuilder() .withData(ImmutableMap.of("credentials", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.git", ANNOTATION_GIT_CREDENTIALS, "true", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); ConfigMap configMap = new ConfigMapBuilder() .withData(ImmutableMap.of(GitConfigProvisioner.GIT_CONFIG, GIT_CONFIG_CONTENT)) .build(); when(environment.getConfigMaps()).thenReturn(ImmutableMap.of(GIT_CONFIG_MAP_NAME, configMap)); // when secretApplier.applySecret(environment, runtimeIdentity, secret); // then String data = configMap.getData().get(GitConfigProvisioner.GIT_CONFIG); assertTrue( data.endsWith("[credential]\n\thelper = store --file /home/user/.git/credentials\n")); assertTrue(data.startsWith(GIT_CONFIG_CONTENT)); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Multiple git credential secrets for user user5 found in namespace test-ns. That may be caused by reinstalling product without user namespaces cleanup or using multiple instances of product with the same namespace namings template.") public void shouldThrowInfrastructureExceptionIfGitConfigAlreadyContainsSecretConfig() throws InfrastructureException { // given Secret secret = new SecretBuilder() .withData(ImmutableMap.of("credentials", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.git", ANNOTATION_GIT_CREDENTIALS, "true", ANNOTATION_AUTOMOUNT, "true", ANNOTATION_USER_NAME, "user5")) .withLabels(emptyMap()) .withNamespace("test-ns") .build()) .build(); ConfigMap configMap = new ConfigMapBuilder() .withData( ImmutableMap.of( GitConfigProvisioner.GIT_CONFIG, GIT_CONFIG_CONTENT + "[credential]\n\thelper = store --file /home/user/.git/credentials\n")) .build(); when(environment.getConfigMaps()).thenReturn(ImmutableMap.of(GIT_CONFIG_MAP_NAME, configMap)); // when secretApplier.applySecret(environment, runtimeIdentity, secret); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/secret/SecretAsContainerResourceProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_AUTOMOUNT; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_ENV_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_GIT_CREDENTIALS; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_AS; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretAnnotationNames.ANNOTATION_MOUNT_PATH; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.LabelSelector; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class SecretAsContainerResourceProvisionerTest { @Mock EnvironmentVariableSecretApplier environmentVariableSecretApplier; @Mock GitCredentialStorageFileSecretApplier gitCredentialStorageFileSecretApplier; @Mock FileSecretApplier fileSecretApplier; private SecretAsContainerResourceProvisioner provisioner; @Mock private KubernetesEnvironment environment; @Mock private KubernetesNamespace namespace; @Mock private KubernetesSecrets secrets; @Mock private RuntimeIdentity runtimeIdentity; @BeforeMethod public void setUp() throws Exception { when(namespace.secrets()).thenReturn(secrets); provisioner = new SecretAsContainerResourceProvisioner<>( fileSecretApplier, environmentVariableSecretApplier, gitCredentialStorageFileSecretApplier, new String[] {"app:che"}); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Unable to mount secret 'test_secret': it has missing or unknown type of the mount. Please make sure that 'che.eclipse.org/mount-as' annotation has value either 'env' or 'file'.") public void shouldThrowExceptionWhenNoMountTypeSpecified() throws Exception { // given Secret secret = new SecretBuilder() .withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations(emptyMap()) .withLabels(emptyMap()) .build()) .build(); when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret)); // when provisioner.provision(environment, runtimeIdentity, namespace); } @Test public void shouldCallEnvironmentVariableSecretApplier() throws InfrastructureException { // given Secret secret = new SecretBuilder() .withData(singletonMap("foo", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_ENV_NAME, "MY_FOO", ANNOTATION_MOUNT_AS, "env", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret)); // when provisioner.provision(environment, runtimeIdentity, namespace); // then verify(environmentVariableSecretApplier) .applySecret(eq(environment), eq(runtimeIdentity), eq(secret)); verifyNoMoreInteractions(fileSecretApplier); verifyNoMoreInteractions(gitCredentialStorageFileSecretApplier); } @Test public void shouldCallFileSecretApplier() throws InfrastructureException { // given Secret secret = new SecretBuilder() .withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.m2", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret)); // when provisioner.provision(environment, runtimeIdentity, namespace); // then verify(fileSecretApplier).applySecret(eq(environment), eq(runtimeIdentity), eq(secret)); verifyNoMoreInteractions(environmentVariableSecretApplier); verifyNoMoreInteractions(gitCredentialStorageFileSecretApplier); } @Test public void shouldCallGitCredentialStorageFileSecretApplier() throws InfrastructureException { // given Secret secret = new SecretBuilder() .withData(ImmutableMap.of("credentials", "random")) .withMetadata( new ObjectMetaBuilder() .withName("test_secret") .withAnnotations( ImmutableMap.of( ANNOTATION_MOUNT_AS, "file", ANNOTATION_MOUNT_PATH, "/home/user/.git", ANNOTATION_GIT_CREDENTIALS, "true", ANNOTATION_AUTOMOUNT, "true")) .withLabels(emptyMap()) .build()) .build(); when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret)); // when provisioner.provision(environment, runtimeIdentity, namespace); // then verify(gitCredentialStorageFileSecretApplier) .applySecret(eq(environment), eq(runtimeIdentity), eq(secret)); verifyNoMoreInteractions(environmentVariableSecretApplier); verifyNoMoreInteractions(fileSecretApplier); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/IngressServerResolverTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.fabric8.kubernetes.api.model.ServicePortBuilder; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPath; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressRuleValue; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressBuilder; import io.fabric8.kubernetes.api.model.networking.v1.IngressLoadBalancerStatusBuilder; import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; import io.fabric8.kubernetes.api.model.networking.v1.IngressServiceBackend; import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPort; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations.Serializer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressPathTransformInverter; import org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver.IngressServerResolver; import org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver.ServerResolver; import org.mockito.Mockito; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * Test for {@link IngressServerResolver}. * * @author Sergii Leshchenko */ public class IngressServerResolverTest { private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); private static final int CONTAINER_PORT = 3054; private static final String INGRESS_IP = "127.0.0.1"; private static final String INGRESS_RULE_PATH_PREFIX = "/server-8080"; private static final String INGRESS_PATH_PREFIX = "server-8080"; private ExternalServiceExposureStrategy externalServiceExposureStrategy; @BeforeMethod public void setupMocks() { externalServiceExposureStrategy = Mockito.mock(ExternalServiceExposureStrategy.class); } @Test public void testResolvingServersWhenThereIsNoTheCorrespondingServiceAndIngressForTheSpecifiedMachine() { // given Service nonMatchedByPodService = createService("nonMatched", "foreignMachine", CONTAINER_PORT, null); Ingress ingress = createIngress( "nonMatched", "foreignMachine", Pair.of("http-server", new ServerConfigImpl("3054", "http", "/api", ATTRIBUTES_MAP))); ServerResolver serverResolver = new IngressServerResolver( new NoopPathTransformInverter(), singletonList(nonMatchedByPodService), singletonList(ingress)); // when Map resolved = serverResolver.resolve("machine"); // then assertTrue(resolved.isEmpty()); } @Test public void testResolvingServersWhenThereIsMatchedIngressForTheSpecifiedMachine() { Ingress ingress = createIngress( "matched", "machine", Pair.of("http-server", new ServerConfigImpl("3054", "http", "/api/", ATTRIBUTES_MAP))); ServerResolver serverResolver = new IngressServerResolver( new NoopPathTransformInverter(), emptyList(), singletonList(ingress)); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("http://" + INGRESS_IP + INGRESS_RULE_PATH_PREFIX + "/api/") .withStatus(ServerStatus.UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, INGRESS_PATH_PREFIX + "/"))); } @Test public void testResolvingServersWhenThereIsMatchedIngressForMachineAndServerPathIsNull() { Ingress ingress = createIngress( "matched", "machine", Pair.of("http-server", new ServerConfigImpl("3054", "http", null, ATTRIBUTES_MAP))); ServerResolver serverResolver = new IngressServerResolver( new NoopPathTransformInverter(), emptyList(), singletonList(ingress)); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("http://" + INGRESS_IP + INGRESS_RULE_PATH_PREFIX + "/") .withStatus(ServerStatus.UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, INGRESS_PATH_PREFIX + "/"))); } @Test public void testResolvingServersWhenThereIsMatchedIngressForMachineAndServerPathIsEmpty() { Ingress ingress = createIngress( "matched", "machine", Pair.of("http-server", new ServerConfigImpl("3054", "http", "", ATTRIBUTES_MAP))); ServerResolver serverResolver = new IngressServerResolver( new NoopPathTransformInverter(), emptyList(), singletonList(ingress)); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("http://" + INGRESS_IP + INGRESS_RULE_PATH_PREFIX + "/") .withStatus(ServerStatus.UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, INGRESS_PATH_PREFIX + "/"))); } @Test public void testResolvingServersWhenThereIsMatchedIngressForMachineAndServerPathIsRelative() { Ingress ingress = createIngress( "matched", "machine", Pair.of("http-server", new ServerConfigImpl("3054", "http", "api", ATTRIBUTES_MAP))); ServerResolver serverResolver = new IngressServerResolver( new NoopPathTransformInverter(), emptyList(), singletonList(ingress)); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("http://" + INGRESS_IP + INGRESS_RULE_PATH_PREFIX + "/api/") .withStatus(ServerStatus.UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, INGRESS_PATH_PREFIX + "/"))); } @Test public void testResolvingInternalServers() { Service service = createService( "service11", "machine", CONTAINER_PORT, singletonMap( "http-server", new ServerConfigImpl("3054", "http", "api", ATTRIBUTES_MAP))); ServerResolver serverResolver = new IngressServerResolver( new NoopPathTransformInverter(), singletonList(service), emptyList()); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("http://service11:3054/api") .withStatus(ServerStatus.UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, "/"))); } @Test public void testResolvingInternalServersWithPortWithTransportProtocol() { Service service = createService( "service11", "machine", CONTAINER_PORT, singletonMap( "http-server", new ServerConfigImpl("3054/udp", "xxx", "api", ATTRIBUTES_MAP))); ServerResolver serverResolver = new IngressServerResolver( new NoopPathTransformInverter(), singletonList(service), emptyList()); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("xxx://service11:3054/api") .withStatus(ServerStatus.UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, "/"))); } private Service createService( String name, String machineName, Integer port, Map servers) { Serializer serializer = Annotations.newSerializer(); serializer.machineName(machineName); if (servers != null) { serializer.servers(servers); } return new ServiceBuilder() .withNewMetadata() .withName(name) .withAnnotations(serializer.annotations()) .endMetadata() .withNewSpec() .withPorts( new ServicePortBuilder() .withPort(port) .withNewTargetPort() .withValue(port) .endTargetPort() .build()) .endSpec() .build(); } private Ingress createIngress( String name, String machineName, Pair server) { Serializer serializer = Annotations.newSerializer(); serializer.machineName(machineName); serializer.server(server.first, server.second); return new IngressBuilder() .withNewMetadata() .withName(name) .withAnnotations(serializer.annotations()) .endMetadata() .withNewSpec() .withRules( new IngressRule( null, new HTTPIngressRuleValue( singletonList( new HTTPIngressPath( new IngressBackend( null, new IngressServiceBackend( name, new ServiceBackendPort("8080", 8080))), INGRESS_PATH_PREFIX, null))))) .endSpec() .withNewStatus() .withLoadBalancer( new IngressLoadBalancerStatusBuilder() .addNewIngress() .withIp("127.0.0.1") .endIngress() .build()) .endStatus() .build(); } private Map defaultAttributeAnd(String... keyValues) { HashMap attributes = new HashMap<>(ATTRIBUTES_MAP); String key = null; for (String v : keyValues) { if (key == null) { key = v; } else { attributes.put(key, v); key = null; } } return attributes; } private static final class NoopPathTransformInverter extends IngressPathTransformInverter { NoopPathTransformInverter() { super("%s"); } } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.DISCOVERABLE_SERVER_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVER_NAME_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_UNIQUE_PART_SIZE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.ContainerPortBuilder; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServicePortBuilder; import java.util.ArrayList; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.regex.Pattern; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Test for {@link KubernetesServerExposer}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class KubernetesServerExposerTest { @Mock private IngressServerExposer externalServerExposer; @Mock private SecureServerExposer secureServerExposer; private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); private static final Map INTERNAL_SERVER_ATTRIBUTE_MAP = singletonMap(ServerConfig.INTERNAL_SERVER_ATTRIBUTE, Boolean.TRUE.toString()); private static final Map SECURE_SERVER_ATTRIBUTE_MAP = singletonMap(ServerConfig.SECURE_SERVER_ATTRIBUTE, Boolean.TRUE.toString()); private static final Pattern SERVER_PREFIX_REGEX = Pattern.compile('^' + SERVER_PREFIX + "[A-z0-9]{" + SERVER_UNIQUE_PART_SIZE + "}-pod-main$"); private static final String MACHINE_NAME = "pod/main"; private static final Map UNIQUE_SERVER_ATTRIBUTES = ImmutableMap.of("key", "value", ServerConfig.UNIQUE_SERVER_ATTRIBUTE, "true"); private static final String SERVICE_NAME = SERVER_PREFIX + "12345678" + "-" + MACHINE_NAME; private KubernetesServerExposer serverExposer; private KubernetesEnvironment kubernetesEnvironment; private Container container; @BeforeMethod public void setUp() throws Exception { container = new ContainerBuilder().withName("main").build(); Pod pod = new PodBuilder() .withNewMetadata() .withName("pod") .endMetadata() .withNewSpec() .withContainers(container) .endSpec() .build(); kubernetesEnvironment = KubernetesEnvironment.builder().setPods(ImmutableMap.of("pod", pod)).build(); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); this.serverExposer = new KubernetesServerExposer<>( externalServerExposer, secureServerExposer, MACHINE_NAME, podData, container, kubernetesEnvironment); } @Test public void shouldExposeContainerPortAndCreateServiceForServer() throws Exception { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); Map serversToExpose = ImmutableMap.of("http-server", httpServerConfig); // when serverExposer.expose(serversToExpose); // then assertThatExternalServerIsExposed( MACHINE_NAME, "tcp", 8080, "http-server", new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); } @Test public void shouldExposeContainerPortAndCreateServiceAndForServerWhenTwoServersHasTheSamePort() throws Exception { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); ServerConfigImpl wsServerConfig = new ServerConfigImpl("8080/tcp", "ws", "/connect", ATTRIBUTES_MAP); Map serversToExpose = ImmutableMap.of( "http-server", httpServerConfig, "ws-server", wsServerConfig); // when serverExposer.expose(serversToExpose); // then assertEquals(kubernetesEnvironment.getServices().size(), 1); assertThatExternalServersAreExposed( MACHINE_NAME, "tcp", 8080, ImmutableMap.of( "http-server", new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP), "ws-server", new ServerConfigImpl(wsServerConfig).withAttributes(ATTRIBUTES_MAP))); } @Test public void shouldExposeContainerPortsAndCreateServiceForServerWhenTwoServersHasDifferentPorts() throws Exception { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); ServerConfigImpl wsServerConfig = new ServerConfigImpl("8081/tcp", "ws", "/connect", ATTRIBUTES_MAP); Map serversToExpose = ImmutableMap.of( "http-server", httpServerConfig, "ws-server", wsServerConfig); // when serverExposer.expose(serversToExpose); // then assertEquals(kubernetesEnvironment.getServices().size(), 1); assertThatExternalServerIsExposed( MACHINE_NAME, "tcp", 8080, "http-server", new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); assertThatExternalServerIsExposed( MACHINE_NAME, "tcp", 8081, "ws-server", new ServerConfigImpl(wsServerConfig).withAttributes(ATTRIBUTES_MAP)); } @Test public void shouldExposeTcpContainerPortsAndCreateServiceAndForServerWhenProtocolIsMissedInPort() throws Exception { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080", "http", "/api", ATTRIBUTES_MAP); Map serversToExpose = ImmutableMap.of("http-server", httpServerConfig); // when serverExposer.expose(serversToExpose); // then assertEquals(kubernetesEnvironment.getServices().size(), 1); assertThatExternalServerIsExposed( MACHINE_NAME, "TCP", 8080, "http-server", new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); } @Test public void shouldNotAddAdditionalContainerPortWhenItIsAlreadyExposed() throws Exception { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); Map serversToExpose = ImmutableMap.of("http-server", httpServerConfig); container.setPorts( singletonList( new ContainerPortBuilder() .withName("port-8080") .withContainerPort(8080) .withProtocol("TCP") .build())); // when serverExposer.expose(serversToExpose); // then assertThatExternalServerIsExposed( MACHINE_NAME, "tcp", 8080, "http-server", new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); } @Test public void shouldAddAdditionalContainerPortWhenThereIsTheSameButWithDifferentProtocol() throws Exception { // given ServerConfigImpl udpServerConfig = new ServerConfigImpl("8080/udp", "udp", "/api", ATTRIBUTES_MAP); Map serversToExpose = ImmutableMap.of("server", udpServerConfig); container.setPorts( new ArrayList<>( singletonList( new ContainerPortBuilder() .withName("port-8080") .withContainerPort(8080) .withProtocol("TCP") .build()))); // when serverExposer.expose(serversToExpose); // then assertEquals(container.getPorts().size(), 2); assertEquals(container.getPorts().get(1).getContainerPort(), new Integer(8080)); assertEquals(container.getPorts().get(1).getProtocol(), "UDP"); assertThatExternalServerIsExposed( MACHINE_NAME, "udp", 8080, "server", new ServerConfigImpl(udpServerConfig).withAttributes(ATTRIBUTES_MAP)); } @Test public void shouldExposeContainerPortAndCreateServiceForInternalServer() throws Exception { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", INTERNAL_SERVER_ATTRIBUTE_MAP); Map serversToExpose = ImmutableMap.of("http-server", httpServerConfig); // when serverExposer.expose(serversToExpose); // then assertThatInternalServerIsExposed( MACHINE_NAME, "http-server", "tcp", 8080, new ServerConfigImpl(httpServerConfig).withAttributes(INTERNAL_SERVER_ATTRIBUTE_MAP)); } @Test public void shouldExposeInternalAndExternalAndSecureServers() throws Exception { // given ServerConfigImpl secureServerConfig = new ServerConfigImpl("8282/tcp", "http", "/api", SECURE_SERVER_ATTRIBUTE_MAP); ServerConfigImpl internalServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", INTERNAL_SERVER_ATTRIBUTE_MAP); ServerConfigImpl externalServerConfig = new ServerConfigImpl("9090/tcp", "http", "/api", ATTRIBUTES_MAP); Map serversToExpose = ImmutableMap.of( "int-server", internalServerConfig, "ext-server", externalServerConfig, "secure-server", secureServerConfig); // when serverExposer.expose(serversToExpose); // then assertThatInternalServerIsExposed( MACHINE_NAME, "int-server", "tcp", 8080, new ServerConfigImpl(internalServerConfig)); assertThatExternalServerIsExposed( MACHINE_NAME, "tcp", 9090, "ext-server", new ServerConfigImpl(externalServerConfig)); assertThatSecureServerIsExposed( MACHINE_NAME, "tcp", 8282, "secure-server", new ServerConfigImpl(secureServerConfig)); } @Test public void shouldCreateIngressPerUniqueServerWithTheSamePort() throws Exception { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", UNIQUE_SERVER_ATTRIBUTES); ServerConfigImpl wsServerConfig = new ServerConfigImpl("8080/tcp", "ws", "/connect", UNIQUE_SERVER_ATTRIBUTES); ServicePort servicePort = new ServicePortBuilder() .withName("server-8080") .withPort(8080) .withProtocol("TCP") .withTargetPort(new IntOrString(8080)) .build(); Map serversToExpose = ImmutableMap.of( "http-server", httpServerConfig, "ws-server", wsServerConfig); // when serverExposer.expose(serversToExpose); // then assertThatExternalServerIsExposed( MACHINE_NAME, "tcp", 8080, "http-server", new ServerConfigImpl(httpServerConfig).withAttributes(UNIQUE_SERVER_ATTRIBUTES)); assertThatExternalServerIsExposed( MACHINE_NAME, "tcp", 8080, "ws-server", new ServerConfigImpl(wsServerConfig).withAttributes(UNIQUE_SERVER_ATTRIBUTES)); } @Test public void shouldCreateIngressForServerWhenTwoServersHasTheSamePort() throws InfrastructureException { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); ServerConfigImpl wsServerConfig = new ServerConfigImpl("8080/tcp", "ws", "/connect", ATTRIBUTES_MAP); IntOrString targetPort = new IntOrString(8080); ServicePort servicePort = new ServicePortBuilder() .withName("server-8080") .withPort(8080) .withProtocol("TCP") .withTargetPort(targetPort) .build(); Map serversToExpose = ImmutableMap.of( "http-server", httpServerConfig, "ws-server", wsServerConfig); // when serverExposer.expose(serversToExpose); // then assertThatExternalServersAreExposed( MACHINE_NAME, "tcp", 8080, ImmutableMap.of( "http-server", new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP), "ws-server", new ServerConfigImpl(wsServerConfig).withAttributes(ATTRIBUTES_MAP))); } @Test public void testCreateExtraServiceForDiscoverableServerConfig() throws InfrastructureException { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl( "8080/tcp", "http", "/api", ImmutableMap.of(SERVER_NAME_ATTRIBUTE, "hello", DISCOVERABLE_SERVER_ATTRIBUTE, "true")); Map serversToExpose = ImmutableMap.of("http-server", httpServerConfig); // when serverExposer.expose(serversToExpose); assertEquals(kubernetesEnvironment.getServices().size(), 2); assertTrue(kubernetesEnvironment.getServices().containsKey("hello")); assertEquals(kubernetesEnvironment.getServices().get("hello").getMetadata().getName(), "hello"); } @Test public void testDiscoverableServerConfigWithoutNameAttributeIsNotProvisioned() throws InfrastructureException { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl( "8080/tcp", "http", "/api", ImmutableMap.of(DISCOVERABLE_SERVER_ATTRIBUTE, "true")); Map serversToExpose = ImmutableMap.of("http-server", httpServerConfig); // when serverExposer.expose(serversToExpose); assertEquals(kubernetesEnvironment.getServices().size(), 1); assertFalse(kubernetesEnvironment.getServices().containsKey("hello")); } @Test public void testExposeUniqueSecureServersWithOnlyMatchingServers() throws InfrastructureException { // https://github.com/eclipse/che/issues/16330 // given 2 servers which one of them is unique ServerConfigImpl theiaSC = new ServerConfigImpl( "3100/tcp", "https", null, ImmutableMap.of( "internal", "false", "discoverable", "false", "secure", "true", "type", "ide")); ServerConfigImpl webviewsSC = new ServerConfigImpl( "3100/tcp", "https", null, ImmutableMap.of( "internal", "false", "discoverable", "false", "unique", "true", "secure", "true", "type", "webview")); Map serversToExpose = ImmutableMap.of("theia", theiaSC, "webviews", webviewsSC); // when serverExposer.expose(serversToExpose); // then expose is called twice with only one server each ArgumentCaptor> serversCaptor = ArgumentCaptor.forClass(Map.class); verify(secureServerExposer, times(2)) .expose(any(), any(), any(), any(), any(), any(), serversCaptor.capture()); for (Map captures : serversCaptor.getAllValues()) { assertEquals(captures.size(), 1); } } @SuppressWarnings("SameParameterValue") private void assertThatExternalServerIsExposed( String machineName, String portProtocol, Integer port, String serverName, ServerConfig expectedServer) { assertThatExternalServersAreExposed( machineName, portProtocol, port, ImmutableMap.of(serverName, expectedServer)); } @SuppressWarnings("SameParameterValue") private void assertThatExternalServersAreExposed( String machineName, String portProtocol, Integer port, Map expectedServers) { // then assertThatContainerPortIsExposed(portProtocol, port); // ensure that service is created Service service = findContainerRelatedService(); assertNotNull(service); // ensure that required service port is exposed ServicePort servicePort = assertThatServicePortIsExposed(port, service); Annotations.Deserializer serviceAnnotations = Annotations.newDeserializer(service.getMetadata().getAnnotations()); assertEquals(serviceAnnotations.machineName(), machineName); // check that we did not create servers for public endpoints assertFalse( serviceAnnotations.servers().keySet().stream() .anyMatch(key -> expectedServers.containsKey(key))); verify(externalServerExposer) .expose( eq(kubernetesEnvironment), eq(machineName), eq(service.getMetadata().getName()), any(), eq(servicePort), eq(expectedServers)); } @SuppressWarnings("SameParameterValue") private void assertThatSecureServerIsExposed( String machineName, String portProtocol, Integer port, String serverName, ServerConfig serverConfig) throws Exception { // then assertThatContainerPortIsExposed(portProtocol, port); // ensure that service is created Service service = findContainerRelatedService(); assertNotNull(service); // ensure that no service port is exposed assertTrue( service.getSpec().getPorts().stream() .noneMatch(p -> p.getTargetPort().getIntVal().equals(port))); ServicePort servicePort = new ServicePortBuilder() .withName("server-" + port) .withPort(port) .withProtocol(portProtocol.toUpperCase()) .withNewTargetPort(port) .build(); verify(secureServerExposer) .expose( eq(kubernetesEnvironment), any(), eq(machineName), isNull(), // no service exists for the backed server isNull(), // a non-unique server eq(servicePort), eq(ImmutableMap.of(serverName, serverConfig))); } @SuppressWarnings("SameParameterValue") private void assertThatInternalServerIsExposed( String machineName, String serverNameRegex, String portProtocol, Integer port, ServerConfigImpl expected) { assertThatContainerPortIsExposed(portProtocol, port); // ensure that service is created Service service = findContainerRelatedService(); assertNotNull(service); // ensure that required service port is exposed assertThatServicePortIsExposed(port, service); Annotations.Deserializer serviceAnnotations = Annotations.newDeserializer(service.getMetadata().getAnnotations()); assertEquals(serviceAnnotations.machineName(), machineName); Map servers = serviceAnnotations.servers(); ServerConfig serverConfig = servers.get(serverNameRegex); assertEquals(serverConfig, expected); } private void assertThatContainerPortIsExposed(String portProtocol, Integer port) { assertTrue( container.getPorts().stream() .anyMatch( p -> p.getContainerPort().equals(port) && p.getProtocol().equals(portProtocol.toUpperCase()))); } private Service findContainerRelatedService() { Service service = null; for (Entry entry : kubernetesEnvironment.getServices().entrySet()) { if (SERVER_PREFIX_REGEX.matcher(entry.getKey()).matches()) { service = entry.getValue(); break; } } return service; } private ServicePort assertThatServicePortIsExposed(Integer port, Service service) { Optional servicePortOpt = service.getSpec().getPorts().stream() .filter(p -> p.getTargetPort().getIntVal().equals(port)) .findAny(); assertTrue(servicePortOpt.isPresent()); ServicePort servicePort = servicePortOpt.get(); assertEquals(servicePort.getTargetPort().getIntVal(), port); assertEquals(servicePort.getPort(), port); assertEquals(servicePort.getName(), SERVER_PREFIX + "-" + port); return servicePort; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/PreviewUrlExposerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server; import static java.util.Collections.singletonList; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServiceSpec; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPath; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressRuleValue; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; import io.fabric8.kubernetes.api.model.networking.v1.IngressServiceBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressSpec; import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPort; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.PreviewUrlImpl; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServerExposer; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class PreviewUrlExposerTest { private PreviewUrlExposer previewUrlExposer; @Mock private ExternalServiceExposureStrategy externalServiceExposureStrategy; @BeforeMethod public void setUp() { IngressServerExposer externalServerExposer = new IngressServerExposer( externalServiceExposureStrategy, Collections.emptyMap(), null, null); previewUrlExposer = new PreviewUrlExposer<>(externalServerExposer); } @Test public void shouldDoNothingWhenNoCommandsDefined() throws InternalInfrastructureException { KubernetesEnvironment env = KubernetesEnvironment.builder().build(); previewUrlExposer.expose(env); assertTrue(env.getCommands().isEmpty()); assertTrue(env.getServices().isEmpty()); assertTrue(env.getIngresses().isEmpty()); } @Test public void shouldDoNothingWhenNoCommandWithPreviewUrlDefined() throws InternalInfrastructureException { CommandImpl command = new CommandImpl("a", "a", "a"); KubernetesEnvironment env = KubernetesEnvironment.builder() .setCommands(singletonList(new CommandImpl(command))) .build(); previewUrlExposer.expose(env); assertEquals(env.getCommands().get(0), command); assertTrue(env.getServices().isEmpty()); assertTrue(env.getIngresses().isEmpty()); } @Test public void shouldNotProvisionWhenServiceAndIngressFound() throws InternalInfrastructureException { final int PORT = 8080; final String SERVER_PORT_NAME = "server-" + PORT; CommandImpl command = new CommandImpl("a", "a", "a", new PreviewUrlImpl(PORT, null), Collections.emptyMap()); Service service = new Service(); ObjectMeta serviceMeta = new ObjectMeta(); serviceMeta.setName("servicename"); service.setMetadata(serviceMeta); ServiceSpec serviceSpec = new ServiceSpec(); serviceSpec.setPorts( singletonList( new ServicePort(null, SERVER_PORT_NAME, null, PORT, "TCP", new IntOrString(PORT)))); service.setSpec(serviceSpec); Ingress ingress = new Ingress(); ObjectMeta ingressMeta = new ObjectMeta(); ingressMeta.setName("ingressname"); ingress.setMetadata(ingressMeta); IngressSpec ingressSpec = new IngressSpec(); IngressRule ingressRule = new IngressRule(); ingressRule.setHost("ingresshost"); IngressBackend ingressBackend = new IngressBackend( null, new IngressServiceBackend( "servicename", new ServiceBackendPort(SERVER_PORT_NAME, PORT))); ingressRule.setHttp( new HTTPIngressRuleValue(singletonList(new HTTPIngressPath(ingressBackend, null, null)))); ingressSpec.setRules(singletonList(ingressRule)); ingress.setSpec(ingressSpec); Map services = new HashMap<>(); services.put("servicename", service); Map ingresses = new HashMap<>(); ingresses.put("ingressname", ingress); KubernetesEnvironment env = KubernetesEnvironment.builder() .setCommands(singletonList(new CommandImpl(command))) .setServices(services) .setIngresses(ingresses) .build(); assertEquals(env.getIngresses().size(), 1); previewUrlExposer.expose(env); assertEquals(env.getIngresses().size(), 1); } @Test public void shouldProvisionIngressWhenNotFound() throws InternalInfrastructureException { Mockito.when( externalServiceExposureStrategy.getExternalPath(Mockito.anyString(), Mockito.any())) .thenReturn("some-server-path"); final int PORT = 8080; final String SERVER_PORT_NAME = "server-" + PORT; final String SERVICE_NAME = "servicename"; CommandImpl command = new CommandImpl("a", "a", "a", new PreviewUrlImpl(PORT, null), Collections.emptyMap()); Service service = new Service(); ObjectMeta serviceMeta = new ObjectMeta(); serviceMeta.setName(SERVICE_NAME); service.setMetadata(serviceMeta); ServiceSpec serviceSpec = new ServiceSpec(); serviceSpec.setPorts( singletonList( new ServicePort(null, SERVER_PORT_NAME, null, PORT, "TCP", new IntOrString(PORT)))); service.setSpec(serviceSpec); Map services = new HashMap<>(); services.put(SERVICE_NAME, service); KubernetesEnvironment env = KubernetesEnvironment.builder() .setCommands(singletonList(new CommandImpl(command))) .setServices(services) .setIngresses(new HashMap<>()) .build(); previewUrlExposer.expose(env); assertEquals(env.getIngresses().size(), 1); Ingress provisionedIngress = env.getIngresses().values().iterator().next(); IngressBackend provisionedIngressBackend = provisionedIngress.getSpec().getRules().get(0).getHttp().getPaths().get(0).getBackend(); assertEquals(provisionedIngressBackend.getService().getPort().getName(), SERVER_PORT_NAME); assertEquals(provisionedIngressBackend.getService().getName(), SERVICE_NAME); } @Test public void shouldProvisionServiceAndIngressWhenNotFound() throws InternalInfrastructureException { Mockito.when( externalServiceExposureStrategy.getExternalPath(Mockito.anyString(), Mockito.any())) .thenReturn("some-server-path"); final int PORT = 8080; final String SERVER_PORT_NAME = "server-" + PORT; CommandImpl command = new CommandImpl("a", "a", "a", new PreviewUrlImpl(PORT, null), Collections.emptyMap()); KubernetesEnvironment env = KubernetesEnvironment.builder() .setCommands(singletonList(new CommandImpl(command))) .setIngresses(new HashMap<>()) .setServices(new HashMap<>()) .build(); previewUrlExposer.expose(env); assertEquals(env.getIngresses().size(), 1); assertEquals(env.getServices().size(), 1); Service provisionedService = env.getServices().values().iterator().next(); ServicePort provisionedServicePort = provisionedService.getSpec().getPorts().get(0); assertEquals(provisionedServicePort.getName(), SERVER_PORT_NAME); assertEquals(provisionedServicePort.getPort().intValue(), PORT); Ingress provisionedIngress = env.getIngresses().values().iterator().next(); IngressBackend provisionedIngressBackend = provisionedIngress.getSpec().getRules().get(0).getHttp().getPaths().get(0).getBackend(); assertEquals(provisionedIngressBackend.getService().getPort().getName(), SERVER_PORT_NAME); assertEquals( provisionedIngressBackend.getService().getName(), provisionedService.getMetadata().getName()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/CombinedSingleHostServerExposerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.REQUIRE_SUBDOMAIN; import static org.mockito.Mockito.verify; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class CombinedSingleHostServerExposerTest { @Mock private KubernetesEnvironment env; private final String MACHINE = "machine"; private final String SERVICE = "service"; private final String SERVER = "server"; private final ServicePort PORT = new ServicePort(); @Mock private ExternalServerExposer subdomainExposer; @Mock private ExternalServerExposer subpathExposer; @Test public void shouldExposeDevfileServersOnSubdomans() { // given ServerConfig s1 = new ServerConfigImpl("1", "http", "/", emptyMap()); ServerConfig s2 = new ServerConfigImpl("2", "http", "/", singletonMap(REQUIRE_SUBDOMAIN, "false")); ServerConfig s3 = new ServerConfigImpl("3", "http", "/", singletonMap(REQUIRE_SUBDOMAIN, "true")); CombinedSingleHostServerExposer serverExposer = new CombinedSingleHostServerExposer<>(subdomainExposer, subpathExposer); // when serverExposer.expose(env, MACHINE, SERVICE, SERVER, PORT, Map.of("s1", s1, "s2", s2, "s3", s3)); // then verify(subdomainExposer).expose(env, MACHINE, SERVICE, SERVER, PORT, Map.of("s3", s3)); verify(subpathExposer).expose(env, MACHINE, SERVICE, SERVER, PORT, Map.of("s1", s1, "s2", s2)); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/DefaultHostExternalServiceExposureStrategyTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static java.lang.String.format; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServicePortBuilder; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * @author Guy Daich */ public class DefaultHostExternalServiceExposureStrategyTest { private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); private static final String MACHINE_NAME = "pod/main"; private static final String SERVICE_NAME = SERVER_PREFIX + "12345678" + "-" + MACHINE_NAME; private static final String LABELS = "foo=bar"; private IngressServerExposer externalServerExposer; private KubernetesEnvironment kubernetesEnvironment; @BeforeMethod public void setUp() throws Exception { Container container = new ContainerBuilder().withName("main").build(); Pod pod = new PodBuilder() .withNewMetadata() .withName("pod") .endMetadata() .withNewSpec() .withContainers(container) .endSpec() .build(); kubernetesEnvironment = KubernetesEnvironment.builder().setPods(ImmutableMap.of("pod", pod)).build(); externalServerExposer = new IngressServerExposer( new DefaultHostExternalServiceExposureStrategy(), emptyMap(), LABELS, "%s"); } @Test public void shouldCreateIngressForServer() { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); ServicePort servicePort = new ServicePortBuilder() .withName("server-8080") .withPort(8080) .withProtocol("TCP") .withTargetPort(new IntOrString(8080)) .build(); Map serversToExpose = ImmutableMap.of("http-server", httpServerConfig); // when externalServerExposer.expose( kubernetesEnvironment, MACHINE_NAME, SERVICE_NAME, null, servicePort, serversToExpose); // then assertThatExternalServerIsExposed( MACHINE_NAME, SERVICE_NAME, "http-server", servicePort, new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); } @Test public void shouldCreateSingleIngressForTwoNonUniqueServersWithTheSamePort() { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); ServerConfigImpl wsServerConfig = new ServerConfigImpl("8080/tcp", "ws", "/connect", ATTRIBUTES_MAP); ServicePort servicePort = new ServicePortBuilder() .withName("server-8080") .withPort(8080) .withProtocol("TCP") .withTargetPort(new IntOrString(8080)) .build(); Map serversToExpose = ImmutableMap.of( "http-server", httpServerConfig, "ws-server", wsServerConfig); // when externalServerExposer.expose( kubernetesEnvironment, MACHINE_NAME, SERVICE_NAME, null, servicePort, serversToExpose); // then assertEquals(kubernetesEnvironment.getIngresses().size(), 1); assertThatExternalServerIsExposed( MACHINE_NAME, SERVICE_NAME, "http-server", servicePort, new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); assertThatExternalServerIsExposed( MACHINE_NAME, SERVICE_NAME, "ws-server", servicePort, new ServerConfigImpl(wsServerConfig).withAttributes(ATTRIBUTES_MAP)); } @SuppressWarnings("SameParameterValue") private void assertThatExternalServerIsExposed( String machineName, String serviceName, String serverNameRegex, ServicePort servicePort, ServerConfigImpl expected) { // ensure that required ingress is created for (Ingress ingress : kubernetesEnvironment.getIngresses().values()) { IngressRule ingressRule = ingress.getSpec().getRules().get(0); IngressBackend backend = ingressRule.getHttp().getPaths().get(0).getBackend(); if (serviceName.equals(backend.getService().getName())) { assertEquals(backend.getService().getPort().getName(), servicePort.getName()); Annotations.Deserializer ingressAnnotations = Annotations.newDeserializer(ingress.getMetadata().getAnnotations()); Map servers = ingressAnnotations.servers(); ServerConfig serverConfig = servers.get(serverNameRegex); if (serverConfig == null) { // ok, this ingress is not for this particular server continue; } assertEquals(serverConfig, expected); assertEquals(ingressAnnotations.machineName(), machineName); return; } } Assert.fail( format( "Could not find an ingress for machine '%s' and service '%s'", machineName, serviceName)); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/ExternalServerIngressBuilderTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static java.util.Collections.singletonMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerIngressBuilder.INGRESS_PATH_TYPE; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPath; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * @author Guy Daich */ public class ExternalServerIngressBuilderTest { private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); private static final ServerConfig SERVER_CONFIG = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); private final Map SERVERS = ImmutableMap.of("http-server", SERVER_CONFIG); private static final Map ANNOTATIONS = singletonMap("annotation-key", "annotation-value"); private static final String MACHINE_NAME = "machine"; private static final String NAME = "IngressName"; private static final String SERVICE_NAME = "ServiceName"; private static final String SERVICE_PORT_NAME = "server-port"; private static final Integer SERVICE_PORT = 7777; private ExternalServerIngressBuilder externalServerIngressBuilder; @BeforeMethod public void setUp() throws Exception { this.externalServerIngressBuilder = new ExternalServerIngressBuilder(); } @Test public void shouldCreateIngress() { // given final String path = "/path/to/server"; final String host = "host-to-server"; // when Ingress ingress = externalServerIngressBuilder .withPath(path) .withHost(host) .withAnnotations(ANNOTATIONS) .withMachineName(MACHINE_NAME) .withName(NAME) .withServers(SERVERS) .withServiceName(SERVICE_NAME) .withServicePortName(SERVICE_PORT_NAME) .withServicePort(SERVICE_PORT) .build(); // then assertIngressSpec(path, host, ingress); } @Test public void shouldCreateIngressWithNoPath() { // given final String host = "host-to-server"; // when Ingress ingress = externalServerIngressBuilder .withHost(host) .withAnnotations(ANNOTATIONS) .withMachineName(MACHINE_NAME) .withName(NAME) .withServers(SERVERS) .withServiceName(SERVICE_NAME) .withServicePortName(SERVICE_PORT_NAME) .withServicePort(SERVICE_PORT) .build(); // then assertIngressSpec(null, host, ingress); } @Test public void shouldCreateIngressWithNoHost() { // given final String path = "/path/to/server"; // when Ingress ingress = externalServerIngressBuilder .withPath(path) .withAnnotations(ANNOTATIONS) .withMachineName(MACHINE_NAME) .withName(NAME) .withServers(SERVERS) .withServiceName(SERVICE_NAME) .withServicePortName(SERVICE_PORT_NAME) .withServicePort(SERVICE_PORT) .build(); // then assertIngressSpec(path, null, ingress); } private void assertIngressSpec(String path, String host, Ingress ingress) { assertEquals(ingress.getSpec().getRules().get(0).getHost(), host); HTTPIngressPath httpIngressPath = ingress.getSpec().getRules().get(0).getHttp().getPaths().get(0); assertEquals(httpIngressPath.getPath(), path); assertEquals(httpIngressPath.getPathType(), INGRESS_PATH_TYPE); assertEquals(httpIngressPath.getBackend().getService().getName(), SERVICE_NAME); assertEquals(httpIngressPath.getBackend().getService().getPort().getName(), SERVICE_PORT_NAME); assertEquals(ingress.getMetadata().getName(), NAME); assertTrue(ingress.getMetadata().getAnnotations().containsKey("annotation-key")); assertEquals(ingress.getMetadata().getAnnotations().get("annotation-key"), "annotation-value"); Annotations.Deserializer ingressAnnotations = Annotations.newDeserializer(ingress.getMetadata().getAnnotations()); Map servers = ingressAnnotations.servers(); ServerConfig serverConfig = servers.get("http-server"); assertEquals(serverConfig, SERVER_CONFIG); assertEquals(ingressAnnotations.machineName(), MACHINE_NAME); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/GatewayServerExposerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.Collections; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.util.GatewayConfigmapLabels; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GatewayServerExposerTest { private static final Map GATEWAY_CONFIGMAP_LABELS = ImmutableMap.builder() .put("app", "che") .put("role", "gateway-config") .build(); @Mock private GatewayConfigmapLabels gatewayConfigmapLabels; private final String machineName = "machine"; private final String serviceName = "service"; private final String serverId = "server"; private final ServicePort servicePort = new ServicePort(null, "portName", 1, 1, "http", new IntOrString(1234)); private final Map s1attrs = Collections.singletonMap("s1attr", "s1val"); private final Map servers = Collections.singletonMap("serverOne", new ServerConfigImpl("1111", "ws", null, s1attrs)); private ExternalServerExposer serverExposer; @BeforeMethod public void setUp() { when(gatewayConfigmapLabels.getLabels()).thenReturn(GATEWAY_CONFIGMAP_LABELS); serverExposer = new GatewayServerExposer<>( new SingleHostExternalServiceExposureStrategy("che-host"), gatewayConfigmapLabels); } @Test public void testExposeServiceWithGatewayConfigmap() { // given KubernetesEnvironment k8sEnv = KubernetesEnvironment.builder().build(); // when serverExposer.expose(k8sEnv, machineName, serviceName, serverId, servicePort, servers); // then Map configMaps = k8sEnv.getConfigMaps(); assertTrue(configMaps.containsKey(serviceName + "-" + serverId)); ConfigMap serverConfigMap = configMaps.get("service-server"); // data should be empty at this point assertTrue(serverConfigMap.getData() == null || serverConfigMap.getData().isEmpty()); assertEquals(serverConfigMap.getMetadata().getLabels(), GATEWAY_CONFIGMAP_LABELS); Map annotations = serverConfigMap.getMetadata().getAnnotations(); Annotations.Deserializer deserializer = Annotations.newDeserializer(annotations); assertEquals(deserializer.machineName(), machineName); Map exposedServers = deserializer.servers(); assertTrue(exposedServers.containsKey("serverOne")); ServerConfig s1 = exposedServers.get("serverOne"); assertEquals( s1.getAttributes().get(s1attrs.keySet().iterator().next()), s1attrs.values().iterator().next()); assertEquals(s1.getAttributes().get(ServerConfigImpl.SERVICE_NAME_ATTRIBUTE), "service"); assertEquals(s1.getAttributes().get(ServerConfigImpl.SERVICE_PORT_ATTRIBUTE), "1234"); assertEquals(s1.getPort(), "1111"); assertEquals(s1.getProtocol(), "ws"); assertNull(s1.getPath()); assertEquals(s1.getEndpointOrigin(), "/service/server/"); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/IngressPathTransformInverterTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static org.testng.Assert.assertEquals; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class IngressPathTransformInverterTest { @Test(dataProvider = "transformationTestCases") public void testUndoPathTransformationWithBasicTransform( String format, String transformedPath, String originalPath) { IngressPathTransformInverter inverter = new IngressPathTransformInverter(format); assertEquals(inverter.undoPathTransformation(transformedPath), originalPath); } @DataProvider public static Object[][] transformationTestCases() { return new Object[][] { {"%s", "path", "path"}, {"invalid", "path", "path"}, {"%ssuffix", "pathsuffix", "path"}, {"prefix%s", "prefixpath", "path"}, {"prefix%ssuffix", "prefixpathsuffix", "path"}, {"prefix%s", "non-matching", "non-matching"}, {null, "some path", "some path"} }; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/IngressServerExposerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static java.util.Collections.emptyMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServerExposer.SERVICE_NAME_PLACEHOLDER; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Serhii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class IngressServerExposerTest { @Mock private ExternalServiceExposureStrategy serviceExposureStrategy; @BeforeMethod public void setUp() throws Exception { when(serviceExposureStrategy.getExternalHost(any(), any())).thenReturn("host"); when(serviceExposureStrategy.getExternalPath(any(), any())).thenReturn("path"); } @Test public void shouldReplaceServerNamePlaceholders() { // given Map annotations = new HashMap<>(); annotations.put("ssl", "true"); annotations.put("websocket-service", SERVICE_NAME_PLACEHOLDER); IngressServerExposer exposer = new IngressServerExposer<>(serviceExposureStrategy, annotations, null, ""); KubernetesEnvironment env = KubernetesEnvironment.builder().build(); Map externalServers = new HashMap<>(); externalServers.put("ide", new ServerConfigImpl("6543", "http", "/", emptyMap())); // when exposer.expose(env, "editor", "ide", "server123", new ServicePort(), externalServers); // then Collection ingresses = env.getIngresses().values(); assertEquals(ingresses.size(), 1); Ingress ingress = ingresses.iterator().next(); assertEquals(ingress.getMetadata().getAnnotations().get("ssl"), "true"); assertEquals(ingress.getMetadata().getAnnotations().get("websocket-service"), "ide"); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/MultiHostExternalServiceExposureStrategyTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServicePortBuilder; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * @author Guy Daich */ public class MultiHostExternalServiceExposureStrategyTest { private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); private static final Map UNIQUE_SERVER_ATTRIBUTES = ImmutableMap.of("key", "value", ServerConfig.UNIQUE_SERVER_ATTRIBUTE, "true"); private static final String MACHINE_NAME = "pod/main"; private static final String SERVICE_NAME = SERVER_PREFIX + "12345678" + "-" + MACHINE_NAME; private static final String DOMAIN = "che.com"; private static final String LABELS = "foo=bar"; private IngressServerExposer externalServerExposer; private KubernetesEnvironment kubernetesEnvironment; @BeforeMethod public void setUp() throws Exception { Container container = new ContainerBuilder().withName("main").build(); Pod pod = new PodBuilder() .withNewMetadata() .withName("pod") .endMetadata() .withNewSpec() .withContainers(container) .endSpec() .build(); kubernetesEnvironment = KubernetesEnvironment.builder().setPods(ImmutableMap.of("pod", pod)).build(); externalServerExposer = new IngressServerExposer( new MultiHostExternalServiceExposureStrategy(DOMAIN, MULTI_HOST_STRATEGY), emptyMap(), LABELS, "%s"); } @Test public void shouldCreateIngressForServer() { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); IntOrString targetPort = new IntOrString(8080); ServicePort servicePort = new ServicePortBuilder() .withName("server-8080") .withPort(8080) .withProtocol("TCP") .withTargetPort(targetPort) .build(); Map serversToExpose = ImmutableMap.of("http-server", httpServerConfig); // when externalServerExposer.expose( kubernetesEnvironment, MACHINE_NAME, SERVICE_NAME, null, servicePort, serversToExpose); // then assertThatExternalServerIsExposed( MACHINE_NAME, SERVICE_NAME, "http-server", "tcp", 8080, servicePort, new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); } private void assertThatExternalServerIsExposed( String machineName, String serviceName, String serverNameRegex, String portProtocol, Integer port, ServicePort servicePort, ServerConfigImpl expected) { // ensure that required ingress is created String ingressName = serviceName + "-" + (expected.isUnique() ? serverNameRegex : servicePort.getName()); Ingress ingress = kubernetesEnvironment.getIngresses().get(ingressName); IngressRule ingressRule = ingress.getSpec().getRules().get(0); assertEquals(ingressRule.getHost(), ingressName + "." + DOMAIN); assertEquals(ingressRule.getHttp().getPaths().get(0).getPath(), "/"); IngressBackend backend = ingressRule.getHttp().getPaths().get(0).getBackend(); assertEquals(backend.getService().getName(), serviceName); assertEquals(backend.getService().getPort().getName(), servicePort.getName()); Annotations.Deserializer ingressAnnotations = Annotations.newDeserializer(ingress.getMetadata().getAnnotations()); Map servers = ingressAnnotations.servers(); ServerConfig serverConfig = servers.get(serverNameRegex); assertEquals(serverConfig, expected); assertEquals(ingressAnnotations.machineName(), machineName); assertEquals(ingress.getMetadata().getLabels().get("foo"), "bar"); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/external/TraefikGatewayRouteConfigGeneratorTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.external; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVICE_NAME_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVICE_PORT_ATTRIBUTE; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class TraefikGatewayRouteConfigGeneratorTest { private GatewayRouteConfigGenerator gatewayConfigGenerator; private GatewayRouteConfigGenerator gatewayConfigGeneratorNonDefaultDomain; @BeforeMethod public void setUp() { gatewayConfigGenerator = new TraefikGatewayRouteConfigGenerator(null); gatewayConfigGeneratorNonDefaultDomain = new TraefikGatewayRouteConfigGenerator("myorg.internal"); } @Test public void testGenerateGatewayConfig() throws InfrastructureException { String expectedConfig = "http:\n" + " routers:\n" + " external-server-1:\n" + " rule: \"PathPrefix(`/blabol-cesta`)\"\n" + " service: \"external-server-1\"\n" + " middlewares:\n" + " - \"external-server-1\"\n" + " priority: 100\n" + " services:\n" + " external-server-1:\n" + " loadBalancer:\n" + " servers:\n" + " - url: \"http://service-url.che-namespace.svc:1234\"\n" + " middlewares:\n" + " external-server-1:\n" + " stripPrefix:\n" + " prefixes:\n" + " - \"/blabol-cesta\""; ServerConfigImpl serverConfig = new ServerConfigImpl( "123", "https", "/", ImmutableMap.of( SERVICE_NAME_ATTRIBUTE, "service-url", SERVICE_PORT_ATTRIBUTE, "1234", ServerConfig.ENDPOINT_ORIGIN, "/blabol-cesta")); Map annotations = new Annotations.Serializer().server("s1", serverConfig).annotations(); ConfigMap routeConfig = new ConfigMapBuilder() .withNewMetadata() .withName("route") .withAnnotations(annotations) .endMetadata() .build(); gatewayConfigGenerator.addRouteConfig("external-server-1", routeConfig); Map generatedConfig = gatewayConfigGenerator.generate("che-namespace"); assertTrue(generatedConfig.containsKey("external-server-1.yml")); assertEquals(generatedConfig.get("external-server-1.yml"), expectedConfig); } @Test public void testGenerateGatewayConfigWithNonDefaultDomain() throws InfrastructureException { String expectedConfig = "http:\n" + " routers:\n" + " external-server-1:\n" + " rule: \"PathPrefix(`/blabol-cesta`)\"\n" + " service: \"external-server-1\"\n" + " middlewares:\n" + " - \"external-server-1\"\n" + " priority: 100\n" + " services:\n" + " external-server-1:\n" + " loadBalancer:\n" + " servers:\n" + " - url: \"http://service-url.che-namespace.svc.myorg.internal:1234\"\n" + " middlewares:\n" + " external-server-1:\n" + " stripPrefix:\n" + " prefixes:\n" + " - \"/blabol-cesta\""; ServerConfigImpl serverConfig = new ServerConfigImpl( "123", "https", "/", ImmutableMap.of( SERVICE_NAME_ATTRIBUTE, "service-url", SERVICE_PORT_ATTRIBUTE, "1234", ServerConfig.ENDPOINT_ORIGIN, "/blabol-cesta")); Map annotations = new Annotations.Serializer().server("s1", serverConfig).annotations(); ConfigMap routeConfig = new ConfigMapBuilder() .withNewMetadata() .withName("route") .withAnnotations(annotations) .endMetadata() .build(); gatewayConfigGeneratorNonDefaultDomain.addRouteConfig("external-server-1", routeConfig); Map generatedConfig = gatewayConfigGeneratorNonDefaultDomain.generate("che-namespace"); assertTrue(generatedConfig.containsKey("external-server-1.yml")); assertEquals(generatedConfig.get("external-server-1.yml"), expectedConfig); } @Test public void testMultipleRouteConfigsAreGeneratedAsMultipleMapEntries() throws InfrastructureException { ServerConfigImpl serverConfig = new ServerConfigImpl( "123", "https", "/", ImmutableMap.of( SERVICE_NAME_ATTRIBUTE, "service-url", SERVICE_PORT_ATTRIBUTE, "1234", ServerConfig.ENDPOINT_ORIGIN, "/blabol-cesta")); Map annotations = new Annotations.Serializer().server("s1", serverConfig).annotations(); ConfigMap routeConfig = new ConfigMapBuilder() .withNewMetadata() .withName("route") .withAnnotations(annotations) .endMetadata() .build(); gatewayConfigGenerator.addRouteConfig("c1", routeConfig); gatewayConfigGenerator.addRouteConfig("c2", routeConfig); Map generatedConfig = gatewayConfigGenerator.generate("che-namespace"); assertTrue(generatedConfig.containsKey("c1.yml")); assertTrue(generatedConfig.containsKey("c2.yml")); } @Test(expectedExceptions = InfrastructureException.class) public void failWhenMultipleServersInConfigmapAnnotations() throws InfrastructureException { ServerConfigImpl serverConfig = new ServerConfigImpl( "123", "https", "/", ImmutableMap.of( SERVICE_NAME_ATTRIBUTE, "service-url", SERVICE_PORT_ATTRIBUTE, "1234", ServerConfig.ENDPOINT_ORIGIN, "/blabol-cesta")); Map annotations = new Annotations.Serializer() .server("s1", serverConfig) .server("s2", serverConfig) .annotations(); ConfigMap routeConfig = new ConfigMapBuilder() .withNewMetadata() .withName("route") .withAnnotations(annotations) .endMetadata() .build(); gatewayConfigGenerator.addRouteConfig("c1", routeConfig); gatewayConfigGenerator.generate("che-namespace"); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/resolver/AbstractServerResolverTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver; import static org.testng.Assert.assertEquals; import org.eclipse.che.commons.annotation.Nullable; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class AbstractServerResolverTest { @Test(dataProvider = "buildPathFragments") public void testBuildPath(String fragment1, @Nullable String fragment2, String expectedResult) { assertEquals(AbstractServerResolver.buildPath(fragment1, fragment2), expectedResult); } @DataProvider public static Object[][] buildPathFragments() { return new Object[][] { new Object[] {"/", null, "/"}, new Object[] {"/a", null, "/a/"}, new Object[] {"/a/", null, "/a/"}, new Object[] {"/a", "", "/a/"}, new Object[] {"/a", "", "/a/"}, new Object[] {"/a", "/", "/a/"}, new Object[] {"/a", "b", "/a/b/"}, new Object[] {"/a", "/b", "/a/b/"}, new Object[] {"/a", "b/", "/a/b/"}, new Object[] {"/a", "/b/", "/a/b/"}, }; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/resolver/ConfigMapServerResolverTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class ConfigMapServerResolverTest { @Mock private ServerResolver nativeServerResolver; @Test public void shouldIncludeServersFromNativeResolver() { // given ServerImpl server = new ServerImpl("server", ServerStatus.UNKNOWN, emptyMap()); when(nativeServerResolver.resolveExternalServers("test")) .thenReturn(singletonMap("s1", server)); ConfigMapServerResolver serverResolver = new ConfigMapServerResolver(emptyList(), emptyList(), "che.host", nativeServerResolver); // when Map resolvedServers = serverResolver.resolve("test"); // then assertTrue(resolvedServers.containsKey("s1")); assertEquals(resolvedServers.get("s1"), server); } @Test public void shouldSetEndpointOrigin() { // given ConfigMap serverConfigMap = new ConfigMapBuilder() .withNewMetadata() .addToAnnotations( Annotations.newSerializer() .machineName("m1") .server( "svr", new ServerConfigImpl() .withPort("8080") .withProtocol("http") .withPath("/kachny") .withAttributes( ImmutableMap.of(ServerConfig.ENDPOINT_ORIGIN, "/kachny"))) .annotations()) .endMetadata() .build(); ConfigMapServerResolver serverResolver = new ConfigMapServerResolver( emptyList(), singletonList(serverConfigMap), "che.host", nativeServerResolver); // when Map resolvedServers = serverResolver.resolve("m1"); // then ServerImpl svr = resolvedServers.get("svr"); assertNotNull(svr); assertEquals("/kachny", ServerConfig.getEndpointOrigin(svr.getAttributes())); assertEquals("http://che.host/kachny/kachny/", svr.getUrl()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactoryProviderTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure; import static java.lang.String.format; import static org.eclipse.che.workspace.infrastructure.kubernetes.Warnings.UNKNOWN_SECURE_SERVER_EXPOSER_CONFIGURED_IN_WS_WARNING_CODE; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactoryProvider.SECURE_EXPOSER_IMPL_PROPERTY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactoryProvider.UNKNOWN_EXPOSER_ERROR_TEMPLATE; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertSame; import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.workspace.server.model.impl.WarningImpl; import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class SecureServerExposerFactoryProviderTest { private static final String EXPOSER1 = "exposer1"; private static final String EXPOSER2 = "exposer2"; private static final String NON_EXISTING_EXPOSER = "non-existing"; @Mock private SecureServerExposerFactory secureServerExposer1; @Mock private SecureServerExposerFactory secureServerExposer2; private Map> factories; private KubernetesEnvironment kubernetesEnvironment; @BeforeMethod public void setUp() { factories = new HashMap<>(); factories.put(EXPOSER1, secureServerExposer1); factories.put(EXPOSER2, secureServerExposer2); kubernetesEnvironment = KubernetesEnvironment.builder().build(); } @Test public void shouldReturnConfiguredSecureServerExposer() { // given SecureServerExposerFactoryProvider factoryProvider = new SecureServerExposerFactoryProvider<>(EXPOSER1, factories); // when SecureServerExposerFactory factory = factoryProvider.get(kubernetesEnvironment); // then assertSame(factory, secureServerExposer1); } @Test( expectedExceptions = ConfigurationException.class, expectedExceptionsMessageRegExp = "Unknown secure servers exposer 'non-existing' is configured. Currently supported: exposer1, exposer2.") public void shouldThrowAnExceptionIfConfiguredSecureServerWasNotFound() { // given new SecureServerExposerFactoryProvider<>(NON_EXISTING_EXPOSER, factories); } @Test public void shouldAddWarningIfSecureServerConfiguredInEnvironmentWasNotFound() { // given SecureServerExposerFactoryProvider factoryProvider = new SecureServerExposerFactoryProvider<>(EXPOSER1, factories); kubernetesEnvironment.setAttributes( ImmutableMap.of(SECURE_EXPOSER_IMPL_PROPERTY, NON_EXISTING_EXPOSER)); WarningImpl expectedWarning = new WarningImpl( UNKNOWN_SECURE_SERVER_EXPOSER_CONFIGURED_IN_WS_WARNING_CODE, format( UNKNOWN_EXPOSER_ERROR_TEMPLATE, NON_EXISTING_EXPOSER, String.join(", ", factories.keySet()))); SecureServerExposerFactory factory = factoryProvider.get(kubernetesEnvironment); // then assertSame(factory, secureServerExposer1); assertEquals(kubernetesEnvironment.getWarnings().size(), 1); assertEquals(kubernetesEnvironment.getWarnings().get(0), expectedWarning); } @Test public void shouldReturnSecureServerExposerConfiguredInEnvironment() { // given SecureServerExposerFactoryProvider factoryProvider = new SecureServerExposerFactoryProvider<>(EXPOSER1, factories); kubernetesEnvironment.setAttributes(ImmutableMap.of(SECURE_EXPOSER_IMPL_PROPERTY, EXPOSER2)); SecureServerExposerFactory factory = factoryProvider.get(kubernetesEnvironment); // then assertSame(factory, secureServerExposer2); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyConfigBuilderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import static org.testng.Assert.assertEquals; import java.net.URI; import java.util.HashSet; import java.util.Set; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.testng.reporters.Files; /** * Tests {@link JwtProxyConfigBuilder}. * * @author Sergii Leshchenko */ public class JwtProxyConfigBuilderTest { private JwtProxyConfigBuilder jwtProxyConfigBuilder; @BeforeMethod public void setUp() { jwtProxyConfigBuilder = new JwtProxyConfigBuilder( URI.create("http://che-host.com"), "wsmaster", "1m", "/app/loader.html", "workspace123"); } @Test public void shouldBuildJwtProxyConfigInYamlFormat() throws Exception { // given Set excludes = new HashSet<>(); jwtProxyConfigBuilder.addVerifierProxy( 8080, "http://tomcat:8080", new HashSet<>(excludes), false, "", "/there"); excludes.add("/api/liveness"); excludes.add("/other/exclude"); jwtProxyConfigBuilder.addVerifierProxy( 4101, "ws://terminal:4101", new HashSet<>(excludes), true, "/cookies", "/here"); // when String jwtProxyConfigYaml = jwtProxyConfigBuilder.build(); // then assertEquals( jwtProxyConfigYaml, Files.readFile(getClass().getClassLoader().getResourceAsStream("jwtproxy-confg.yaml"))); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyProvisionerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SECURE_SERVER_COOKIES_AUTH_ENABLED_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_UNIQUE_PART_SIZE; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.JWT_PROXY_CONFIG_FILE; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.JWT_PROXY_CONFIG_FOLDER; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.JWT_PROXY_PUBLIC_KEY_FILE; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.PUBLIC_KEY_FOOTER; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.PUBLIC_KEY_HEADER; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import java.net.URI; import java.security.KeyPair; import java.security.PublicKey; import java.util.Base64; import java.util.regex.Pattern; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ServiceExposureStrategyProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory.JwtProxyConfigBuilderFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link JwtProxyProvisioner}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class JwtProxyProvisionerTest { private static final String WORKSPACE_ID = "workspace123"; private static final Pattern JWTPROXY_SERVICE_NAME_PATTERN = Pattern.compile(SERVER_PREFIX + "\\w{" + SERVER_UNIQUE_PART_SIZE + "}-jwtproxy"); private final RuntimeIdentity runtimeId = new RuntimeIdentityImpl(WORKSPACE_ID, "env123", "owner123", "infraNamespace"); @Mock private SignatureKeyManager signatureKeyManager; @Mock private PublicKey publicKey; @Mock private JwtProxyConfigBuilderFactory configBuilderFactory; @Mock private ServiceExposureStrategyProvider serviceExposureStrategyProvider; @Mock private ExternalServiceExposureStrategy externalServiceExposureStrategy; @Mock private ExternalServiceExposureStrategy multiHostExternalServiceExposureStrategy; private CookiePathStrategy cookiePathStrategy = spy(new CookiePathStrategy(MULTI_HOST_STRATEGY)); private MultiHostCookiePathStrategy multiHostCookiePathStrategy = spy(new MultiHostCookiePathStrategy()); private JwtProxyProvisioner jwtProxyProvisioner; private KubernetesEnvironment k8sEnv; @BeforeMethod public void setUp() throws Exception { when(signatureKeyManager.getOrCreateKeyPair(anyString())) .thenReturn(new KeyPair(publicKey, null)); lenient().when(publicKey.getEncoded()).thenReturn("publickey".getBytes()); when(configBuilderFactory.create(any())) .thenReturn( new JwtProxyConfigBuilder( URI.create("http://che.api"), "iss", "1h", "", runtimeId.getWorkspaceId())); when(serviceExposureStrategyProvider.get()).thenReturn(externalServiceExposureStrategy); when(serviceExposureStrategyProvider.getMultiHostStrategy()) .thenReturn(multiHostExternalServiceExposureStrategy); jwtProxyProvisioner = new JwtProxyProvisioner( signatureKeyManager, configBuilderFactory, serviceExposureStrategyProvider, cookiePathStrategy, multiHostCookiePathStrategy, "eclipse/che-jwtproxy", "10m", "128mb", "0.02", "0.5", runtimeId); k8sEnv = KubernetesEnvironment.builder().build(); } @Test public void shouldReturnGeneratedJwtProxyServiceName() { // when String jwtProxyServiceName = jwtProxyProvisioner.getServiceName(); // then assertTrue(JWTPROXY_SERVICE_NAME_PATTERN.matcher(jwtProxyServiceName).matches()); } @Test public void shouldProvisionJwtProxyRelatedObjectsIntoKubernetesEnvironment() throws Exception { // given ServerConfigImpl secureServer = new ServerConfigImpl("4401/tcp", "ws", "/", emptyMap()); ServicePort port = new ServicePort(); port.setTargetPort(new IntOrString(4401)); // when jwtProxyProvisioner.expose( k8sEnv, podWithName(), "machine", "terminal", port, "TCP", false, ImmutableMap.of("server", secureServer)); // then InternalMachineConfig jwtProxyMachine = k8sEnv.getMachines().get(JwtProxyProvisioner.JWT_PROXY_MACHINE_NAME); assertNotNull(jwtProxyMachine); ConfigMap configMap = k8sEnv.getConfigMaps().get(jwtProxyProvisioner.getConfigMapName()); assertNotNull(configMap); assertEquals( configMap.getData().get(JWT_PROXY_PUBLIC_KEY_FILE), PUBLIC_KEY_HEADER + Base64.getEncoder().encodeToString("publickey".getBytes()) + PUBLIC_KEY_FOOTER); assertNotNull(configMap.getData().get(JWT_PROXY_CONFIG_FILE)); Pod jwtProxyPod = k8sEnv.getInjectablePodsCopy().getOrDefault("machine", emptyMap()).get("che-jwtproxy"); assertNotNull(jwtProxyPod); assertEquals(1, jwtProxyPod.getSpec().getContainers().size()); Container jwtProxyContainer = jwtProxyPod.getSpec().getContainers().get(0); assertEquals(jwtProxyContainer.getArgs().size(), 2); assertEquals(jwtProxyContainer.getArgs().get(0), "-config"); assertEquals( jwtProxyContainer.getArgs().get(1), JWT_PROXY_CONFIG_FOLDER + "/" + JWT_PROXY_CONFIG_FILE); assertEquals(jwtProxyContainer.getEnv().size(), 1); EnvVar xdgHome = jwtProxyContainer.getEnv().get(0); assertEquals(xdgHome.getName(), "XDG_CONFIG_HOME"); assertEquals(xdgHome.getValue(), JWT_PROXY_CONFIG_FOLDER); Service jwtProxyService = k8sEnv.getServices().get(jwtProxyProvisioner.getServiceName()); assertNotNull(jwtProxyService); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Secure servers which expose the same port should have " + "the same `cookiesAuthEnabled` value\\.") public void shouldThrowAnExceptionIfServersHaveDifferentValueForCookiesAuthEnabled() throws Exception { // given ServerConfigImpl server1 = new ServerConfigImpl( "4401/tcp", "ws", "/", ImmutableMap.of(SECURE_SERVER_COOKIES_AUTH_ENABLED_ATTRIBUTE, "true")); ServerConfigImpl server2 = new ServerConfigImpl( "4401/tcp", "http", "/", ImmutableMap.of(SECURE_SERVER_COOKIES_AUTH_ENABLED_ATTRIBUTE, "false")); ServerConfigImpl server3 = new ServerConfigImpl("4401/tcp", "ws", "/", emptyMap()); ServicePort port = new ServicePort(); port.setTargetPort(new IntOrString(4401)); // when jwtProxyProvisioner.expose( k8sEnv, podWithName(), "machine", "terminal", port, "TCP", false, ImmutableMap.of("server1", server1, "server2", server2, "server3", server3)); } @Test public void shouldUseCookiesAuthEnabledFromServersConfigs() throws Exception { // given JwtProxyConfigBuilder configBuilder = mock(JwtProxyConfigBuilder.class); when(configBuilderFactory.create(any())).thenReturn(configBuilder); jwtProxyProvisioner = new JwtProxyProvisioner( signatureKeyManager, configBuilderFactory, serviceExposureStrategyProvider, cookiePathStrategy, multiHostCookiePathStrategy, "eclipse/che-jwtproxy", "10m", "128mb", "0.02", "500m", runtimeId); ServerConfigImpl server1 = new ServerConfigImpl( "4401/tcp", "http", "/", ImmutableMap.of(SECURE_SERVER_COOKIES_AUTH_ENABLED_ATTRIBUTE, "true")); ServerConfigImpl server2 = new ServerConfigImpl( "4401/tcp", "ws", "/", ImmutableMap.of(SECURE_SERVER_COOKIES_AUTH_ENABLED_ATTRIBUTE, "true")); ServicePort port = new ServicePort(); port.setTargetPort(new IntOrString(4401)); // when jwtProxyProvisioner.expose( k8sEnv, podWithName(), "machine", "terminal", port, "TCP", false, ImmutableMap.of("server1", server1)); // then verify(configBuilder).addVerifierProxy(any(), any(), any(), eq(true), any(), any()); } @Test public void shouldFalseValueAsDefaultForCookiesAuthEnabledAttribute() throws Exception { // given JwtProxyConfigBuilder configBuilder = mock(JwtProxyConfigBuilder.class); when(configBuilderFactory.create(any())).thenReturn(configBuilder); jwtProxyProvisioner = new JwtProxyProvisioner( signatureKeyManager, configBuilderFactory, serviceExposureStrategyProvider, cookiePathStrategy, multiHostCookiePathStrategy, "eclipse/che-jwtproxy", "10m", "128mb", "0.02", "0.5", runtimeId); ServerConfigImpl server1 = new ServerConfigImpl("4401/tcp", "http", "/", emptyMap()); ServicePort port = new ServicePort(); port.setTargetPort(new IntOrString(4401)); // when jwtProxyProvisioner.expose( k8sEnv, podWithName(), "machine", "terminal", port, "TCP", false, ImmutableMap.of("server1", server1)); // then verify(configBuilder) .addVerifierProxy( eq(4400), eq("http://terminal:4401"), eq(emptySet()), eq(false), eq("/"), isNull()); } @Test public void shouldBindToLocalhostWhenNoServiceForServerExists() throws Exception { // given JwtProxyConfigBuilder configBuilder = mock(JwtProxyConfigBuilder.class); when(configBuilderFactory.create(any())).thenReturn(configBuilder); jwtProxyProvisioner = new JwtProxyProvisioner( signatureKeyManager, configBuilderFactory, serviceExposureStrategyProvider, cookiePathStrategy, multiHostCookiePathStrategy, "eclipse/che-jwtproxy", "10m", "128mb", "0.02", "0.5", runtimeId); ServerConfigImpl server1 = new ServerConfigImpl("4401/tcp", "http", "/", emptyMap()); ServicePort port = new ServicePort(); port.setTargetPort(new IntOrString(4401)); // when jwtProxyProvisioner.expose( k8sEnv, podWithName(), "machine", null, port, "TCP", false, ImmutableMap.of("server1", server1)); // then verify(configBuilder) .addVerifierProxy( eq(4400), eq("http://127.0.0.1:4401"), eq(emptySet()), eq(false), eq("/"), isNull()); } @Test public void multiHostStrategiesUsedForServerRequiringSubdomain() throws Exception { // given JwtProxyConfigBuilder configBuilder = mock(JwtProxyConfigBuilder.class); when(configBuilderFactory.create(any())).thenReturn(configBuilder); jwtProxyProvisioner = new JwtProxyProvisioner( signatureKeyManager, configBuilderFactory, serviceExposureStrategyProvider, cookiePathStrategy, multiHostCookiePathStrategy, "eclipse/che-jwtproxy", "10m", "128mb", "0.02", "0.5", runtimeId); ServerConfigImpl server1 = new ServerConfigImpl("4401/tcp", "http", "/", emptyMap()); ServicePort port = new ServicePort(); port.setTargetPort(new IntOrString(4401)); // when jwtProxyProvisioner.expose( k8sEnv, podWithName(), "machine", null, port, "TCP", true, ImmutableMap.of("server1", server1)); // then verify(configBuilder) .addVerifierProxy( eq(4400), eq("http://127.0.0.1:4401"), eq(emptySet()), eq(false), eq("/"), isNull()); verify(externalServiceExposureStrategy, never()).getExternalPath(any(), any()); verify(cookiePathStrategy, never()).get(any(), any()); verify(multiHostExternalServiceExposureStrategy).getExternalPath(any(), any()); verify(multiHostCookiePathStrategy).get(any(), any()); } private static PodData podWithName() { ObjectMeta meta = new ObjectMeta(); meta.setName("a-pod-name"); return new PodData(null, meta); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxySecureServerExposerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.Collections; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServerExposer; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link JwtProxySecureServerExposer} * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class JwtProxySecureServerExposerTest { private static final String MACHINE_SERVICE_NAME = "service123"; private static final String MACHINE_NAME = "machine123"; public static final String JWT_PROXY_SERVICE_NAME = "jwtProxyServiceName"; @Mock private KubernetesEnvironment k8sEnv; @Mock private JwtProxyProvisioner jwtProxyProvisioner; @Mock private IngressServerExposer externalServerExposer; private JwtProxySecureServerExposer secureServerExposer; @BeforeMethod public void setUp() { secureServerExposer = new JwtProxySecureServerExposer<>(jwtProxyProvisioner, externalServerExposer); } @Test public void shouldExposeSecureServersWithNewJwtProxyServicePort() throws Exception { // given ServicePort machineServicePort = new ServicePort(); machineServicePort.setTargetPort(new IntOrString(8080)); machineServicePort.setProtocol("TCP"); Map servers = ImmutableMap.of( "server1", new ServerConfigImpl("8080/tcp", "http", "/api", ImmutableMap.of("secure", "true")), "server2", new ServerConfigImpl("8080/tcp", "ws", "/connect", ImmutableMap.of("secure", "true"))); ServicePort jwtProxyServicePort = new ServicePort(); doReturn(jwtProxyServicePort) .when(jwtProxyProvisioner) .expose(any(), any(), anyString(), anyString(), any(), anyString(), anyBoolean(), any()); when(jwtProxyProvisioner.getServiceName()).thenReturn(JWT_PROXY_SERVICE_NAME); when(externalServerExposer.getStrategyConformingServers(eq(servers))).thenReturn(servers); // when secureServerExposer.expose( k8sEnv, null, MACHINE_NAME, MACHINE_SERVICE_NAME, null, machineServicePort, servers); // then verify(jwtProxyProvisioner) .expose( eq(k8sEnv), any(), anyString(), eq(MACHINE_SERVICE_NAME), eq(machineServicePort), eq("TCP"), eq(false), any()); verify(externalServerExposer) .expose( eq(k8sEnv), eq(MACHINE_NAME), eq(JWT_PROXY_SERVICE_NAME), isNull(), eq(jwtProxyServicePort), eq(servers)); } @Test public void shouldUseMultiHostStrategyForSubdomainRequiringServers() throws Exception { // given ServicePort machineServicePort = new ServicePort(); machineServicePort.setTargetPort(new IntOrString(8080)); machineServicePort.setProtocol("TCP"); Map servers = ImmutableMap.of( "server1", new ServerConfigImpl("8080/tcp", "http", "/api", ImmutableMap.of("secure", "true")), "server2", new ServerConfigImpl("8080/tcp", "ws", "/connect", ImmutableMap.of("secure", "true"))); Map conformingServers = Collections.singletonMap("server1", servers.get("server1")); Map subdomainServers = Collections.singletonMap("server2", servers.get("server2")); ServicePort jwtProxyServicePort = new ServicePort(); doReturn(jwtProxyServicePort) .when(jwtProxyProvisioner) .expose(any(), any(), anyString(), anyString(), any(), anyString(), anyBoolean(), any()); when(jwtProxyProvisioner.getServiceName()).thenReturn(JWT_PROXY_SERVICE_NAME); when(externalServerExposer.getStrategyConformingServers(eq(servers))) .thenReturn(conformingServers); when(externalServerExposer.getServersRequiringSubdomain(eq(servers))) .thenReturn(subdomainServers); // when secureServerExposer.expose( k8sEnv, null, MACHINE_NAME, MACHINE_SERVICE_NAME, null, machineServicePort, servers); // then verify(jwtProxyProvisioner) .expose( eq(k8sEnv), any(), anyString(), eq(MACHINE_SERVICE_NAME), eq(machineServicePort), eq("TCP"), eq(false), any()); verify(jwtProxyProvisioner) .expose( eq(k8sEnv), any(), anyString(), eq(MACHINE_SERVICE_NAME), eq(machineServicePort), eq("TCP"), eq(true), any()); verify(externalServerExposer) .expose( eq(k8sEnv), eq(MACHINE_NAME), eq(JWT_PROXY_SERVICE_NAME), isNull(), eq(jwtProxyServicePort), eq(conformingServers)); verify(externalServerExposer) .expose( eq(k8sEnv), eq(MACHINE_NAME), eq(JWT_PROXY_SERVICE_NAME), isNull(), eq(jwtProxyServicePort), eq(subdomainServers)); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/PassThroughProxyProvisionerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy; import static java.util.Collections.singleton; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ServicePort; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ServiceExposureStrategyProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.factory.JwtProxyConfigBuilderFactory; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class PassThroughProxyProvisionerTest { private static final String WORKSPACE_ID = "workspace123"; private final RuntimeIdentity runtimeId = new RuntimeIdentityImpl(WORKSPACE_ID, "env123", "owner123", "infraNamespace"); // PassThroughProxyProvisioner shares much of the codebase with the JwtProxyProvisioner. We only // test the different behavior here, while the majority of the tests are present in the // JwtProxyProvisionerTest @Test public void shouldConfigureProxyWithExcludes() throws Exception { // given KubernetesEnvironment k8sEnv = KubernetesEnvironment.builder().build(); JwtProxyConfigBuilderFactory configBuilderFactory = mock(JwtProxyConfigBuilderFactory.class); JwtProxyConfigBuilder configBuilder = mock(JwtProxyConfigBuilder.class); when(configBuilderFactory.create(any())).thenReturn(configBuilder); ServiceExposureStrategyProvider exposureStrategyProvider = mock(ServiceExposureStrategyProvider.class); when(exposureStrategyProvider.get()).thenReturn(mock(ExternalServiceExposureStrategy.class)); when(exposureStrategyProvider.getMultiHostStrategy()) .thenReturn(mock(ExternalServiceExposureStrategy.class)); PassThroughProxyProvisioner passThroughProxyProvisioner = new PassThroughProxyProvisioner( configBuilderFactory, exposureStrategyProvider, new CookiePathStrategy(MULTI_HOST_STRATEGY), new MultiHostCookiePathStrategy(), "eclipse/che-jwtproxy", "10m", "128mb", "0.02", "0.5", runtimeId); Map attrs = new HashMap<>(); ServerConfig.setCookiesAuthEnabled(attrs, true); ServerConfig.setSecure(attrs, true); ServerConfigImpl server1 = new ServerConfigImpl("4401/tcp", "http", "/", attrs); ServicePort port = new ServicePort(); port.setTargetPort(new IntOrString(8080)); // when passThroughProxyProvisioner.expose( k8sEnv, podWithName(), "machine", "terminal", port, "TCP", false, ImmutableMap.of("server1", server1)); // then verify(configBuilder) .addVerifierProxy( eq(4400), eq("http://terminal:8080"), eq(singleton("/")), eq(false), eq("/"), isNull()); } private static PodData podWithName() { ObjectMeta meta = new ObjectMeta(); meta.setName("a-pod-name"); return new PodData(null, meta); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/ContainersTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.Quantity; import io.fabric8.kubernetes.api.model.ResourceRequirements; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link Containers}. * * @author Anton Korneta */ @Listeners(MockitoTestNGListener.class) public class ContainersTest { private static final long RAM_LIMIT = 2147483648L; private static final long RAM_REQUEST = 1474836480L; private static final float CPU_LIMIT = 1.782f; private static final float CPU_REQUEST = 0.223f; @Mock private Container container; @Mock private ResourceRequirements resource; @Captor private ArgumentCaptor resourceCaptor; private final Map limits = new HashMap<>(); @BeforeMethod public void setup() { when(container.getResources()).thenReturn(resource); limits.put("memory", new Quantity(String.valueOf(RAM_LIMIT))); limits.put("cpu", new Quantity("1.5")); lenient() .when(resource.getLimits()) .thenReturn( ImmutableMap.of( "memory", new Quantity(String.valueOf(RAM_LIMIT)), "cpu", new Quantity(String.valueOf(CPU_LIMIT)))); lenient() .when(resource.getRequests()) .thenReturn( ImmutableMap.of( "memory", new Quantity(String.valueOf(RAM_REQUEST)), "cpu", new Quantity(String.valueOf(CPU_REQUEST)))); } @Test public void testReturnContainerRamLimitAndRequest() { long limit = Containers.getRamLimit(container); long request = Containers.getRamRequest(container); assertEquals(limit, RAM_LIMIT); assertEquals(request, RAM_REQUEST); } @Test public void testReturnContainerCPULimitAndRequest() { float limit = Containers.getCpuLimit(container); float request = Containers.getCpuRequest(container); assertEquals(limit, CPU_LIMIT); assertEquals(request, CPU_REQUEST); } @Test public void testReturnsZeroWhenContainerResourcesIsNull() { when(container.getResources()).thenReturn(null); assertEquals(Containers.getRamLimit(container), 0); assertEquals(Containers.getRamRequest(container), 0); assertEquals(Containers.getCpuLimit(container), 0, 0.0); assertEquals(Containers.getCpuRequest(container), 0, 0.0); } @Test public void testReturnsZeroResourceWhenResourcesDoesNotContainIt() { when(resource.getLimits()).thenReturn(Collections.emptyMap()); when(resource.getRequests()).thenReturn(Collections.emptyMap()); assertEquals(Containers.getRamLimit(container), 0); assertEquals(Containers.getRamRequest(container), 0); assertEquals(Containers.getCpuLimit(container), 0, 0.0); assertEquals(Containers.getCpuRequest(container), 0, 0.0); } @Test public void testReturnsZeroContainerLimitWhenActualValueIsNull() { when(resource.getLimits()) .thenReturn(ImmutableMap.of("memory", new Quantity(), "cpu", new Quantity())); assertEquals(Containers.getRamLimit(container), 0); assertEquals(Containers.getCpuLimit(container), 0, 0.0); } @Test public void testReturnsZeroContainerRequestWhenActualValueIsNull() { when(resource.getRequests()) .thenReturn(ImmutableMap.of("memory", new Quantity(), "cpu", new Quantity())); assertEquals(Containers.getRamRequest(container), 0); assertEquals(Containers.getCpuRequest(container), 0, 0.0); } @Test public void testAddContainerRamLimitWhenResourceIsNull() { when(container.getResources()).thenReturn(null); Containers.addRamLimit(container, RAM_LIMIT); verify(container).setResources(resourceCaptor.capture()); final ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getLimits().get("memory").getAmount(), String.valueOf(RAM_LIMIT)); } @Test public void testAddContainerCPULimitWhenResourceIsNull() { when(container.getResources()).thenReturn(null); Containers.addCpuLimit(container, CPU_LIMIT); verify(container).setResources(resourceCaptor.capture()); final ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getLimits().get("cpu").getAmount(), String.valueOf(CPU_LIMIT)); } @Test public void testAddContainerRamRequestWhenResourceIsNull() { when(container.getResources()).thenReturn(null); Containers.addRamRequest(container, RAM_REQUEST); verify(container).setResources(resourceCaptor.capture()); final ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getRequests().get("memory").getAmount(), String.valueOf(RAM_REQUEST)); } @Test public void testAddContainerCPURequestWhenResourceIsNull() { when(container.getResources()).thenReturn(null); Containers.addCpuRequest(container, CPU_REQUEST); verify(container).setResources(resourceCaptor.capture()); final ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getRequests().get("cpu").getAmount(), String.valueOf(CPU_REQUEST)); } @Test public void testAddContainerRamLimitWhenResourceDoesNotContainAnyLimits() { when(resource.getLimits()).thenReturn(null); Containers.addRamLimit(container, RAM_LIMIT); verify(container).setResources(resourceCaptor.capture()); final ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getLimits().get("memory").getAmount(), String.valueOf(RAM_LIMIT)); } @Test public void testAddContainerCPULimitWhenResourceDoesNotContainAnyLimits() { when(resource.getLimits()).thenReturn(null); Containers.addCpuLimit(container, CPU_LIMIT); verify(container).setResources(resourceCaptor.capture()); final ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getLimits().get("cpu").getAmount(), String.valueOf(CPU_LIMIT)); } @Test public void testAddContainerRamRequestWhenResourceDoesNotContainAnyLimits() { when(resource.getLimits()).thenReturn(null); Containers.addRamRequest(container, RAM_REQUEST); verify(container).setResources(resourceCaptor.capture()); final ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getRequests().get("memory").getAmount(), String.valueOf(RAM_REQUEST)); } @Test public void testAddContainerCPURequestWhenResourceDoesNotContainAnyLimits() { when(resource.getLimits()).thenReturn(null); Containers.addCpuRequest(container, CPU_REQUEST); verify(container).setResources(resourceCaptor.capture()); final ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getRequests().get("cpu").getAmount(), String.valueOf(CPU_REQUEST)); } @Test(dataProvider = "k8sNotionRamLimitProvider") public void testAddContainerRamLimitInK8sNotion( String ramLimit, String amount, String format, ResourceRequirements resources) { when(container.getResources()).thenReturn(resources); Containers.addRamLimit(container, ramLimit); verify(container).setResources(resourceCaptor.capture()); ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getLimits().get("memory").getAmount(), amount); assertEquals(captured.getLimits().get("memory").getFormat(), format); } @Test(dataProvider = "k8sNotionRamLimitProvider") public void testAddContainerRamRequestInK8sNotion( String ramRequest, String amount, String format, ResourceRequirements resources) { when(container.getResources()).thenReturn(resources); Containers.addRamRequest(container, ramRequest); verify(container).setResources(resourceCaptor.capture()); ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getRequests().get("memory").getAmount(), amount); assertEquals(captured.getRequests().get("memory").getFormat(), format); } @DataProvider public static Object[][] k8sNotionRamLimitProvider() { return new Object[][] { {"123456789", "123456789", "", new ResourceRequirements()}, {"1M", "1", "M", new ResourceRequirements()}, {"10Ki", "10", "Ki", null}, {"10G", "10", "G", null}, }; } @Test(dataProvider = "k8sNotionCpuLimitProvider") public void testAddContainerCPULimitInK8sNotion( String cpuLimit, String amount, String format, ResourceRequirements resources) { when(container.getResources()).thenReturn(resources); Containers.addCpuLimit(container, cpuLimit); verify(container).setResources(resourceCaptor.capture()); ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getLimits().get("cpu").getAmount(), amount); assertEquals(captured.getLimits().get("cpu").getFormat(), format); } @Test(dataProvider = "k8sNotionCpuLimitProvider") public void testAddContainerCpuRequestInK8sNotion( String cpuRequest, String amount, String format, ResourceRequirements resources) { when(container.getResources()).thenReturn(resources); Containers.addCpuRequest(container, cpuRequest); verify(container).setResources(resourceCaptor.capture()); ResourceRequirements captured = resourceCaptor.getValue(); assertEquals(captured.getRequests().get("cpu").getAmount(), amount); assertEquals(captured.getRequests().get("cpu").getFormat(), format); } @DataProvider public static Object[][] k8sNotionCpuLimitProvider() { return new Object[][] { {"100m", "100", "m", new ResourceRequirements()}, {"1000m", "1000", "m", new ResourceRequirements()}, {"112m", "112", "m", null}, {"155m", "155", "m", null}, }; } @Test public void testReturnContainerCPULimitAndRequestConvertedToFullCores() { when(resource.getLimits()).thenReturn(ImmutableMap.of("cpu", new Quantity("1000", "m"))); when(resource.getRequests()).thenReturn(ImmutableMap.of("cpu", new Quantity("30", "m"))); assertEquals(Containers.getCpuLimit(container), 1); assertEquals(Containers.getCpuRequest(container), 0.03, 0.000000001); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/EnvVarsTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.eclipse.che.workspace.infrastructure.kubernetes.util.EnvVars.extractReferencedVariables; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.EnvVarSource; import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.SecretKeySelectorBuilder; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class EnvVarsTest { private EnvVars envVars = new EnvVars(); @Test(dataProvider = "detectReferencesTestValues") public void shouldDetectReferences(String value, Set expected, String caseName) { assertEquals( extractReferencedVariables(var("name", value)), expected, caseName + ": just value"); assertEquals( extractReferencedVariables(var("name", "v" + value)), expected, caseName + ": value with prefix"); assertEquals( extractReferencedVariables(var("name", "v" + value + "v")), expected, caseName + ": value with prefix and postfix"); assertEquals( extractReferencedVariables(var("name", value + "v")), expected, caseName + ": value with postfix"); } @DataProvider public static Object[][] detectReferencesTestValues() { return new Object[][] { new Object[] {"value", emptySet(), "no refs"}, new Object[] {"$(NO_REF", emptySet(), "unclosed ref"}, new Object[] {"$$(NO_REF)", emptySet(), "escaped ref"}, new Object[] {"$NO_REF)", emptySet(), "invalid start ref"}, new Object[] {"$(NO REF)", emptySet(), "invalid name ref"}, new Object[] {"$(REF)", singleton("REF"), "valid ref"} }; } @Test public void shouldDetectMultipleReferences() throws Exception { // given EnvVar envVar = var("a", "$(b) $(c) $$(d)"); // when Set refs = extractReferencedVariables(envVar); // then assertEquals(refs, new HashSet<>(Arrays.asList("b", "c"))); } @Test public void shouldReturnEmptySetOnEnvContainingValueFrom() throws Exception { // given EnvVar envVar = var("a", null); envVar.setValueFrom( new EnvVarSourceBuilder() .withSecretKeyRef( new SecretKeySelectorBuilder() .withName("secret_name") .withKey("secret_key") .build()) .build()); // when Set refs = extractReferencedVariables(envVar); // then assertTrue(refs.isEmpty()); } @Test public void shouldProvisionEnvIfContainersDoeNotHaveEnvAtAll() throws Exception { // given PodData pod = new PodData( new PodBuilder() .withNewMetadata() .withName("pod") .endMetadata() .withNewSpec() .withInitContainers(new ContainerBuilder().withName("initContainer").build()) .withContainers(new ContainerBuilder().withName("container").build()) .endSpec() .build()); // when envVars.apply(pod, singletonList(new EnvImpl("TEST_ENV", "anyValue"))); // then List initCEnv = pod.getSpec().getInitContainers().get(0).getEnv(); assertEquals(initCEnv.size(), 1); assertEquals(initCEnv.get(0), new EnvVar("TEST_ENV", "anyValue", null)); List containerEnv = pod.getSpec().getContainers().get(0).getEnv(); assertEquals(containerEnv.size(), 1); assertEquals(containerEnv.get(0), new EnvVar("TEST_ENV", "anyValue", null)); } @Test public void shouldOverrideEnvOnApplyingEnvVarIfContainersAlreadyHaveSomeEnvVars() throws Exception { // given EnvVar existingInitCEnvVar = new EnvVar("TEST_ENV", "value", null); EnvVar existingCEnvVar = new EnvVar("TEST_ENV", null, new EnvVarSource()); PodData pod = new PodData( new PodBuilder() .withNewMetadata() .withName("pod") .endMetadata() .withNewSpec() .withInitContainers( new ContainerBuilder() .withName("initContainer") .withEnv(copy(existingInitCEnvVar)) .build()) .withContainers( new ContainerBuilder() .withName("container") .withEnv(copy(existingCEnvVar)) .build()) .endSpec() .build()); // when envVars.apply(pod, singletonList(new EnvImpl("TEST_ENV", "anyValue"))); // then List initCEnv = pod.getSpec().getInitContainers().get(0).getEnv(); assertEquals(initCEnv.size(), 1); assertEquals(initCEnv.get(0), new EnvVar("TEST_ENV", "anyValue", null)); List containerEnv = pod.getSpec().getContainers().get(0).getEnv(); assertEquals(containerEnv.size(), 1); assertEquals(containerEnv.get(0), new EnvVar("TEST_ENV", "anyValue", null)); } @Test public void shouldProvisionEnvIntoK8SListIfContainerAlreadyHasSomeEnvVars() throws Exception { // given EnvVar existingInitCEnvVar = new EnvVar("ENV", "value", null); EnvVar existingCEnvVar = new EnvVar("ENV", null, new EnvVarSource()); PodData pod = new PodData( new PodBuilder() .withNewMetadata() .withName("pod") .endMetadata() .withNewSpec() .withInitContainers( new ContainerBuilder() .withName("initContainer") .withEnv(copy(existingInitCEnvVar)) .build()) .withContainers( new ContainerBuilder() .withName("container") .withEnv(copy(existingCEnvVar)) .build()) .endSpec() .build()); // when envVars.apply(pod, singletonList(new EnvImpl("TEST_ENV", "anyValue"))); // then List initCEnv = pod.getSpec().getInitContainers().get(0).getEnv(); assertEquals(initCEnv.size(), 2); assertEquals(initCEnv.get(0), existingInitCEnvVar); assertEquals(initCEnv.get(1), new EnvVar("TEST_ENV", "anyValue", null)); List containerEnv = pod.getSpec().getContainers().get(0).getEnv(); assertEquals(containerEnv.size(), 2); assertEquals(containerEnv.get(0), existingCEnvVar); assertEquals(containerEnv.get(1), new EnvVar("TEST_ENV", "anyValue", null)); } private static EnvVar var(String name, String value) { return new EnvVar(name, value, null); } private EnvVar copy(EnvVar envVar) { return new EnvVar(envVar.getName(), envVar.getValue(), envVar.getValueFrom()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/GatewayConfigmapLabelsTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import java.util.Map; import org.eclipse.che.inject.ConfigurationException; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class GatewayConfigmapLabelsTest { @Test(dataProvider = "isGatewayConfigData") public void testIsGatewayConfig( String labelsProperty, Map labels, boolean isGatewayConfigExpected) { GatewayConfigmapLabels gatewayConfigmapLabels = new GatewayConfigmapLabels(labelsProperty); ConfigMap cm = new ConfigMapBuilder().withNewMetadata().withLabels(labels).endMetadata().build(); assertEquals(gatewayConfigmapLabels.isGatewayConfig(cm), isGatewayConfigExpected); } @Test(expectedExceptions = ConfigurationException.class) public void failsToConstructWhenLabelsAreNull() { new GatewayConfigmapLabels(null); } @Test(expectedExceptions = ConfigurationException.class) public void failsToConstructWhenLabelsAreEmpty() { new GatewayConfigmapLabels(""); } @Test(expectedExceptions = ConfigurationException.class) public void failsToConstructWhenLabelsAreNotValid() { new GatewayConfigmapLabels("badvalue"); } @DataProvider public Object[][] isGatewayConfigData() { return new Object[][] { { "app=che,component=che-gateway-config", ImmutableMap.of("app", "che", "component", "che-gateway-config"), true }, { "app=che, component=che-gateway-config", ImmutableMap.of("app", "che", "component", "che-gateway-config"), true }, {"app=che", ImmutableMap.of("app", "che", "component", "che-gateway-config"), true}, { "app=che,component=che-gateway-config", ImmutableMap.of("app", "cheche", "component", "che-gateway-config"), false }, {"app=che,component=che-gateway-config", ImmutableMap.of("app", "cheche"), false}, { "app=che,component=che-gateway-config", ImmutableMap.of("component", "che-gateway-config"), false }, }; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/IngressesTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import static java.util.Collections.singletonList; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServiceSpec; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPath; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressRuleValue; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; import io.fabric8.kubernetes.api.model.networking.v1.IngressServiceBackend; import io.fabric8.kubernetes.api.model.networking.v1.IngressSpec; import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPort; import java.util.Optional; import org.testng.annotations.Test; public class IngressesTest { @Test public void findHostWhenPortDefinedByString() { final String SERVER_PORT_NAME = "server-8080"; final int PORT = 8080; Service service = createService(SERVER_PORT_NAME, PORT); Ingress ingress = createIngress( new IngressBackend( null, new IngressServiceBackend( "servicename", new ServiceBackendPort(SERVER_PORT_NAME, PORT)))); Optional foundRule = Ingresses.findIngressRuleForServicePort(singletonList(ingress), service, PORT); assertTrue(foundRule.isPresent()); assertEquals(foundRule.get().getHost(), "ingresshost"); } @Test public void findHostWhenPortDefinedByInt() { final String SERVER_PORT_NAME = "server-8080"; final int PORT = 8080; Service service = createService(SERVER_PORT_NAME, PORT); Ingress ingress = createIngress( new IngressBackend( null, new IngressServiceBackend( "servicename", new ServiceBackendPort(SERVER_PORT_NAME, PORT)))); Optional foundRule = Ingresses.findIngressRuleForServicePort(singletonList(ingress), service, PORT); assertTrue(foundRule.isPresent()); assertEquals(foundRule.get().getHost(), "ingresshost"); } @Test public void emptyWhenPortByStringAndNotFound() { final String SERVER_PORT_NAME = "server-8080"; final int PORT = 8080; Service service = createService(SERVER_PORT_NAME, PORT); Ingress ingress = createIngress( new IngressBackend( null, new IngressServiceBackend( "servicename", new ServiceBackendPort("does not exist", null)))); Optional foundRule = Ingresses.findIngressRuleForServicePort(singletonList(ingress), service, PORT); assertFalse(foundRule.isPresent()); } @Test public void emptyWhenPortByIntAndNotFound() { final String SERVER_PORT_NAME = "server-8080"; final int PORT = 8080; Service service = createService(SERVER_PORT_NAME, PORT); Ingress ingress = createIngress( new IngressBackend( null, new IngressServiceBackend("servicename", new ServiceBackendPort(null, 666)))); Optional foundRule = Ingresses.findIngressRuleForServicePort(singletonList(ingress), service, PORT); assertFalse(foundRule.isPresent()); } private Service createService(String serverPortName, int port) { Service service = new Service(); ObjectMeta serviceMeta = new ObjectMeta(); serviceMeta.setName("servicename"); service.setMetadata(serviceMeta); ServiceSpec serviceSpec = new ServiceSpec(); serviceSpec.setPorts( singletonList( new ServicePort(null, serverPortName, null, port, "TCP", new IntOrString(port)))); service.setSpec(serviceSpec); return service; } private Ingress createIngress(IngressBackend backend) { Ingress ingress = new Ingress(); ObjectMeta ingressMeta = new ObjectMeta(); ingressMeta.setName("ingressname"); ingress.setMetadata(ingressMeta); IngressSpec ingressSpec = new IngressSpec(); IngressRule ingressRule = new IngressRule(); ingressRule.setHost("ingresshost"); ingressRule.setHttp( new HTTPIngressRuleValue(singletonList(new HTTPIngressPath(backend, null, null)))); ingressSpec.setRules(singletonList(ingressRule)); ingress.setSpec(ingressSpec); return ingress; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/KubernetesSizeTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import static org.testng.Assert.assertEquals; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link KubernetesSize}. * * @author Anton Korneta */ @Listeners(MockitoTestNGListener.class) public class KubernetesSizeTest { @Test(dataProvider = "validSizes") public void testParseKubernetesMemoryFormatsToBytes(String kubeSize, long expectedBytes) throws Exception { assertEquals(KubernetesSize.toBytes(kubeSize), expectedBytes); } @Test(dataProvider = "validCpuLimits") public void testParseKubernetesCpuFormatsToCores(String kubeSize, float expectedCores) throws Exception { assertEquals(KubernetesSize.toCores(kubeSize), expectedCores); } @DataProvider(name = "validSizes") public Object[][] correctKubernetesMemoryFormats() { return new Object[][] { {"123Mi", 128974848}, {"129M", 129000000}, {"129e6", 129000000}, {"129e+6", 129000000} }; } @DataProvider(name = "validCpuLimits") public Object[][] correctKubernetesCpuLimits() { return new Object[][] {{"0.1", 0.1f}, {"1250m", 1.250f}, {"-1", -1f}, {"60m", 0.06f}}; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/PodEventsTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import java.text.ParseException; import java.util.Date; import org.testng.Assert; import org.testng.annotations.Test; /** * Tests {@link PodEvents}. * * @author Ilya Buziuk */ public class PodEventsTest { @Test public void eventDateShouldBeBeforeCurrentDate() throws ParseException { String eventTime = "2018-05-15T16:17:54Z"; Date eventDate = PodEvents.convertEventTimestampToDate(eventTime); Assert.assertTrue(eventDate.before(new Date())); } @Test(expectedExceptions = ParseException.class) public void throwsParseExceptionWhenDateFormatIsInvalid() throws ParseException { String eventTime = "2018-05-15T16:143435Z"; PodEvents.convertEventTimestampToDate(eventTime); } @Test(expectedExceptions = ParseException.class) public void throwsIllegalArgumentExceptionWhenDateIs12DotSring() throws ParseException { String eventTime = "12."; PodEvents.convertEventTimestampToDate(eventTime); } @Test(expectedExceptions = IllegalArgumentException.class) public void throwsIllegalArgumentExceptionWhenDateIsNull() throws ParseException { String eventTime = null; PodEvents.convertEventTimestampToDate(eventTime); } @Test(expectedExceptions = IllegalArgumentException.class) public void throwsIllegalArgumentExceptionWhenDateIsEmptyString() throws ParseException { String eventTime = ""; PodEvents.convertEventTimestampToDate(eventTime); } @Test public void getEventTimestampFromDate() throws ParseException { String timestamp = "2018-05-15T16:17:54Z"; Date date = PodEvents.convertEventTimestampToDate(timestamp); String timestampFromDate = PodEvents.convertDateToEventTimestamp(date); Date dateAfterParsingTimestamp = PodEvents.convertEventTimestampToDate(timestampFromDate); Assert.assertEquals(date, dateAfterParsingTimestamp); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/ServicesTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import static org.testng.Assert.assertEquals; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServiceSpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class ServicesTest { @Test(dataProvider = "nullService") public void testFindPortShouldReturnEmptyWhenSomethingIsNull(Service service) { assertEquals(Services.findPort(service, 1), Optional.empty()); } @Test public void testFindPortWhenExists() { final int PORT = 1234; Service service = new Service(); ServiceSpec spec = new ServiceSpec(); ServicePort port = new ServicePort(); port.setPort(PORT); spec.setPorts(Arrays.asList(port, new ServicePort())); service.setSpec(spec); assertEquals(Services.findPort(service, PORT).get(), port); } @Test(dataProvider = "nullServices") public void testFindServiceWithPortShouldReturnEmptyWhenSomethingIsNull( Collection services) { assertEquals(Services.findServiceWithPort(services, 1), Optional.empty()); } @Test public void testFindServiceWhenExists() { final int PORT = 1234; Service service = new Service(); ServiceSpec spec = new ServiceSpec(); ServicePort port = new ServicePort(); port.setPort(PORT); spec.setPorts(Collections.singletonList(port)); service.setSpec(spec); assertEquals( Services.findServiceWithPort(Arrays.asList(service, new Service()), PORT).get(), service); } @DataProvider public Object[][] nullService() { List nullServices = createNullServices(); Object[][] returnObjects = new Object[nullServices.size()][1]; for (int i = 0; i < nullServices.size(); i++) { returnObjects[i][0] = nullServices.get(i); } return returnObjects; } @DataProvider public Object[][] nullServices() { List nullServices = createNullServices(); Object[][] returnObjects = new Object[nullServices.size() + 1][1]; for (int i = 0; i < nullServices.size(); i++) { returnObjects[i][0] = Collections.singletonList(nullServices.get(i)); } returnObjects[returnObjects.length - 1][0] = null; return returnObjects; } private List createNullServices() { List nullServices = new ArrayList<>(); nullServices.add(null); Service service = new Service(); service.setSpec(null); nullServices.add(service); service = new Service(); ServiceSpec spec = new ServiceSpec(); spec.setPorts(null); service.setSpec(spec); nullServices.add(service); service = new Service(); spec = new ServiceSpec(); ServicePort port = new ServicePort(); port.setPort(null); spec.setPorts(Collections.singletonList(port)); service.setSpec(spec); nullServices.add(service); return nullServices; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/util/UnrecoverablePodEventListenerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.util; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableSet; import java.util.Date; import java.util.Set; import java.util.function.Consumer; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.event.PodEvent; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link UnrecoverablePodEventListener}. * * @author Sergii Leshchenko * @author Ilya Buziuk */ @Listeners(MockitoTestNGListener.class) public class UnrecoverablePodEventListenerTest { private static final String WORKSPACE_POD_NAME = "app"; private static final String CONTAINER_NAME_1 = "test1"; private static final String EVENT_CREATION_TIMESTAMP = "2018-05-15T16:17:54Z"; /* Pods created by a deployment are created with a random suffix, so Pod names won't match exactly. */ private static final String POD_NAME_RANDOM_SUFFIX = "-12345"; private static final Set UNRECOVERABLE_EVENTS = ImmutableSet.of("Failed Mount", "Failed Scheduling", "Failed to pull image"); @Mock private Consumer unrecoverableEventConsumer; private UnrecoverablePodEventListener unrecoverableEventListener; @BeforeMethod public void setUp() { unrecoverableEventListener = new UnrecoverablePodEventListener( UNRECOVERABLE_EVENTS, ImmutableSet.of(WORKSPACE_POD_NAME), unrecoverableEventConsumer); } @Test public void testHandleUnrecoverableEventByReason() throws Exception { // given String unrecoverableEventReason = "Failed Mount"; PodEvent unrecoverableEvent = mockContainerEvent( WORKSPACE_POD_NAME, unrecoverableEventReason, "Failed to mount volume 'claim-che-workspace'", EVENT_CREATION_TIMESTAMP, getCurrentTimestampWithOneHourShiftAhead()); // when unrecoverableEventListener.handle(unrecoverableEvent); // then verify(unrecoverableEventConsumer).accept(unrecoverableEvent); } @Test public void testHandleUnrecoverableEventByMessage() throws Exception { // given String unrecoverableEventMessage = "Failed to pull image eclipse/che-server:nightly-centos"; PodEvent unrecoverableEvent = mockContainerEvent( WORKSPACE_POD_NAME, "Pulling", unrecoverableEventMessage, EVENT_CREATION_TIMESTAMP, getCurrentTimestampWithOneHourShiftAhead()); // when unrecoverableEventListener.handle(unrecoverableEvent); // then verify(unrecoverableEventConsumer).accept(unrecoverableEvent); } @Test public void testDoNotHandleUnrecoverableEventFromNonWorkspacePod() throws Exception { // given final String unrecoverableEventMessage = "Failed to pull image eclipse/che-server:nightly-centos"; final PodEvent unrecoverableEvent = mockContainerEvent( "NonWorkspacePod", "Pulling", unrecoverableEventMessage, EVENT_CREATION_TIMESTAMP, getCurrentTimestampWithOneHourShiftAhead()); // when unrecoverableEventListener.handle(unrecoverableEvent); // then verify(unrecoverableEventConsumer, never()).accept(any()); } @Test public void testHandleRegularEvent() throws Exception { // given final PodEvent regularEvent = mockContainerEvent( WORKSPACE_POD_NAME, "Pulling", "pulling image", EVENT_CREATION_TIMESTAMP, getCurrentTimestampWithOneHourShiftAhead()); // when unrecoverableEventListener.handle(regularEvent); // then verify(unrecoverableEventConsumer, never()).accept(any()); } @Test public void testFailedContainersInWorkspacePodAlwaysHandled() { // given PodEvent ev = mockContainerEvent( WORKSPACE_POD_NAME, "Failed", "bah", EVENT_CREATION_TIMESTAMP, getCurrentTimestampWithOneHourShiftAhead()); // when unrecoverableEventListener.handle(ev); // then verify(unrecoverableEventConsumer).accept(any()); } /** * Mock a container event, as though it was triggered by the OpenShift API. As workspace Pods are * created indirectly through deployments, they are given generated names with the provided name * as a root.
    * Use this method in a test to ensure that tested code manages this fact correctly. For example, * code such as unrecoverable events handling cannot directly look at an event's pod name and * compare it to the internal representation, and so must confirm the event is relevant in some * other way. */ private static PodEvent mockContainerEvent( String podName, String reason, String message, String creationTimestamp, String lastTimestamp) { final PodEvent event = mock(PodEvent.class); when(event.getPodName()).thenReturn(podName + POD_NAME_RANDOM_SUFFIX); lenient().when(event.getContainerName()).thenReturn(CONTAINER_NAME_1); lenient().when(event.getReason()).thenReturn(reason); lenient().when(event.getMessage()).thenReturn(message); lenient().when(event.getCreationTimeStamp()).thenReturn(creationTimestamp); lenient().when(event.getLastTimestamp()).thenReturn(lastTimestamp); return event; } private String getCurrentTimestampWithOneHourShiftAhead() { Date currentTimestampWithOneHourShiftAhead = new Date(new Date().getTime() + 3600 * 1000); return PodEvents.convertDateToEventTimestamp(currentTimestampWithOneHourShiftAhead); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/BrokersResultTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.testng.Assert.assertEquals; import static org.testng.Assert.fail; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ public class BrokersResultTest { private ExecutorService executor; private BrokersResult brokersResult; @BeforeMethod public void setUp() { brokersResult = new BrokersResult(); executor = Executors.newSingleThreadExecutor(); } @AfterMethod public void tearDown() { executor.shutdown(); executor.shutdownNow(); } @Test( expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "Submitting a broker error is not allowed before calling BrokerResult#get") public void shouldThrowExceptionOnCallingErrorBeforeCallGet() { brokersResult.error(new Exception()); } @Test( expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "Submitting a broker result is not allowed before calling BrokerResult#get") public void shouldThrowExceptionOnCallingAddResultBeforeCallGet() throws Exception { brokersResult.setResult(emptyList()); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Plugins brokering result is unexpectedly submitted more than one time. This indicates unexpected behavior of the system") public void shouldThrowExceptionIfResultIsSubmittedSecondTime() throws Exception { // given executeBrokerGet(); waitBrokerGetCalled(); // when brokersResult.setResult(singletonList(new ChePlugin())); // then brokersResult.setResult(singletonList(new ChePlugin())); } @Test public void shouldReturnResultOfBroker() throws Exception { // given ChePlugin chePlugin = new ChePlugin(); executeWhenResultIsStarted( () -> { brokersResult.setResult(singletonList(chePlugin)); return null; }); // when List chePlugins = brokersResult.get(2, TimeUnit.SECONDS); // then assertEquals(chePlugins, singletonList(chePlugin)); } @Test( expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "BrokerResult#get doesn't support multiple calls") public void shouldThrowExceptionIfGetCalledTwice() throws Exception { executeBrokerGet(); waitBrokerGetCalled(); brokersResult.get(100, TimeUnit.MILLISECONDS); } @Test(expectedExceptions = TimeoutException.class) public void shouldThrowTimeoutExceptionIfResultIsNotSubmitted() throws Exception { brokersResult.get(1, TimeUnit.MILLISECONDS); } @Test public void shouldThrowExceptionIfErrorIsSubmitted() throws Exception { executeWhenResultIsStarted( () -> { // when brokersResult.error(new InfrastructureException("test")); return null; }); // given try { brokersResult.get(3, TimeUnit.SECONDS); } catch (ExecutionException e) { // then assertEquals(e.getCause().getClass(), InfrastructureException.class); assertEquals(e.getCause().getMessage(), "test"); return; } fail(); } private void executeBrokerGet() { executor.submit(() -> brokersResult.get(2, TimeUnit.SECONDS)); } private void waitBrokerGetCalled() throws InterruptedException { while (!brokersResult.isStarted()) { Thread.sleep(100); } } private void executeWhenResultIsStarted(Callable c) { executor.submit( () -> { while (!brokersResult.isStarted()) { Thread.sleep(100); } c.call(); return null; }); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/K8sContainerResolverBuilderTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static java.util.Arrays.asList; import static org.testng.Assert.assertEqualsNoOrder; import java.util.ArrayList; import java.util.List; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainerPort; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ public class K8sContainerResolverBuilderTest { @Test(dataProvider = "allFieldsSetProvider", expectedExceptions = IllegalStateException.class) public void shouldCheckThatAllFieldsAreSet(K8sContainerResolverBuilder builder) { builder.build(); } @DataProvider public static Object[][] allFieldsSetProvider() { return new Object[][] { {new K8sContainerResolverBuilder()}, {new K8sContainerResolverBuilder().setContainer(new CheContainer())}, {new K8sContainerResolverBuilder().setPluginEndpoints(new ArrayList<>())}, }; } @Test public void shouldPassOnlyParticularContainerEndpoints() { // given K8sContainerResolverBuilder builder = new K8sContainerResolverBuilder(); builder.setContainer( new CheContainer() .ports( asList( new CheContainerPort().exposedPort(9014), new CheContainerPort().exposedPort(4040)))); builder.setPluginEndpoints( asList( new ChePluginEndpoint().targetPort(9014), new ChePluginEndpoint().targetPort(9013), new ChePluginEndpoint().targetPort(8080), new ChePluginEndpoint().targetPort(4040))); K8sContainerResolver resolver = builder.build(); ArrayList expected = new ArrayList<>(); expected.add(new ChePluginEndpoint().targetPort(9014)); expected.add(new ChePluginEndpoint().targetPort(4040)); // when List actualEndpoints = resolver.getEndpoints(); // then assertEqualsNoOrder(actualEndpoints.toArray(), expected.toArray()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/K8sContainerResolverTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static org.eclipse.che.workspace.infrastructure.kubernetes.Names.MAX_CONTAINER_NAME_LENGTH; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEqualsNoOrder; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerPort; import io.fabric8.kubernetes.api.model.Quantity; import io.fabric8.kubernetes.api.model.ResourceRequirements; import io.fabric8.kubernetes.api.model.ResourceRequirementsBuilder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint; import org.eclipse.che.api.workspace.server.wsplugins.model.EnvVar; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ public class K8sContainerResolverTest { private static final String IMAGE = "testImage:tag"; private CheContainer cheContainer; private K8sContainerResolver resolver; private List endpoints; @BeforeMethod public void setUp() { cheContainer = new CheContainer(); endpoints = new ArrayList<>(); resolver = new K8sContainerResolver("Always", cheContainer, endpoints); } @Test public void shouldSetImageFromSidecar() throws Exception { cheContainer.setImage(IMAGE); Container container = resolver.resolve(); assertEquals(container.getImage(), IMAGE); } @Test public void shouldSetName() throws Exception { cheContainer.setName("cheContainerName"); Container container = resolver.resolve(); assertTrue(container.getName().startsWith((cheContainer.getName()).toLowerCase())); } @Test public void shouldLimitNameByMaxAllowedLength() throws Exception { cheContainer.setName("cheContainerNameWhichIsGreatlySucceedsMaxAllowedLengthByKubernetes"); Container container = resolver.resolve(); assertEquals(container.getName().length(), MAX_CONTAINER_NAME_LENGTH); } @Test public void shouldSetEnvVarsFromSidecar() throws Exception { Map env = ImmutableMap.of("name1", "value1", "name2", "value2"); cheContainer.setEnv(toSidecarEnvVars(env)); Container container = resolver.resolve(); assertEqualsNoOrder(container.getEnv().toArray(), toK8sEnvVars(env).toArray()); } @Test public void shouldSetPortsFromContainerEndpoints() throws Exception { Integer[] ports = new Integer[] {3030, 10000}; endpoints.addAll(toEndpoints(ports)); Container container = resolver.resolve(); assertEqualsNoOrder(container.getPorts().toArray(), toK8sPorts(ports).toArray()); } @Test(dataProvider = "memLimitResourcesProvider") public void shouldProvisionSidecarMemoryLimitAndRequest( String sidecarMemLimit, ResourceRequirements resources) throws Exception { cheContainer.setMemoryLimit(sidecarMemLimit); cheContainer.setMemoryRequest(sidecarMemLimit); Container container = resolver.resolve(); assertEquals(container.getResources(), resources); } @Test(dataProvider = "cpuLimitResourcesProvider") public void shouldProvisionSidecarCPULimitAndRequest( String sidecarCpuLimit, ResourceRequirements resources) throws Exception { cheContainer.setCpuLimit(sidecarCpuLimit); cheContainer.setCpuRequest(sidecarCpuLimit); Container container = resolver.resolve(); assertEquals(container.getResources(), resources); } @DataProvider public static Object[][] memLimitResourcesProvider() { return new Object[][] { {"", null}, {null, null}, {"123456789", toK8sMemoryLimitRequestResources("123456789")}, {"1Ki", toK8sMemoryLimitRequestResources("1Ki")}, {"100M", toK8sMemoryLimitRequestResources("100M")}, }; } @DataProvider public static Object[][] cpuLimitResourcesProvider() { return new Object[][] { {"", null}, {null, null}, {"0.156", toK8sCPULimitRequestResources("0.156")}, {"1", toK8sCPULimitRequestResources("1")}, {"100m", toK8sCPULimitRequestResources("100m")}, }; } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Sidecar memory limit field contains illegal value .*") public void shouldThrowExceptionIfMemoryLimitIsInIllegalFormat() throws Exception { cheContainer.setMemoryLimit("IllegalValue"); resolver.resolve(); } private static ResourceRequirements toK8sMemoryLimitRequestResources(String memLimit) { return new ResourceRequirementsBuilder() .addToLimits("memory", new Quantity(memLimit)) .addToRequests("memory", new Quantity(memLimit)) .build(); } private static ResourceRequirements toK8sCPULimitRequestResources(String cpuLimit) { return new ResourceRequirementsBuilder() .addToLimits("cpu", new Quantity(cpuLimit)) .addToRequests("cpu", new Quantity(cpuLimit)) .build(); } private List toSidecarEnvVars(Map envVars) { return envVars.entrySet().stream() .map(entry -> new EnvVar().name(entry.getKey()).value(entry.getValue())) .collect(Collectors.toList()); } private List toK8sEnvVars(Map envVars) { return envVars.entrySet().stream() .map( entry -> new io.fabric8.kubernetes.api.model.EnvVar(entry.getKey(), entry.getValue(), null)) .collect(Collectors.toList()); } private List toK8sPorts(Integer[] ports) { return Arrays.stream(ports).map(this::k8sPort).collect(Collectors.toList()); } private ContainerPort k8sPort(Integer port) { ContainerPort containerPort = new ContainerPort(); containerPort.setContainerPort(port); containerPort.setProtocol("TCP"); return containerPort; } private List toEndpoints(Integer[] ports) { return Arrays.stream(ports) .map(p -> new ChePluginEndpoint().targetPort(p)) .collect(Collectors.toList()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/KubernetesPluginsToolingApplierTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static com.google.common.collect.ImmutableMap.of; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.config.Command.MACHINE_NAME_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.Command.WORKING_DIRECTORY_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.CONTAINER_SOURCE_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.PLUGIN_MACHINE_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.TOOL_CONTAINER_SOURCE; import static org.eclipse.che.commons.lang.NameGenerator.generate; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactoryProvider.SECURE_EXPOSER_IMPL_PROPERTY; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerPort; import io.fabric8.kubernetes.api.model.ContainerPortBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServicePortBuilder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainerPort; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint; import org.eclipse.che.api.workspace.server.wsplugins.model.Command; import org.eclipse.che.api.workspace.server.wsplugins.model.EnvVar; import org.eclipse.che.api.workspace.server.wsplugins.model.Volume; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.util.EnvVars; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ @Listeners(MockitoTestNGListener.class) public class KubernetesPluginsToolingApplierTest { private static final String TEST_IMAGE = "testImage/test/test"; private static final String ENV_VAR = "PLUGINS_ENV_VAR"; private static final String ENV_VAR_VALUE = "PLUGINS_ENV_VAR_VALUE"; private static final String POD_NAME = "pod12"; private static final String VOLUME_NAME = "test_volume_name"; private static final String VOLUME_MOUNT_PATH = "/path/test"; private static final String USER_MACHINE_NAME = POD_NAME + "/userContainer"; private static final int MEMORY_LIMIT_MB = 200; private static final int MEMORY_REQUEST_MB = 100; private static final String CPU_LIMIT = "200m"; private static final String CPU_REQUEST = "100m"; private static final String CHE_PLUGIN_ENDPOINT_NAME = "test-endpoint-1"; @Mock private Pod pod; @Mock private PodSpec podSpec; @Mock private ObjectMeta meta; @Mock private Container userContainer; @Mock private InternalMachineConfig userMachineConfig; @Mock private RuntimeIdentity runtimeIdentity; @Mock private ChePluginsVolumeApplier chePluginsVolumeApplier; @Mock private EnvVars envVars; private KubernetesEnvironment internalEnvironment; private KubernetesPluginsToolingApplier applier; @BeforeMethod public void setUp() { internalEnvironment = spy(KubernetesEnvironment.builder().build()); internalEnvironment.setDevfile(new DevfileImpl()); applier = new KubernetesPluginsToolingApplier( MEMORY_LIMIT_MB, MEMORY_REQUEST_MB, CPU_LIMIT, CPU_REQUEST, false, chePluginsVolumeApplier, envVars); Map machines = new HashMap<>(); List containers = new ArrayList<>(); containers.add(userContainer); machines.put(USER_MACHINE_NAME, userMachineConfig); when(pod.getSpec()).thenReturn(podSpec); lenient().when(podSpec.getContainers()).thenReturn(containers); lenient().when(pod.getMetadata()).thenReturn(meta); lenient().when(meta.getName()).thenReturn(POD_NAME); internalEnvironment.addPod(pod); internalEnvironment.getMachines().putAll(machines); } @Test public void shouldProvisionPluginsCommandsToEnvironment() throws Exception { // given Command pluginCommand = new Command() .name("test-command") .workingDir("~") .command(Arrays.asList("./build.sh", "--no-pull")); // when applier.apply( runtimeIdentity, internalEnvironment, singletonList(createChePlugin(createContainer("container", pluginCommand)))); // then List envCommands = internalEnvironment.getCommands(); assertEquals(envCommands.size(), 1); CommandImpl envCommand = envCommands.get(0); assertEquals(envCommand.getName(), pluginCommand.getName()); assertEquals(envCommand.getCommandLine(), String.join(" ", pluginCommand.getCommand())); assertEquals(envCommand.getType(), "custom"); assertEquals( envCommand.getAttributes().get(WORKING_DIRECTORY_ATTRIBUTE), pluginCommand.getWorkingDir()); validateContainerNameName(envCommand.getAttributes().get(MACHINE_NAME_ATTRIBUTE), "container"); } @Test public void shouldProvisionApplyEnvironmentVariableToContainersAndInitContainersOfPlugin() throws Exception { // given CheContainer container = new CheContainer(); container.setName("container"); CheContainer initContainer = new CheContainer(); initContainer.setName("initContainer"); ChePlugin chePlugin = createChePlugin( "publisher/id/1.0.0", singletonList(container), singletonList(initContainer)); ComponentImpl component = internalEnvironment.getDevfile().getComponents().get(0); component.getEnv().add(new EnvImpl("TEST", "VALUE")); ArgumentCaptor containerArgumentCaptor = ArgumentCaptor.forClass(Container.class); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then verify(envVars, times(2)).apply(containerArgumentCaptor.capture(), eq(component.getEnv())); List containers = containerArgumentCaptor.getAllValues(); // containers names are suffixed to provide uniqueness and converted to be k8s API compatible assertTrue(containers.get(0).getName().startsWith("initcontainer")); assertTrue(containers.get(1).getName().startsWith("container")); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "The downloaded plugin 'publisher/id/version' configuration does " + "not have the corresponding component in devfile. Devfile contains the following " + "cheEditor/chePlugins: \\[publisher/editor/1, publisher/plugin/1\\]") public void shouldThrowExceptionWhenDownloadedPluginIsNotMatchedWitComponent() throws Exception { // given ChePlugin chePlugin = createChePlugin("publisher/id/version"); internalEnvironment.getDevfile().getComponents().clear(); internalEnvironment .getDevfile() .getComponents() .add(new ComponentImpl("cheEditor", "publisher/editor/1")); internalEnvironment .getDevfile() .getComponents() .add(new ComponentImpl("chePlugin", "publisher/plugin/1")); internalEnvironment.getDevfile().getComponents().add(new ComponentImpl("k8s", null)); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); } @Test public void shouldResolveMachineNameForCommandsInEnvironment() throws Exception { // given ChePlugin chePlugin = createChePlugin(createContainer("container")); String pluginRef = chePlugin.getId(); CommandImpl pluginCommand = new CommandImpl("test-command", "echo Hello World!", "custom"); pluginCommand.getAttributes().put("plugin", pluginRef); internalEnvironment.getCommands().add(pluginCommand); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then List envCommands = internalEnvironment.getCommands(); assertEquals(envCommands.size(), 1); CommandImpl envCommand = envCommands.get(0); assertEquals(envCommand.getName(), pluginCommand.getName()); assertEquals(envCommand.getType(), pluginCommand.getType()); assertEquals(envCommand.getCommandLine(), pluginCommand.getCommandLine()); assertEquals(envCommand.getAttributes().get("plugin"), pluginRef); validateContainerNameName(envCommand.getAttributes().get(MACHINE_NAME_ATTRIBUTE), "container"); } @Test public void shouldFillInWarningIfChePluginDoesNotHaveAnyContainersButThereAreRelatedCommands() throws Exception { // given ChePlugin chePlugin = createChePlugin("somePublisher/custom-name/0.0.3"); String pluginRef = chePlugin.getId(); CommandImpl pluginCommand = new CommandImpl("test-command", "echo Hello World!", "custom"); pluginCommand.getAttributes().put("plugin", pluginRef); internalEnvironment.getCommands().add(pluginCommand); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then List envWarnings = internalEnvironment.getWarnings(); assertEquals(envWarnings.size(), 1); Warning warning = envWarnings.get(0); assertEquals( warning.getCode(), Warnings.COMMAND_IS_CONFIGURED_IN_PLUGIN_WITHOUT_CONTAINERS_WARNING_CODE); assertEquals( warning.getMessage(), "There are configured commands for plugin 'somePublisher/custom-name/0.0.3' that doesn't have any containers"); } @Test public void shouldNotFillInWarningIfChePluginDoesNotHaveAnyContainersAndThereAreNotRelatedCommands() throws Exception { // given ChePlugin chePlugin = createChePlugin(); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then assertTrue(internalEnvironment.getWarnings().isEmpty()); } @Test public void shouldFillInWarningIfChePluginHasMultiplyContainersButThereAreRelatedCommands() throws Exception { // given ChePlugin chePlugin = createChePlugin(createContainer(), createContainer()); String pluginRef = chePlugin.getId(); CommandImpl pluginCommand = new CommandImpl("test-command", "echo Hello World!", "custom"); pluginCommand.getAttributes().put("plugin", pluginRef); internalEnvironment.getCommands().add(pluginCommand); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then List envWarnings = internalEnvironment.getWarnings(); assertEquals(envWarnings.size(), 1); Warning warning = envWarnings.get(0); assertEquals( warning.getCode(), Warnings.COMMAND_IS_CONFIGURED_IN_PLUGIN_WITH_MULTIPLY_CONTAINERS_WARNING_CODE); assertEquals( warning.getMessage(), "There are configured commands for plugin '" + pluginRef + "' that has multiply containers. Commands will be configured to be run in first container"); } @Test public void shouldNotFillInWarningIfChePluginHasMultiplyContainersAndThereAreNotRelatedCommands() throws Exception { // given ChePlugin chePlugin = createChePlugin(createContainer(), createContainer()); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then assertTrue(internalEnvironment.getWarnings().isEmpty()); } @Test public void shouldUseContainerNameForMachinesName() throws Exception { // given internalEnvironment.getMachines().clear(); ChePlugin chePlugin = createChePlugin("publisher/plugin1/0.2.1", createContainer("container1")); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then Map machines = internalEnvironment.getMachines(); assertEquals(machines.size(), 1); validateContainerNameName(machines.keySet().iterator().next(), "container1"); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Che plugins tooling configuration can be applied to a workspace with one pod only") public void throwsExceptionWhenTheNumberOfPodsIsNot1() throws Exception { PodData podData = new PodData(podSpec, meta); when(internalEnvironment.getPodsData()).thenReturn(of("pod1", podData, "pod2", podData)); applier.apply(runtimeIdentity, internalEnvironment, singletonList(createChePlugin())); } @Test public void addToolingContainerToAPod() throws Exception { applier.apply(runtimeIdentity, internalEnvironment, singletonList(createChePlugin())); verifyPodAndContainersNumber(2); Container toolingContainer = getOneAndOnlyNonUserContainer(internalEnvironment); verifyContainer(toolingContainer); } @Test public void addToolingInitContainerToAPod() throws Exception { lenient().when(podSpec.getInitContainers()).thenReturn(new ArrayList<>()); ChePlugin chePlugin = createChePlugin(); chePlugin.setInitContainers(singletonList(createContainer())); applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); verifyPodAndInitContainersNumber(1); Container toolingInitContainer = getOnlyOneInitContainerFromPod(internalEnvironment); verifyContainer(toolingInitContainer); } @Test public void addToolingInitContainerWithCommand() throws InfrastructureException { List command = Arrays.asList("cp", "-rf", "test-file", "/some-volume/test"); lenient().when(podSpec.getInitContainers()).thenReturn(new ArrayList<>()); ChePlugin chePlugin = createChePlugin(); List initContainers = singletonList(createContainer(command, null)); chePlugin.setInitContainers(initContainers); applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); verifyPodAndInitContainersNumber(1); Container toolingInitContainer = getOnlyOneInitContainerFromPod(internalEnvironment); verifyContainer(toolingInitContainer); assertEquals(toolingInitContainer.getCommand(), command); } @Test public void addToolingInitContainerWithArgs() throws InfrastructureException { List args = Arrays.asList("cp", "-rf", "test-file", "/some-volume/test"); lenient().when(podSpec.getInitContainers()).thenReturn(new ArrayList<>()); ChePlugin chePlugin = createChePlugin(); List initContainers = singletonList(createContainer(null, args)); chePlugin.setInitContainers(initContainers); applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); verifyPodAndInitContainersNumber(1); Container toolingInitContainer = getOnlyOneInitContainerFromPod(internalEnvironment); verifyContainer(toolingInitContainer); assertEquals(toolingInitContainer.getArgs(), args); } @Test public void addToolingInitContainerWithCommandAndArgs() throws InfrastructureException { List command = singletonList("cp"); List args = Arrays.asList("-rf", "test-file", "/some-volume/test"); lenient().when(podSpec.getInitContainers()).thenReturn(new ArrayList<>()); ChePlugin chePlugin = createChePlugin(); List initContainers = singletonList(createContainer(command, args)); chePlugin.setInitContainers(initContainers); applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); verifyPodAndInitContainersNumber(1); Container toolingInitContainer = getOnlyOneInitContainerFromPod(internalEnvironment); verifyContainer(toolingInitContainer); assertEquals(toolingInitContainer.getCommand(), command); assertEquals(toolingInitContainer.getArgs(), args); } @Test public void addToolingContainerWithCommand() throws InfrastructureException { List command = Arrays.asList("tail", "-f", "/dev/null"); lenient().when(podSpec.getContainers()).thenReturn(new ArrayList<>()); ChePlugin chePlugin = createChePlugin(); List containers = singletonList(createContainer(command, null)); chePlugin.setContainers(containers); applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); verifyPodAndContainersNumber(1); Container toolingContainer = getOneAndOnlyNonUserContainer(internalEnvironment); verifyContainer(toolingContainer); assertEquals(toolingContainer.getCommand(), command); } @Test public void addToolingContainerWithArgs() throws InfrastructureException { List args = Arrays.asList("tail", "-f", "/dev/null"); lenient().when(podSpec.getContainers()).thenReturn(new ArrayList<>()); ChePlugin chePlugin = createChePlugin(); List containers = singletonList(createContainer(null, args)); chePlugin.setContainers(containers); applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); verifyPodAndContainersNumber(1); Container toolingContainer = getOneAndOnlyNonUserContainer(internalEnvironment); verifyContainer(toolingContainer); assertEquals(toolingContainer.getArgs(), args); } @Test public void addToolingContainerWithCommandAndArgs() throws InfrastructureException { List command = singletonList("tail"); List args = Arrays.asList("-f", "/dev/null"); lenient().when(podSpec.getContainers()).thenReturn(new ArrayList<>()); ChePlugin chePlugin = createChePlugin(); List containers = singletonList(createContainer(command, args)); chePlugin.setContainers(containers); applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); verifyPodAndContainersNumber(1); Container toolingContainer = getOneAndOnlyNonUserContainer(internalEnvironment); verifyContainer(toolingContainer); assertEquals(toolingContainer.getCommand(), command); assertEquals(toolingContainer.getArgs(), args); } @Test public void createsPodAndAddToolingIfNoPodIsPresent() throws Exception { internalEnvironment = spy(KubernetesEnvironment.builder().build()); internalEnvironment.setDevfile(new DevfileImpl()); Map machines = new HashMap<>(); machines.put(USER_MACHINE_NAME, userMachineConfig); internalEnvironment.getMachines().putAll(machines); applier.apply(runtimeIdentity, internalEnvironment, singletonList(createChePlugin())); verifyPodAndContainersNumber(1); Container toolingContainer = getOneAndOnlyNonUserContainer(internalEnvironment); verifyContainer(toolingContainer); } @Test public void canAddMultipleToolingContainersToAPodFromOnePlugin() throws Exception { applier.apply( runtimeIdentity, internalEnvironment, singletonList(createChePlugin(createContainer(), createContainer()))); verifyPodAndContainersNumber(3); List nonUserContainers = getNonUserContainers(internalEnvironment); verifyContainers(nonUserContainers); } @Test public void canAddMultipleToolingContainersToAPodFromSeveralPlugins() throws Exception { applier.apply( runtimeIdentity, internalEnvironment, ImmutableList.of(createChePlugin(), createChePlugin())); verifyPodAndContainersNumber(3); List nonUserContainers = getNonUserContainers(internalEnvironment); verifyContainers(nonUserContainers); } @Test public void addsMachineWithVolumeFromChePlugin() throws Exception { // given ChePlugin chePluginWithNoVolume = createChePlugin(); chePluginWithNoVolume.getContainers().get(0).setVolumes(emptyList()); // when applier.apply( runtimeIdentity, internalEnvironment, asList(createChePlugin(), chePluginWithNoVolume)); // then Collection machineConfigs = getNonUserMachines(internalEnvironment); assertEquals(machineConfigs.size(), 2); verifyNumberOfMachinesWithSpecificNumberOfVolumes(machineConfigs, 2, 0); } @Test public void addsMachineWithServersForContainer() throws Exception { // given ChePlugin chePlugin = createChePlugin(); addPortToSingleContainerPlugin(chePlugin, 80, "test-port", emptyMap(), true); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then InternalMachineConfig machineConfig = getOneAndOnlyNonUserMachine(internalEnvironment); assertEquals( machineConfig.getServers(), expectedSingleServer(80, "test-port", emptyMap(), true)); } @Test public void addsTwoServersForContainers() throws Exception { // given ChePlugin chePlugin = createChePlugin(); addPortToSingleContainerPlugin(chePlugin, 80, "test-port", emptyMap(), true); addPortToSingleContainerPlugin(chePlugin, 8090, "another-test-port", emptyMap(), false); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then InternalMachineConfig machineConfig = getOneAndOnlyNonUserMachine(internalEnvironment); assertEquals( machineConfig.getServers(), expectedTwoServers( 80, "test-port", emptyMap(), true, 8090, "another-test-port", emptyMap(), false)); } @Test public void addsMachineWithServersThatUseSamePortButDifferentNames() throws Exception { // given ChePlugin chePlugin = createChePlugin(); addPortToSingleContainerPlugin(chePlugin, 80, "test-port/http", emptyMap(), true); addPortToSingleContainerPlugin(chePlugin, 80, "test-port/ws", emptyMap(), true); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then InternalMachineConfig machineConfig = getOneAndOnlyNonUserMachine(internalEnvironment); assertEquals( machineConfig.getServers(), expectedTwoServers( 80, "test-port/http", emptyMap(), true, 80, "test-port/ws", emptyMap(), true)); } @Test public void addsMachineWithServersThatSetProtocolAndPath() throws Exception { // given ChePlugin chePlugin = createChePlugin(); addPortToSingleContainerPlugin( chePlugin, 443, "test-port", ImmutableMap.of("path", "/path/1", "protocol", "https", "attr1", "value1"), true); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then InternalMachineConfig machineConfig = getOneAndOnlyNonUserMachine(internalEnvironment); assertEquals( machineConfig.getServers(), expectedSingleServer( 443, "test-port", singletonMap("attr1", "value1"), true, "https", "/path/1")); } @Test public void setsSourceAndPluginAttributeForMachineAssociatedWithSidecar() throws Exception { ChePlugin chePlugin = createChePlugin(); applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); InternalMachineConfig machineConfig = getOneAndOnlyNonUserMachine(internalEnvironment); Map attributes = machineConfig.getAttributes(); assertEquals(attributes.get(CONTAINER_SOURCE_ATTRIBUTE), TOOL_CONTAINER_SOURCE); assertEquals(attributes.get(PLUGIN_MACHINE_ATTRIBUTE), chePlugin.getId()); } @Test public void setsDefaultMemoryLimitForMachineAssociatedWithContainer() throws Exception { applier.apply(runtimeIdentity, internalEnvironment, singletonList(createChePlugin())); InternalMachineConfig machineConfig = getOneAndOnlyNonUserMachine(internalEnvironment); String memoryLimitAttribute = machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE); assertEquals(memoryLimitAttribute, Integer.toString(MEMORY_LIMIT_MB * 1024 * 1024)); } @Test public void shouldExposeChePluginEndpointsPortsInToolingContainer() throws Exception { // given ChePluginEndpoint endpoint1 = new ChePluginEndpoint().name(CHE_PLUGIN_ENDPOINT_NAME).targetPort(101010).setPublic(true); ChePluginEndpoint endpoint2 = new ChePluginEndpoint().name("test-endpoint-2").targetPort(2020).setPublic(false); CheContainerPort cheContainerPort1 = new CheContainerPort().exposedPort(101010); CheContainerPort cheContainerPort2 = new CheContainerPort().exposedPort(2020); ChePlugin chePlugin = createChePlugin(); chePlugin.setEndpoints(asList(endpoint1, endpoint2)); chePlugin.getContainers().get(0).setPorts(asList(cheContainerPort1, cheContainerPort2)); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then Container container = getOneAndOnlyNonUserContainer(internalEnvironment); verifyPortsExposed(container, 101010, 2020); } @Test public void shouldNotExposeChePluginPortIfThereIsNoEndpoint() throws Exception { // given ChePluginEndpoint endpoint1 = new ChePluginEndpoint().name(CHE_PLUGIN_ENDPOINT_NAME).targetPort(101010).setPublic(true); CheContainerPort cheContainerPort1 = new CheContainerPort().exposedPort(101010); CheContainerPort cheContainerPort2 = new CheContainerPort().exposedPort(2020); ChePlugin chePlugin = createChePlugin(); chePlugin.setEndpoints(singletonList(endpoint1)); chePlugin.getContainers().get(0).setPorts(asList(cheContainerPort1, cheContainerPort2)); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then Container container = getOneAndOnlyNonUserContainer(internalEnvironment); verifyPortsExposed(container, 101010); } @Test public void shouldAddK8sServicesForChePluginEndpoints() throws Exception { // given ChePluginEndpoint endpoint1 = new ChePluginEndpoint().name(CHE_PLUGIN_ENDPOINT_NAME).targetPort(101010).setPublic(true); ChePluginEndpoint endpoint2 = new ChePluginEndpoint().name("test-endpoint-2").targetPort(2020).setPublic(false); CheContainerPort cheContainerPort1 = new CheContainerPort().exposedPort(101010); CheContainerPort cheContainerPort2 = new CheContainerPort().exposedPort(2020); ChePlugin chePlugin = createChePlugin(); chePlugin.setEndpoints(asList(endpoint1, endpoint2)); chePlugin.getContainers().get(0).setPorts(asList(cheContainerPort1, cheContainerPort2)); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); // then verifyK8sServices(internalEnvironment, endpoint1, endpoint2); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Applying of sidecar tooling failed. Kubernetes service with name '" + CHE_PLUGIN_ENDPOINT_NAME + "' already exists in the workspace environment.") public void throwsExceptionOnAddingChePluginEndpointServiceIfServiceExists() throws Exception { // given ChePluginEndpoint endpoint1 = new ChePluginEndpoint().name(CHE_PLUGIN_ENDPOINT_NAME).targetPort(101010).setPublic(true); CheContainerPort cheContainerPort1 = new CheContainerPort().exposedPort(101010); ChePlugin chePlugin = createChePlugin(); chePlugin.setEndpoints(singletonList(endpoint1)); chePlugin.getContainers().get(0).setPorts(singletonList(cheContainerPort1)); // make collision of service names internalEnvironment.getServices().put(CHE_PLUGIN_ENDPOINT_NAME, new Service()); // when applier.apply(runtimeIdentity, internalEnvironment, singletonList(chePlugin)); } @Test public void shouldNotSetJWTServerExposerAttributeIfAuthEnabledButAttributeIsPresent() throws Exception { applier = new KubernetesPluginsToolingApplier( MEMORY_LIMIT_MB, MEMORY_REQUEST_MB, CPU_LIMIT, CPU_REQUEST, true, chePluginsVolumeApplier, envVars); internalEnvironment.getAttributes().put(SECURE_EXPOSER_IMPL_PROPERTY, "somethingElse"); applier.apply(runtimeIdentity, internalEnvironment, singletonList(createChePlugin())); assertEquals( internalEnvironment.getAttributes().get(SECURE_EXPOSER_IMPL_PROPERTY), "somethingElse"); } @Test public void shouldSetNullImagePullPolicyIfValueIsNotStandard() throws Exception { applier = new KubernetesPluginsToolingApplier( MEMORY_LIMIT_MB, MEMORY_REQUEST_MB, CPU_LIMIT, CPU_REQUEST, true, chePluginsVolumeApplier, envVars); applier.apply(runtimeIdentity, internalEnvironment, singletonList(createChePlugin())); assertNull( internalEnvironment .getPodsCopy() .values() .iterator() .next() .getSpec() .getContainers() .get(1) .getImagePullPolicy()); } @Test public void shouldNotSetJWTServerExposerAttributeIfAuthDisabled() throws Exception { applier.apply(runtimeIdentity, internalEnvironment, singletonList(createChePlugin())); assertNull(internalEnvironment.getAttributes().get(SECURE_EXPOSER_IMPL_PROPERTY)); } private ChePlugin createChePlugin() { return createChePlugin(createContainer()); } private ChePlugin createChePlugin(CheContainer... containers) { return createChePlugin( "publisher/" + NameGenerator.generate("name", 3) + "/0.0.1", Arrays.asList(containers), emptyList()); } private ChePlugin createChePlugin(String id, CheContainer... containers) { return createChePlugin(id, Arrays.asList(containers), emptyList()); } private ChePlugin createChePlugin( String id, List containers, List initContainers) { String[] splittedId = id.split("/"); ChePlugin plugin = new ChePlugin(); plugin.setPublisher(splittedId[0]); plugin.setName(splittedId[1]); plugin.setVersion(splittedId[2]); plugin.setId(id); plugin.setContainers(containers); plugin.setInitContainers(initContainers); internalEnvironment.getDevfile().getComponents().add(new ComponentImpl("chePlugin", id)); return plugin; } private CheContainer createContainer(List command, List args) { CheContainer container = createContainer(); container.setCommand(command); container.setArgs(args); return container; } private CheContainer createContainer(Command... commands) { return createContainer(generate("container", 5), commands); } private CheContainer createContainer(String name, Command... commands) { CheContainer cheContainer = new CheContainer(); cheContainer.setImage(TEST_IMAGE); cheContainer.setName(name); cheContainer.setEnv(singletonList(new EnvVar().name(ENV_VAR).value(ENV_VAR_VALUE))); cheContainer.setVolumes( singletonList(new Volume().name(VOLUME_NAME).mountPath(VOLUME_MOUNT_PATH))); cheContainer.setCommands(Arrays.asList(commands)); return cheContainer; } private ServicePort createServicePort(int port) { return new ServicePortBuilder() .withPort(port) .withProtocol("TCP") .withNewTargetPort(port) .build(); } private void verifyPodAndContainersNumber(int containersNumber) { assertEquals(internalEnvironment.getPodsCopy().size(), 1); Pod pod = internalEnvironment.getPodsCopy().values().iterator().next(); assertEquals(pod.getSpec().getContainers().size(), containersNumber); } private void verifyPodAndInitContainersNumber(int containersNumber) { assertEquals(internalEnvironment.getPodsCopy().size(), 1); Pod pod = internalEnvironment.getPodsCopy().values().iterator().next(); assertEquals(pod.getSpec().getInitContainers().size(), containersNumber); } private void verifyContainer(Container toolingContainer) { assertEquals(toolingContainer.getImage(), TEST_IMAGE); assertEquals( toolingContainer.getEnv(), singletonList(new io.fabric8.kubernetes.api.model.EnvVar(ENV_VAR, ENV_VAR_VALUE, null))); } private void verifyContainers(List containers) { for (Container container : containers) { verifyContainer(container); } } @SuppressWarnings("SameParameterValue") private void verifyNumberOfMachinesWithSpecificNumberOfVolumes( Collection machineConfigs, int numberOfMachines, int numberOfVolumes) { long numberOfMatchingMachines = machineConfigs.stream() .filter(machineConfig -> machineConfig.getVolumes().size() == numberOfVolumes) .count(); assertEquals(numberOfMatchingMachines, numberOfMachines); } private void verifyPortsExposed(Container container, int... ports) { List actualPorts = container.getPorts(); List expectedPorts = new ArrayList<>(); for (int port : ports) { expectedPorts.add( new ContainerPortBuilder().withContainerPort(port).withProtocol("TCP").build()); } assertEquals(actualPorts, expectedPorts); } private void verifyK8sServices( KubernetesEnvironment internalEnvironment, ChePluginEndpoint... endpoints) { Map services = internalEnvironment.getServices(); for (ChePluginEndpoint endpoint : endpoints) { assertTrue(services.containsKey(endpoint.getName())); Service service = services.get(endpoint.getName()); assertEquals(service.getMetadata().getName(), endpoint.getName()); assertEquals( service.getSpec().getSelector(), singletonMap(CHE_ORIGINAL_NAME_LABEL, POD_NAME)); assertEquals( service.getSpec().getPorts(), singletonList(createServicePort(endpoint.getTargetPort()))); } } private Collection getNonUserMachines( InternalEnvironment internalEnvironment) { Map machines = internalEnvironment.getMachines(); Map nonUserMachines = machines.entrySet().stream() .filter(entry -> !USER_MACHINE_NAME.equals(entry.getKey())) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); return nonUserMachines.values(); } private InternalMachineConfig getOneAndOnlyNonUserMachine( InternalEnvironment internalEnvironment) { Collection nonUserMachines = getNonUserMachines(internalEnvironment); assertEquals(nonUserMachines.size(), 1); return nonUserMachines.iterator().next(); } private List getNonUserContainers(KubernetesEnvironment kubernetesEnvironment) { Pod pod = kubernetesEnvironment.getPodsCopy().values().iterator().next(); return pod.getSpec().getContainers().stream() .filter(container -> userContainer != container) .collect(Collectors.toList()); } private Container getOneAndOnlyNonUserContainer(KubernetesEnvironment kubernetesEnvironment) { List nonUserContainers = getNonUserContainers(kubernetesEnvironment); assertEquals(nonUserContainers.size(), 1); return nonUserContainers.get(0); } private Container getOnlyOneInitContainerFromPod(KubernetesEnvironment kubernetesEnvironment) { Pod pod = kubernetesEnvironment.getPodsCopy().values().iterator().next(); List initContainer = pod.getSpec().getInitContainers(); assertEquals(initContainer.size(), 1); return initContainer.get(0); } private void addPortToSingleContainerPlugin( ChePlugin plugin, int port, String portName, Map attributes, boolean isPublic) { assertEquals(plugin.getContainers().size(), 1); ChePluginEndpoint endpoint = new ChePluginEndpoint() .attributes(attributes) .name(portName) .setPublic(isPublic) .targetPort(port); plugin.getEndpoints().add(endpoint); List ports = plugin.getContainers().get(0).getPorts(); if (ports.stream() .map(CheContainerPort::getExposedPort) .noneMatch(integer -> integer == port)) { ports.add(new CheContainerPort().exposedPort(port)); } } @SuppressWarnings("SameParameterValue") private Map expectedSingleServer( int port, String portName, Map attributes, boolean isExternal) { Map servers = new HashMap<>(); addExpectedServer(servers, port, portName, attributes, isExternal, null, null); return servers; } @SuppressWarnings("SameParameterValue") private Map expectedSingleServer( int port, String portName, Map attributes, boolean isExternal, String protocol, String path) { Map servers = new HashMap<>(); addExpectedServer(servers, port, portName, attributes, isExternal, protocol, path); return servers; } @SuppressWarnings("SameParameterValue") private Map expectedTwoServers( int port, String portName, Map attributes, boolean isExternal, int port2, String portName2, Map attributes2, boolean isExternal2) { Map servers = new HashMap<>(); addExpectedServer(servers, port, portName, attributes, isExternal, null, null); addExpectedServer(servers, port2, portName2, attributes2, isExternal2, null, null); return servers; } private void addExpectedServer( Map servers, int port, String portName, Map attributes, boolean isExternal, String protocol, String path) { Map serverAttributes = new HashMap<>(attributes); serverAttributes.put("internal", Boolean.toString(!isExternal)); servers.put(portName, new ServerConfigImpl(port + "/tcp", protocol, path, serverAttributes)); } private void validateContainerNameName(String actual, String prefixExpected) { Pattern pattern = Pattern.compile(prefixExpected + "\\w{3}"); assertTrue(pattern.matcher(actual).matches()); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/MachineResolverTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static com.google.common.collect.ImmutableMap.of; import static java.util.Arrays.asList; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_REQUEST_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_REQUEST_ATTRIBUTE; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Container; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.wsplugins.model.CheContainer; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint; import org.eclipse.che.api.workspace.server.wsplugins.model.Volume; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSize; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Oleksandr Garagatyi */ @Listeners(MockitoTestNGListener.class) public class MachineResolverTest { private static final String DEFAULT_MEM_LIMIT = "100001"; private static final String DEFAULT_MEM_REQUEST = "5001"; private static final String DEFAULT_CPU_LIMIT = "2"; private static final String DEFAULT_CPU_REQUEST = "1"; private static final String PLUGIN_NAME = "testplugin"; private static final String PLUGIN_PUBLISHER = "testpublisher"; private static final String PLUGIN_PUBLISHER_NAME = PLUGIN_PUBLISHER + "/" + PLUGIN_NAME; private static final String PLUGIN_ID = PLUGIN_PUBLISHER_NAME + "/" + "latest"; private static final String PROJECTS_ENV_VAR = "env_with_with_location_of_projects"; private static final String PROJECTS_MOUNT_PATH = "/wherever/i/may/roam"; private List endpoints; private CheContainer cheContainer; private Container container; private MachineResolver resolver; private ComponentImpl component; @BeforeMethod public void setUp() { endpoints = new ArrayList<>(); cheContainer = new CheContainer(); container = new Container(); component = new ComponentImpl("chePlugin", PLUGIN_ID); resolver = new MachineResolver( container, cheContainer, DEFAULT_MEM_LIMIT, DEFAULT_MEM_REQUEST, DEFAULT_CPU_LIMIT, DEFAULT_CPU_REQUEST, endpoints, component); } @Test(dataProvider = "serverProvider") public void shouldSetServersInMachineConfig( List containerEndpoints, Map expected) throws InfrastructureException { endpoints.addAll(containerEndpoints); InternalMachineConfig machineConfig = resolver.resolve(); assertEquals(machineConfig.getServers(), expected); } @DataProvider public static Object[][] serverProvider() { return new Object[][] { // default minimal case { asList(endpt("endp1", 8080), endpt("endp2", 10000)), of("endp1", server(8080), "endp2", server(10000)) }, // case with publicity setting { asList(endpt("endp1", 8080, false), endpt("endp2", 10000, true)), of("endp1", server(8080, false), "endp2", server(10000, true)) }, // case with protocol attribute { asList(endptPrtc("endp1", 8080, "http"), endptPrtc("endp2", 10000, "ws")), of("endp1", serverPrtc(8080, "http"), "endp2", serverPrtc(10000, "ws")) }, // case with path attribute { asList(endptPath("endp1", 8080, "/"), endptPath("endp2", 10000, "/some/thing")), of("endp1", serverPath(8080, "/"), "endp2", serverPath(10000, "/some/thing")) }, // case with other attributes { asList( endpt("endp1", 8080, of("a1", "v1")), endpt("endp2", 10000, of("a2", "v1", "a3", "v3"))), of( "endp1", server(8080, of("a1", "v1")), "endp2", server(10000, of("a2", "v1", "a3", "v3"))) }, }; } @Test public void shouldSetDefaultMemLimitAndRequestIfSidecarDoesNotHaveOne() throws InfrastructureException { InternalMachineConfig machineConfig = resolver.resolve(); assertEquals(machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), DEFAULT_MEM_LIMIT); assertEquals(machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE), DEFAULT_MEM_REQUEST); } @Test public void shouldSetDefaultCPULimitAndRequestIfSidecarDoesNotHaveOne() throws InfrastructureException { InternalMachineConfig machineConfig = resolver.resolve(); assertEquals(machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE), DEFAULT_CPU_LIMIT); assertEquals(machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE), DEFAULT_CPU_REQUEST); } @Test(dataProvider = "memoryLimitAttributeProvider") public void shouldSetMemoryLimitOfASidecarIfCorrespondingComponentFieldIsSet( String memoryLimit, String expectedMemLimit) throws InfrastructureException { component.setMemoryLimit(memoryLimit); InternalMachineConfig machineConfig = resolver.resolve(); assertEquals(machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), expectedMemLimit); } @Test(dataProvider = "memoryRequestAttributeProvider") public void shouldSetMemoryRequestOfASidecarIfCorrespondingComponentFieldIsSet( String memoryRequest, String expectedMemRequest) throws InfrastructureException { component.setMemoryRequest(memoryRequest); InternalMachineConfig machineConfig = resolver.resolve(); assertEquals(machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE), expectedMemRequest); } @DataProvider public static Object[][] memoryLimitAttributeProvider() { return new Object[][] { {"", DEFAULT_MEM_LIMIT}, {null, DEFAULT_MEM_LIMIT}, {"100Ki", toBytesString("100Ki")}, {"1M", toBytesString("1M")}, {"10Gi", toBytesString("10Gi")}, }; } @DataProvider public static Object[][] memoryRequestAttributeProvider() { return new Object[][] { {"", DEFAULT_MEM_REQUEST}, {null, DEFAULT_MEM_REQUEST}, {"100Ki", toBytesString("100Ki")}, {"1M", toBytesString("1M")}, {"10Gi", toBytesString("10Gi")}, }; } @Test(dataProvider = "cpuAttributeLimitProvider") public void shouldSetCPULimitOfASidecarIfCorrespondingComponentFieldIsSet( String cpuLimit, String expectedCpuLimit) throws InfrastructureException { component.setCpuLimit(cpuLimit); InternalMachineConfig machineConfig = resolver.resolve(); assertEquals(machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE), expectedCpuLimit); } @Test(dataProvider = "cpuAttributeRequestProvider") public void shouldSetCPURequestOfASidecarIfCorrespondingComponentFieldIsSet( String cpuRequest, String expectedCpuRequest) throws InfrastructureException { component.setCpuRequest(cpuRequest); InternalMachineConfig machineConfig = resolver.resolve(); assertEquals(machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE), expectedCpuRequest); } @DataProvider public static Object[][] cpuAttributeLimitProvider() { return new Object[][] { {"", DEFAULT_CPU_LIMIT}, {null, DEFAULT_CPU_LIMIT}, {"100m", "0.1"}, {"1", "1.0"}, {"1578m", "1.578"}, }; } @DataProvider public static Object[][] cpuAttributeRequestProvider() { return new Object[][] { {"", DEFAULT_CPU_REQUEST}, {null, DEFAULT_CPU_REQUEST}, {"100m", "0.1"}, {"1", "1.0"}, {"1578m", "1.578"}, }; } @Test public void shouldOverrideMemoryLimitOfASidecarIfCorrespondingWSConfigAttributeIsSet() throws InfrastructureException { String memoryLimit = "300Mi"; String expectedMemLimit = toBytesString(memoryLimit); Containers.addRamLimit(container, 123456789); component.setMemoryLimit(memoryLimit); InternalMachineConfig machineConfig = resolver.resolve(); assertEquals(machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), expectedMemLimit); } @Test public void shouldNotSetMemLimitAttributeIfLimitIsInContainer() throws InfrastructureException { Containers.addRamLimit(container, 123456789); InternalMachineConfig machineConfig = resolver.resolve(); assertNull(machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE)); } @Test(expectedExceptions = InfrastructureException.class) public void shouldRefuseToMountProjectsManually() throws InfrastructureException { cheContainer.setMountSources(false); Volume volume = new Volume(); volume.setName(Constants.PROJECTS_VOLUME_NAME); volume.setMountPath("anything, like"); cheContainer.getVolumes().add(volume); resolver.resolve(); } @Test public void shouldAddVolumesFromDevfileComponent() throws InfrastructureException { component.setVolumes( asList( new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("foo", "/bar"), new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl( "test", "/foo/test"))); InternalMachineConfig config = resolver.resolve(); assertEquals(2, config.getVolumes().size()); assertEquals("/bar", config.getVolumes().get("foo").getPath()); assertEquals("/foo/test", config.getVolumes().get("test").getPath()); } @Test(expectedExceptions = InfrastructureException.class) public void shouldFailWhenEndpointNameAlreadyExist() throws InfrastructureException { // given ChePluginEndpoint pluginEndpoint = new ChePluginEndpoint(); pluginEndpoint.setName("test-endpoint"); endpoints.add(pluginEndpoint); component.setEndpoints( Collections.singletonList(new EndpointImpl("test-endpoint", 8080, Collections.emptyMap()))); // when -> then exception resolver.resolve(); } @Test public void shouldAddEndpointsFromDevfileComponent() throws InfrastructureException { component.setEndpoints( asList( new EndpointImpl("endpoint8080", 8080, Collections.emptyMap()), new EndpointImpl("endpoint9999", 9999, ImmutableMap.of("secure", "true")))); InternalMachineConfig config = resolver.resolve(); assertEquals(config.getServers().size(), 2); assertEquals(config.getServers().get("endpoint8080").getPort(), "8080"); assertEquals(config.getServers().get("endpoint9999").getPort(), "9999"); assertEquals(config.getServers().get("endpoint9999").getAttributes().get("secure"), "true"); } private static String toBytesString(String k8sMemorySize) { return Long.toString(KubernetesSize.toBytes(k8sMemorySize)); } private static ChePluginEndpoint endptPath(String name, int port, String path) { return new ChePluginEndpoint().name(name).targetPort(port).attributes(of("path", path)); } private static ChePluginEndpoint endptPrtc(String name, int port, String protocol) { return new ChePluginEndpoint().name(name).targetPort(port).attributes(of("protocol", protocol)); } private static ChePluginEndpoint endpt(String name, int port, boolean isPublic) { return new ChePluginEndpoint().name(name).targetPort(port).setPublic(isPublic); } private static ChePluginEndpoint endpt(String name, int port, Map attributes) { return new ChePluginEndpoint().name(name).targetPort(port).attributes(attributes); } private static ChePluginEndpoint endpt(String name, int port) { return new ChePluginEndpoint().name(name).targetPort(port); } private static ServerConfigImpl server(int port) { return server(port, false); } private static ServerConfig server(int port, Map attributes) { ServerConfigImpl server = server(port); server.getAttributes().putAll(attributes); return server; } private static ServerConfigImpl serverPath(int port, String path) { return server(port).withPath(path); } private static ServerConfigImpl serverPrtc(int port, String protocol) { return server(port).withProtocol(protocol); } private static ServerConfigImpl server(int port, boolean external) { return new ServerConfigImpl() .withPort(port + "/tcp") .withAttributes(of("internal", Boolean.toString(!external))); } private org.eclipse.che.api.core.model.workspace.config.Volume volume(String mountPath) { return new VolumeImpl().withPath(mountPath); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/SidecarServicesProvisionerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static java.util.stream.Collectors.toMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServicePortBuilder; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Function; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePluginEndpoint; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * @author Oleksandr Garagatyi */ public class SidecarServicesProvisionerTest { private static final String POD_NAME = "testPod"; private static final String CONFLICTING_SERVICE_NAME = "testService"; private SidecarServicesProvisioner provisioner; private List endpoints; @BeforeMethod public void setUp() { endpoints = new ArrayList<>(); provisioner = new SidecarServicesProvisioner(endpoints, POD_NAME); } @Test public void shouldAddServiceForEachEndpoint() throws Exception { List actualEndpoints = asList( new ChePluginEndpoint().name("testE1").targetPort(8080), new ChePluginEndpoint().name("testE2").targetPort(10000)); endpoints.addAll(actualEndpoints); KubernetesEnvironment kubernetesEnvironment = KubernetesEnvironment.builder().build(); provisioner.provision(kubernetesEnvironment); assertEquals(kubernetesEnvironment.getServices(), toK8sServices(actualEndpoints)); } @Test public void shouldNotAddServiceForNotdDiscoverableEndpoint() throws Exception { List actualEndpoints = asList( new ChePluginEndpoint().name("testE1").targetPort(8080), new ChePluginEndpoint() .name("testE2") .targetPort(10000) .attributes(ImmutableMap.of("discoverable", "false"))); endpoints.addAll(actualEndpoints); KubernetesEnvironment kubernetesEnvironment = KubernetesEnvironment.builder().build(); provisioner.provision(kubernetesEnvironment); assertEquals( kubernetesEnvironment.getServices(), toK8sServices(ImmutableList.of(new ChePluginEndpoint().name("testE1").targetPort(8080)))); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Applying of sidecar tooling failed. Kubernetes service with name '" + CONFLICTING_SERVICE_NAME + "' already exists in the workspace environment.") public void shouldNotDuplicateServicesWhenThereAreConflictingEndpoints() throws Exception { List actualEndpoints = asList( new ChePluginEndpoint().name(CONFLICTING_SERVICE_NAME).targetPort(8080), new ChePluginEndpoint().name(CONFLICTING_SERVICE_NAME).targetPort(10000)); endpoints.addAll(actualEndpoints); KubernetesEnvironment kubernetesEnvironment = KubernetesEnvironment.builder().build(); provisioner.provision(kubernetesEnvironment); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Applying of sidecar tooling failed. Kubernetes service with name '" + CONFLICTING_SERVICE_NAME + "' already exists in the workspace environment.") public void shouldNotDuplicateServicesWhenThereIsConflictingServiceInK8sEnv() throws Exception { List actualEndpoints = singletonList(new ChePluginEndpoint().name(CONFLICTING_SERVICE_NAME).targetPort(8080)); endpoints.addAll(actualEndpoints); KubernetesEnvironment kubernetesEnvironment = KubernetesEnvironment.builder() .setServices(singletonMap(CONFLICTING_SERVICE_NAME, new Service())) .build(); provisioner.provision(kubernetesEnvironment); } private Map toK8sServices(List endpoints) { return endpoints.stream() .map(this::createService) .collect(toMap(s -> s.getMetadata().getName(), Function.identity())); } private Service createService(ChePluginEndpoint endpoint) { ServicePort servicePort = new ServicePortBuilder() .withPort(endpoint.getTargetPort()) .withProtocol("TCP") .withNewTargetPort(endpoint.getTargetPort()) .build(); return new ServiceBuilder() .withNewMetadata() .withName(endpoint.getName()) .endMetadata() .withNewSpec() .withSelector(singletonMap(CHE_ORIGINAL_NAME_LABEL, POD_NAME)) .withPorts(singletonList(servicePort)) .endSpec() .build(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/BrokerEnvironmentFactoryTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases; import static java.util.Collections.singletonList; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableList; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.Quantity; import java.net.URI; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.provision.env.AgentAuthEnableEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.MachineTokenEnvVarProvider; import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.BrokerEnvironmentFactory.BrokersConfigs; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ @Listeners(MockitoTestNGListener.class) public class BrokerEnvironmentFactoryTest { private static final String ARTIFACTS_BROKER_IMAGE = "artifacts:image"; private static final String METADATA_BROKER_IMAGE = "metadata:image"; private static final String DEFAULT_REGISTRY = "default.registry"; private static final String IMAGE_PULL_POLICY = "Never"; private static final String PUSH_ENDPOINT = "http://localhost:8080"; private static final String PLUGINS_VOLUME_NAME = "plugins"; private static final String CA_CERTIFICATES_MOUNT_PATH = "/public-certs"; @Mock private CertificateProvisioner certProvisioner; @Mock private AgentAuthEnableEnvVarProvider authEnableEnvVarProvider; @Mock private MachineTokenEnvVarProvider machineTokenEnvVarProvider; @Mock private RuntimeIdentity runtimeId; private BrokerEnvironmentFactory factory; @BeforeMethod public void setUp() throws Exception { factory = spy( new BrokerEnvironmentFactory( PUSH_ENDPOINT, null, IMAGE_PULL_POLICY, authEnableEnvVarProvider, machineTokenEnvVarProvider, ARTIFACTS_BROKER_IMAGE, METADATA_BROKER_IMAGE, DEFAULT_REGISTRY, "", CA_CERTIFICATES_MOUNT_PATH, certProvisioner) { @Override protected KubernetesEnvironment doCreate(BrokersConfigs brokersConfigs) { return null; } }); lenient() .when(authEnableEnvVarProvider.get(any(RuntimeIdentity.class))) .thenReturn(new Pair<>("test1", "value1")); lenient() .when(machineTokenEnvVarProvider.get(any(RuntimeIdentity.class))) .thenReturn(new Pair<>("test2", "value2")); lenient().when(runtimeId.getEnvName()).thenReturn("env"); lenient().when(runtimeId.getOwnerId()).thenReturn("owner"); lenient().when(runtimeId.getWorkspaceId()).thenReturn("wsid"); lenient().when(certProvisioner.isConfigured()).thenReturn(false); } @Test public void testMetadataBrokerSelfSignedCertificate() throws Exception { when(certProvisioner.isConfigured()).thenReturn(true); when(certProvisioner.getCertPath()).thenReturn("/tmp/che/cacert"); // given Collection pluginFQNs = singletonList(new PluginFQN(null, "id")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when factory.createForMetadataBroker(pluginFQNs, runtimeId, false); // then verify(factory).doCreate(captor.capture()); BrokersConfigs brokersConfigs = captor.getValue(); List containers = brokersConfigs.pods.values().stream() .flatMap(p -> p.getSpec().getContainers().stream()) .collect(Collectors.toList()); assertEquals(containers.size(), 1); Container container = containers.get(0); assertEquals( container.getArgs().toArray(), new String[] { "--push-endpoint", PUSH_ENDPOINT, "--runtime-id", String.format( "%s:%s:%s", runtimeId.getWorkspaceId(), runtimeId.getEnvName(), runtimeId.getOwnerId()), "--cacert", "/tmp/che/cacert", "--registry-address", DEFAULT_REGISTRY, "--metas", "/broker-config/config.json", }); } @Test public void testArtifactsBrokerSelfSignedCertificate() throws Exception { when(certProvisioner.isConfigured()).thenReturn(true); when(certProvisioner.getCertPath()).thenReturn("/tmp/che/cacert"); // given Collection pluginFQNs = singletonList(new PluginFQN(null, "id")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when factory.createForArtifactsBroker(pluginFQNs, runtimeId, false); // then verify(factory).doCreate(captor.capture()); BrokersConfigs brokersConfigs = captor.getValue(); List containers = brokersConfigs.pods.values().stream() .flatMap(p -> p.getSpec().getContainers().stream()) .collect(Collectors.toList()); assertEquals(containers.size(), 1); Container container = containers.get(0); assertEquals( container.getArgs().toArray(), new String[] { "--push-endpoint", PUSH_ENDPOINT, "--runtime-id", String.format( "%s:%s:%s", runtimeId.getWorkspaceId(), runtimeId.getEnvName(), runtimeId.getOwnerId()), "--cacert", "/tmp/che/cacert", "--registry-address", DEFAULT_REGISTRY, "--metas", "/broker-config/config.json", }); } @Test public void testAddsMergeArgToArtifactsBroker() throws Exception { // given Collection pluginFQNs = singletonList(new PluginFQN(null, "id")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when factory.createForArtifactsBroker(pluginFQNs, runtimeId, true); // then verify(factory).doCreate(captor.capture()); BrokersConfigs brokersConfigs = captor.getValue(); List containers = brokersConfigs.pods.values().stream() .flatMap(p -> p.getSpec().getContainers().stream()) .collect(Collectors.toList()); assertEquals(containers.size(), 1); Container container = containers.get(0); assertTrue(container.getArgs().stream().anyMatch(e -> "--merge-plugins".equals(e))); } @Test public void testAddsMergeArgToMetadataBroker() throws Exception { // given Collection pluginFQNs = singletonList(new PluginFQN(null, "id")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when factory.createForMetadataBroker(pluginFQNs, runtimeId, true); // then verify(factory).doCreate(captor.capture()); BrokersConfigs brokersConfigs = captor.getValue(); List containers = brokersConfigs.pods.values().stream() .flatMap(p -> p.getSpec().getContainers().stream()) .collect(Collectors.toList()); assertEquals(containers.size(), 1); Container container = containers.get(0); assertTrue(container.getArgs().stream().anyMatch(e -> "--merge-plugins".equals(e))); } @Test public void shouldNameContainersAfterMetadataPluginBrokerImage() throws Exception { // given Collection pluginFQNs = singletonList(new PluginFQN(null, "id")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when factory.createForMetadataBroker(pluginFQNs, runtimeId, false); // then verify(factory).doCreate(captor.capture()); BrokersConfigs brokersConfigs = captor.getValue(); PodSpec brokerPodSpec = brokersConfigs.pods.values().iterator().next().getSpec(); List containers = brokerPodSpec.getContainers(); assertEquals(containers.size(), 1); assertEquals(containers.get(0).getName(), "metadata-image"); } @Test public void shouldNameContainersAfterArtifactsPluginBrokerImage() throws Exception { // given Collection pluginFQNs = singletonList(new PluginFQN(null, "id")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when factory.createForArtifactsBroker(pluginFQNs, runtimeId, false); // then verify(factory).doCreate(captor.capture()); BrokersConfigs brokersConfigs = captor.getValue(); PodSpec brokerPodSpec = brokersConfigs.pods.values().iterator().next().getSpec(); List containers = brokerPodSpec.getContainers(); assertEquals(containers.size(), 1); assertEquals(containers.get(0).getName(), "artifacts-image"); } @Test public void shouldCreateConfigMapWithPluginFQNsWithMetadataBroker() throws Exception { // given Collection pluginFQNs = ImmutableList.of( new PluginFQN(null, "testPublisher/testPlugin1/testver1"), new PluginFQN(new URI("testregistry"), "testPublisher/testPlugin2/testver2")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when factory.createForMetadataBroker(pluginFQNs, runtimeId, false); // then verify(factory).doCreate(captor.capture()); BrokersConfigs brokersConfigs = captor.getValue(); ConfigMap brokerConfigMap = brokersConfigs.configMaps.values().iterator().next(); String config = brokerConfigMap.getData().get(BrokerEnvironmentFactory.CONFIG_FILE); assertFalse(config.contains("\"registry\":null"), "Should not serialize null registry"); List expected = ImmutableList.of( "\"id\":\"testPublisher/testPlugin1/testver1\"", "\"registry\":\"testregistry\"", "\"id\":\"testPublisher/testPlugin2/testver2\""); for (String expect : expected) { assertTrue( config.contains(expect), String.format( "Missing field from serialized config: expected '%s' in '%s'", expect, config)); } } @Test public void shouldCreateConfigMapWithPluginFQNsWithArtifactsBroker() throws Exception { // given Collection pluginFQNs = ImmutableList.of( new PluginFQN(null, "testPublisher/testPlugin1/testver1"), new PluginFQN(new URI("testregistry"), "testPublisher/testPlugin2/testver2")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when factory.createForArtifactsBroker(pluginFQNs, runtimeId, false); // then verify(factory).doCreate(captor.capture()); BrokersConfigs brokersConfigs = captor.getValue(); ConfigMap brokerConfigMap = brokersConfigs.configMaps.values().iterator().next(); String config = brokerConfigMap.getData().get(BrokerEnvironmentFactory.CONFIG_FILE); assertFalse(config.contains("\"registry\":null"), "Should not serialize null registry"); List expected = ImmutableList.of( "\"id\":\"testPublisher/testPlugin1/testver1\"", "\"registry\":\"testregistry\"", "\"id\":\"testPublisher/testPlugin2/testver2\""); for (String expect : expected) { assertTrue( config.contains(expect), String.format( "Missing field from serialized config: expected '%s' in '%s'", expect, config)); } } @Test public void shouldIncludePluginsVolumeInArtifactsBroker() throws Exception { // given Collection pluginFQNs = singletonList(new PluginFQN(null, "id")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when factory.createForArtifactsBroker(pluginFQNs, runtimeId, false); // then verify(factory).doCreate(captor.capture()); BrokersConfigs brokersConfigs = captor.getValue(); InternalMachineConfig machine = brokersConfigs.machines.values().iterator().next(); assertTrue(machine.getVolumes().containsKey(PLUGINS_VOLUME_NAME)); assertEquals(machine.getVolumes().get(PLUGINS_VOLUME_NAME).getPath(), "/plugins"); } @Test public void shouldNotIncludePluginsVolumeInMetadataBroker() throws Exception { // given Collection pluginFQNs = singletonList(new PluginFQN(null, "id")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when factory.createForMetadataBroker(pluginFQNs, runtimeId, false); // then verify(factory).doCreate(captor.capture()); BrokersConfigs brokersConfigs = captor.getValue(); InternalMachineConfig machine = brokersConfigs.machines.values().iterator().next(); assertFalse(machine.getVolumes().containsKey(PLUGINS_VOLUME_NAME)); } @Test(dataProvider = "imageRefs") public void testImageToContainerNameConversion(Object image, Object expected) { String actual = factory.generateContainerNameFromImageRef((String) image); assertEquals( actual, expected, String.format("Should generate name '%s' from image '%s'.", expected, image)); } @Test public void testQuotasBroker() throws Exception { // given Collection pluginFQNs = singletonList(new PluginFQN(null, "id")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when factory.createForMetadataBroker(pluginFQNs, runtimeId, false); // then verify(factory).doCreate(captor.capture()); BrokersConfigs brokersConfigs = captor.getValue(); List containers = brokersConfigs.pods.values().stream() .flatMap(p -> p.getSpec().getContainers().stream()) .collect(Collectors.toList()); assertEquals(containers.size(), 1); Container container = containers.get(0); assertEquals(container.getResources().getRequests().get("memory"), new Quantity("250Mi")); assertEquals(container.getResources().getLimits().get("memory"), new Quantity("250Mi")); assertEquals(container.getResources().getRequests().get("cpu"), new Quantity("300m")); assertEquals(container.getResources().getLimits().get("cpu"), new Quantity("300m")); } @DataProvider(name = "imageRefs") public Object[][] imageRefs() { return new Object[][] { {"quay.io/eclipse/che-unified-plugin-broker:v0.20", "che-unified-plugin-broker-v0-20"}, {"very-long-registry-hostname-url.service/eclipse/image:tag", "image-tag"}, {"eclipse/che-unified-plugin-broker:v0.20", "che-unified-plugin-broker-v0-20"}, {"very-long-organization.name-eclipse-che/image:tag", "image-tag"}, {"very-long-registry-hostname-url.service/very-long-organization/image:tag", "image-tag"}, { "image-with-digest@sha256:7b868470f7b63d9da10a788d26abf4c076f90dc4c7de24d1298a8160c9a3dcc9", "image-with-digest-7b868470f7" }, {"image-with-short-digest@sha256:abcd", "image-with-short-digest-abcd"}, {"no-exception-when-no-colon@sha256abcd", "no-exception-when-no-colon-sha256abcd"}, { "image-and-tag-longer-than-63-chars:really-long-tag-for-some-reason", "image-and-tag-longer-than-63-chars-really-long-tag-for-some-rea" } }; } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/DeployBrokerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.workspace.shared.Constants.DEBUG_WORKSPACE_START; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertSame; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.opentracing.Tracer; import java.util.List; import java.util.Set; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.workspace.infrastructure.kubernetes.RuntimeLogsPublisher; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesDeployments; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.LogWatchTimeouts; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.LogWatcher; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.PodLogHandler; import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher; import org.eclipse.che.workspace.infrastructure.kubernetes.util.UnrecoverablePodEventListener; import org.eclipse.che.workspace.infrastructure.kubernetes.util.UnrecoverablePodEventListenerFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.BrokersResult; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link DeployBroker}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class DeployBrokerTest { private static final String PLUGIN_BROKER_POD_NAME = "pluginBrokerPodName"; private static final RuntimeIdentity RUNTIME_ID = new RuntimeIdentityImpl("workspaceId", "env", "userId", "infraNamespace"); @Mock private BrokerPhase nextBrokerPhase; @Mock private KubernetesNamespace k8sNamespace; @Mock private KubernetesDeployments k8sDeployments; @Mock private KubernetesConfigsMaps k8sConfigMaps; @Mock private KubernetesSecrets k8sSecrets; @Mock private KubernetesEnvironment k8sEnvironment; @Mock private ConfigMap configMap; @Mock private Secret tlsSecret; private Pod pod; @Mock private BrokersResult brokersResult; @Mock private UnrecoverablePodEventListenerFactory unrecoverableEventListenerFactory; @Mock private RuntimeEventsPublisher runtimeEventPublisher; @Mock(answer = Answers.RETURNS_MOCKS) private Tracer tracer; private List plugins = emptyList(); private DeployBroker deployBrokerPhase; @BeforeMethod public void setUp() throws Exception { deployBrokerPhase = new DeployBroker( RUNTIME_ID, k8sNamespace, k8sEnvironment, brokersResult, unrecoverableEventListenerFactory, runtimeEventPublisher, tracer, emptyMap()); deployBrokerPhase.then(nextBrokerPhase); when(nextBrokerPhase.execute()).thenReturn(plugins); when(k8sNamespace.configMaps()).thenReturn(k8sConfigMaps); when(k8sNamespace.deployments()).thenReturn(k8sDeployments); when(k8sNamespace.secrets()).thenReturn(k8sSecrets); pod = new PodBuilder().withNewMetadata().withName(PLUGIN_BROKER_POD_NAME).endMetadata().build(); when(k8sEnvironment.getPodsCopy()).thenReturn(ImmutableMap.of(PLUGIN_BROKER_POD_NAME, pod)); when(k8sEnvironment.getConfigMaps()).thenReturn(ImmutableMap.of("configMap", configMap)); when(k8sEnvironment.getSecrets()).thenReturn(ImmutableMap.of("secret", tlsSecret)); when(k8sDeployments.create(any())).thenReturn(pod); } @Test public void shouldDeployPluginBrokerEnvironment() throws Exception { // when List result = deployBrokerPhase.execute(); // then assertSame(result, plugins); verify(k8sConfigMaps).create(configMap); verify(k8sDeployments).create(pod); verify(k8sSecrets).create(tlsSecret); verify(k8sDeployments).stopWatch(); verify(k8sDeployments).delete(); verify(k8sConfigMaps).delete(); verify(k8sSecrets).delete(); } @Test public void shouldListenToUnrecoverableEventsIfFactoryIsConfigured() throws Exception { // given when(unrecoverableEventListenerFactory.isConfigured()).thenReturn(true); UnrecoverablePodEventListener listener = mock(UnrecoverablePodEventListener.class); when(unrecoverableEventListenerFactory.create(any(Set.class), any())).thenReturn(listener); // when deployBrokerPhase.execute(); // then verify(unrecoverableEventListenerFactory).isConfigured(); verify(unrecoverableEventListenerFactory) .create(eq(ImmutableSet.of(PLUGIN_BROKER_POD_NAME)), any()); verify(k8sDeployments).watchEvents(listener); verify(k8sDeployments).stopWatch(); } @Test public void shouldListenToPodEventsToPropagateThemAsLogs() throws Exception { // given // when deployBrokerPhase.execute(); // then verify(k8sDeployments).watchEvents(any(RuntimeLogsPublisher.class)); verify(k8sDeployments).stopWatch(); } @Test public void shouldWatchTheLogsWhenSetInStartOptions() throws Exception { // given deployBrokerPhase = new DeployBroker( RUNTIME_ID, k8sNamespace, k8sEnvironment, brokersResult, unrecoverableEventListenerFactory, runtimeEventPublisher, tracer, singletonMap(DEBUG_WORKSPACE_START, Boolean.TRUE.toString())); deployBrokerPhase.then(nextBrokerPhase); when(nextBrokerPhase.execute()).thenReturn(plugins); // when deployBrokerPhase.execute(); // then verify(k8sDeployments) .watchLogs( any(PodLogHandler.class), any(RuntimeEventsPublisher.class), any(LogWatchTimeouts.class), any(), eq(LogWatcher.DEFAULT_LOG_LIMIT_BYTES)); verify(k8sDeployments).stopWatch(); } @Test public void shouldWatchTheLogsWithProperBytesLimitWhenSet() throws Exception { // given deployBrokerPhase = new DeployBroker( RUNTIME_ID, k8sNamespace, k8sEnvironment, brokersResult, unrecoverableEventListenerFactory, runtimeEventPublisher, tracer, ImmutableMap.of( DEBUG_WORKSPACE_START, Boolean.TRUE.toString(), Constants.DEBUG_WORKSPACE_START_LOG_LIMIT_BYTES, "123")); deployBrokerPhase.then(nextBrokerPhase); when(nextBrokerPhase.execute()).thenReturn(plugins); // when deployBrokerPhase.execute(); // then verify(k8sDeployments) .watchLogs( any(PodLogHandler.class), any(RuntimeEventsPublisher.class), any(LogWatchTimeouts.class), any(), eq(123L)); verify(k8sDeployments).stopWatch(); } @Test public void shouldNotWatchTheLogsWhenSetFalseInStartOptions() throws Exception { // given deployBrokerPhase = new DeployBroker( RUNTIME_ID, k8sNamespace, k8sEnvironment, brokersResult, unrecoverableEventListenerFactory, runtimeEventPublisher, tracer, singletonMap(DEBUG_WORKSPACE_START, Boolean.FALSE.toString())); deployBrokerPhase.then(nextBrokerPhase); when(nextBrokerPhase.execute()).thenReturn(plugins); // when deployBrokerPhase.execute(); // then verify(k8sDeployments, never()) .watchLogs( any(PodLogHandler.class), any(RuntimeEventsPublisher.class), any(LogWatchTimeouts.class), any(), eq(LogWatcher.DEFAULT_LOG_LIMIT_BYTES)); verify(k8sDeployments).stopWatch(); } @Test public void shouldNotWatchTheLogsWhenEmptyStartOptions() throws Exception { // given // when deployBrokerPhase.execute(); // then verify(k8sDeployments, never()) .watchLogs( any(PodLogHandler.class), any(RuntimeEventsPublisher.class), any(LogWatchTimeouts.class), any(), eq(LogWatcher.DEFAULT_LOG_LIMIT_BYTES)); verify(k8sDeployments).stopWatch(); } @Test public void shouldNotWatchTheLogsWhenNullStartOptions() throws Exception { // given deployBrokerPhase = new DeployBroker( RUNTIME_ID, k8sNamespace, k8sEnvironment, brokersResult, unrecoverableEventListenerFactory, runtimeEventPublisher, tracer, null); deployBrokerPhase.then(nextBrokerPhase); when(nextBrokerPhase.execute()).thenReturn(plugins); // when deployBrokerPhase.execute(); // then verify(k8sDeployments, never()) .watchLogs( any(PodLogHandler.class), any(RuntimeEventsPublisher.class), any(LogWatchTimeouts.class), any(), eq(LogWatcher.DEFAULT_LOG_LIMIT_BYTES)); verify(k8sDeployments).stopWatch(); } @Test public void shouldDoNotListenToUnrecoverableEventsIfFactoryIsConfigured() throws Exception { // given when(unrecoverableEventListenerFactory.isConfigured()).thenReturn(false); // when deployBrokerPhase.execute(); // then verify(unrecoverableEventListenerFactory).isConfigured(); verify(unrecoverableEventListenerFactory, never()) .create(eq(ImmutableSet.of(PLUGIN_BROKER_POD_NAME)), any()); verify(k8sDeployments, never()).watchEvents(any(UnrecoverablePodEventListener.class)); verify(k8sDeployments).stopWatch(); } @Test( expectedExceptions = InternalInfrastructureException.class, expectedExceptionsMessageRegExp = "Plugin broker environment must have only one pod\\. Workspace `workspaceId` contains `0` pods\\.") public void shouldThrowExceptionIfThereIsNoAnyPodsInEnvironment() throws Exception { // given when(k8sEnvironment.getPodsCopy()).thenReturn(emptyMap()); // when deployBrokerPhase.execute(); } @Test( expectedExceptions = InternalInfrastructureException.class, expectedExceptionsMessageRegExp = "Plugin broker environment must have only one pod\\. Workspace `workspaceId` contains `2` pods\\.") public void shouldThrowExceptionIfThereAreMoreThanOnePodsInEnvironment() throws Exception { // given when(k8sEnvironment.getPodsCopy()).thenReturn(ImmutableMap.of("pod1", pod, "pod2", pod)); // when deployBrokerPhase.execute(); } } ================================================ FILE: infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/events/BrokerStatusListenerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events; import static java.util.Collections.emptyList; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.shared.dto.BrokerStatus; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.BrokersResult; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.KubernetesPluginsToolingValidator; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link BrokerStatusListener}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class BrokerStatusListenerTest { public static final String WORKSPACE_ID = "workspace123"; @Mock BrokersResult brokersResult; @Mock KubernetesPluginsToolingValidator validator; private BrokerStatusListener brokerStatusListener; @BeforeMethod public void setUp() { brokerStatusListener = new BrokerStatusListener(WORKSPACE_ID, validator, brokersResult); } @Test public void shouldDoNothingIfEventWithForeignWorkspaceIdIsReceived() { // given BrokerEvent event = new BrokerEvent() .withRuntimeId(new RuntimeIdentityImpl("foreignWorkspace", null, null, null)); // when brokerStatusListener.onEvent(event); // then verifyNoMoreInteractions(brokersResult); } @Test public void shouldDoNothingIfEventWithoutRuntimeIdentityIsReceived() { // given BrokerEvent event = new BrokerEvent().withRuntimeId(null); // when brokerStatusListener.onEvent(event); // then verifyNoMoreInteractions(brokersResult); } @Test public void shouldAddResultWhenDoneEventIsReceivedAndToolingIsNotNull() throws Exception { // given BrokerEvent event = new BrokerEvent() .withRuntimeId(new RuntimeIdentityImpl(WORKSPACE_ID, null, null, null)) .withStatus(BrokerStatus.DONE) .withTooling(emptyList()); // when brokerStatusListener.onEvent(event); // then verify(brokersResult).setResult(emptyList()); } @Test public void shouldSubmitErrorWhenDoneEventIsReceivedButToolingIsValidationFails() throws Exception { // given doThrow(new ValidationException("test")).when(validator).validatePluginNames(anyList()); BrokerEvent event = new BrokerEvent() .withRuntimeId(new RuntimeIdentityImpl(WORKSPACE_ID, null, null, null)) .withStatus(BrokerStatus.DONE) .withTooling(emptyList()); // when brokerStatusListener.onEvent(event); // then verify(brokersResult).error(any(ValidationException.class)); } @Test public void shouldNotCallAddResultWhenValidationFails() throws Exception { // given doThrow(new ValidationException("test")).when(validator).validatePluginNames(anyList()); BrokerEvent event = new BrokerEvent() .withRuntimeId(new RuntimeIdentityImpl(WORKSPACE_ID, null, null, null)) .withStatus(BrokerStatus.DONE) .withTooling(emptyList()); // when brokerStatusListener.onEvent(event); // then verify(brokersResult, never()).setResult(anyList()); } @Test public void shouldSubmitErrorWhenDoneEventIsReceivedButToolingIsNull() { // given BrokerEvent event = new BrokerEvent() .withRuntimeId(new RuntimeIdentityImpl(WORKSPACE_ID, null, null, null)) .withStatus(BrokerStatus.DONE) .withTooling(null); // when brokerStatusListener.onEvent(event); // then verify(brokersResult).error(any(InternalInfrastructureException.class)); } @Test public void shouldSubmitErrorWhenFailedEventIsReceived() { // given BrokerEvent event = new BrokerEvent() .withRuntimeId(new RuntimeIdentityImpl(WORKSPACE_ID, null, null, null)) .withStatus(BrokerStatus.FAILED) .withError("error"); // when brokerStatusListener.onEvent(event); // then verify(brokersResult).error(any(InfrastructureException.class)); } } ================================================ FILE: infrastructures/kubernetes/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule ================================================ org.eclipse.che.workspace.infrastructure.kubernetes.cache.jpa.JpaTckModule ================================================ FILE: infrastructures/kubernetes/src/test/resources/devfile/petclinic.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: v1 kind: List items: - apiVersion: v1 kind: Pod metadata: name: petclinic labels: app.kubernetes.io/name: petclinic app.kubernetes.io/component: webapp app.kubernetes.io/part-of: petclinic spec: initContainers: - command: - "echo foo:bar" image: "openjdk:11-jre-openjdk9" name: "init" containers: - name: server image: mariolet/petclinic ports: - containerPort: 8080 protocol: TCP resources: limits: memory: 512Mi - apiVersion: v1 kind: Pod metadata: name: mysql labels: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic spec: containers: - name: mysql image: centos/mysql-57-centos7 env: - name: MYSQL_USER value: petclinic - name: MYSQL_PASSWORD value: petclinic - name: MYSQL_ROOT_PASSWORD value: petclinic - name: MYSQL_DATABASE value: petclinic ports: - containerPort: 3306 protocol: TCP resources: limits: memory: 512Mi - apiVersion: v1 kind: Pod metadata: name: withoutLabels spec: containers: - name: server imagePullPolicy: Always image: test/petclinic ports: - containerPort: 8080 protocol: TCP resources: limits: memory: 512Mi volumeMounts: - name: foo_volume mountPath: /foo/bar - kind: Service apiVersion: v1 metadata: name: mysql labels: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic spec: ports: - name: mysql port: 3306 targetPort: 3360 selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic - kind: Service apiVersion: v1 metadata: name: petclinic labels: app.kubernetes.io/name: petclinic app.kubernetes.io/component: webapp app.kubernetes.io/part-of: petclinic spec: ports: - name: web port: 8080 targetPort: 8080 selector: app: petclinic component: webapp - kind: Route apiVersion: v1 metadata: name: petclinic labels: app.kubernetes.io/name: petclinic app.kubernetes.io/component: webapp app.kubernetes.io/part-of: petclinic spec: to: kind: Service name: petclinic port: targetPort: web ================================================ FILE: infrastructures/kubernetes/src/test/resources/devfile/petclinicPods.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: v1 kind: List items: - apiVersion: v1 kind: Pod metadata: name: petclinic labels: app.kubernetes.io/name: petclinic app.kubernetes.io/component: webapp app.kubernetes.io/part-of: petclinic spec: containers: - name: server image: mariolet/petclinic ports: - containerPort: 8080 protocol: TCP resources: limits: memory: 512Mi - apiVersion: v1 kind: Pod metadata: name: mysql labels: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic spec: containers: - name: mysql image: centos/mysql-57-centos7 env: - name: MYSQL_USER value: petclinic - name: MYSQL_PASSWORD value: petclinic - name: MYSQL_ROOT_PASSWORD value: petclinic - name: MYSQL_DATABASE value: petclinic ports: - containerPort: 3306 protocol: TCP resources: limits: memory: 512Mi - apiVersion: v1 kind: Pod metadata: name: withoutLabels spec: containers: - name: server imagePullPolicy: Always image: test/petclinic ports: - containerPort: 8080 protocol: TCP resources: limits: memory: 512Mi ================================================ FILE: infrastructures/kubernetes/src/test/resources/jwtproxy-confg.yaml ================================================ --- jwtproxy: signer_proxy: enabled: false verifier_proxies: - listen_addr: ":8080" verifier: audience: "workspace123" auth_cookies_enabled: false claims_verifiers: - options: iss: "wsmaster" type: "static" cookie_path: "/" key_server: options: issuer: "wsmaster" key_id: "workspace123" public_key_path: "/che-jwtproxy-config/mykey.pub" type: "preshared" max_skew: "1m" max_ttl: "1m" nonce_storage: type: "void" public_base_path: "/there" upstream: "http://tomcat:8080" - listen_addr: ":4101" verifier: audience: "workspace123" auth_cookies_enabled: true auth_redirect_url: "http://che-host.com/app/loader.html" claims_verifiers: - options: iss: "wsmaster" type: "static" cookie_path: "/cookies" excludes: - "/api/liveness" - "/other/exclude" key_server: options: issuer: "wsmaster" key_id: "workspace123" public_key_path: "/che-jwtproxy-config/mykey.pub" type: "preshared" max_skew: "1m" max_ttl: "1m" nonce_storage: type: "void" public_base_path: "/here" upstream: "ws://terminal:4101" ================================================ FILE: infrastructures/kubernetes/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n target/log/log.log %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: infrastructures/openshift/pom.xml ================================================ 4.0.0 che-infrastructures-parent org.eclipse.che.infrastructure 7.118.0-SNAPSHOT infrastructure-openshift Infrastructure :: OpenShift com.google.guava guava com.google.inject guice com.google.inject.extensions guice-assistedinject io.fabric8 kubernetes-client-api io.fabric8 kubernetes-model-apps io.fabric8 kubernetes-model-core io.fabric8 kubernetes-model-networking io.fabric8 openshift-client netty-handler io.netty vertx-core io.vertx io.fabric8 openshift-client-api io.fabric8 openshift-model jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-api-system org.eclipse.che.core che-core-api-user org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang org.eclipse.che.core che-core-commons-tracing org.eclipse.che.infrastructure infrastructure-kubernetes org.eclipse.che.multiuser che-multiuser-api-authentication-commons org.eclipse.che.multiuser che-multiuser-api-authorization org.slf4j slf4j-api jakarta.servlet jakarta.servlet-api provided org.eclipse.che.core che-core-commons-inject provided io.netty netty-codec-http runtime io.netty netty-handler runtime io.vertx vertx-core runtime netty-resolver io.netty netty-resolver-dns io.netty netty-buffer io.netty netty-transport io.netty netty-common io.netty netty-codec-http io.netty ch.qos.logback logback-classic test io.fabric8 kubernetes-server-mock test junit-jupiter-api org.junit.jupiter io.fabric8 mockwebserver test netty-handler io.netty netty-handler-proxy io.netty netty-codec-http io.netty vertx-core io.vertx netty-resolver io.netty netty-buffer io.netty netty-transport io.netty netty-codec io.netty netty-common io.netty io.netty netty-handler-proxy test org.eclipse.che.core che-core-api-dto test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/CheServerOpenshiftClientFactory.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift; import io.fabric8.kubernetes.client.Config; import io.fabric8.openshift.client.OpenShiftClient; import javax.inject.Inject; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientConfigFactory; /** * This {@link OpenShiftClientFactory} ensures that we use `che` ServiceAccount and not related to * any workspace. It always provides client with default {@link Config}. It's useful for operations * that needs permissions of `che` SA, such as operations inside `che` namespace (like projects) or * some cluster-wide actions (like labeling the namespaces). * *

    See also {@link * org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory} */ public class CheServerOpenshiftClientFactory extends OpenShiftClientFactory { @Inject public CheServerOpenshiftClientFactory(KubernetesClientConfigFactory configBuilder) { super(configBuilder); } @Override protected Config buildConfig(Config config, String workspaceId) throws InfrastructureException { return config; } @Override public OpenShiftClient createOC(String workspaceId) throws InfrastructureException { return super.createOC(); } @Override public OpenShiftClient createOC() throws InfrastructureException { return super.createOC(); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/Constants.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift; /** * Constants for OpenShift implementation of spi. * * @author Sergii Leshchenko */ public final class Constants { private Constants() {} /** * Attribute name of {@link * org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta} that * stores project display name. */ public static final String PROJECT_DISPLAY_NAME_ATTRIBUTE = "displayName"; /** * Attribute name of {@link * org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta} that * stores project description. */ public static final String PROJECT_DESCRIPTION_ATTRIBUTE = "description"; /** * Annotation name of {@link io.fabric8.openshift.api.model.Project} that stores project display * name. */ public static final String PROJECT_DISPLAY_NAME_ANNOTATION = "openshift.io/display-name"; /** * Annotation name of {@link io.fabric8.openshift.api.model.Project} that stores project * description. */ public static final String PROJECT_DESCRIPTION_ANNOTATION = "openshift.io/description"; } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftClientFactory.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.http.HttpClient; import io.fabric8.kubernetes.client.utils.TokenRefreshInterceptor; import io.fabric8.openshift.client.DefaultOpenShiftClient; import io.fabric8.openshift.client.OpenShiftClient; import io.fabric8.openshift.client.OpenShiftConfig; import io.fabric8.openshift.client.internal.OpenShiftOAuthInterceptor; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientConfigFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; /** * @author Sergii Leshchenko * @author Anton Korneta */ @Singleton public class OpenShiftClientFactory extends KubernetesClientFactory { @Inject public OpenShiftClientFactory(KubernetesClientConfigFactory configBuilder) { super(configBuilder); } /** * Creates an instance of {@link OpenShiftClient} that can be used to perform any operation * related to a given workspace.
    Important note: However, in some * use-cases involving web sockets, the Openshift client may introduce connection leaks. That's * why this method should only be used for API calls that are specific to Openshift and thus not * available in the {@link KubernetesClient} class: mainly route-related calls and project-related * calls. For all other Kubernetes standard calls, prefer the {@code create(String workspaceId)} * method that returns a Kubernetes client. * * @param workspaceId Identifier of the workspace on which Openshift operations will be performed * @throws InfrastructureException if any error occurs on client instance creation. */ public OpenShiftClient createOC(String workspaceId) throws InfrastructureException { Config configForWorkspace = buildConfig(getDefaultConfig(), workspaceId); return create(configForWorkspace); } /** * Creates an instance of {@link OpenShiftClient} that can be used to perform any operation * that is not related to a given workspace.
    For operations performed in * the context of a given workspace (workspace start, workspace stop, etc ...), the {@code * createOC(String workspaceId)} method should be used to retrieve an Openshift client.
    * Important note: However in some use-cases involving web sockets, the * Openshift client may introduce connection leaks. That's why this method should only be used for * API calls that are specific to Openshift and thus not available in the {@link KubernetesClient} * class: mainly route-related calls and project-related calls. For all other Kubernetes standard * calls, just use the {@code create()} or {@code create(String workspaceId)} methods that return * a Kubernetes client. * * @throws InfrastructureException if any error occurs on client instance creation. */ public OpenShiftClient createOC() throws InfrastructureException { return create(buildConfig(getDefaultConfig(), null)); } public OpenShiftClient createAuthenticatedClient(String token) { Config config = getDefaultConfig(); config.setOauthToken(token); return create(config); } protected DefaultOpenShiftClient create(Config config) { OpenShiftConfig openshiftConfig = new OpenShiftConfig(config); return new UnclosableOpenShiftClient(clientForConfig(openshiftConfig), openshiftConfig); } private HttpClient clientForConfig(OpenShiftConfig config) { HttpClient.DerivedClientBuilder builder = httpClient.newBuilder().authenticatorNone(); builder.addOrReplaceInterceptor("HEADER", null); return builder .addOrReplaceInterceptor( TokenRefreshInterceptor.NAME, new OpenShiftOAuthInterceptor(httpClient, config)) .build(); } /** * Decorates the {@link DefaultKubernetesClient} so that it can not be closed from the outside. */ private static class UnclosableOpenShiftClient extends DefaultOpenShiftClient { public UnclosableOpenShiftClient(HttpClient httpClient, OpenShiftConfig config) { super(httpClient, config); } @Override public void close() {} } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisioner.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesEnvironmentProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GatewayRouterProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitConfigProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ImagePullSecretProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.PodTerminationGracePeriodProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ServiceAccountProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SshKeysProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisionerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.UniqueNamesProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSslCertificateProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.env.EnvVarsConverter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.limits.ram.ContainerResourceProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter; import org.eclipse.che.workspace.infrastructure.kubernetes.server.PreviewUrlExposer; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftUniqueNamesProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftPreviewUrlExposer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Applies the set of configurations to the OpenShift environment and environment configuration with * the desired order, which corresponds to the needs of the OpenShift infrastructure. * * @author Anton Korneta * @author Alexander Garagatyi */ @Singleton public class OpenShiftEnvironmentProvisioner implements KubernetesEnvironmentProvisioner { private static final Logger LOG = LoggerFactory.getLogger(OpenShiftEnvironmentProvisioner.class); private final UniqueNamesProvisioner uniqueNamesProvisioner; private final TlsProvisioner routeTlsProvisioner; private final ServersConverter serversConverter; private final EnvVarsConverter envVarsConverter; private final RestartPolicyRewriter restartPolicyRewriter; private final ContainerResourceProvisioner resourceLimitRequestProvisioner; private final PodTerminationGracePeriodProvisioner podTerminationGracePeriodProvisioner; private final ImagePullSecretProvisioner imagePullSecretProvisioner; private final ServiceAccountProvisioner serviceAccountProvisioner; private final CertificateProvisioner certificateProvisioner; private final SshKeysProvisioner sshKeysProvisioner; private final GitConfigProvisioner gitConfigProvisioner; private final PreviewUrlExposer previewUrlExposer; private final VcsSslCertificateProvisioner vcsSslCertificateProvisioner; private final GatewayRouterProvisioner gatewayRouterProvisioner; @Inject public OpenShiftEnvironmentProvisioner( OpenShiftUniqueNamesProvisioner uniqueNamesProvisioner, TlsProvisionerProvider routeTlsProvisionerProvider, ServersConverter serversConverter, EnvVarsConverter envVarsConverter, RestartPolicyRewriter restartPolicyRewriter, ContainerResourceProvisioner resourceLimitRequestProvisioner, PodTerminationGracePeriodProvisioner podTerminationGracePeriodProvisioner, ImagePullSecretProvisioner imagePullSecretProvisioner, ServiceAccountProvisioner serviceAccountProvisioner, CertificateProvisioner certificateProvisioner, SshKeysProvisioner sshKeysProvisioner, GitConfigProvisioner gitConfigProvisioner, OpenShiftPreviewUrlExposer previewUrlEndpointsProvisioner, VcsSslCertificateProvisioner vcsSslCertificateProvisioner, GatewayRouterProvisioner gatewayRouterProvisioner) { this.uniqueNamesProvisioner = uniqueNamesProvisioner; this.routeTlsProvisioner = routeTlsProvisionerProvider.get(); this.serversConverter = serversConverter; this.envVarsConverter = envVarsConverter; this.restartPolicyRewriter = restartPolicyRewriter; this.resourceLimitRequestProvisioner = resourceLimitRequestProvisioner; this.podTerminationGracePeriodProvisioner = podTerminationGracePeriodProvisioner; this.imagePullSecretProvisioner = imagePullSecretProvisioner; this.serviceAccountProvisioner = serviceAccountProvisioner; this.certificateProvisioner = certificateProvisioner; this.sshKeysProvisioner = sshKeysProvisioner; this.gitConfigProvisioner = gitConfigProvisioner; this.previewUrlExposer = previewUrlEndpointsProvisioner; this.vcsSslCertificateProvisioner = vcsSslCertificateProvisioner; this.gatewayRouterProvisioner = gatewayRouterProvisioner; } @Override @Traced public void provision(OpenShiftEnvironment osEnv, RuntimeIdentity identity) throws InfrastructureException { TracingTags.WORKSPACE_ID.set(identity::getWorkspaceId); LOG.debug( "Start provisioning OpenShift environment for workspace '{}'", identity.getWorkspaceId()); // 1st stage - converting Che model env to OpenShift env serversConverter.provision(osEnv, identity); previewUrlExposer.expose(osEnv); envVarsConverter.provision(osEnv, identity); // 2nd stage - add OpenShift env items restartPolicyRewriter.provision(osEnv, identity); routeTlsProvisioner.provision(osEnv, identity); resourceLimitRequestProvisioner.provision(osEnv, identity); podTerminationGracePeriodProvisioner.provision(osEnv, identity); imagePullSecretProvisioner.provision(osEnv, identity); serviceAccountProvisioner.provision(osEnv, identity); certificateProvisioner.provision(osEnv, identity); sshKeysProvisioner.provision(osEnv, identity); vcsSslCertificateProvisioner.provision(osEnv, identity); gitConfigProvisioner.provision(osEnv, identity); gatewayRouterProvisioner.provision(osEnv, identity); uniqueNamesProvisioner.provision(osEnv, identity); LOG.debug( "Provisioning OpenShift environment done for workspace '{}'", identity.getWorkspaceId()); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift; import static org.eclipse.che.api.workspace.server.devfile.Constants.DOCKERIMAGE_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.OPENSHIFT_COMPONENT_TYPE; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY; import com.google.inject.AbstractModule; import com.google.inject.TypeLiteral; import com.google.inject.assistedinject.FactoryModuleBuilder; import com.google.inject.multibindings.MapBinder; import com.google.inject.multibindings.Multibinder; import org.eclipse.che.api.system.server.ServiceTermination; import org.eclipse.che.api.workspace.server.NoEnvironmentFactory; import org.eclipse.che.api.workspace.server.WorkspaceAttributeValidator; import org.eclipse.che.api.workspace.server.devfile.DevfileBindings; import org.eclipse.che.api.workspace.server.devfile.validator.ComponentIntegrityValidator.NoopComponentIntegrityValidator; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory; import org.eclipse.che.api.workspace.server.spi.provision.env.CheApiExternalEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.CheApiInternalEnvVarProvider; import org.eclipse.che.api.workspace.server.wsplugins.ChePluginsApplier; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.workspace.infrastructure.kubernetes.K8sInfraNamespaceWsAttributeValidator; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientTermination; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesEnvironmentProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.StartSynchronizerFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.KubernetesNamespaceService; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner; import org.eclipse.che.workspace.infrastructure.kubernetes.cache.jpa.JpaKubernetesRuntimeCacheModule; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.DockerimageComponentToWorkspaceApplier; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesComponentToWorkspaceApplier; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesComponentValidator; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesDevfileBindings; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.CredentialsSecretConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.GitconfigConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.OAuthTokenSecretsConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.PreferencesConfigMapConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.SshConfigConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserPermissionConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserPreferencesConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserProfileConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GatewayTlsProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.KubernetesCheApiExternalEnvVarProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.KubernetesCheApiInternalEnvVarProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.PreviewUrlCommandProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter; import org.eclipse.che.workspace.infrastructure.kubernetes.server.PreviewUrlExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.GatewayServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ServiceExposureStrategyProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactoryProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.CookiePathStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.util.NonTlsDistributedClusterModeNotifier; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.KubernetesPluginsToolingApplier; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.PluginBrokerManager; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.BrokerEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events.BrokerService; import org.eclipse.che.workspace.infrastructure.openshift.devfile.OpenshiftComponentToWorkspaceApplier; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftProjectFactory; import org.eclipse.che.workspace.infrastructure.openshift.project.configurator.OpenShiftWorkspaceServiceAccountConfigurator; import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftPreviewUrlCommandProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftCookiePathStrategy; import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftPreviewUrlExposer; import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftServerExposureStrategy; import org.eclipse.che.workspace.infrastructure.openshift.server.RouteServerExposer; import org.eclipse.che.workspace.infrastructure.openshift.server.external.OpenShiftExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.openshift.wsplugins.brokerphases.OpenshiftBrokerEnvironmentFactory; /** * @author Sergii Leshchenko */ public class OpenShiftInfraModule extends AbstractModule { @Override protected void configure() { Multibinder workspaceAttributeValidators = Multibinder.newSetBinder(binder(), WorkspaceAttributeValidator.class); workspaceAttributeValidators.addBinding().to(K8sInfraNamespaceWsAttributeValidator.class); Multibinder namespaceConfigurators = Multibinder.newSetBinder(binder(), NamespaceConfigurator.class); namespaceConfigurators.addBinding().to(UserPermissionConfigurator.class); namespaceConfigurators.addBinding().to(UserProfileConfigurator.class); namespaceConfigurators.addBinding().to(UserPreferencesConfigurator.class); namespaceConfigurators.addBinding().to(CredentialsSecretConfigurator.class); namespaceConfigurators.addBinding().to(OAuthTokenSecretsConfigurator.class); namespaceConfigurators.addBinding().to(PreferencesConfigMapConfigurator.class); namespaceConfigurators.addBinding().to(OpenShiftWorkspaceServiceAccountConfigurator.class); namespaceConfigurators.addBinding().to(SshConfigConfigurator.class); namespaceConfigurators.addBinding().to(GitconfigConfigurator.class); bind(PermissionsCleaner.class).asEagerSingleton(); bind(KubernetesNamespaceService.class); MapBinder factories = MapBinder.newMapBinder(binder(), String.class, InternalEnvironmentFactory.class); factories.addBinding(OpenShiftEnvironment.TYPE).to(OpenShiftEnvironmentFactory.class); factories.addBinding(KubernetesEnvironment.TYPE).to(KubernetesEnvironmentFactory.class); factories.addBinding(Constants.NO_ENVIRONMENT_RECIPE_TYPE).to(NoEnvironmentFactory.class); String kubernetesNamespacesCreation = System.getenv("CHE_INFRA_OPENSHIFT__KUBERNETES_NAMESPACES_CREATION"); if (!"true".equalsIgnoreCase(kubernetesNamespacesCreation)) { bind(KubernetesNamespaceFactory.class).to(OpenShiftProjectFactory.class); } bind(KubernetesClientFactory.class).to(OpenShiftClientFactory.class); bind(CheServerOpenshiftClientFactory.class); install(new FactoryModuleBuilder().build(StartSynchronizerFactory.class)); bind(CheApiInternalEnvVarProvider.class).to(KubernetesCheApiInternalEnvVarProvider.class); bind(CheApiExternalEnvVarProvider.class).to(KubernetesCheApiExternalEnvVarProvider.class); MapBinder> exposureStrategies = MapBinder.newMapBinder(binder(), new TypeLiteral<>() {}, new TypeLiteral<>() {}); exposureStrategies.addBinding(WorkspaceExposureType.NATIVE).to(RouteServerExposer.class); exposureStrategies .addBinding(WorkspaceExposureType.GATEWAY) .to(new TypeLiteral>() {}); bind(new TypeLiteral>() {}) .annotatedWith(com.google.inject.name.Names.named("multihost-exposer")) .to(RouteServerExposer.class); bind(new TypeLiteral>() {}) .to(OpenShiftExternalServerExposerProvider.class); bind(ServersConverter.class).to(new TypeLiteral>() {}); bind(PreviewUrlExposer.class).to(new TypeLiteral() {}); bind(PreviewUrlCommandProvisioner.class) .to(new TypeLiteral() {}); install(new JpaKubernetesRuntimeCacheModule()); Multibinder.newSetBinder(binder(), ServiceTermination.class) .addBinding() .to(KubernetesClientTermination.class); MapBinder pluginsAppliers = MapBinder.newMapBinder(binder(), String.class, ChePluginsApplier.class); pluginsAppliers.addBinding(OpenShiftEnvironment.TYPE).to(KubernetesPluginsToolingApplier.class); bind(SecureServerExposerFactoryProvider.class) .to(new TypeLiteral>() {}); bind(BrokerService.class); bind(new TypeLiteral>() {}) .to(OpenshiftBrokerEnvironmentFactory.class); bind(PluginBrokerManager.class) .to(new TypeLiteral>() {}); MapBinder> tlsProvisioners = MapBinder.newMapBinder( binder(), new TypeLiteral() {}, new TypeLiteral>() {}); tlsProvisioners .addBinding(WorkspaceExposureType.GATEWAY) .to(new TypeLiteral>() {}); tlsProvisioners.addBinding(WorkspaceExposureType.NATIVE).to(RouteTlsProvisioner.class); bind(new TypeLiteral>() {}) .to(OpenShiftEnvironmentProvisioner.class); DevfileBindings.onComponentIntegrityValidatorBinder( binder(), binder -> { binder.addBinding(KUBERNETES_COMPONENT_TYPE).to(KubernetesComponentValidator.class); binder.addBinding(OPENSHIFT_COMPONENT_TYPE).to(KubernetesComponentValidator.class); binder.addBinding(DOCKERIMAGE_COMPONENT_TYPE).to(NoopComponentIntegrityValidator.class); }); DevfileBindings.onWorkspaceApplierBinder( binder(), binder -> { binder .addBinding(KUBERNETES_COMPONENT_TYPE) .to(KubernetesComponentToWorkspaceApplier.class); binder .addBinding(DOCKERIMAGE_COMPONENT_TYPE) .to(DockerimageComponentToWorkspaceApplier.class); binder .addBinding(OPENSHIFT_COMPONENT_TYPE) .to(OpenshiftComponentToWorkspaceApplier.class); }); KubernetesDevfileBindings.addKubernetesBasedEnvironmentTypeBindings( binder(), KubernetesEnvironment.TYPE, OpenShiftEnvironment.TYPE); KubernetesDevfileBindings.addKubernetesBasedComponentTypeBindings( binder(), KUBERNETES_COMPONENT_TYPE, OPENSHIFT_COMPONENT_TYPE); KubernetesDevfileBindings.addAllowedEnvironmentTypeUpgradeBindings( binder(), OpenShiftEnvironment.TYPE, KubernetesEnvironment.TYPE); MapBinder ingressStrategies = MapBinder.newMapBinder(binder(), String.class, ExternalServiceExposureStrategy.class); ingressStrategies.addBinding(MULTI_HOST_STRATEGY).to(OpenShiftServerExposureStrategy.class); ingressStrategies .addBinding(SINGLE_HOST_STRATEGY) .to(SingleHostExternalServiceExposureStrategy.class); bind(ExternalServiceExposureStrategy.class).toProvider(ServiceExposureStrategyProvider.class); bind(CookiePathStrategy.class).to(OpenShiftCookiePathStrategy.class); bind(NonTlsDistributedClusterModeNotifier.class); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfrastructure.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift; import javax.inject.Singleton; /** * @author Sergii Leshchenko */ @Singleton public class OpenShiftInfrastructure { public static final String NAME = "openshift"; } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/authorization/OpenShiftAuthorizationCheckerImpl.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.authorization; import static org.eclipse.che.commons.lang.StringUtils.strToSet; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.openshift.api.model.Group; import java.util.List; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationChecker; /** This {@link OpenShiftAuthorizationCheckerImpl} checks if user is allowed to use Che. */ @Singleton public class OpenShiftAuthorizationCheckerImpl implements AuthorizationChecker { private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; private final Set allowUsers; private final Set allowGroups; private final Set denyUsers; private final Set denyGroups; @Inject public OpenShiftAuthorizationCheckerImpl( @Nullable @Named("che.infra.kubernetes.advanced_authorization.allow_users") String allowUsers, @Nullable @Named("che.infra.kubernetes.advanced_authorization.allow_groups") String allowGroups, @Nullable @Named("che.infra.kubernetes.advanced_authorization.deny_users") String denyUsers, @Nullable @Named("che.infra.kubernetes.advanced_authorization.deny_groups") String denyGroups, @Named("che.infra.kubernetes.advanced_authorization.delimiter") String delimiter, CheServerKubernetesClientFactory cheServerKubernetesClientFactory) { this.allowUsers = strToSet(allowUsers, delimiter); this.allowGroups = strToSet(allowGroups, delimiter); this.denyUsers = strToSet(denyUsers, delimiter); this.denyGroups = strToSet(denyGroups, delimiter); this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; } public boolean isAuthorized(Subject subject) throws InfrastructureException { String username = subject.getUserName(); return isAllowedUser(cheServerKubernetesClientFactory.create(), username) && !isDeniedUser(cheServerKubernetesClientFactory.create(), username); } private boolean isAllowedUser(KubernetesClient client, String username) { // All users from all groups are allowed by default if (allowUsers.isEmpty() && allowGroups.isEmpty()) { return true; } if (allowUsers.contains(username)) { return true; } for (String groupName : allowGroups) { Group group = client.resources(Group.class).withName(groupName).get(); if (group != null) { List users = group.getUsers(); if (users != null && users.contains(username)) { return true; } } } return false; } private boolean isDeniedUser(KubernetesClient client, String username) { // All users from all groups are allowed by default if (denyUsers.isEmpty() && denyGroups.isEmpty()) { return false; } if (denyUsers.contains(username)) { return true; } for (String groupName : denyGroups) { Group group = client.resources(Group.class).withName(groupName).get(); if (group != null) { List users = group.getUsers(); if (users != null && users.contains(username)) { return true; } } } return false; } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/devfile/OpenshiftComponentToWorkspaceApplier.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.devfile; import static org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesDevfileBindings.KUBERNETES_BASED_COMPONENTS_KEY_NAME; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesComponentToWorkspaceApplier; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesEnvironmentProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser; import org.eclipse.che.workspace.infrastructure.kubernetes.util.EnvVars; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; public class OpenshiftComponentToWorkspaceApplier extends KubernetesComponentToWorkspaceApplier { @Inject public OpenshiftComponentToWorkspaceApplier( KubernetesRecipeParser objectsParser, KubernetesEnvironmentProvisioner k8sEnvProvisioner, EnvVars envVars, @Named("che.infra.kubernetes.singlehost.workspace.devfile_endpoint_exposure") String devfileEndpointsExposure, @Named(KUBERNETES_BASED_COMPONENTS_KEY_NAME) Set kubernetesBasedComponentTypes) { super( objectsParser, k8sEnvProvisioner, envVars, OpenShiftEnvironment.TYPE, devfileEndpointsExposure, kubernetesBasedComponentTypes); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironment.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.environment; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.openshift.api.model.Route; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** * Holds objects of OpenShift environment. * * @author Sergii Leshchenko */ public class OpenShiftEnvironment extends KubernetesEnvironment { public static final String TYPE = "openshift"; private final Map routes; /** Returns builder for creating environment from blank {@link KubernetesEnvironment}. */ public static Builder builder() { return new Builder(); } /** * Returns builder for creating environment based on specified {@link InternalEnvironment}. * *

    It means that {@link InternalEnvironment} specific fields like machines, warnings will be * preconfigured in Builder. */ public static Builder builder(InternalEnvironment internalEnvironment) { return new Builder(internalEnvironment); } public OpenShiftEnvironment(KubernetesEnvironment k8sEnv) { super(k8sEnv); setType(TYPE); this.routes = new HashMap<>(); } public OpenShiftEnvironment( InternalEnvironment internalEnvironment, Map pods, Map deployments, Map services, Map ingresses, Map pvcs, Map secrets, Map configMaps, Map routes) { super(internalEnvironment, pods, deployments, services, ingresses, pvcs, secrets, configMaps); setType(TYPE); this.routes = routes; } public OpenShiftEnvironment( InternalRecipe internalRecipe, Map machines, List warnings, Map pods, Map deployments, Map services, Map ingresses, Map pvcs, Map secrets, Map configMaps, Map routes) { super( internalRecipe, machines, warnings, pods, deployments, services, ingresses, pvcs, secrets, configMaps); setType(TYPE); this.routes = routes; } @Override public OpenShiftEnvironment setType(String type) { return (OpenShiftEnvironment) super.setType(type); } /** Returns services that should be created when environment starts. */ public Map getRoutes() { return routes; } public static class Builder extends KubernetesEnvironment.Builder { private final Map routes = new HashMap<>(); private Builder() {} private Builder(InternalEnvironment internalEnvironment) { super(internalEnvironment); } @Override public Builder setInternalRecipe(InternalRecipe internalRecipe) { super.setInternalRecipe(internalRecipe); return this; } @Override public Builder setMachines(Map machines) { super.setMachines(machines); return this; } @Override public Builder setWarnings(List warnings) { super.setWarnings(warnings); return this; } @Override public Builder setCommands(List commands) { super.setCommands(new ArrayList<>(commands)); return this; } @Override public Builder setPods(Map pods) { this.pods.putAll(pods); return this; } @Override public Builder setDeployments(Map deployments) { this.deployments.putAll(deployments); return this; } @Override public Builder setServices(Map services) { this.services.putAll(services); return this; } @Override public Builder setIngresses(Map ingresses) { this.ingresses.putAll(ingresses); return this; } @Override public Builder setPersistentVolumeClaims(Map pvcs) { this.pvcs.putAll(pvcs); return this; } @Override public Builder setSecrets(Map secrets) { this.secrets.putAll(secrets); return this; } @Override public Builder setConfigMaps(Map configMaps) { this.configMaps.putAll(configMaps); return this; } @Override public Builder setAttributes(Map attributes) { this.attributes.putAll(attributes); return this; } public Builder setRoutes(Map route) { this.routes.putAll(route); return this; } public OpenShiftEnvironment build() { return new OpenShiftEnvironment( internalEnvironment, pods, deployments, services, ingresses, pvcs, secrets, configMaps, routes); } } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.environment; import static java.lang.String.format; import static org.eclipse.che.workspace.infrastructure.kubernetes.environment.PodMerger.DEPLOYMENT_NAME_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabel; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.setSelector; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.openshift.api.model.DeploymentConfig; import io.fabric8.openshift.api.model.Route; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.inject.Inject; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe; import org.eclipse.che.api.workspace.server.spi.environment.MachineConfigsValidator; import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.PodMerger; /** * Parses {@link InternalEnvironment} into {@link OpenShiftEnvironment}. * * @author Sergii Leshchenko */ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory { private final OpenShiftEnvironmentValidator envValidator; private final KubernetesRecipeParser k8sObjectsParser; private final PodMerger podMerger; @Inject public OpenShiftEnvironmentFactory( RecipeRetriever recipeRetriever, MachineConfigsValidator machinesValidator, OpenShiftEnvironmentValidator envValidator, KubernetesRecipeParser k8sObjectsParser, PodMerger podMerger) { super(recipeRetriever, machinesValidator); this.envValidator = envValidator; this.k8sObjectsParser = k8sObjectsParser; this.podMerger = podMerger; } @Override protected OpenShiftEnvironment doCreate( @Nullable InternalRecipe recipe, Map machines, List sourceWarnings) throws InfrastructureException, ValidationException { checkNotNull(recipe, "Null recipe is not supported by openshift environment factory"); List warnings = new ArrayList<>(); if (sourceWarnings != null) { warnings.addAll(sourceWarnings); } final List list = k8sObjectsParser.parse(recipe); Map pods = new HashMap<>(); Map deployments = new HashMap<>(); Map services = new HashMap<>(); Map configMaps = new HashMap<>(); Map pvcs = new HashMap<>(); Map routes = new HashMap<>(); Map secrets = new HashMap<>(); for (HasMetadata object : list) { checkNotNull(object.getKind(), "Environment contains object without specified kind field"); checkNotNull(object.getMetadata(), "%s metadata must not be null", object.getKind()); checkNotNull(object.getMetadata().getName(), "%s name must not be null", object.getKind()); if (object instanceof DeploymentConfig) { throw new ValidationException("Supporting of deployment configs is not implemented yet."); } else if (object instanceof Pod) { putInto(pods, object.getMetadata().getName(), (Pod) object); } else if (object instanceof Deployment) { putInto(deployments, object.getMetadata().getName(), (Deployment) object); } else if (object instanceof Service) { putInto(services, object.getMetadata().getName(), (Service) object); } else if (object instanceof Route) { putInto(routes, object.getMetadata().getName(), (Route) object); } else if (object instanceof PersistentVolumeClaim) { putInto(pvcs, object.getMetadata().getName(), (PersistentVolumeClaim) object); } else if (object instanceof Secret) { putInto(secrets, object.getMetadata().getName(), (Secret) object); } else if (object instanceof ConfigMap) { putInto(configMaps, object.getMetadata().getName(), (ConfigMap) object); } else { throw new ValidationException( format( "Found unknown object type in recipe -- name: '%s', kind: '%s'", object.getMetadata().getName(), object.getKind())); } } if (deployments.size() + pods.size() > 1) { mergePods(pods, deployments, services); } OpenShiftEnvironment osEnv = OpenShiftEnvironment.builder() .setInternalRecipe(recipe) .setMachines(machines) .setWarnings(warnings) .setPods(pods) .setDeployments(deployments) .setServices(services) .setPersistentVolumeClaims(pvcs) .setSecrets(secrets) .setConfigMaps(configMaps) .setRoutes(routes) .build(); envValidator.validate(osEnv); return osEnv; } /** * Merges the specified pods and deployments to a single Deployment. * *

    Note that method will modify the specified collections and put work result there. * * @param pods pods to merge * @param deployments deployments to merge * @param services services to reconfigure to point new deployment * @throws ValidationException if the specified lists has pods with conflicting configuration */ private void mergePods( Map pods, Map deployments, Map services) throws ValidationException { List podsData = Stream.concat( pods.values().stream().map(PodData::new), deployments.values().stream().map(PodData::new)) .collect(Collectors.toList()); Deployment deployment = podMerger.merge(podsData); String deploymentName = deployment.getMetadata().getName(); // provision merged deployment instead of recipe pods/deployments pods.clear(); deployments.clear(); deployments.put(deploymentName, deployment); // multiple pods/deployments are merged to one deployment // to avoid issues because of overriding labels // provision const label and selector to match all services to merged Deployment putLabel( deployment.getSpec().getTemplate().getMetadata(), DEPLOYMENT_NAME_LABEL, deploymentName); services.values().forEach(s -> setSelector(s, DEPLOYMENT_NAME_LABEL, deploymentName)); } /** * Puts the specified key/value pair into the specified map or throw an exception if map already * contains such key. * * @param map the map to put key/value pair * @param key key that should be put * @param value value that should be put * @param type of object to put * @throws ValidationException if the specified map already contains the specified key */ private void putInto(Map map, String key, T value) throws ValidationException { if (map.put(key, value) != null) { String kind = value.getKind(); String name = value.getMetadata().getName(); throw new ValidationException( format( "Environment can not contain two '%s' objects with the same name '%s'", kind, name)); } } private void checkNotNull(Object object, String errorMessage) throws ValidationException { if (object == null) { throw new ValidationException(errorMessage); } } private void checkNotNull(Object object, String messageFmt, Object... messageArguments) throws ValidationException { if (object == null) { throw new ValidationException(format(messageFmt, messageArguments)); } } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.environment; import io.fabric8.openshift.api.model.Route; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentPodsValidator; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentValidator; /** * Adds additional OpenShift-specific validation to {@link KubernetesEnvironmentValidator} * * @author Angel Misevski */ public class OpenShiftEnvironmentValidator extends KubernetesEnvironmentValidator { private static final String SERVICE_KIND = "Service"; @Inject public OpenShiftEnvironmentValidator(KubernetesEnvironmentPodsValidator podsValidator) { super(podsValidator); } public void validate(OpenShiftEnvironment env) throws ValidationException { super.validate(env); validateRoutesMatchServices(env); } private void validateRoutesMatchServices(OpenShiftEnvironment env) throws ValidationException { Set recipeServices = env.getServices().values().stream() .map(s -> s.getMetadata().getName()) .collect(Collectors.toSet()); for (Route route : env.getRoutes().values()) { if (route.getSpec() == null || route.getSpec().getTo() == null || !route.getSpec().getTo().getKind().equals(SERVICE_KIND)) { continue; } String serviceName = route.getSpec().getTo().getName(); if (!recipeServices.contains(serviceName)) { throw new ValidationException( String.format( "Route '%s' refers to Service '%s'. Routes must refer to Services included in recipe", route.getMetadata().getName(), serviceName)); } } } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/multiuser/oauth/OpenshiftTokenInitializationFilter.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth; import static com.google.common.base.MoreObjects.firstNonNull; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.openshift.client.OpenShiftClient; import java.util.Collections; import java.util.Optional; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.multiuser.api.authentication.commons.SessionStore; import org.eclipse.che.multiuser.api.authentication.commons.filter.MultiUserEnvironmentInitializationFilter; import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor; import org.eclipse.che.multiuser.api.permission.server.AuthorizedSubject; import org.eclipse.che.multiuser.api.permission.server.PermissionChecker; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This filter uses given token directly. It's used for native OpenShift user authentication. * Requests without token or with invalid token are rejected. */ @Singleton public class OpenshiftTokenInitializationFilter extends MultiUserEnvironmentInitializationFilter { private static final Logger LOG = LoggerFactory.getLogger(OpenshiftTokenInitializationFilter.class); private final PermissionChecker permissionChecker; private final OpenShiftClientFactory clientFactory; private final UserManager userManager; @Inject public OpenshiftTokenInitializationFilter( SessionStore sessionStore, RequestTokenExtractor tokenExtractor, OpenShiftClientFactory clientFactory, UserManager userManager, PermissionChecker permissionChecker) { super(sessionStore, tokenExtractor); this.clientFactory = clientFactory; this.userManager = userManager; this.permissionChecker = permissionChecker; } @Override protected Optional processToken(String token) { // We're effectively creating new client for each request. It might be a good idea to somehow // cache the client or user object. However, it may require non-trivial refactoring which may // be unnecessary. Keeping it as is for now to avoid premature optimization. try { OpenShiftClient client = clientFactory.createAuthenticatedClient(token); return Optional.ofNullable(client.currentUser()); } catch (KubernetesClientException e) { if (e.getCode() == 401) { LOG.error( "Unauthorized when getting current user. Invalid OpenShift token, probably expired. Re-login? Re-request the token?"); return Optional.empty(); } throw e; } } @Override protected String getUserId(io.fabric8.openshift.api.model.User user) { return firstNonNull(user.getMetadata().getUid(), user.getMetadata().getName()); } @Override protected Subject extractSubject(String token, io.fabric8.openshift.api.model.User osu) { try { ObjectMeta userMeta = osu.getMetadata(); User user = userManager.getOrCreateUser(getUserId(osu), userMeta.getName()); return new AuthorizedSubject( new SubjectImpl(user.getName(), Collections.emptyList(), user.getId(), token, false), permissionChecker); } catch (ServerException | ConflictException e) { throw new RuntimeException(e); } } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProject.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.project; import static java.lang.String.format; import com.google.common.annotations.VisibleForTesting; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.openshift.api.model.Project; import io.fabric8.openshift.api.model.ProjectRequestBuilder; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.client.OpenShiftClient; import java.util.Map; import java.util.concurrent.Executor; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesDeployments; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesIngresses; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesPersistentVolumeClaims; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesServices; import org.eclipse.che.workspace.infrastructure.openshift.CheServerOpenshiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Defines an internal API for managing subset of objects inside {@link Project} instance. * * @author Sergii Leshchenko */ public class OpenShiftProject extends KubernetesNamespace { private static final Logger LOG = LoggerFactory.getLogger(OpenShiftProject.class); private final OpenShiftRoutes routes; private final OpenShiftClientFactory openShiftClientFactory; private final CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory; @VisibleForTesting OpenShiftProject( OpenShiftClientFactory openShiftClientFactory, CheServerKubernetesClientFactory cheServerKubernetesClientFactory, CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory, String workspaceId, String name, KubernetesDeployments deployments, KubernetesServices services, OpenShiftRoutes routes, KubernetesPersistentVolumeClaims pvcs, KubernetesIngresses ingresses, KubernetesSecrets secrets, KubernetesConfigsMaps configMaps) { super( cheServerKubernetesClientFactory, workspaceId, name, deployments, services, pvcs, ingresses, secrets, configMaps); this.routes = routes; this.openShiftClientFactory = openShiftClientFactory; this.cheServerOpenshiftClientFactory = cheServerOpenshiftClientFactory; } public OpenShiftProject( OpenShiftClientFactory openShiftClientFactory, CheServerKubernetesClientFactory cheServerKubernetesClientFactory, CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory, Executor executor, String name, String workspaceId) { super(cheServerKubernetesClientFactory, executor, name, workspaceId); this.routes = new OpenShiftRoutes(name, workspaceId, cheServerOpenshiftClientFactory); this.openShiftClientFactory = openShiftClientFactory; this.cheServerOpenshiftClientFactory = cheServerOpenshiftClientFactory; } /** * Prepare a project for using. * *

    Preparing includes creating if needed and waiting for default service account. * * @param canCreate defines what to do when the project is not found. The project is created when * {@code true}, otherwise an exception is thrown. * @param initWithCheServerSa defines condition whether the project should be created with Che * Server SA. * @throws InfrastructureException if any exception occurs during project preparation or if the * project doesn't exist and {@code canCreate} is {@code false}. */ void prepare( boolean canCreate, boolean initWithCheServerSa, Map labels, Map annotations) throws InfrastructureException { String workspaceId = getWorkspaceId(); String projectName = getName(); OpenShiftClient osClient = cheServerOpenshiftClientFactory.createOC(); Project project = get(projectName, osClient); if (project == null) { if (!canCreate) { throw new InfrastructureException( format( "Creating the namespace '%s' is not allowed, yet" + " it was not found.", projectName)); } if (initWithCheServerSa) { create(projectName, osClient); waitDefaultServiceAccount(projectName, osClient); } else { create(projectName, openShiftClientFactory.createOC(workspaceId)); waitDefaultServiceAccount(projectName, openShiftClientFactory.create(workspaceId)); } } label(osClient.namespaces().withName(projectName).get(), labels); annotate(osClient.namespaces().withName(projectName).get(), annotations); } /** * Deletes the project. Deleting a non-existent projects is not an error as is not an attempt to * delete a project that is already being deleted. * * @throws InfrastructureException if any unexpected exception occurs during project deletion */ void delete() throws InfrastructureException { String workspaceId = getWorkspaceId(); String projectName = getName(); OpenShiftClient osClient = cheServerOpenshiftClientFactory.createOC(workspaceId); try { delete(projectName, osClient); } catch (KubernetesClientException e) { if (e.getCode() == 403) { throw new InfrastructureException( format( "Could not access the project %s when deleting it for workspace %s", projectName, workspaceId), e); } throw new KubernetesInfrastructureException(e); } } /** Returns object for managing {@link Route} instances inside project. */ public OpenShiftRoutes routes() { return routes; } /** Removes all object except persistent volume claims inside project. */ public void cleanUp() throws InfrastructureException { doRemove( routes::delete, services()::delete, deployments()::delete, secrets()::delete, configMaps()::delete); } private void create(String projectName, OpenShiftClient osClient) throws InfrastructureException { try { osClient .projectrequests() .create( new ProjectRequestBuilder() .withNewMetadata() .withName(projectName) .endMetadata() .build()); } catch (KubernetesClientException e) { if (e.getCode() == 403) { LOG.error( "Unable to create new OpenShift project due to lack of permissions." + "HINT: When using workspace project name placeholders, os-oauth or service account with more lenient permissions (cluster-admin) must be used."); } throw new KubernetesInfrastructureException(e); } } private void update(Project project, OpenShiftClient client) throws InfrastructureException { try { client.projects().createOrReplace(project); } catch (KubernetesClientException e) { if (e.getCode() == 403) { LOG.error( "Unable to update new Kubernetes project due to lack of permissions." + "When using workspace namespace placeholders, service account with lenient permissions (cluster-admin) must be used."); } throw new KubernetesInfrastructureException(e); } } private void delete(String projectName, OpenShiftClient osClient) throws InfrastructureException { try { osClient.projects().withName(projectName).delete(); } catch (KubernetesClientException e) { if (e.getCode() == 404) { LOG.warn( format( "Tried to delete project '%s' but it doesn't exist in the cluster.", projectName), e); } else if (e.getCode() == 409) { LOG.info(format("The project '%s' is currently being deleted.", projectName), e); } else { throw new KubernetesInfrastructureException(e); } } } private Project get(String projectName, OpenShiftClient client) throws InfrastructureException { try { return client.projects().withName(projectName).get(); } catch (KubernetesClientException e) { if (e.getCode() == 403) { // project is foreign or doesn't exist LOG.warn( "Trying to get namespace '{}', but failed because the lack of permissions.", projectName); return null; } else { throw new KubernetesInfrastructureException(e); } } } private boolean isProjectManaged(OpenShiftClient client) throws InfrastructureException { try { Project namespace = client.projects().withName(getName()).get(); return namespace.getMetadata().getLabels() != null && "true".equals(namespace.getMetadata().getLabels().get(MANAGED_NAMESPACE_LABEL)); } catch (KubernetesClientException e) { if (e.getCode() == 403) { throw new InfrastructureException( format( "Could not access the project %s when trying to determine if it is managed " + "for workspace %s", getName(), getWorkspaceId()), e); } else if (e.getCode() == 404) { // we don't want to block whatever work the caller is doing on the namespace. The caller // will fail anyway if the project doesn't exist. return true; } throw new InternalInfrastructureException( format( "Failed to determine whether the project" + " %s is managed. OpenShift client said: %s", getName(), e.getMessage()), e); } } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactory.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.project; import static java.lang.String.format; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE; import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import com.google.inject.Singleton; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.openshift.api.model.Project; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationChecker; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool; import org.eclipse.che.workspace.infrastructure.openshift.CheServerOpenshiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.Constants; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Helps to create {@link OpenShiftProject} instances. * * @author Anton Korneta */ @Singleton public class OpenShiftProjectFactory extends KubernetesNamespaceFactory { private static final Logger LOG = LoggerFactory.getLogger(OpenShiftProjectFactory.class); private final boolean initWithCheServerSa; private final OpenShiftClientFactory openShiftClientFactory; private final CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory; private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Inject public OpenShiftProjectFactory( @Nullable @Named("che.infra.kubernetes.namespace.default") String defaultNamespaceName, @Named("che.infra.kubernetes.namespace.creation_allowed") boolean namespaceCreationAllowed, @Named("che.infra.kubernetes.namespace.label") boolean labelProjects, @Named("che.infra.kubernetes.namespace.annotate") boolean annotateProjects, @Named("che.infra.kubernetes.namespace.labels") String projectLabels, @Named("che.infra.kubernetes.namespace.annotations") String projectAnnotations, @Named("che.infra.openshift.project.init_with_server_sa") boolean initWithCheServerSa, Set namespaceConfigurators, OpenShiftClientFactory openShiftClientFactory, CheServerKubernetesClientFactory cheServerKubernetesClientFactory, CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory, PreferenceManager preferenceManager, KubernetesSharedPool sharedPool, AuthorizationChecker authorizationChecker, PermissionsCleaner permissionsCleaner) { super( defaultNamespaceName, namespaceCreationAllowed, labelProjects, annotateProjects, projectLabels, projectAnnotations, namespaceConfigurators, cheServerKubernetesClientFactory, preferenceManager, sharedPool, authorizationChecker, permissionsCleaner); this.initWithCheServerSa = initWithCheServerSa; this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory; this.cheServerOpenshiftClientFactory = cheServerOpenshiftClientFactory; this.openShiftClientFactory = openShiftClientFactory; } public OpenShiftProject getOrCreate(RuntimeIdentity identity) throws InfrastructureException { OpenShiftProject osProject = get(identity); var subject = EnvironmentContext.getCurrent().getSubject(); var userName = subject.getUserName(); validateAuthorization(osProject.getName(), subject); NamespaceResolutionContext resolutionCtx = new NamespaceResolutionContext(identity.getWorkspaceId(), subject.getUserId(), userName); Map namespaceAnnotationsEvaluated = evaluateAnnotationPlaceholders(resolutionCtx); osProject.prepare( canCreateNamespace(), initWithCheServerSa, labelNamespaces ? namespaceLabels : emptyMap(), annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap()); configureNamespace(resolutionCtx, osProject.getName()); return osProject; } @Override public OpenShiftProject get(Workspace workspace) throws InfrastructureException { return doCreateProjectAccess(workspace.getId(), getNamespaceName(workspace)); } public OpenShiftProject get(RuntimeIdentity identity) throws InfrastructureException { return doCreateProjectAccess(identity.getWorkspaceId(), identity.getInfrastructureNamespace()); } @Override public void deleteIfManaged(Workspace workspace) throws InfrastructureException { OpenShiftProject osProject = get(workspace); if (isWorkspaceNamespaceManaged(osProject.getName(), workspace)) { osProject.delete(); } } /** * Creates a kubernetes namespace for the specified workspace. * *

    Project won't be prepared. This method should be used only in case workspace recovering. * * @param workspaceId identifier of the workspace * @return created namespace */ public OpenShiftProject access(String workspaceId, String projectName) { return doCreateProjectAccess(workspaceId, projectName); } @VisibleForTesting OpenShiftProject doCreateProjectAccess(String workspaceId, String name) { return new OpenShiftProject( openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, sharedPool.getExecutor(), name, workspaceId); } @Override public Optional fetchNamespace(String name) throws InfrastructureException { return fetchNamespaceObject(name).map(this::asNamespaceMeta); } private Optional fetchNamespaceObject(String name) throws InfrastructureException { try { Project project = cheServerOpenshiftClientFactory.createOC().projects().withName(name).get(); return Optional.ofNullable(project); } catch (KubernetesClientException e) { if (e.getCode() == 403) { // 403 means that the project does not exist // or a user really is not permitted to access it which is Che Server misconfiguration return Optional.empty(); } else { throw new InfrastructureException( format("Error while trying to fetch the project '%s'. Cause: %s", name, e.getMessage()), e); } } } protected List findPreparedNamespaces( NamespaceResolutionContext namespaceCtx) throws InfrastructureException { try { List workspaceProjects = cheServerOpenshiftClientFactory .createOC() .projects() .withLabels(namespaceLabels) .list() .getItems(); if (!workspaceProjects.isEmpty()) { Map evaluatedAnnotations = evaluateAnnotationPlaceholders(namespaceCtx); return workspaceProjects.stream() .filter(p -> matchesAnnotations(p, evaluatedAnnotations)) .map(this::asNamespaceMeta) .collect(Collectors.toList()); } else { return emptyList(); } } catch (KubernetesClientException kce) { if (kce.getCode() == 403) { LOG.warn( "Trying to fetch projects with labels '{}', but failed for lack of permissions. Cause: '{}'", namespaceLabels, kce.getMessage()); return emptyList(); } else { throw new InfrastructureException( "Error occurred when tried to list all available projects. Cause: " + kce.getMessage(), kce); } } } private KubernetesNamespaceMeta asNamespaceMeta(io.fabric8.openshift.api.model.Project project) { Map attributes = new HashMap<>(4); ObjectMeta metadata = project.getMetadata(); Map annotations = metadata.getAnnotations(); String displayName = annotations.get(Constants.PROJECT_DISPLAY_NAME_ANNOTATION); if (displayName != null) { attributes.put(Constants.PROJECT_DISPLAY_NAME_ATTRIBUTE, displayName); } String description = annotations.get(Constants.PROJECT_DESCRIPTION_ANNOTATION); if (description != null) { attributes.put(Constants.PROJECT_DESCRIPTION_ATTRIBUTE, description); } if (project.getStatus() != null && project.getStatus().getPhase() != null) { attributes.put(PHASE_ATTRIBUTE, project.getStatus().getPhase()); } return new KubernetesNamespaceMetaImpl(metadata.getName(), attributes); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftRoutes.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.project; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_WORKSPACE_ID_LABEL; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabel; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.openshift.api.model.Route; import java.util.List; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; /** * Defines an internal API for managing {@link Route} instances in {@link OpenShiftRoutes#namespace * predefined namespace}. * * @author Sergii Leshchenko */ public class OpenShiftRoutes { private final String namespace; private final String workspaceId; private final OpenShiftClientFactory clientFactory; OpenShiftRoutes(String namespace, String workspaceId, OpenShiftClientFactory clientFactory) { this.namespace = namespace; this.workspaceId = workspaceId; this.clientFactory = clientFactory; } /** * Creates specified route. * * @param route route to create * @return created route * @throws InfrastructureException when any exception occurs */ public Route create(Route route) throws InfrastructureException { putLabel(route, CHE_WORKSPACE_ID_LABEL, workspaceId); try { return clientFactory.createOC(workspaceId).routes().inNamespace(namespace).create(route); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Returns all existing routes. * * @throws InfrastructureException when any exception occurs */ public List get() throws InfrastructureException { try { return clientFactory .createOC(workspaceId) .routes() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .list() .getItems(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } /** * Deletes all existing routes. * * @throws InfrastructureException when any exception occurs */ public void delete() throws InfrastructureException { try { clientFactory .createOC(workspaceId) .routes() .inNamespace(namespace) .withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId) .delete(); } catch (KubernetesClientException e) { throw new KubernetesInfrastructureException(e); } } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.project; import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder; import io.fabric8.openshift.api.model.PolicyRuleBuilder; import io.fabric8.openshift.api.model.Role; import io.fabric8.openshift.api.model.RoleBinding; import io.fabric8.openshift.api.model.RoleBindingBuilder; import io.fabric8.openshift.api.model.RoleBindingFluent; import io.fabric8.openshift.api.model.RoleBuilder; import io.fabric8.openshift.client.OpenShiftClient; import java.util.List; import java.util.Set; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; /** * Holds logic for preparing workspace service account. * *

    It checks that required service account, roles and role bindings exist and creates if needed. * * @author Sergii Leshchenko * @see * org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesWorkspaceServiceAccount */ public class OpenShiftWorkspaceServiceAccount extends AbstractWorkspaceServiceAccount { public OpenShiftWorkspaceServiceAccount( String workspaceId, String projectName, String serviceAccountName, Set clusterRoleNames, OpenShiftClientFactory clientFactory) { super( workspaceId, projectName, serviceAccountName, clusterRoleNames, clientFactory::createOC, OpenShiftClient::roles, OpenShiftClient::roleBindings); } @Override protected Role buildRole( String name, List resources, List resourceNames, List apiGroups, List verbs) { return new RoleBuilder() .withNewMetadata() .withName(name) .endMetadata() .withRules( new PolicyRuleBuilder() .withResources(resources) .withResourceNames(resourceNames) .withApiGroups(apiGroups) .withVerbs(verbs) .build()) .build(); } @Override protected RoleBinding createRoleBinding( String roleName, String bindingName, boolean clusterRole) { RoleBindingFluent.RoleRefNested bld = new RoleBindingBuilder() .withNewMetadata() .withName(bindingName) .withNamespace(namespace) .endMetadata() .withNewRoleRef() .withName(roleName); if (!clusterRole) { bld.withNamespace(namespace); } return bld.endRoleRef() .withSubjects( new ObjectReferenceBuilder() .withKind("ServiceAccount") .withName(serviceAccountName) .build()) .build(); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/configurator/OpenShiftWorkspaceServiceAccountConfigurator.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.project.configurator; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import com.google.common.collect.Sets; import java.util.Collections; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; import org.eclipse.che.workspace.infrastructure.openshift.CheServerOpenshiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftWorkspaceServiceAccount; /** * This {@link NamespaceConfigurator} ensures that workspace ServiceAccount with proper ClusterRole * is set in Workspace project. */ @Singleton public class OpenShiftWorkspaceServiceAccountConfigurator implements NamespaceConfigurator { private final CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory; private final String serviceAccountName; private final Set clusterRoleNames; @Inject public OpenShiftWorkspaceServiceAccountConfigurator( @Nullable @Named("che.infra.kubernetes.service_account_name") String serviceAccountName, @Nullable @Named("che.infra.kubernetes.workspace_sa_cluster_roles") String clusterRoleNames, CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory) { this.cheServerOpenshiftClientFactory = cheServerOpenshiftClientFactory; this.serviceAccountName = serviceAccountName; if (!isNullOrEmpty(clusterRoleNames)) { this.clusterRoleNames = Sets.newHashSet( Splitter.on(",").trimResults().omitEmptyStrings().split(clusterRoleNames)); } else { this.clusterRoleNames = Collections.emptySet(); } } @Override public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) throws InfrastructureException { if (!isNullOrEmpty(serviceAccountName)) { OpenShiftWorkspaceServiceAccount osWorkspaceServiceAccount = createServiceAccount(namespaceResolutionContext.getWorkspaceId(), namespaceName); osWorkspaceServiceAccount.prepare(); } } @VisibleForTesting public OpenShiftWorkspaceServiceAccount createServiceAccount(String wsId, String namespaceName) { return new OpenShiftWorkspaceServiceAccount( wsId, namespaceName, serviceAccountName, clusterRoleNames, cheServerOpenshiftClientFactory); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftPreviewUrlCommandProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.provision; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.openshift.api.model.Route; import java.util.List; import java.util.Optional; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.PreviewUrlCommandProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftProject; import org.eclipse.che.workspace.infrastructure.openshift.util.Routes; /** * Extends {@link PreviewUrlCommandProvisioner} where needed. For OpenShift, we work with {@link * Route}s and {@link OpenShiftProject}. Other than that, logic is the same as for k8s. */ @Singleton public class OpenShiftPreviewUrlCommandProvisioner extends PreviewUrlCommandProvisioner { @Override protected List loadExposureObjects(KubernetesNamespace namespace) throws InfrastructureException { if (!(namespace instanceof OpenShiftProject)) { throw new InternalInfrastructureException( String.format( "OpenShiftProject instance expected, but got '%s'. Please report a bug!", namespace.getClass().getCanonicalName())); } OpenShiftProject project = (OpenShiftProject) namespace; return project.routes().get(); } @Override protected Optional findHostForServicePort(List routes, Service service, int port) { return Routes.findRouteForServicePort(routes, service, port).map(r -> r.getSpec().getHost()); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftUniqueNamesProvisioner.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.provision; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabel; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.openshift.api.model.Route; import java.util.HashSet; import java.util.Set; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.Constants; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.UniqueNamesProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; /** * @author Sergii Leshchenko */ public class OpenShiftUniqueNamesProvisioner extends UniqueNamesProvisioner { @Override public void provision(OpenShiftEnvironment osEnv, RuntimeIdentity identity) throws InfrastructureException { super.provision(osEnv, identity); final Set routes = new HashSet<>(osEnv.getRoutes().values()); osEnv.getRoutes().clear(); for (Route route : routes) { final ObjectMeta routeMeta = route.getMetadata(); putLabel(route, Constants.CHE_ORIGINAL_NAME_LABEL, routeMeta.getName()); final String routeName = Names.generateName("route"); routeMeta.setName(routeName); osEnv.getRoutes().put(routeName, route); } } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/RouteTlsProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.provision; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putAnnotations; import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisioner.getSecureProtocol; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RouteSpec; import io.fabric8.openshift.api.model.TLSConfig; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.commons.tracing.TracingTags; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; /** * Enables Transport Layer Security (TLS) for workspace routes and changes protocol to secure (wss / * https) in servers' configuration * * @author Ilya Buziuk */ @Singleton public class RouteTlsProvisioner implements TlsProvisioner, ConfigurationProvisioner { static final String TERMINATION_EDGE = "edge"; static final String TERMINATION_POLICY_REDIRECT = "Redirect"; private final boolean isTlsEnabled; @Inject public RouteTlsProvisioner(@Named("che.infra.kubernetes.tls_enabled") boolean isTlsEnabled) { this.isTlsEnabled = isTlsEnabled; } @Override @Traced public void provision(OpenShiftEnvironment osEnv, RuntimeIdentity identity) { TracingTags.WORKSPACE_ID.set(identity::getWorkspaceId); if (!isTlsEnabled) { return; } final Set routes = new HashSet<>(osEnv.getRoutes().values()); for (Route route : routes) { useSecureProtocolForServers(route); enableTls(route); } } private void useSecureProtocolForServers(final Route route) { Map servers = Annotations.newDeserializer(route.getMetadata().getAnnotations()).servers(); servers.values().forEach(s -> s.setProtocol(getSecureProtocol(s.getProtocol()))); Map annotations = Annotations.newSerializer().servers(servers).annotations(); putAnnotations(route.getMetadata(), annotations); } private void enableTls(final Route route) { RouteSpec spec = route.getSpec(); spec.setTls(getTLSConfig()); } private TLSConfig getTLSConfig() { TLSConfig config = new TLSConfig(); config.setTermination(TERMINATION_EDGE); config.setInsecureEdgeTerminationPolicy(TERMINATION_POLICY_REDIRECT); return config; } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/server/OpenShiftCookiePathStrategy.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.server; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.CookiePathStrategy; /** * On OpenShift we always use the equivalent of the multi-host strategy and therefore use the * appropriate cookie path strategy for the secured endpoints. */ public class OpenShiftCookiePathStrategy extends CookiePathStrategy { public OpenShiftCookiePathStrategy() { super(MULTI_HOST_STRATEGY); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/server/OpenShiftPreviewUrlExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.server; import com.google.common.annotations.VisibleForTesting; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.openshift.api.model.Route; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.workspace.infrastructure.kubernetes.server.PreviewUrlExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.eclipse.che.workspace.infrastructure.openshift.util.Routes; /** * Extends {@link PreviewUrlExposer} with OpenShift capabilities. We work with {@link Route} and * {@link OpenShiftEnvironment}. */ @Singleton public class OpenShiftPreviewUrlExposer extends PreviewUrlExposer { @Inject public OpenShiftPreviewUrlExposer( ExternalServerExposerProvider externalServerExposer) { super(externalServerExposer); } @VisibleForTesting protected OpenShiftPreviewUrlExposer( ExternalServerExposer externalServerExposer) { super(externalServerExposer); } @Override protected boolean hasMatchingEndpoint(OpenShiftEnvironment env, Service service, int port) { return Routes.findRouteForServicePort(env.getRoutes().values(), service, port).isPresent(); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/server/OpenShiftServerExposureStrategy.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.server; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy; /** * Even though we don't use ingresses to expose the services on OpenShift, we still need to provide * an implementation of this strategy that gives the rest of the system the idea of the paths on * which the services are exposed. */ public class OpenShiftServerExposureStrategy implements ExternalServiceExposureStrategy { @Override public String getExternalHost(String serviceName, String serverName) { return null; } @Override public String getExternalPath(String serviceName, String serverName) { return "/"; } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/server/OpenShiftServerResolverFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.server; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType.GATEWAY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType.NATIVE; import com.google.common.collect.ImmutableMap; import io.fabric8.openshift.api.model.Route; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver.AbstractServerResolverFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver.ConfigMapServerResolver; import org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver.ServerResolver; /** * Factory that decides by configuration, which {@link ServerResolver} implementation to use in * OpenShift environment. */ public class OpenShiftServerResolverFactory extends AbstractServerResolverFactory { @Inject public OpenShiftServerResolverFactory( @Named("che.host") String cheHost, @Named("che.infra.kubernetes.server_strategy") String exposureStrategy, @Named("che.infra.kubernetes.singlehost.workspace.exposure") String wsExposureType) { super( exposureStrategy, wsExposureType, ImmutableMap.of( GATEWAY, (ss, rs, cs) -> new ConfigMapServerResolver(ss, cs, cheHost, new RouteServerResolver(ss, rs)), NATIVE, (ss, rs, cs) -> new RouteServerResolver(ss, rs)), "Failed to initialize OpenShiftServerResolverFactory for workspace exposure type '%s'."); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/server/RouteServerExposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.server; import static java.util.Collections.emptyMap; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.openshift.api.model.Route; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; /** * Helps to modify {@link OpenShiftEnvironment} to make servers that are configured by {@link * ServerConfig} publicly or workspace-wide accessible. * *

    To make server accessible it is needed to make sure that container port is declared, create * {@link Service}. To make it also publicly accessible it is needed to create corresponding {@link * Route} for exposing this port. * *

    Created services and routes will have serialized servers which are exposed by the * corresponding object and machine name to which these servers belongs to. * *

    Container, service and route are linked in the following way: * *

     * Pod
     * metadata:
     *   labels:
     *     type: web-app
     * spec:
     *   containers:
     *   ...
     *   - ports:
     *     - containerPort: 8080
     *       name: web-app
     *       protocol: TCP
     *   ...
     * 
    * * Then services expose containers ports in the following way: * *
     * Service
     * metadata:
     *   name: service123
     * spec:
     *   selector:                        ---->> Pod.metadata.labels
     *     type: web-app
     *   ports:
     *     - name: web-app
     *       port: 8080
     *       targetPort: [8080|web-app]   ---->> Pod.spec.ports[0].[containerPort|name]
     *       protocol: TCP                ---->> Pod.spec.ports[0].protocol
     * 
    * * Then corresponding route expose one of the service's port: * *
     * Route
     * ...
     * spec:
     *   to:
     *     name: dev-machine              ---->> Service.metadata.name
     *   port:
     *     targetPort: [8080|web-app]     ---->> Service.spec.ports[0].[port|name]
     * 
    * *

    For accessing publicly accessible server user will use route host. For accessing * workspace-wide accessible server user will use service name. Information about servers that are * exposed by route and/or service are stored in annotations of a route or service. * * @author Sergii Leshchenko * @author Alexander Garagatyi * @see Annotations */ @Singleton public class RouteServerExposer implements ExternalServerExposer { private final Map labels; private final String domainSuffix; @Inject public RouteServerExposer( @Nullable @Named("che.infra.openshift.route.labels") String labelsProperty, @Nullable @Named("che.infra.openshift.route.host.domain_suffix") String domainSuffix) { this.labels = labelsProperty != null ? Splitter.on(",").withKeyValueSeparator("=").split(labelsProperty) : emptyMap(); this.domainSuffix = domainSuffix; } @Override public void expose( OpenShiftEnvironment env, String machineName, String serviceName, String serverId, ServicePort servicePort, Map externalServers) { Route commonRoute = new RouteBuilder() .withName(Names.generateName("route")) .withMachineName(machineName) .withTargetPort(servicePort.getName()) .withServers(externalServers) .withLabels(labels) .withHost( domainSuffix != null ? NameGenerator.generate("route", "." + domainSuffix, 6) : null) .withTo(serviceName) .build(); env.getRoutes().put(commonRoute.getMetadata().getName(), commonRoute); } private static class RouteBuilder { private String name; private String host; private String serviceName; private IntOrString targetPort; private Map servers; private Map labels; private String machineName; private RouteBuilder withName(String name) { this.name = name; return this; } private RouteBuilder withTo(String serviceName) { this.serviceName = serviceName; return this; } private RouteBuilder withHost(String host) { this.host = host; return this; } private RouteBuilder withTargetPort(String targetPortName) { this.targetPort = new IntOrString(targetPortName); return this; } private RouteBuilder withServer(String serverName, ServerConfig serverConfig) { return withServers(ImmutableMap.of(serverName, serverConfig)); } private RouteBuilder withServers(Map servers) { if (this.servers == null) { this.servers = new HashMap<>(); } this.servers.putAll(servers); return this; } private RouteBuilder withLabels(Map labels) { this.labels = labels; return this; } public RouteBuilder withMachineName(String machineName) { this.machineName = machineName; return this; } private Route build() { io.fabric8.openshift.api.model.RouteBuilder builder = new io.fabric8.openshift.api.model.RouteBuilder(); return builder .withNewMetadata() .withName(name.replace("/", "-")) .withAnnotations( Annotations.newSerializer().servers(servers).machineName(machineName).annotations()) .withLabels(labels) .endMetadata() .withNewSpec() .withHost(host) .withNewTo() .withName(serviceName) .endTo() .withNewPort() .withTargetPort(targetPort) .endPort() .endSpec() .build(); } } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/server/RouteServerResolver.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.server; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.openshift.api.model.Route; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.server.RuntimeServerBuilder; import org.eclipse.che.workspace.infrastructure.kubernetes.server.resolver.AbstractServerResolver; /** * Helps to resolve {@link ServerImpl servers} by machine name according to specified {@link Route * routes} and {@link Service services}. * *

    Objects annotations are used to check if {@link Service service} or {@link Route route} * exposes the specified machine servers. * * @author Sergii Leshchenko * @author Alexander Garagatyi * @see Annotations */ public class RouteServerResolver extends AbstractServerResolver { private final Multimap routes; public RouteServerResolver(List services, List routes) { super(services); this.routes = ArrayListMultimap.create(); for (Route route : routes) { String machineName = Annotations.newDeserializer(route.getMetadata().getAnnotations()).machineName(); this.routes.put(machineName, route); } } @Override public Map resolveExternalServers(String machineName) { return routes.get(machineName).stream() .map(r -> resolveRouteServers(machineName, r)) .flatMap(m -> m.entrySet().stream()) .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (v1, v2) -> v2)); } private Map resolveRouteServers(String serviceName, Route route) { return Annotations.newDeserializer(route.getMetadata().getAnnotations()) .servers() .entrySet() .stream() .collect( Collectors.toMap( Entry::getKey, e -> new RuntimeServerBuilder() .protocol(e.getValue().getProtocol()) .host(route.getSpec().getHost()) .path(e.getValue().getPath()) .endpointOrigin( "/") // routes are always multihost, so we don't need to think here... .attributes(e.getValue().getAttributes()) .targetPort(e.getValue().getPort()) .build(), (s1, s2) -> s2)); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/server/external/OpenShiftExternalServerExposerProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.server.external; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.workspace.infrastructure.kubernetes.server.WorkspaceExposureType; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.CombinedSingleHostServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.KubernetesExternalServerExposerProvider; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.eclipse.che.workspace.infrastructure.openshift.server.RouteServerExposer; /** * Provides {@link ExternalServerExposer} based on `che.infra.kubernetes.server_strategy` and * `che.infra.kubernetes.singlehost.workspace.exposure` properties. * *

    Based on server strategy, it can create a {@link CombinedSingleHostServerExposer} with * OpenShift specific {@link RouteServerExposer} for exposing servers on subdomains. */ public class OpenShiftExternalServerExposerProvider extends KubernetesExternalServerExposerProvider implements ExternalServerExposerProvider { @Inject public OpenShiftExternalServerExposerProvider( @Named("che.infra.kubernetes.server_strategy") String exposureStrategy, @Named("che.infra.kubernetes.singlehost.workspace.exposure") String exposureType, @Named("che.infra.kubernetes.singlehost.workspace.devfile_endpoint_exposure") String devfileEndpointExposure, @Named("multihost-exposer") ExternalServerExposer multihostExposer, Map> exposers) { super(exposureStrategy, exposureType, devfileEndpointExposure, multihostExposer, exposers); } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/util/Routes.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.util; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RouteSpec; import java.util.Collection; import java.util.Optional; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Services; /** Util class that helps working with OpenShift Routes */ public class Routes { /** * In given {@code routes} finds route that for given {@code service} and {@code port} * * @return found {@link Route} or {@link Optional#empty()} */ public static Optional findRouteForServicePort( Collection routes, Service service, int port) { Optional foundPort = Services.findPort(service, port); if (!foundPort.isPresent()) { return Optional.empty(); } for (Route route : routes) { RouteSpec spec = route.getSpec(); if (spec.getTo().getName().equals(service.getMetadata().getName()) && matchesPort(foundPort.get(), spec.getPort().getTargetPort())) { return Optional.of(route); } } return Optional.empty(); } private static boolean matchesPort(ServicePort servicePort, IntOrString routePort) { if (routePort.getStrVal() != null && routePort.getStrVal().equals(servicePort.getName())) { return true; } if (routePort.getIntVal() != null && routePort.getIntVal().equals(servicePort.getPort())) { return true; } return false; } } ================================================ FILE: infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/wsplugins/brokerphases/OpenshiftBrokerEnvironmentFactory.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.wsplugins.brokerphases; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.workspace.server.spi.provision.env.AgentAuthEnableEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.MachineTokenEnvVarProvider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.BrokerEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.KubernetesBrokerEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; /** * Extends {@link KubernetesBrokerEnvironmentFactory} to be used in the openshift infrastructure. * * @author Oleksandr Garagatyi */ public class OpenshiftBrokerEnvironmentFactory extends BrokerEnvironmentFactory { @Inject public OpenshiftBrokerEnvironmentFactory( @Named("che.websocket.endpoint") String cheWebsocketEndpoint, @Nullable @Named("che.websocket.internal.endpoint") String cheWebsocketInternalEndpoint, @Named("che.workspace.plugin_broker.pull_policy") String brokerPullPolicy, AgentAuthEnableEnvVarProvider authEnableEnvVarProvider, MachineTokenEnvVarProvider machineTokenEnvVarProvider, @Named("che.workspace.plugin_broker.artifacts.image") String artifactsBrokerImage, @Named("che.workspace.plugin_broker.metadata.image") String metadataBrokerImage, @Nullable @Named("che.workspace.plugin_registry_url") String pluginRegistryUrl, @Nullable @Named("che.workspace.plugin_registry_internal_url") String pluginRegistryInternalUrl, @Named("che.infra.openshift.trusted_ca_bundles_mount_path") String caCertificatesMountPath, CertificateProvisioner certProvisioner) { super( cheWebsocketEndpoint, cheWebsocketInternalEndpoint, brokerPullPolicy, authEnableEnvVarProvider, machineTokenEnvVarProvider, artifactsBrokerImage, metadataBrokerImage, pluginRegistryUrl, pluginRegistryInternalUrl, caCertificatesMountPath, certProvisioner); } @Override protected OpenShiftEnvironment doCreate(BrokersConfigs brokersConfigs) { return OpenShiftEnvironment.builder() .setConfigMaps(brokersConfigs.configMaps) .setMachines(brokersConfigs.machines) .setPods(brokersConfigs.pods) .build(); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisionerTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.when; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GatewayRouterProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitConfigProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ImagePullSecretProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.PodTerminationGracePeriodProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ServiceAccountProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SshKeysProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.TlsProvisionerProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSslCertificateProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.env.EnvVarsConverter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.limits.ram.ContainerResourceProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftUniqueNamesProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftPreviewUrlExposer; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link OpenShiftEnvironmentProvisioner}. * * @author Anton Korneta */ @Listeners(MockitoTestNGListener.class) public class OpenShiftEnvironmentProvisionerTest { @Mock private OpenShiftUniqueNamesProvisioner uniqueNamesProvisioner; @Mock private OpenShiftEnvironment osEnv; @Mock private RuntimeIdentity runtimeIdentity; @Mock private RouteTlsProvisioner tlsRouteProvisioner; @Mock private TlsProvisionerProvider tlsRouteProvisionerProvider; @Mock private EnvVarsConverter envVarsProvisioner; @Mock private ServersConverter serversProvisioner; @Mock private RestartPolicyRewriter restartPolicyRewriter; @Mock private ContainerResourceProvisioner ramLimitProvisioner; @Mock private PodTerminationGracePeriodProvisioner podTerminationGracePeriodProvisioner; @Mock private ImagePullSecretProvisioner imagePullSecretProvisioner; @Mock private ServiceAccountProvisioner serviceAccountProvisioner; @Mock private CertificateProvisioner certificateProvisioner; @Mock private SshKeysProvisioner sshKeysProvisioner; @Mock private GitConfigProvisioner gitConfigProvisioner; @Mock private OpenShiftPreviewUrlExposer previewUrlEndpointsProvisioner; @Mock private VcsSslCertificateProvisioner vcsSslCertificateProvisioner; @Mock private GatewayRouterProvisioner gatewayRouterProvisioner; private OpenShiftEnvironmentProvisioner osInfraProvisioner; private InOrder provisionOrder; @BeforeMethod public void setUp() { when(tlsRouteProvisionerProvider.get()).thenReturn(tlsRouteProvisioner); osInfraProvisioner = new OpenShiftEnvironmentProvisioner( uniqueNamesProvisioner, tlsRouteProvisionerProvider, serversProvisioner, envVarsProvisioner, restartPolicyRewriter, ramLimitProvisioner, podTerminationGracePeriodProvisioner, imagePullSecretProvisioner, serviceAccountProvisioner, certificateProvisioner, sshKeysProvisioner, gitConfigProvisioner, previewUrlEndpointsProvisioner, vcsSslCertificateProvisioner, gatewayRouterProvisioner); provisionOrder = inOrder( serversProvisioner, envVarsProvisioner, uniqueNamesProvisioner, tlsRouteProvisioner, restartPolicyRewriter, ramLimitProvisioner, podTerminationGracePeriodProvisioner, imagePullSecretProvisioner, serviceAccountProvisioner, certificateProvisioner, sshKeysProvisioner, vcsSslCertificateProvisioner, gitConfigProvisioner, previewUrlEndpointsProvisioner, gatewayRouterProvisioner); } @Test public void performsOrderedProvisioning() throws Exception { osInfraProvisioner.provision(osEnv, runtimeIdentity); provisionOrder.verify(serversProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(envVarsProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(restartPolicyRewriter).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(tlsRouteProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(ramLimitProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder .verify(podTerminationGracePeriodProvisioner) .provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(imagePullSecretProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(serviceAccountProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(certificateProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(sshKeysProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(vcsSslCertificateProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(gitConfigProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(gatewayRouterProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(uniqueNamesProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verifyNoMoreInteractions(); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/RouteServerResolverTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.runtime.ServerStatus.UNKNOWN; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.fabric8.kubernetes.api.model.ServicePortBuilder; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RouteBuilder; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations.Serializer; import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy; import org.eclipse.che.workspace.infrastructure.openshift.server.RouteServerResolver; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Test for {@link RouteServerResolver}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class RouteServerResolverTest { private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); private static final int CONTAINER_PORT = 3054; private static final String ROUTE_HOST = "localhost"; @Mock private ExternalServiceExposureStrategy externalServiceExposureStrategy; @Test public void testResolvingServersWhenThereIsNoTheCorrespondingServiceAndRouteForTheSpecifiedMachine() { // given Service nonMatchedByPodService = createService("nonMatched", "foreignMachine", CONTAINER_PORT, null); Route route = createRoute( "nonMatched", "foreignMachine", ImmutableMap.of( "http-server", new ServerConfigImpl("3054", "http", "/api", ATTRIBUTES_MAP))); RouteServerResolver serverResolver = new RouteServerResolver(singletonList(nonMatchedByPodService), singletonList(route)); // when Map resolved = serverResolver.resolve("machine"); // then assertTrue(resolved.isEmpty()); } @Test public void testResolvingServersWhenThereIsMatchedRouteForTheSpecifiedMachine() { Route route = createRoute( "matched", "machine", ImmutableMap.of( "http-server", new ServerConfigImpl("3054", "http", "/api", ATTRIBUTES_MAP))); RouteServerResolver serverResolver = new RouteServerResolver(emptyList(), singletonList(route)); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("http://localhost/api") .withStatus(UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, "/"))); } @Test public void testResolvingServersWhenThereIsMatchedRouteForMachineAndServerPathIsNull() { Route route = createRoute( "matched", "machine", singletonMap( "http-server", new ServerConfigImpl("3054", "http", null, ATTRIBUTES_MAP))); RouteServerResolver serverResolver = new RouteServerResolver(emptyList(), singletonList(route)); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("http://localhost") .withStatus(UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, "/"))); } @Test public void testResolvingServersWhenThereIsMatchedRouteForMachineAndServerPathIsEmpty() { Route route = createRoute( "matched", "machine", singletonMap("http-server", new ServerConfigImpl("3054", "http", "", ATTRIBUTES_MAP))); RouteServerResolver serverResolver = new RouteServerResolver(emptyList(), singletonList(route)); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("http://localhost") .withStatus(UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, "/"))); } @Test public void testResolvingServersWhenThereIsMatchedRouteForMachineAndServerPathIsRelative() { Route route = createRoute( "matched", "machine", singletonMap( "http-server", new ServerConfigImpl("3054", "http", "api", ATTRIBUTES_MAP))); RouteServerResolver serverResolver = new RouteServerResolver(emptyList(), singletonList(route)); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("http://localhost/api") .withStatus(UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, "/"))); } @Test public void testResolvingInternalServers() { Service service = createService( "service11", "machine", CONTAINER_PORT, singletonMap( "http-server", new ServerConfigImpl("3054", "http", "api", ATTRIBUTES_MAP))); Route route = createRoute("matched", "machine", null); RouteServerResolver serverResolver = new RouteServerResolver(singletonList(service), singletonList(route)); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("http://service11:3054/api") .withStatus(UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, "/"))); } @Test public void testResolvingInternalServersWithPortWithTransportProtocol() { Service service = createService( "service11", "machine", CONTAINER_PORT, singletonMap( "http-server", new ServerConfigImpl("3054/udp", "xxx", "api", ATTRIBUTES_MAP))); Route route = createRoute("matched", "machine", null); RouteServerResolver serverResolver = new RouteServerResolver(singletonList(service), singletonList(route)); Map resolved = serverResolver.resolve("machine"); assertEquals(resolved.size(), 1); assertEquals( resolved.get("http-server"), new ServerImpl() .withUrl("xxx://service11:3054/api") .withStatus(UNKNOWN) .withAttributes( defaultAttributeAnd( Constants.SERVER_PORT_ATTRIBUTE, "3054", ServerConfig.ENDPOINT_ORIGIN, "/"))); } @Test public void shouldSetEndpointOrigin() { // given Route route = new RouteBuilder() .withNewMetadata() .addToAnnotations( Annotations.newSerializer() .machineName("m1") .server( "svr", new ServerConfigImpl() .withPort("8080") .withProtocol("http") .withPath("/kachny")) .annotations()) .endMetadata() .withNewSpec() .withHost("che.host") .endSpec() .build(); RouteServerResolver serverResolver = new RouteServerResolver(emptyList(), singletonList(route)); // when Map resolvedServers = serverResolver.resolve("m1"); // then ServerImpl svr = resolvedServers.get("svr"); assertNotNull(svr); assertEquals("/", ServerConfig.getEndpointOrigin(svr.getAttributes())); assertEquals("http://che.host/kachny", svr.getUrl()); } private Service createService( String name, String machineName, Integer port, Map servers) { Serializer serializer = Annotations.newSerializer(); serializer.machineName(machineName); if (servers != null) { serializer.servers(servers); } return new ServiceBuilder() .withNewMetadata() .withName(name) .withAnnotations(serializer.annotations()) .endMetadata() .withNewSpec() .withPorts( new ServicePortBuilder() .withPort(port) .withNewTargetPort() .withValue(port) .endTargetPort() .build()) .endSpec() .build(); } private Route createRoute( String name, String machineName, Map servers) { Serializer serializer = Annotations.newSerializer(); serializer.machineName(machineName); if (servers != null) { serializer.servers(servers); } return new RouteBuilder() .withNewMetadata() .withName(name) .withAnnotations(serializer.annotations()) .endMetadata() .withNewSpec() .withHost(ROUTE_HOST) .withNewTo() .withName(name) .endTo() .endSpec() .build(); } private Map defaultAttributeAnd(String... keyValues) { HashMap attributes = new HashMap<>(ATTRIBUTES_MAP); String key = null; for (String v : keyValues) { if (key == null) { key = v; } else { attributes.put(key, v); key = null; } } return attributes; } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/authorization/OpenShiftAuthorizationCheckerTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.authorization; import static org.mockito.Mockito.*; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMixedDispatcher; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.mockwebserver.Context; import io.fabric8.mockwebserver.MockWebServer; import io.fabric8.mockwebserver.ServerRequest; import io.fabric8.mockwebserver.ServerResponse; import io.fabric8.openshift.api.model.Group; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class OpenShiftAuthorizationCheckerTest { @Mock private CheServerKubernetesClientFactory clientFactory; private KubernetesClient client; private KubernetesMockServer kubernetesMockServer; private static Subject user1 = new SubjectImpl("user1", Collections.emptyList(), "id", "token", false); private static Subject user2 = new SubjectImpl("user2", Collections.emptyList(), "id", "token", false); @BeforeMethod public void setUp() throws InfrastructureException { final Map> responses = new HashMap<>(); kubernetesMockServer = new KubernetesMockServer( new Context(), new MockWebServer(), responses, new KubernetesMixedDispatcher(responses), true); kubernetesMockServer.init(); client = spy(kubernetesMockServer.createClient()); lenient().when(clientFactory.create()).thenReturn(client); } @AfterMethod public void tearDown() { kubernetesMockServer.destroy(); } @Test(dataProvider = "advancedAuthorizationData") public void advancedAuthorization( Subject subject, List groups, String allowedUsers, String allowedGroups, String deniedUsers, String deniedGroups, boolean expectedIsAuthorized) throws InfrastructureException { // give OpenShiftAuthorizationCheckerImpl authorizationChecker = new OpenShiftAuthorizationCheckerImpl( allowedUsers, allowedGroups, deniedUsers, deniedGroups, ",", clientFactory); groups.forEach(group -> client.resources(Group.class).create(group)); // when boolean isAuthorized = authorizationChecker.isAuthorized(subject); // then Assert.assertEquals(isAuthorized, expectedIsAuthorized); } @DataProvider public static Object[][] advancedAuthorizationData() { Group groupWithUser1 = new Group( "v1", "Group", new ObjectMetaBuilder().withName("groupWithUser1").build(), List.of("user1")); Group groupWithUser2 = new Group( "v1", "Group", new ObjectMetaBuilder().withName("groupWithUser2").build(), List.of("user2")); return new Object[][] { {user1, Collections.emptyList(), "", "", "", "", true}, {user1, Collections.emptyList(), "user1", "", "", "", true}, {user1, Collections.emptyList(), "user1", "", "user2", "", true}, {user1, List.of(groupWithUser2), "user1", "", "", "groupWithUser2", true}, {user1, List.of(groupWithUser1), "", "groupWithUser1", "", "", true}, {user2, List.of(groupWithUser1), "user2", "groupWithUser1", "", "", true}, { user1, List.of(groupWithUser1, groupWithUser2), "", "groupWithUser1", "", "groupWithUser2", true }, {user1, Collections.emptyList(), "user1", "", "user1", "", false}, {user2, Collections.emptyList(), "user1", "", "", "", false}, {user2, Collections.emptyList(), "user1", "", "user2", "", false}, {user2, List.of(groupWithUser1), "", "groupWithUser1", "", "", false}, {user1, Collections.emptyList(), "", "", "user1", "", false}, {user1, List.of(groupWithUser1), "", "", "", "groupWithUser1", false}, {user1, List.of(groupWithUser1), "", "groupWithUser1", "", "groupWithUser1", false}, { user2, List.of(groupWithUser1, groupWithUser2), "", "groupWithUser1", "", "groupWithUser2", false }, }; } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/devfile/OpenshiftComponentToWorkspaceApplierTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.devfile; import static io.fabric8.kubernetes.client.utils.Serialization.unmarshal; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.OPENSHIFT_COMPONENT_TYPE; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.KubernetesList; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesComponentToWorkspaceApplier; import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesEnvironmentProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser; import org.eclipse.che.workspace.infrastructure.kubernetes.util.EnvVars; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import org.testng.reporters.Files; @Listeners(MockitoTestNGListener.class) public class OpenshiftComponentToWorkspaceApplierTest { public static final String REFERENCE_FILENAME = "reference.yaml"; public static final String COMPONENT_NAME = "foo"; private WorkspaceConfigImpl workspaceConfig; private KubernetesComponentToWorkspaceApplier applier; @Mock private KubernetesEnvironmentProvisioner k8sEnvProvisioner; @Mock private KubernetesRecipeParser k8sRecipeParser; @Mock private FileContentProvider contentProvider; @Mock private EnvVars envVars; @BeforeMethod public void setUp() { Set k8sBasedComponents = new HashSet<>(); k8sBasedComponents.add(KUBERNETES_COMPONENT_TYPE); applier = new OpenshiftComponentToWorkspaceApplier( k8sRecipeParser, k8sEnvProvisioner, envVars, MULTI_HOST_STRATEGY, k8sBasedComponents); workspaceConfig = new WorkspaceConfigImpl(); } @Test public void shouldProvisionEnvironmentWithCorrectRecipeTypeAndContentFromOSList() throws Exception { // given doReturn(emptyList()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); when(contentProvider.fetchContent(anyString())).thenReturn("content"); // when applier.apply(workspaceConfig, component, contentProvider); // then verify(k8sEnvProvisioner) .provision(workspaceConfig, OpenShiftEnvironment.TYPE, emptyList(), emptyMap()); } @Test public void serverCantHaveRequireSubdomainWhenSinglehostDevfileExpose() throws DevfileException, IOException, ValidationException, InfrastructureException { Set openshiftBasedComponents = new HashSet<>(); openshiftBasedComponents.add(OPENSHIFT_COMPONENT_TYPE); applier = new OpenshiftComponentToWorkspaceApplier( k8sRecipeParser, k8sEnvProvisioner, envVars, SINGLE_HOST_STRATEGY, openshiftBasedComponents); String yamlRecipeContent = getResource("devfile/petclinic.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); // given ComponentImpl component = new ComponentImpl(); component.setType(OPENSHIFT_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setEndpoints( Arrays.asList( new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); // when applier.apply(workspaceConfig, component, contentProvider); // then @SuppressWarnings("unchecked") ArgumentCaptor> objectsCaptor = ArgumentCaptor.forClass(Map.class); verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); Map machineConfigs = objectsCaptor.getValue(); assertEquals(machineConfigs.size(), 4); machineConfigs .values() .forEach( machineConfig -> { assertEquals(machineConfig.getServers().size(), 2); assertFalse( ServerConfig.isRequireSubdomain( machineConfig.getServers().get("e1").getAttributes())); assertFalse( ServerConfig.isRequireSubdomain( machineConfig.getServers().get("e2").getAttributes())); }); } @Test public void serverMustHaveRequireSubdomainWhenNonSinglehostDevfileExpose() throws DevfileException, IOException, ValidationException, InfrastructureException { Set openshiftBasedComponents = new HashSet<>(); openshiftBasedComponents.add(OPENSHIFT_COMPONENT_TYPE); applier = new OpenshiftComponentToWorkspaceApplier( k8sRecipeParser, k8sEnvProvisioner, envVars, MULTI_HOST_STRATEGY, openshiftBasedComponents); String yamlRecipeContent = getResource("devfile/petclinic.yaml"); when(contentProvider.fetchContent(anyString())).thenReturn(yamlRecipeContent); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); // given ComponentImpl component = new ComponentImpl(); component.setType(OPENSHIFT_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setEndpoints( Arrays.asList( new EndpointImpl("e1", 1111, emptyMap()), new EndpointImpl("e2", 2222, emptyMap()))); // when applier.apply(workspaceConfig, component, contentProvider); // then @SuppressWarnings("unchecked") ArgumentCaptor> objectsCaptor = ArgumentCaptor.forClass(Map.class); verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); Map machineConfigs = objectsCaptor.getValue(); assertEquals(machineConfigs.size(), 4); machineConfigs .values() .forEach( machineConfig -> { assertEquals(machineConfig.getServers().size(), 2); assertTrue( ServerConfig.isRequireSubdomain( machineConfig.getServers().get("e1").getAttributes())); assertTrue( ServerConfig.isRequireSubdomain( machineConfig.getServers().get("e2").getAttributes())); }); } private KubernetesList toK8SList(String content) { return unmarshal(content, KubernetesList.class); } private String getResource(String resourceName) throws IOException { return Files.readFile(getClass().getClassLoader().getResourceAsStream(resourceName)); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentFactoryTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.environment; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static org.eclipse.che.workspace.infrastructure.kubernetes.environment.PodMerger.DEPLOYMENT_NAME_LABEL; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.PodTemplateSpec; import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder; import io.fabric8.kubernetes.api.model.Quantity; import io.fabric8.kubernetes.api.model.ResourceRequirements; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RouteBuilder; import java.util.Map; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.PodMerger; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link OpenShiftEnvironmentFactory}. * * @author Anton Korneta */ @Listeners(MockitoTestNGListener.class) public class OpenShiftEnvironmentFactoryTest { private static final String MACHINE_NAME_1 = "machine1"; private static final String MACHINE_NAME_2 = "machine2"; private OpenShiftEnvironmentFactory osEnvFactory; @Mock private OpenShiftEnvironmentValidator openShiftEnvValidator; @Mock private InternalRecipe internalRecipe; @Mock private InternalMachineConfig machineConfig1; @Mock private InternalMachineConfig machineConfig2; @Mock private KubernetesRecipeParser k8sRecipeParser; @Mock private PodMerger podMerger; private Map machines; @BeforeMethod public void setup() throws Exception { osEnvFactory = new OpenShiftEnvironmentFactory( null, null, openShiftEnvValidator, k8sRecipeParser, podMerger); machines = ImmutableMap.of(MACHINE_NAME_1, machineConfig1, MACHINE_NAME_2, machineConfig2); } @Test public void shouldCreateOpenShiftEnvironmentWithServicesFromRecipe() throws Exception { // given Service service1 = new ServiceBuilder().withNewMetadata().withName("service1").endMetadata().build(); Service service2 = new ServiceBuilder().withNewMetadata().withName("service2").endMetadata().build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(service1, service2)); // when KubernetesEnvironment osEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then assertEquals(osEnv.getServices().size(), 2); assertEquals(osEnv.getServices().get("service1"), service1); assertEquals(osEnv.getServices().get("service2"), service2); } @Test public void shouldCreateOpenShiftEnvironmentWithPVCsFromRecipe() throws Exception { // given PersistentVolumeClaim pvc1 = new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc1").endMetadata().build(); PersistentVolumeClaim pvc2 = new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc2").endMetadata().build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(pvc1, pvc2)); // when OpenShiftEnvironment osEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then assertEquals(osEnv.getPersistentVolumeClaims().size(), 2); assertEquals(osEnv.getPersistentVolumeClaims().get("pvc1"), pvc1); assertEquals(osEnv.getPersistentVolumeClaims().get("pvc2"), pvc2); } @Test public void addRoutesWhenRecipeContainsThem() throws Exception { Route route = new RouteBuilder().withNewMetadata().withName("test-route").endMetadata().build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(route)); final OpenShiftEnvironment parsed = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); assertEquals(parsed.getRoutes().size(), 1); assertEquals( parsed.getRoutes().get("test-route").getMetadata().getName(), route.getMetadata().getName()); } @Test public void addSecretsWhenRecipeContainsThem() throws Exception { Secret secret = new SecretBuilder().withNewMetadata().withName("test-secret").endMetadata().build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(secret)); final OpenShiftEnvironment parsed = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); assertEquals(parsed.getSecrets().size(), 1); assertEquals( parsed.getSecrets().get("test-secret").getMetadata().getName(), secret.getMetadata().getName()); } @Test public void addConfigMapsWhenRecipeContainsThem() throws Exception { ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("test-configmap").endMetadata().build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(configMap)); final KubernetesEnvironment parsed = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); assertEquals(parsed.getConfigMaps().size(), 1); assertEquals( parsed.getConfigMaps().values().iterator().next().getMetadata().getName(), configMap.getMetadata().getName()); } @Test public void addPodsWhenRecipeContainsThem() throws Exception { // given Pod pod = new PodBuilder() .withNewMetadata() .withName("pod") .endMetadata() .withSpec(new PodSpec()) .build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(pod)); // when KubernetesEnvironment osEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then assertEquals(osEnv.getPodsCopy().size(), 1); assertEquals(osEnv.getPodsCopy().get("pod"), pod); assertEquals(osEnv.getPodsData().size(), 1); assertEquals(osEnv.getPodsData().get("pod").getMetadata(), pod.getMetadata()); assertEquals(osEnv.getPodsData().get("pod").getSpec(), pod.getSpec()); } @Test public void addDeploymentsWhenRecipeContainsThem() throws Exception { // given PodTemplateSpec podTemplate = new PodTemplateSpecBuilder() .withNewMetadata() .withName("deployment-pod") .endMetadata() .withNewSpec() .endSpec() .build(); Deployment deployment = new DeploymentBuilder() .withNewMetadata() .withName("deployment-test") .endMetadata() .withNewSpec() .withTemplate(podTemplate) .endSpec() .build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(deployment)); // when final KubernetesEnvironment osEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then assertEquals(osEnv.getDeploymentsCopy().size(), 1); assertEquals(osEnv.getDeploymentsCopy().get("deployment-test"), deployment); assertEquals(osEnv.getPodsData().size(), 1); assertEquals( osEnv.getPodsData().get("deployment-test").getMetadata(), podTemplate.getMetadata()); assertEquals(osEnv.getPodsData().get("deployment-test").getSpec(), podTemplate.getSpec()); } @Test public void shouldUseDeploymentNameAsPodTemplateNameIfItIsMissing() throws Exception { // given PodTemplateSpec podTemplate = new PodTemplateSpecBuilder().withNewSpec().endSpec().build(); Deployment deployment = new DeploymentBuilder() .withNewMetadata() .withName("deployment-test") .endMetadata() .withNewSpec() .withTemplate(podTemplate) .endSpec() .build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(deployment)); // when final KubernetesEnvironment k8sEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then Deployment deploymentTest = k8sEnv.getDeploymentsCopy().get("deployment-test"); assertNotNull(deploymentTest); PodTemplateSpec resultPodTemplate = deploymentTest.getSpec().getTemplate(); assertEquals(resultPodTemplate.getMetadata().getName(), "deployment-test"); } @Test public void shouldMergeDeploymentAndPodIntoOneDeployment() throws Exception { // given PodTemplateSpec podTemplate = new PodTemplateSpecBuilder() .withNewMetadata() .withName("deployment-pod") .endMetadata() .withNewSpec() .endSpec() .build(); Deployment deployment = new DeploymentBuilder() .withNewMetadata() .withName("deployment-test") .endMetadata() .withNewSpec() .withTemplate(podTemplate) .endSpec() .build(); Pod pod = new PodBuilder() .withNewMetadata() .withName("bare-pod") .endMetadata() .withNewSpec() .endSpec() .build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(deployment, pod)); Deployment merged = createEmptyDeployment("merged"); when(podMerger.merge(any())).thenReturn(merged); // when final KubernetesEnvironment k8sEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then verify(podMerger).merge(asList(new PodData(pod), new PodData(deployment))); assertEquals(k8sEnv.getPodsData().size(), 1); assertTrue(k8sEnv.getPodsCopy().isEmpty()); assertEquals(k8sEnv.getDeploymentsCopy().size(), 1); assertEquals(k8sEnv.getDeploymentsCopy().get("merged"), merged); } @Test public void shouldReconfigureServiceToMatchMergedDeployment() throws Exception { // given Pod pod1 = new PodBuilder() .withNewMetadata() .withName("bare-pod1") .withLabels(ImmutableMap.of("name", "pod1")) .endMetadata() .withNewSpec() .endSpec() .build(); Pod pod2 = new PodBuilder() .withNewMetadata() .withName("bare-pod2") .withLabels(ImmutableMap.of("name", "pod2")) .endMetadata() .withNewSpec() .endSpec() .build(); Service service1 = new ServiceBuilder() .withNewMetadata() .withName("pod1-service") .endMetadata() .withNewSpec() .withSelector(ImmutableMap.of("name", "pod1")) .endSpec() .build(); Service service2 = new ServiceBuilder() .withNewMetadata() .withName("pod2-service") .endMetadata() .withNewSpec() .withSelector(ImmutableMap.of("name", "pod2")) .endSpec() .build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))) .thenReturn(asList(pod1, pod2, service1, service2)); Deployment merged = createEmptyDeployment("merged"); when(podMerger.merge(any())).thenReturn(merged); // when final KubernetesEnvironment k8sEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); // then verify(podMerger).merge(asList(new PodData(pod1), new PodData(pod2))); PodData mergedPodData = k8sEnv.getPodsData().get("merged"); assertEquals(mergedPodData.getMetadata().getLabels().get(DEPLOYMENT_NAME_LABEL), "merged"); assertTrue( k8sEnv.getServices().values().stream() .allMatch( s -> ImmutableMap.of(DEPLOYMENT_NAME_LABEL, "merged") .equals(s.getSpec().getSelector()))); } @Test(expectedExceptions = ValidationException.class) public void exceptionOnRecipeLoadError() throws Exception { when(k8sRecipeParser.parse(any(InternalRecipe.class))) .thenThrow(new ValidationException("Could not parse recipe")); osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Environment can not contain two 'Service' objects with the same name 'db'") public void exceptionOnObjectsWithTheSameNameAndKind() throws Exception { HasMetadata object1 = new ServiceBuilder().withNewMetadata().withName("db").endMetadata().build(); HasMetadata object2 = new ServiceBuilder().withNewMetadata().withName("db").endMetadata().build(); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(object1, object2)); osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Environment contains object without specified kind field") public void exceptionOnObjectWithNoKindSpecified() throws Exception { HasMetadata object = mock(HasMetadata.class); when(object.getKind()).thenReturn(null); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object)); osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "MyObject metadata must not be null") public void exceptionOnObjectWithNoMetadataSpecified() throws Exception { HasMetadata object = mock(HasMetadata.class); when(object.getKind()).thenReturn("MyObject"); when(object.getMetadata()).thenReturn(null); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object)); osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "MyObject name must not be null") public void exceptionOnObjectWithNoNameSpecified() throws Exception { HasMetadata object = mock(HasMetadata.class); when(object.getKind()).thenReturn("MyObject"); when(object.getMetadata()).thenReturn(new ObjectMetaBuilder().withName(null).build()); when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object)); osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList()); } /** If provided {@code ramLimit} is {@code null} ram limit won't be set in POD */ private static PodData createPodData(String machineName, Long ramLimit, Long ramRequest) { final String containerName = "container_" + machineName; final Container containerMock = mock(Container.class); final Pod podMock = mock(Pod.class); final PodSpec specMock = mock(PodSpec.class); final ObjectMeta metadataMock = mock(ObjectMeta.class); when(podMock.getSpec()).thenReturn(specMock); when(podMock.getMetadata()).thenReturn(metadataMock); final ResourceRequirements resourcesMock = mock(ResourceRequirements.class); when(containerMock.getResources()).thenReturn(resourcesMock); if (ramLimit != null) { final Quantity limitQuantityMock = mock(Quantity.class); when(limitQuantityMock.getAmount()).thenReturn(String.valueOf(ramLimit)); when(resourcesMock.getLimits()).thenReturn(ImmutableMap.of("memory", limitQuantityMock)); } if (ramRequest != null) { final Quantity requestQuantityMock = mock(Quantity.class); when(requestQuantityMock.getAmount()).thenReturn(String.valueOf(ramRequest)); when(resourcesMock.getRequests()).thenReturn(ImmutableMap.of("memory", requestQuantityMock)); } when(containerMock.getName()).thenReturn(containerName); when(metadataMock.getAnnotations()) .thenReturn(Names.createMachineNameAnnotations(containerName, machineName)); when(specMock.getContainers()).thenReturn(ImmutableList.of(containerMock)); return new PodData(specMock, metadataMock); } private Deployment createEmptyDeployment(String name) { return new DeploymentBuilder() .withNewMetadata() .withName(name) .endMetadata() .withNewSpec() .withNewTemplate() .withNewMetadata() .endMetadata() .withNewSpec() .endSpec() .endTemplate() .endSpec() .build(); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentValidatorTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.environment; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RouteBuilder; import java.util.Map; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentPodsValidator; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link OpenShiftEnvironmentValidator}. * * @author Angel Misevski */ @Listeners(MockitoTestNGListener.class) public class OpenShiftEnvironmentValidatorTest { @Mock private KubernetesEnvironmentPodsValidator podsValidator; @Mock private OpenShiftEnvironment environment; @InjectMocks private OpenShiftEnvironmentValidator envValidator; @Test public void shouldDoNothingWhenRoutesMatchServices() throws Exception { // Given Map services = ImmutableMap.of("service1", makeService("service1")); Map routes = ImmutableMap.of("route1", makeRoute("route1", "service1")); when(environment.getRoutes()).thenReturn(routes); when(environment.getServices()).thenReturn(services); // When envValidator.validate(environment); // Then (nothing) } @Test public void shouldDoNothingWhenRoutesDoNotHaveToField() throws Exception { // Given Map routes = ImmutableMap.of("route1", makeRoute("route1", null)); when(environment.getRoutes()).thenReturn(routes); // When envValidator.validate(environment); // Then (nothing) } @Test(expectedExceptions = ValidationException.class) public void shouldThrowExceptionWhenRouteRefersToMissingService() throws Exception { // Given Map services = ImmutableMap.of("service1", makeService("service1")); Map routes = ImmutableMap.of("route1", makeRoute("route1", "notservice1")); when(environment.getRoutes()).thenReturn(routes); when(environment.getServices()).thenReturn(services); // When envValidator.validate(environment); // Then (ValidationException) } private Service makeService(String serviceName) { return new ServiceBuilder() .withNewMetadata() .withName(serviceName) .endMetadata() .withNewSpec() .endSpec() .build(); } /** * Make route for use in tests. If serviceName is null, return a route that does not refer to a * service. */ private Route makeRoute(String routeName, String serviceName) { RouteBuilder routeBuilder = new RouteBuilder().withNewMetadata().withName(routeName).endMetadata(); if (serviceName != null) { routeBuilder .withNewSpec() .withNewTo() .withKind("Service") .withName(serviceName) .endTo() .endSpec(); } else { routeBuilder.withNewSpec().endSpec(); } return routeBuilder.build(); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/multiuser/oauth/OpenshiftTokenInitializationFilterTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.*; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.openshift.api.model.User; import io.fabric8.openshift.client.OpenShiftClient; import java.util.Optional; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.multiuser.api.authentication.commons.SessionStore; import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor; import org.eclipse.che.multiuser.api.permission.server.PermissionChecker; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class OpenshiftTokenInitializationFilterTest { @Mock private SessionStore sessionStore; @Mock private RequestTokenExtractor tokenExtractor; @Mock private UserManager userManager; @Mock private PermissionChecker permissionChecker; @Mock private OpenShiftClientFactory openShiftClientFactory; @Mock private OpenShiftClient openShiftClient; @Mock private User openshiftUser; @Mock private ObjectMeta openshiftUserMeta; private static final String TOKEN = "touken"; private static final String USER_UID = "almost-certainly-unique-id"; private static final String USERNAME = "test_username"; private OpenshiftTokenInitializationFilter openshiftTokenInitializationFilter; @BeforeMethod public void setUp() throws InfrastructureException { openshiftTokenInitializationFilter = new OpenshiftTokenInitializationFilter( sessionStore, tokenExtractor, openShiftClientFactory, userManager, permissionChecker); } @Test public void getUserIdGetsCurrentUserWithAuthenticatedOCClient() { when(openShiftClientFactory.createAuthenticatedClient(TOKEN)).thenReturn(openShiftClient); when(openShiftClient.currentUser()).thenReturn(openshiftUser); when(openshiftUser.getMetadata()).thenReturn(openshiftUserMeta); when(openshiftUserMeta.getUid()).thenReturn(USER_UID); User u = openshiftTokenInitializationFilter.processToken(TOKEN).get(); String userId = openshiftTokenInitializationFilter.getUserId(u); assertEquals(u, openshiftUser); assertEquals(userId, USER_UID); verify(openShiftClientFactory).createAuthenticatedClient(TOKEN); verify(openShiftClient).currentUser(); } @Test public void shouldBeAbleToHandleKubeAdminUserWithoutUid() throws ServerException, ConflictException { String KUBE_ADMIN_USERNAME = "kube:admin"; when(openShiftClientFactory.createAuthenticatedClient(TOKEN)).thenReturn(openShiftClient); when(openShiftClient.currentUser()).thenReturn(openshiftUser); when(openshiftUser.getMetadata()).thenReturn(openshiftUserMeta); when(openshiftUserMeta.getUid()).thenReturn(null); when(openshiftUserMeta.getName()).thenReturn(KUBE_ADMIN_USERNAME); when(userManager.getOrCreateUser(KUBE_ADMIN_USERNAME, KUBE_ADMIN_USERNAME)) .thenReturn( new UserImpl(KUBE_ADMIN_USERNAME, KUBE_ADMIN_USERNAME + "@che", KUBE_ADMIN_USERNAME)); User u = openshiftTokenInitializationFilter.processToken(TOKEN).get(); Subject subject = openshiftTokenInitializationFilter.extractSubject(TOKEN, u); assertEquals(subject.getUserId(), KUBE_ADMIN_USERNAME); assertEquals(subject.getUserName(), KUBE_ADMIN_USERNAME); } @Test public void extractSubjectCreatesSubjectWithCurrentlyAuthenticatedUser() throws ServerException, ConflictException { when(openShiftClientFactory.createAuthenticatedClient(TOKEN)).thenReturn(openShiftClient); when(openShiftClient.currentUser()).thenReturn(openshiftUser); when(openshiftUser.getMetadata()).thenReturn(openshiftUserMeta); when(openshiftUserMeta.getUid()).thenReturn(USER_UID); when(openshiftUserMeta.getName()).thenReturn(USERNAME); when(userManager.getOrCreateUser(USER_UID, USERNAME)) .thenReturn(new UserImpl(USER_UID, USERNAME + "@che", USERNAME)); User u = openshiftTokenInitializationFilter.processToken(TOKEN).get(); Subject subject = openshiftTokenInitializationFilter.extractSubject(TOKEN, u); assertEquals(u, openshiftUser); assertEquals(subject.getUserId(), USER_UID); assertEquals(subject.getUserName(), USERNAME); } @Test public void invalidTokenShouldBeHandledAsMissing() throws Exception { when(openShiftClientFactory.createAuthenticatedClient(TOKEN)).thenReturn(openShiftClient); when(openShiftClient.currentUser()) .thenThrow(new KubernetesClientException("failah", 401, null)); Optional u = openshiftTokenInitializationFilter.processToken(TOKEN); assertTrue(u.isEmpty()); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactoryTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.project; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import static java.util.Collections.singletonList; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.DEFAULT_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME; import static org.eclipse.che.workspace.infrastructure.openshift.Constants.PROJECT_DESCRIPTION_ANNOTATION; import static org.eclipse.che.workspace.infrastructure.openshift.Constants.PROJECT_DESCRIPTION_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.openshift.Constants.PROJECT_DISPLAY_NAME_ANNOTATION; import static org.eclipse.che.workspace.infrastructure.openshift.Constants.PROJECT_DISPLAY_NAME_ATTRIBUTE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.Status; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.openshift.api.model.Project; import io.fabric8.openshift.api.model.ProjectBuilder; import io.fabric8.openshift.api.model.ProjectList; import io.fabric8.openshift.client.OpenShiftClient; import io.fabric8.openshift.client.dsl.ProjectOperation; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationChecker; import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.PreferencesConfigMapConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool; import org.eclipse.che.workspace.infrastructure.openshift.CheServerOpenshiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.project.configurator.OpenShiftWorkspaceServiceAccountConfigurator; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Ignore; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link OpenShiftProjectFactory}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class OpenShiftProjectFactoryTest { private static final String USER_ID = "2342-2559-234"; private static final String USER_NAME = "johndoe"; private static final String NAMESPACE_LABEL_NAME = "component"; private static final String NAMESPACE_LABELS = NAMESPACE_LABEL_NAME + "=workspace"; private static final String NAMESPACE_ANNOTATION_NAME = "owner"; private static final String NAMESPACE_ANNOTATIONS = NAMESPACE_ANNOTATION_NAME + "="; // @Mock private OpenShiftClientConfigFactory configFactory; @Mock private OpenShiftClientFactory openShiftClientFactory; @Mock private CheServerKubernetesClientFactory cheServerKubernetesClientFactory; @Mock private CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory; @Mock private PreferenceManager preferenceManager; @Mock private KubernetesSharedPool pool; @Mock private AuthorizationChecker authorizationChecker; @Mock private PermissionsCleaner permissionsCleaner; @Mock private ProjectOperation projectOperation; @Mock private Resource projectResource; @Mock private OpenShiftClient osClient; private OpenShiftProjectFactory projectFactory; @Mock private FilterWatchListDeletable> projectListResource; @Mock private ProjectList projectList; @BeforeMethod public void setUp() throws Exception { lenient().when(openShiftClientFactory.createOC()).thenReturn(osClient); lenient().when(cheServerOpenshiftClientFactory.create()).thenReturn(osClient); lenient().when(cheServerOpenshiftClientFactory.createOC()).thenReturn(osClient); lenient().when(cheServerKubernetesClientFactory.create()).thenReturn(osClient); lenient().when(osClient.projects()).thenReturn(projectOperation); lenient().when(authorizationChecker.isAuthorized(any(Subject.class))).thenReturn(true); lenient().when(projectOperation.withName(any())).thenReturn(projectResource); lenient().when(projectResource.get()).thenReturn(mock(Project.class)); lenient().when(projectOperation.withLabels(any())).thenReturn(projectListResource); lenient().when(projectListResource.list()).thenReturn(projectList); lenient().when(projectList.getItems()).thenReturn(emptyList()); EnvironmentContext.getCurrent() .setSubject( new SubjectImpl( USER_NAME, Collections.emptyList(), USER_ID, "t-354t53xff34234", false)); } @AfterMethod public void cleanup() { EnvironmentContext.reset(); } @Test public void shouldNotThrowExceptionIfDefaultNamespaceIsSpecifiedOnCheckingIfNamespaceIsAllowed() throws Exception { projectFactory = new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); projectFactory.checkIfNamespaceIsAllowed(USER_NAME + "-che"); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "User defined namespaces are not allowed. Only the default namespace 'johndoe-che' is available.") public void shouldThrowExceptionIfNonDefaultNamespaceIsSpecifiedAndUserDefinedAreNotAllowedOnCheckingIfNamespaceIsAllowed() throws Exception { System.out.println("0--------"); System.out.println(EnvironmentContext.getCurrent().getSubject()); System.out.println("2--------"); projectFactory = new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); try { projectFactory.checkIfNamespaceIsAllowed("any-namespace"); } catch (ValidationException e) { e.printStackTrace(); throw e; } } @Test( expectedExceptions = ConfigurationException.class, expectedExceptionsMessageRegExp = "che.infra.kubernetes.namespace.default must be configured") public void shouldThrowExceptionIfNoDefaultNamespaceIsConfiguredAndUserDefinedNamespacesAreNotAllowed() throws Exception { projectFactory = new OpenShiftProjectFactory( null, true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); } @Test public void shouldReturnPreparedNamespacesWhenFound() throws InfrastructureException { // given List projects = Arrays.asList( createProject( "ns1", "project1", "desc1", "Active", Map.of(NAMESPACE_ANNOTATION_NAME, "jondoe")), createProject( "ns3", "project3", "desc3", "Active", Map.of(NAMESPACE_ANNOTATION_NAME, "some_other_user")), createProject( "ns2", "project2", "desc2", "Active", Map.of(NAMESPACE_ANNOTATION_NAME, "jondoe"))); doReturn(projects).when(projectList).getItems(); projectFactory = new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "123", null, false)); // when List availableNamespaces = projectFactory.list(); // then assertEquals(availableNamespaces.size(), 2); assertEquals(availableNamespaces.get(0).getName(), "ns1"); assertEquals(availableNamespaces.get(1).getName(), "ns2"); } @Test public void shouldNotThrowAnExceptionWhenNotAllowedToListNamespaces() throws Exception { // given Project p = createProject("ns1", "project1", "desc1", "Active"); doThrow(new KubernetesClientException("Not allowed.", 403, new Status())) .when(projectList) .getItems(); prepareNamespaceToBeFoundByName("u123-che", p); projectFactory = new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "u123", null, false)); // when List availableNamespaces = projectFactory.list(); // then assertEquals(availableNamespaces.get(0).getName(), "ns1"); } @Test(expectedExceptions = InfrastructureException.class) public void throwAnExceptionWhenErrorListingNamespaces() throws Exception { // given doThrow(new KubernetesClientException("Not allowed.", 500, new Status())) .when(projectList) .getItems(); projectFactory = new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); // when projectFactory.list(); // then throw } @Test public void shouldReturnDefaultProjectWhenItExistsAndUserDefinedIsNotAllowed() throws Exception { prepareNamespaceToBeFoundByName( USER_NAME + "-che", new ProjectBuilder() .withNewMetadata() .withName(USER_NAME + "-che") .withAnnotations( ImmutableMap.of( PROJECT_DISPLAY_NAME_ANNOTATION, "Default Che Project", PROJECT_DESCRIPTION_ANNOTATION, "some description")) .endMetadata() .withNewStatus() .withPhase("Active") .endStatus() .build()); projectFactory = new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); List availableNamespaces = projectFactory.list(); assertEquals(availableNamespaces.size(), 1); KubernetesNamespaceMeta defaultNamespace = availableNamespaces.get(0); assertEquals(defaultNamespace.getName(), USER_NAME + "-che"); assertEquals(defaultNamespace.getAttributes().get(DEFAULT_ATTRIBUTE), "true"); assertEquals( defaultNamespace.getAttributes().get(PROJECT_DISPLAY_NAME_ATTRIBUTE), "Default Che Project"); assertEquals( defaultNamespace.getAttributes().get(PROJECT_DESCRIPTION_ATTRIBUTE), "some description"); assertEquals(defaultNamespace.getAttributes().get(PHASE_ATTRIBUTE), "Active"); } @Test public void shouldReturnDefaultProjectWhenItDoesNotExistAndUserDefinedIsNotAllowed() throws Exception { throwOnTryToGetProjectByName( USER_NAME + "-che", new KubernetesClientException("forbidden", 403, null)); projectFactory = new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); List availableNamespaces = projectFactory.list(); assertEquals(availableNamespaces.size(), 1); KubernetesNamespaceMeta defaultNamespace = availableNamespaces.get(0); assertEquals(defaultNamespace.getName(), USER_NAME + "-che"); assertEquals(defaultNamespace.getAttributes().get(DEFAULT_ATTRIBUTE), "true"); assertNull( defaultNamespace .getAttributes() .get(PHASE_ATTRIBUTE)); // no phase - means such project does not exist } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Error while trying to fetch the project 'johndoe-che'. Cause: connection refused") public void shouldThrowExceptionWhenFailedToGetInfoAboutDefaultNamespace() throws Exception { throwOnTryToGetProjectByName( USER_NAME + "-che", new KubernetesClientException("connection refused")); projectFactory = new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); projectFactory.list(); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Error occurred when tried to list all available projects. Cause: connection refused") public void shouldThrowExceptionWhenFailedToGetNamespaces() throws Exception { throwOnTryToGetProjectsList(new KubernetesClientException("connection refused")); projectFactory = new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); projectFactory.list(); } @Test public void shouldRequireNamespacePriorExistenceIfDifferentFromDefaultAndUserDefinedIsNotAllowed() throws Exception { // There is only one scenario where this can happen. The workspace was created and started in // some default namespace. Then server was reconfigured to use a different default namespace // AND the namespace of the workspace was MANUALLY deleted in the cluster. In this case, we // should NOT try to re-create the namespace because it would be created in a namespace that // is not configured. We DO allow it to start if the namespace still exists though. // given projectFactory = spy( new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); OpenShiftProject toReturnProject = mock(OpenShiftProject.class); prepareProject(toReturnProject); doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any()); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "old-default"); OpenShiftProject project = projectFactory.getOrCreate(identity); // then assertEquals(toReturnProject, project); verify(toReturnProject).prepare(eq(true), eq(true), any(), any()); } @Test public void shouldCreatePreferencesConfigmapIfNotExists() throws Exception { // given projectFactory = spy( new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, Set.of(new PreferencesConfigMapConfigurator(cheServerKubernetesClientFactory)), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); OpenShiftProject toReturnProject = mock(OpenShiftProject.class); doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any()); when(toReturnProject.getName()).thenReturn("namespace123"); NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class); MixedOperation mixedOperation = mock(MixedOperation.class); when(osClient.configMaps()).thenReturn(mixedOperation); when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); Resource nullCm = mock(Resource.class); when(namespaceOperation.withName(PREFERENCES_CONFIGMAP_NAME)).thenReturn(nullCm); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123"); projectFactory.getOrCreate(identity); // then ArgumentCaptor configMapCaptor = ArgumentCaptor.forClass(ConfigMap.class); verify(namespaceOperation).create(configMapCaptor.capture()); ConfigMap configmap = configMapCaptor.getValue(); Assert.assertEquals(configmap.getMetadata().getName(), PREFERENCES_CONFIGMAP_NAME); } @Test public void shouldNotCreatePreferencesConfigmapIfExist() throws Exception { // given projectFactory = spy( new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, Set.of(new PreferencesConfigMapConfigurator(cheServerKubernetesClientFactory)), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); OpenShiftProject toReturnProject = mock(OpenShiftProject.class); prepareProject(toReturnProject); doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any()); when(toReturnProject.getName()).thenReturn("namespace123"); NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class); MixedOperation mixedOperation = mock(MixedOperation.class); when(osClient.configMaps()).thenReturn(mixedOperation); when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); Resource cmResource = mock(Resource.class); when(namespaceOperation.withName(PREFERENCES_CONFIGMAP_NAME)).thenReturn(cmResource); when(cmResource.get()).thenReturn(mock(ConfigMap.class)); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123"); projectFactory.getOrCreate(identity); // then verify(namespaceOperation, never()).create(any()); } @Test public void shouldCallStopWorkspaceRoleProvisionWhenIdentityProviderIsDefined() throws Exception { var saConf = spy( new OpenShiftWorkspaceServiceAccountConfigurator( "serviceAccount", "", cheServerOpenshiftClientFactory)); projectFactory = spy( new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, Set.of(saConf), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); OpenShiftProject toReturnProject = mock(OpenShiftProject.class); when(toReturnProject.getName()).thenReturn("workspace123"); prepareProject(toReturnProject); doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any()); OpenShiftWorkspaceServiceAccount serviceAccount = mock(OpenShiftWorkspaceServiceAccount.class); doReturn(serviceAccount).when(saConf).createServiceAccount("workspace123", "workspace123"); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123"); projectFactory.getOrCreate(identity); // then verify(serviceAccount).prepare(); } @Ignore @Test public void testEvalNamespaceNameWhenPreparedNamespacesFound() throws InfrastructureException { List projects = Arrays.asList( createProject( "ns1", "project1", "desc1", "Active", Map.of(NAMESPACE_ANNOTATION_NAME, "jondoe")), createProject( "ns3", "project3", "desc3", "Active", Map.of(NAMESPACE_ANNOTATION_NAME, "some_other_user")), createProject( "ns2", "project2", "desc2", "Active", Map.of(NAMESPACE_ANNOTATION_NAME, "jondoe"))); doReturn(projects).when(projectList).getItems(); projectFactory = new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); String namespace = projectFactory.evaluateNamespaceName( new NamespaceResolutionContext("workspace123", "user123", "jondoe")); assertEquals(namespace, "ns1"); } @Test public void testUsernamePlaceholderInLabelsIsNotEvaluated() throws InfrastructureException { List projects = singletonList( createProject( "ns1", "project1", "desc1", "Active", Map.of(NAMESPACE_ANNOTATION_NAME, "jondoe"))); doReturn(projects).when(projectList).getItems(); projectFactory = new OpenShiftProjectFactory( "-che", true, true, true, "try_placeholder_here=", NAMESPACE_ANNOTATIONS, true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "123", null, false)); projectFactory.list(); verify(projectOperation).withLabels(Map.of("try_placeholder_here", "")); } @Test public void testUsernamePlaceholderInAnnotationsIsEvaluated() throws InfrastructureException { // given projectFactory = spy( new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, "try_placeholder_here=", true, emptySet(), openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "123", null, false)); OpenShiftProject toReturnProject = mock(OpenShiftProject.class); prepareProject(toReturnProject); doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any()); // when RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "old-che"); OpenShiftProject project = projectFactory.getOrCreate(identity); // then assertEquals(toReturnProject, project); verify(toReturnProject) .prepare(eq(true), eq(true), any(), eq(Map.of("try_placeholder_here", "jondoe"))); } @Test public void testAllConfiguratorsAreCalledWhenCreatingProject() throws InfrastructureException { // given String projectName = "testprojectname"; NamespaceConfigurator configurator1 = Mockito.mock(NamespaceConfigurator.class); NamespaceConfigurator configurator2 = Mockito.mock(NamespaceConfigurator.class); Set namespaceConfigurators = Set.of(configurator1, configurator2); projectFactory = spy( new OpenShiftProjectFactory( "-che", true, true, true, NAMESPACE_LABELS, "try_placeholder_here=", true, namespaceConfigurators, openShiftClientFactory, cheServerKubernetesClientFactory, cheServerOpenshiftClientFactory, preferenceManager, pool, authorizationChecker, permissionsCleaner)); EnvironmentContext.getCurrent() .setSubject(new SubjectImpl("jondoe", Collections.emptyList(), "123", null, false)); OpenShiftProject toReturnProject = mock(OpenShiftProject.class); when(toReturnProject.getName()).thenReturn(projectName); RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "old-che"); doReturn(toReturnProject).when(projectFactory).get(identity); // when OpenShiftProject project = projectFactory.getOrCreate(identity); // then NamespaceResolutionContext resolutionCtx = new NamespaceResolutionContext("workspace123", "123", "jondoe"); verify(configurator1).configure(resolutionCtx, projectName); verify(configurator2).configure(resolutionCtx, projectName); assertEquals(project, toReturnProject); } private void prepareNamespaceToBeFoundByName(String name, Project project) throws Exception { @SuppressWarnings("unchecked") Resource getProjectByNameOperation = mock(Resource.class); when(projectOperation.withName(name)).thenReturn(getProjectByNameOperation); when(getProjectByNameOperation.get()).thenReturn(project); } private void throwOnTryToGetProjectByName(String name, KubernetesClientException e) throws Exception { @SuppressWarnings("unchecked") Resource getProjectByNameOperation = mock(Resource.class); when(projectOperation.withName(name)).thenReturn(getProjectByNameOperation); when(getProjectByNameOperation.get()).thenThrow(e); } private void prepareListedProjects(List projects) throws Exception { @SuppressWarnings("unchecked") ProjectList projectList = mock(ProjectList.class); when(projectOperation.list()).thenReturn(projectList); when(projectList.getItems()).thenReturn(projects); } private void prepareProject(OpenShiftProject project) throws InfrastructureException { KubernetesSecrets secrets = mock(KubernetesSecrets.class); lenient().when(project.secrets()).thenReturn(secrets); KubernetesConfigsMaps configsMaps = mock(KubernetesConfigsMaps.class); Secret secretMock = mock(Secret.class); ObjectMeta objectMeta = mock(ObjectMeta.class); lenient().when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME); lenient().when(secretMock.getMetadata()).thenReturn(objectMeta); lenient().when(secrets.get()).thenReturn(Collections.singletonList(secretMock)); } private void throwOnTryToGetProjectsList(Throwable e) throws Exception { when(projectListResource.list()).thenThrow(e); } private Project createProject(String name, String displayName, String description, String phase) { return createProject(name, displayName, description, phase, emptyMap()); } private Project createProject( String name, String displayName, String description, String phase, Map extraAnnotations) { Map annotations = new HashMap<>(); if (displayName != null) { annotations.put(PROJECT_DISPLAY_NAME_ANNOTATION, displayName); } if (description != null) { annotations.put(PROJECT_DESCRIPTION_ANNOTATION, description); } if (extraAnnotations != null) { annotations.putAll(extraAnnotations); } return new ProjectBuilder() .withNewMetadata() .withName(name) .withAnnotations(annotations) .endMetadata() .withNewStatus() .withPhase(phase) .endStatus() .build(); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.project; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.NamespaceBuilder; import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.Status; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.openshift.api.model.Project; import io.fabric8.openshift.api.model.ProjectBuilder; import io.fabric8.openshift.api.model.ProjectRequest; import io.fabric8.openshift.api.model.ProjectRequestFluent.MetadataNested; import io.fabric8.openshift.api.model.RoleBinding; import io.fabric8.openshift.api.model.RoleBindingList; import io.fabric8.openshift.client.OpenShiftClient; import io.fabric8.openshift.client.dsl.ProjectOperation; import io.fabric8.openshift.client.dsl.ProjectRequestOperation; import java.util.Map; import java.util.concurrent.Executor; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesDeployments; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesIngresses; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesPersistentVolumeClaims; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesServices; import org.eclipse.che.workspace.infrastructure.openshift.CheServerOpenshiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link OpenShiftProject} * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class OpenShiftProjectTest { public static final String PROJECT_NAME = "testProject"; public static final String WORKSPACE_ID = "workspace123"; @Mock private KubernetesDeployments deployments; @Mock private KubernetesServices services; @Mock private OpenShiftRoutes routes; @Mock private KubernetesPersistentVolumeClaims pvcs; @Mock private KubernetesIngresses ingresses; @Mock private KubernetesSecrets secrets; @Mock private KubernetesConfigsMaps configsMaps; @Mock private OpenShiftClientFactory clientFactory; @Mock private CheServerKubernetesClientFactory cheClientFactory; @Mock private CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory; @Mock private Executor executor; @Mock private OpenShiftClient openShiftClient; @Mock private OpenShiftClient openShiftCheServerClient; @Mock private KubernetesClient kubernetesClient; @Mock private Resource serviceAccountResource; @Mock private ProjectRequestOperation projectRequestOperation; @Mock private MetadataNested metadataNested; @Mock private MixedOperation> mixedRoleBindingOperation; @Mock private NonNamespaceOperation> nonNamespaceRoleBindingOperation; private OpenShiftProject openShiftProject; @BeforeMethod public void setUp() throws Exception { lenient().when(clientFactory.create(anyString())).thenReturn(kubernetesClient); lenient().when(clientFactory.createOC()).thenReturn(openShiftClient); lenient().when(clientFactory.createOC(anyString())).thenReturn(openShiftClient); lenient().when(cheServerOpenshiftClientFactory.createOC()).thenReturn(openShiftCheServerClient); lenient() .when(cheServerOpenshiftClientFactory.createOC(anyString())) .thenReturn(openShiftCheServerClient); lenient().when(openShiftClient.adapt(OpenShiftClient.class)).thenReturn(openShiftClient); final MixedOperation mixedOperation = mock(MixedOperation.class); final NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class); lenient().doReturn(mixedOperation).when(kubernetesClient).serviceAccounts(); lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); lenient().when(namespaceOperation.withName(anyString())).thenReturn(serviceAccountResource); lenient().when(serviceAccountResource.get()).thenReturn(mock(ServiceAccount.class)); lenient().doReturn(projectRequestOperation).when(openShiftClient).projectrequests(); lenient().doReturn(metadataNested).when(metadataNested).withName(anyString()); openShiftProject = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, WORKSPACE_ID, PROJECT_NAME, deployments, services, routes, pvcs, ingresses, secrets, configsMaps); } @Test public void testOpenShiftProjectPreparingWhenProjectExists() throws Exception { // given prepareNamespaceGet(PROJECT_NAME); prepareProject(PROJECT_NAME); OpenShiftProject project = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); // when project.prepare(true, true, Map.of(), Map.of()); // then verify(metadataNested, never()).withName(PROJECT_NAME); } @Test public void testOpenShiftProjectPreparingWhenProjectDoesNotExist() throws Exception { // given prepareNamespaceGet(PROJECT_NAME); Resource resource = prepareProjectResource(PROJECT_NAME); doThrow(new KubernetesClientException("error", 403, null)).when(resource).get(); OpenShiftProject project = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); // when openShiftProject.prepare(true, false, Map.of(), Map.of()); // then ArgumentCaptor captor = ArgumentCaptor.forClass(ProjectRequest.class); verify(projectRequestOperation).create(captor.capture()); Assert.assertEquals(captor.getValue().getMetadata().getName(), PROJECT_NAME); } @Test public void testOpenShiftProjectPreparingWhenProjectDoesNotExistWithCheServerSA() throws Exception { // given prepareNamespaceGet(PROJECT_NAME); Resource resource = prepareProjectResource(PROJECT_NAME); doThrow(new KubernetesClientException("error", 403, null)).when(resource).get(); final MixedOperation mixedOperation = mock(MixedOperation.class); final NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class); doReturn(mixedOperation).when(openShiftCheServerClient).serviceAccounts(); when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); when(namespaceOperation.withName(anyString())).thenReturn(serviceAccountResource); when(serviceAccountResource.get()).thenReturn(mock(ServiceAccount.class)); doReturn(projectRequestOperation).when(openShiftCheServerClient).projectrequests(); // when openShiftProject.prepare(true, true, Map.of(), Map.of()); // then ArgumentCaptor captor = ArgumentCaptor.forClass(ProjectRequest.class); verify(projectRequestOperation).create(captor.capture()); Assert.assertEquals(captor.getValue().getMetadata().getName(), PROJECT_NAME); verifyNoMoreInteractions(openShiftCheServerClient); verifyNoMoreInteractions(kubernetesClient); } @Test(expectedExceptions = InfrastructureException.class) public void throwsExceptionIfNamespaceDoesntExistAndNotAllowedToCreateIt() throws Exception { // given Resource resource = prepareProjectResource(PROJECT_NAME); doThrow(new KubernetesClientException("error", 403, null)).when(resource).get(); OpenShiftProject project = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); // when project.prepare(false, true, Map.of(), Map.of()); // then // exception is thrown } @Test public void testOpenShiftProjectCleaningUp() throws Exception { // when openShiftProject.cleanUp(); verify(routes).delete(); verify(services).delete(); verify(deployments).delete(); verify(secrets).delete(); verify(configsMaps).delete(); } @Test public void testOpenShiftProjectCleaningUpIfExceptionsOccurs() throws Exception { doThrow(new InfrastructureException("err1.")).when(services).delete(); doThrow(new InfrastructureException("err2.")).when(deployments).delete(); InfrastructureException error = null; // when try { openShiftProject.cleanUp(); } catch (InfrastructureException e) { error = e; } // then assertNotNull(error); String message = error.getMessage(); assertEquals(message, "Error(s) occurs while cleaning up the namespace. err1. err2."); verify(routes).delete(); } @Test public void testDeletesExistingProject() throws Exception { // given OpenShiftProject project = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); Resource resource = prepareProjectResource(PROJECT_NAME); // when project.delete(); // then verify(resource).delete(); } @Test public void testDoesntFailIfDeletedProjectDoesntExist() throws Exception { // given OpenShiftProject project = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); Resource resource = prepareProjectResource(PROJECT_NAME); when(resource.delete()).thenThrow(new KubernetesClientException("err", 404, null)); // when project.delete(); // then verify(resource).delete(); // and no exception is thrown } @Test public void testDoesntFailIfDeletedProjectIsBeingDeleted() throws Exception { // given OpenShiftProject project = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); Resource resource = prepareProjectResource(PROJECT_NAME); when(resource.delete()).thenThrow(new KubernetesClientException("err", 409, null)); // when project.delete(); // then verify(resource).delete(); // and no exception is thrown } @Test public void testLabelNamespace() throws InfrastructureException { // given prepareProject(PROJECT_NAME); prepareNamespaceGet(PROJECT_NAME); OpenShiftProject openShiftProject = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); KubernetesClient cheKubeClient = mock(KubernetesClient.class); doReturn(cheKubeClient).when(cheClientFactory).create(); NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces(); ArgumentCaptor namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class); doAnswer(a -> a.getArgument(0)) .when(nonNamespaceOperation) .createOrReplace(namespaceArgumentCaptor.capture()); Map labels = Map.of("label.with.this", "yes", "and.this", "of courese"); // when openShiftProject.prepare(true, true, labels, Map.of()); // then Namespace updatedNamespace = namespaceArgumentCaptor.getValue(); assertTrue( updatedNamespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet())); assertEquals(updatedNamespace.getMetadata().getName(), PROJECT_NAME); } @Test public void testDontTryToLabelNamespaceIfAlreadyLabeled() throws InfrastructureException { // given Map labels = Map.of("label.with.this", "yes", "and.this", "of courese"); prepareProject(PROJECT_NAME); Namespace namespace = prepareNamespaceGet(PROJECT_NAME); namespace.getMetadata().setLabels(labels); OpenShiftProject openShiftProject = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); KubernetesClient cheKubeClient = mock(KubernetesClient.class); lenient().doReturn(cheKubeClient).when(cheClientFactory).create(); NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces(); lenient() .doAnswer(a -> a.getArgument(0)) .when(nonNamespaceOperation) .createOrReplace(any(Namespace.class)); // when openShiftProject.prepare(true, true, labels, Map.of()); // then assertTrue(namespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet())); verify(nonNamespaceOperation, never()).createOrReplace(any()); } @Test public void testAnnotateNamespace() throws InfrastructureException { // given prepareProject(PROJECT_NAME); prepareNamespaceGet(PROJECT_NAME); OpenShiftProject openShiftProject = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); KubernetesClient cheKubeClient = mock(KubernetesClient.class); doReturn(cheKubeClient).when(cheClientFactory).create(); NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces(); ArgumentCaptor namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class); doAnswer(a -> a.getArgument(0)) .when(nonNamespaceOperation) .createOrReplace(namespaceArgumentCaptor.capture()); Map annotations = Map.of("annotation.with.this", "yes", "and.this", "of courese"); // when openShiftProject.prepare(true, true, Map.of(), annotations); // then Namespace updatedNamespace = namespaceArgumentCaptor.getValue(); assertTrue( updatedNamespace .getMetadata() .getAnnotations() .entrySet() .containsAll(annotations.entrySet())); assertEquals(updatedNamespace.getMetadata().getName(), PROJECT_NAME); } @Test public void testDontTryToAnnotateNamespaceIfAlreadyAnnotated() throws InfrastructureException { // given Map annotations = Map.of("annotation.with.this", "yes", "and.this", "of courese"); prepareProject(PROJECT_NAME); Namespace namespace = prepareNamespaceGet(PROJECT_NAME); namespace.getMetadata().setAnnotations(annotations); OpenShiftProject openShiftProject = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); // when openShiftProject.prepare(true, true, Map.of(), annotations); // then assertTrue( namespace.getMetadata().getAnnotations().entrySet().containsAll(annotations.entrySet())); } @Test public void testDoNotFailWhenNoPermissionsToUpdateNamespace() throws InfrastructureException { // given Map labels = Map.of("label.with.this", "yes", "and.this", "of courese"); prepareProject(PROJECT_NAME); prepareNamespaceGet(PROJECT_NAME); OpenShiftProject openShiftProject = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); KubernetesClient cheKubeClient = mock(KubernetesClient.class); lenient().doReturn(cheKubeClient).when(cheClientFactory).create(); NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces(); ArgumentCaptor namespaceArgumentCaptor = ArgumentCaptor.forClass(Namespace.class); lenient() .doThrow(new KubernetesClientException("No permissions.", 403, new Status())) .when(nonNamespaceOperation) .createOrReplace(namespaceArgumentCaptor.capture()); // when openShiftProject.prepare(true, true, labels, Map.of()); // then Namespace updatedNamespace = namespaceArgumentCaptor.getValue(); assertTrue( updatedNamespace.getMetadata().getLabels().entrySet().containsAll(labels.entrySet())); assertEquals(updatedNamespace.getMetadata().getName(), PROJECT_NAME); } @Test(expectedExceptions = InfrastructureException.class) public void testFailWhenFailToUpdateNamespace() throws InfrastructureException { // given Map labels = Map.of("label.with.this", "yes", "and.this", "of courese"); prepareProject(PROJECT_NAME); Namespace namespace = prepareNamespaceGet(PROJECT_NAME); OpenShiftProject openShiftProject = new OpenShiftProject( clientFactory, cheClientFactory, cheServerOpenshiftClientFactory, executor, PROJECT_NAME, WORKSPACE_ID); KubernetesClient cheKubeClient = mock(KubernetesClient.class); lenient().doReturn(cheKubeClient).when(cheClientFactory).create(); NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); lenient().doReturn(nonNamespaceOperation).when(cheKubeClient).namespaces(); lenient() .doThrow(new KubernetesClientException("Some other error", 500, new Status())) .when(nonNamespaceOperation) .createOrReplace(any(Namespace.class)); // when openShiftProject.prepare(true, true, labels, Map.of()); // then verify(nonNamespaceOperation).createOrReplace(namespace); } private Resource prepareProjectResource(String projectName) { Resource projectResource = mock(Resource.class); ProjectOperation projectOperation = mock(ProjectOperation.class); doReturn(projectResource).when(projectOperation).withName(projectName); doReturn(projectOperation).when(openShiftCheServerClient).projects(); when(projectResource.get()) .thenReturn( new ProjectBuilder().withNewMetadata().withName(projectName).endMetadata().build()); openShiftCheServerClient.projects().withName(projectName).get(); return projectResource; } private Namespace prepareNamespaceGet(String namespaceName) { Namespace namespace = new NamespaceBuilder().withNewMetadata().withName(namespaceName).endMetadata().build(); NonNamespaceOperation nsOperation = mock(NonNamespaceOperation.class); doReturn(nsOperation).when(openShiftCheServerClient).namespaces(); Resource nsResource = mock(Resource.class); doReturn(nsResource).when(nsOperation).withName(namespaceName); doReturn(namespace).when(nsResource).get(); return namespace; } private Project prepareProject(String projectName) { Project project = new ProjectBuilder().withNewMetadata().withName(projectName).endMetadata().build(); Resource projectResource = prepareProjectResource(projectName); doReturn(project).when(projectResource).get(); return project; } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/configurator/OpenShiftWorkspaceServiceAccountConfiguratorTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.project.configurator; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.openshift.CheServerOpenshiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftWorkspaceServiceAccount; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class OpenShiftWorkspaceServiceAccountConfiguratorTest { private final String SA_NAME = "test-serviceaccout"; private final String CLUSTER_ROLES = "role1, role2"; private final String WS_ID = "ws123"; private final String USER_ID = "user123"; private final String USERNAME = "user-che"; private final String NS_NAME = "namespace-che"; private NamespaceResolutionContext nsContext; @Mock private CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory; private OpenShiftWorkspaceServiceAccountConfigurator saConfigurator; @BeforeMethod public void setUp() { nsContext = new NamespaceResolutionContext(WS_ID, USER_ID, USERNAME); } @Test public void testPreparesServiceAccount() throws InfrastructureException { saConfigurator = spy( new OpenShiftWorkspaceServiceAccountConfigurator( SA_NAME, CLUSTER_ROLES, cheServerOpenshiftClientFactory)); OpenShiftWorkspaceServiceAccount serviceAccount = mock(OpenShiftWorkspaceServiceAccount.class); doReturn(serviceAccount).when(saConfigurator).createServiceAccount(WS_ID, NS_NAME); saConfigurator.configure(nsContext, NS_NAME); verify(serviceAccount).prepare(); } @Test public void testDoNothingWhenServiceAccountNotSet() throws InfrastructureException { saConfigurator = spy( new OpenShiftWorkspaceServiceAccountConfigurator( null, CLUSTER_ROLES, cheServerOpenshiftClientFactory)); saConfigurator.configure(nsContext, NS_NAME); verify(saConfigurator, times(0)).createServiceAccount(any(), any()); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftPreviewUrlCommandProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.provision; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServiceSpec; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RoutePort; import io.fabric8.openshift.api.model.RouteSpec; import io.fabric8.openshift.api.model.RouteTargetReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.PreviewUrlImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesServices; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftProject; import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftRoutes; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class OpenShiftPreviewUrlCommandProvisionerTest { private OpenShiftPreviewUrlCommandProvisioner previewUrlCommandProvisioner; @Mock private OpenShiftEnvironment mockEnvironment; @Mock private OpenShiftProject mockProject; @Mock private KubernetesServices mockServices; @Mock private OpenShiftRoutes mockRoutes; @BeforeMethod public void setUp() { previewUrlCommandProvisioner = new OpenShiftPreviewUrlCommandProvisioner(); } @Test public void shouldDoNothingWhenGetCommandsIsNull() throws InfrastructureException { Mockito.when(mockEnvironment.getCommands()).thenReturn(null); previewUrlCommandProvisioner.provision(mockEnvironment, mockProject); } @Test(expectedExceptions = InternalInfrastructureException.class) public void throwsInfrastructureExceptionWhenK8sNamespaces() throws InfrastructureException { KubernetesNamespace namespace = Mockito.mock(KubernetesNamespace.class); previewUrlCommandProvisioner.provision(mockEnvironment, namespace); } @Test public void shouldDoNothingWhenNoCommandsDefined() throws InfrastructureException { Mockito.when(mockEnvironment.getCommands()).thenReturn(Collections.emptyList()); Mockito.when(mockProject.routes()).thenReturn(mockRoutes); Mockito.when(mockProject.services()).thenReturn(mockServices); previewUrlCommandProvisioner.provision(mockEnvironment, mockProject); } @Test public void shouldDoNothingWhenCommandsWithoutPreviewUrlDefined() throws InfrastructureException { List commands = Arrays.asList(new CommandImpl("a", "a", "a"), new CommandImpl("b", "b", "b")); OpenShiftEnvironment env = OpenShiftEnvironment.builder().setCommands(new ArrayList<>(commands)).build(); Mockito.when(mockProject.routes()).thenReturn(mockRoutes); Mockito.when(mockProject.services()).thenReturn(mockServices); previewUrlCommandProvisioner.provision(env, mockProject); assertTrue(commands.containsAll(env.getCommands())); assertTrue(env.getCommands().containsAll(commands)); assertTrue(env.getWarnings().isEmpty()); } @Test public void shouldDoNothingWhenCantFindServiceForPreviewurl() throws InfrastructureException { List commands = Collections.singletonList( new CommandImpl("a", "a", "a", new PreviewUrlImpl(8080, null), Collections.emptyMap())); OpenShiftEnvironment env = OpenShiftEnvironment.builder().setCommands(new ArrayList<>(commands)).build(); Mockito.when(mockProject.routes()).thenReturn(mockRoutes); Mockito.when(mockProject.services()).thenReturn(mockServices); Mockito.when(mockServices.get()).thenReturn(Collections.emptyList()); previewUrlCommandProvisioner.provision(env, mockProject); assertTrue(commands.containsAll(env.getCommands())); assertTrue(env.getCommands().containsAll(commands)); assertEquals( env.getWarnings().get(0).getCode(), Warnings.NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL); } @Test public void shouldDoNothingWhenCantFindRouteForPreviewUrl() throws InfrastructureException { int port = 8080; List commands = Collections.singletonList( new CommandImpl("a", "a", "a", new PreviewUrlImpl(port, null), Collections.emptyMap())); OpenShiftEnvironment env = OpenShiftEnvironment.builder().setCommands(new ArrayList<>(commands)).build(); Mockito.when(mockProject.services()).thenReturn(mockServices); Service service = new Service(); ServiceSpec spec = new ServiceSpec(); spec.setPorts( Collections.singletonList( new ServicePort(null, "a", null, port, "TCP", new IntOrString(port)))); service.setSpec(spec); Mockito.when(mockServices.get()).thenReturn(Collections.singletonList(service)); Mockito.when(mockProject.routes()).thenReturn(mockRoutes); Mockito.when(mockRoutes.get()).thenReturn(Collections.emptyList()); previewUrlCommandProvisioner.provision(env, mockProject); assertTrue(commands.containsAll(env.getCommands())); assertTrue(env.getCommands().containsAll(commands)); assertEquals( env.getWarnings().get(0).getCode(), Warnings.NOT_ABLE_TO_PROVISION_OBJECTS_FOR_PREVIEW_URL); } @Test public void shouldUpdateCommandWhenServiceAndIngressFound() throws InfrastructureException { int port = 8080; List commands = Collections.singletonList( new CommandImpl("a", "a", "a", new PreviewUrlImpl(port, null), Collections.emptyMap())); OpenShiftEnvironment env = OpenShiftEnvironment.builder().setCommands(new ArrayList<>(commands)).build(); Mockito.when(mockProject.services()).thenReturn(mockServices); Service service = new Service(); ObjectMeta metadata = new ObjectMeta(); metadata.setName("servicename"); service.setMetadata(metadata); ServiceSpec spec = new ServiceSpec(); spec.setPorts( Collections.singletonList( new ServicePort(null, "8080", null, port, "TCP", new IntOrString(port)))); service.setSpec(spec); Mockito.when(mockServices.get()).thenReturn(Collections.singletonList(service)); Route route = new Route(); RouteSpec routeSpec = new RouteSpec(); routeSpec.setPort(new RoutePort(new IntOrString("8080"))); routeSpec.setTo(new RouteTargetReference("a", "servicename", 1)); routeSpec.setHost("testhost"); route.setSpec(routeSpec); Mockito.when(mockProject.routes()).thenReturn(mockRoutes); Mockito.when(mockRoutes.get()).thenReturn(Collections.singletonList(route)); previewUrlCommandProvisioner.provision(env, mockProject); assertTrue(env.getCommands().get(0).getAttributes().containsKey("previewUrl")); assertEquals(env.getCommands().get(0).getAttributes().get("previewUrl"), "testhost"); assertTrue(env.getWarnings().isEmpty()); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftUniqueNamesProvisionerTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.provision; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotEquals; import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RouteBuilder; import java.util.HashMap; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link OpenShiftUniqueNamesProvisioner}. * * @author Anton Korneta */ @Listeners(MockitoTestNGListener.class) public class OpenShiftUniqueNamesProvisionerTest { private static final String WORKSPACE_ID = "workspace37"; private static final String POD_NAME = "testPod"; private static final String ROUTE_NAME = "testRoute"; @Mock private OpenShiftEnvironment osEnv; @Mock private RuntimeIdentity runtimeIdentity; private OpenShiftUniqueNamesProvisioner uniqueNamesProvisioner; @BeforeMethod public void setup() { uniqueNamesProvisioner = new OpenShiftUniqueNamesProvisioner(); } @Test public void provideUniquePodsNames() throws Exception { when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); Pod pod = newPod(); PodData podData = new PodData(pod.getSpec(), pod.getMetadata()); doReturn(ImmutableMap.of(POD_NAME, podData)).when(osEnv).getPodsData(); uniqueNamesProvisioner.provision(osEnv, runtimeIdentity); ObjectMeta podMetadata = pod.getMetadata(); assertNotEquals(podMetadata.getName(), POD_NAME); assertEquals(podMetadata.getLabels().get(CHE_ORIGINAL_NAME_LABEL), POD_NAME); } @Test public void provideUniqueRoutesNames() throws Exception { final HashMap routes = new HashMap<>(); Route route = newRoute(); routes.put(POD_NAME, route); doReturn(routes).when(osEnv).getRoutes(); uniqueNamesProvisioner.provision(osEnv, runtimeIdentity); final ObjectMeta routeData = route.getMetadata(); assertNotEquals(routeData.getName(), ROUTE_NAME); assertEquals(routeData.getLabels().get(CHE_ORIGINAL_NAME_LABEL), ROUTE_NAME); } private static Pod newPod() { return new PodBuilder() .withMetadata(new ObjectMetaBuilder().withName(POD_NAME).build()) .build(); } private static Route newRoute() { return new RouteBuilder() .withMetadata(new ObjectMetaBuilder().withName(ROUTE_NAME).build()) .build(); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/RouteTlsProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.provision; import static java.util.Collections.emptyMap; import static org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner.TERMINATION_EDGE; import static org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner.TERMINATION_POLICY_REDIRECT; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableMap; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RouteBuilder; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link RouteTlsProvisioner}. * * @author Ilya Buziuk * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class RouteTlsProvisionerTest { @Mock private OpenShiftEnvironment osEnv; @Mock private RuntimeIdentity runtimeIdentity; @Test public void doNothingWhenTlsDisabled() throws Exception { // given RouteTlsProvisioner tlsProvisioner = new RouteTlsProvisioner(false); // when tlsProvisioner.provision(osEnv, runtimeIdentity); // then verify(osEnv, never()).getRoutes(); } @Test public void provisionTlsForRoutes() throws Exception { // given RouteTlsProvisioner tlsProvisioner = new RouteTlsProvisioner(true); ServerConfigImpl httpServer = new ServerConfigImpl("8080/tpc", "http", "/api", emptyMap()); ServerConfigImpl wsServer = new ServerConfigImpl("8080/tpc", "ws", "/ws", emptyMap()); final Map routes = new HashMap<>(); Route route = createRoute("route", ImmutableMap.of("http-server", httpServer, "ws-server", wsServer)); routes.put("route", route); when(osEnv.getRoutes()).thenReturn(routes); // when tlsProvisioner.provision(osEnv, runtimeIdentity); // then assertEquals(route.getSpec().getTls().getTermination(), TERMINATION_EDGE); assertEquals( route.getSpec().getTls().getInsecureEdgeTerminationPolicy(), TERMINATION_POLICY_REDIRECT); Map servers = Annotations.newDeserializer(route.getMetadata().getAnnotations()).servers(); assertEquals(servers.get("http-server").getProtocol(), "https"); assertEquals(servers.get("ws-server").getProtocol(), "wss"); } @Test public void shouldNotThrowNPE() throws Exception { // given RouteTlsProvisioner tlsProvisioner = new RouteTlsProvisioner(true); final Map routes = new HashMap<>(); Route route = new RouteBuilder() .withNewMetadata() .withName("name") .endMetadata() .withNewSpec() .endSpec() .build(); routes.put("route", route); when(osEnv.getRoutes()).thenReturn(routes); // when tlsProvisioner.provision(osEnv, runtimeIdentity); } private Route createRoute(String name, Map servers) { return new RouteBuilder() .withNewMetadata() .withName(name) .withAnnotations(Annotations.newSerializer().servers(servers).annotations()) .endMetadata() .withNewSpec() .endSpec() .build(); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/server/OpenShiftExternalServerExposerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.server; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.openshift.api.model.Route; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations.Deserializer; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.testng.annotations.Test; /** * Tests {@link RouteServerExposer}. * * @author Sergii Leshchenko */ public class OpenShiftExternalServerExposerTest { private static final String LABELS = "foo=bar"; private RouteServerExposer osExternalServerExposer = new RouteServerExposer(LABELS, null); @Test public void shouldAddRouteToEnvForExposingSpecifiedServer() { // given OpenShiftEnvironment osEnv = OpenShiftEnvironment.builder().build(); Map servers = new HashMap<>(); servers.put("server", new ServerConfigImpl()); // when osExternalServerExposer.expose( osEnv, "machine123", "service123", null, new ServicePort(null, "servicePort", null, null, "TCP", null), servers); // then assertEquals(1, osEnv.getRoutes().size()); Route route = osEnv.getRoutes().values().iterator().next(); assertNotNull(route); assertEquals(route.getSpec().getTo().getName(), "service123"); assertEquals(route.getSpec().getPort().getTargetPort().getStrVal(), "servicePort"); Deserializer annotations = Annotations.newDeserializer(route.getMetadata().getAnnotations()); assertEquals(annotations.machineName(), "machine123"); assertEquals(annotations.servers(), servers); assertEquals(route.getMetadata().getLabels().get("foo"), "bar"); assertNull(route.getSpec().getHost()); } @Test public void shouldAddRouteToEnvForExposingSpecifiedServerWithSpecificHost() { // given RouteServerExposer osExternalServerExposer = new RouteServerExposer(LABELS, "open.che.org"); OpenShiftEnvironment osEnv = OpenShiftEnvironment.builder().build(); Map servers = new HashMap<>(); servers.put("server", new ServerConfigImpl()); // when osExternalServerExposer.expose( osEnv, "machine123", "service123", null, new ServicePort(null, "servicePort", null, null, "TCP", null), servers); // then assertEquals(1, osEnv.getRoutes().size()); Route route = osEnv.getRoutes().values().iterator().next(); assertNotNull(route); assertEquals(route.getSpec().getTo().getName(), "service123"); assertEquals(route.getSpec().getPort().getTargetPort().getStrVal(), "servicePort"); assertTrue(route.getSpec().getHost().endsWith(".open.che.org")); assertTrue(route.getSpec().getHost().startsWith("route")); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/server/OpenShiftPreviewUrlExposerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.server; import static java.util.Collections.singletonList; import static org.testng.Assert.*; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServiceSpec; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RoutePort; import io.fabric8.openshift.api.model.RouteSpec; import io.fabric8.openshift.api.model.RouteTargetReference; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.PreviewUrlImpl; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class OpenShiftPreviewUrlExposerTest { private OpenShiftPreviewUrlExposer previewUrlEndpointsProvisioner; @BeforeMethod public void setUp() { RouteServerExposer externalServerExposer = new RouteServerExposer("a=b", null); previewUrlEndpointsProvisioner = new OpenShiftPreviewUrlExposer(externalServerExposer); } @Test public void shouldDoNothingWhenNoCommandsDefined() throws InternalInfrastructureException { OpenShiftEnvironment env = OpenShiftEnvironment.builder().build(); previewUrlEndpointsProvisioner.expose(env); assertTrue(env.getCommands().isEmpty()); assertTrue(env.getServices().isEmpty()); assertTrue(env.getRoutes().isEmpty()); } @Test public void shouldDoNothingWhenNoCommandWithPreviewUrlDefined() throws InternalInfrastructureException { CommandImpl command = new CommandImpl("a", "a", "a"); OpenShiftEnvironment env = OpenShiftEnvironment.builder().setCommands(singletonList(new CommandImpl(command))).build(); previewUrlEndpointsProvisioner.expose(env); assertEquals(env.getCommands().get(0), command); assertTrue(env.getServices().isEmpty()); assertTrue(env.getRoutes().isEmpty()); } @Test public void shouldNotProvisionWhenServiceAndRouteFound() throws InternalInfrastructureException { final int PORT = 8080; final String SERVER_PORT_NAME = "server-" + PORT; CommandImpl command = new CommandImpl("a", "a", "a", new PreviewUrlImpl(PORT, null), Collections.emptyMap()); Service service = new Service(); ObjectMeta serviceMeta = new ObjectMeta(); serviceMeta.setName("servicename"); service.setMetadata(serviceMeta); ServiceSpec serviceSpec = new ServiceSpec(); serviceSpec.setPorts( singletonList( new ServicePort(null, SERVER_PORT_NAME, null, PORT, "TCP", new IntOrString(PORT)))); service.setSpec(serviceSpec); Route route = new Route(); RouteSpec routeSpec = new RouteSpec(); routeSpec.setPort(new RoutePort(new IntOrString(SERVER_PORT_NAME))); routeSpec.setTo(new RouteTargetReference("routekind", "servicename", 1)); route.setSpec(routeSpec); Map services = new HashMap<>(); services.put("servicename", service); Map routes = new HashMap<>(); routes.put("routename", route); OpenShiftEnvironment env = OpenShiftEnvironment.builder() .setCommands(singletonList(new CommandImpl(command))) .setServices(services) .setRoutes(routes) .build(); assertEquals(env.getRoutes().size(), 1); previewUrlEndpointsProvisioner.expose(env); assertEquals(env.getRoutes().size(), 1); } @Test public void shouldProvisionRouteWhenNotFound() throws InternalInfrastructureException { final int PORT = 8080; final String SERVER_PORT_NAME = "server-" + PORT; final String SERVICE_NAME = "servicename"; CommandImpl command = new CommandImpl("a", "a", "a", new PreviewUrlImpl(PORT, null), Collections.emptyMap()); Service service = new Service(); ObjectMeta serviceMeta = new ObjectMeta(); serviceMeta.setName(SERVICE_NAME); service.setMetadata(serviceMeta); ServiceSpec serviceSpec = new ServiceSpec(); serviceSpec.setPorts( singletonList( new ServicePort(null, SERVER_PORT_NAME, null, PORT, "TCP", new IntOrString(PORT)))); service.setSpec(serviceSpec); Map services = new HashMap<>(); services.put(SERVICE_NAME, service); OpenShiftEnvironment env = OpenShiftEnvironment.builder() .setCommands(singletonList(new CommandImpl(command))) .setServices(services) .setRoutes(new HashMap<>()) .build(); previewUrlEndpointsProvisioner.expose(env); assertEquals(env.getRoutes().size(), 1); Route provisionedRoute = env.getRoutes().values().iterator().next(); assertEquals(provisionedRoute.getSpec().getTo().getName(), SERVICE_NAME); assertEquals( provisionedRoute.getSpec().getPort().getTargetPort().getStrVal(), SERVER_PORT_NAME); } @Test public void shouldProvisionServiceAndRouteWhenNotFound() throws InternalInfrastructureException { final int PORT = 8080; final String SERVER_PORT_NAME = "server-" + PORT; CommandImpl command = new CommandImpl("a", "a", "a", new PreviewUrlImpl(PORT, null), Collections.emptyMap()); OpenShiftEnvironment env = OpenShiftEnvironment.builder() .setCommands(singletonList(new CommandImpl(command))) .setRoutes(new HashMap<>()) .setServices(new HashMap<>()) .build(); previewUrlEndpointsProvisioner.expose(env); assertEquals(env.getRoutes().size(), 1); assertEquals(env.getServices().size(), 1); Service provisionedService = env.getServices().values().iterator().next(); ServicePort provisionedServicePort = provisionedService.getSpec().getPorts().get(0); assertEquals(provisionedServicePort.getName(), SERVER_PORT_NAME); assertEquals(provisionedServicePort.getPort().intValue(), PORT); Route provisionedRoute = env.getRoutes().values().iterator().next(); assertEquals( provisionedRoute.getSpec().getTo().getName(), provisionedService.getMetadata().getName()); assertEquals( provisionedRoute.getSpec().getPort().getTargetPort().getStrVal(), SERVER_PORT_NAME); } } ================================================ FILE: infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/util/RoutesTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.workspace.infrastructure.openshift.util; import static org.testng.Assert.*; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServiceSpec; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RoutePort; import io.fabric8.openshift.api.model.RouteSpec; import io.fabric8.openshift.api.model.RouteTargetReference; import java.util.Collections; import java.util.Optional; import org.testng.annotations.Test; public class RoutesTest { @Test public void shouldFindRouteWhenPortDefinedByString() { int portInt = 8080; String portString = "8080"; Service service = createService(portString, portInt); Route route = createRoute(new IntOrString(portString)); Optional foundRoute = Routes.findRouteForServicePort(Collections.singletonList(route), service, portInt); assertTrue(foundRoute.isPresent()); assertEquals(foundRoute.get().getSpec().getHost(), "testhost"); } @Test public void shouldFindRouteWhenPortDefinedByInt() { int portInt = 8080; String portString = "8080"; Service service = createService(portString, portInt); Route route = createRoute(new IntOrString(portInt)); Optional foundRoute = Routes.findRouteForServicePort(Collections.singletonList(route), service, portInt); assertTrue(foundRoute.isPresent()); assertEquals(foundRoute.get().getSpec().getHost(), "testhost"); } @Test public void shouldReturnEmptyWhenNotFoundByInt() { int portInt = 8080; String portString = "8080"; Service service = createService(portString, portInt); Route route = createRoute(new IntOrString(666)); Optional foundRoute = Routes.findRouteForServicePort(Collections.singletonList(route), service, portInt); assertFalse(foundRoute.isPresent()); } @Test public void shouldReturnEmptyWhenNotFoundByString() { int portInt = 8080; String portString = "8080"; Service service = createService(portString, portInt); Route route = createRoute(new IntOrString("666")); Optional foundRoute = Routes.findRouteForServicePort(Collections.singletonList(route), service, portInt); assertFalse(foundRoute.isPresent()); } private Route createRoute(IntOrString port) { Route route = new Route(); RouteSpec routeSpec = new RouteSpec(); routeSpec.setPort(new RoutePort(port)); routeSpec.setTo(new RouteTargetReference("a", "servicename", 1)); routeSpec.setHost("testhost"); route.setSpec(routeSpec); return route; } private Service createService(String portString, int portInt) { Service service = new Service(); ObjectMeta metadata = new ObjectMeta(); metadata.setName("servicename"); service.setMetadata(metadata); ServiceSpec spec = new ServiceSpec(); spec.setPorts( Collections.singletonList(new ServicePort(null, portString, null, portInt, "TCP", null))); service.setSpec(spec); return service; } } ================================================ FILE: infrastructures/openshift/src/test/resources/devfile/petclinic.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: v1 kind: List items: - apiVersion: v1 kind: Pod metadata: name: petclinic labels: app.kubernetes.io/name: petclinic app.kubernetes.io/component: webapp app.kubernetes.io/part-of: petclinic spec: initContainers: - command: - "echo foo:bar" image: "openjdk:11-jre-openjdk9" name: "init" containers: - name: server image: mariolet/petclinic ports: - containerPort: 8080 protocol: TCP resources: limits: memory: 512Mi - apiVersion: v1 kind: Pod metadata: name: mysql labels: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic spec: containers: - name: mysql image: centos/mysql-57-centos7 env: - name: MYSQL_USER value: petclinic - name: MYSQL_PASSWORD value: petclinic - name: MYSQL_ROOT_PASSWORD value: petclinic - name: MYSQL_DATABASE value: petclinic ports: - containerPort: 3306 protocol: TCP resources: limits: memory: 512Mi - apiVersion: v1 kind: Pod metadata: name: withoutLabels spec: containers: - name: server imagePullPolicy: Always image: test/petclinic ports: - containerPort: 8080 protocol: TCP resources: limits: memory: 512Mi volumeMounts: - name: foo_volume mountPath: /foo/bar - kind: Service apiVersion: v1 metadata: name: mysql labels: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic spec: ports: - name: mysql port: 3306 targetPort: 3360 selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic - kind: Service apiVersion: v1 metadata: name: petclinic labels: app.kubernetes.io/name: petclinic app.kubernetes.io/component: webapp app.kubernetes.io/part-of: petclinic spec: ports: - name: web port: 8080 targetPort: 8080 selector: app: petclinic component: webapp - kind: Route apiVersion: v1 metadata: name: petclinic labels: app.kubernetes.io/name: petclinic app.kubernetes.io/component: webapp app.kubernetes.io/part-of: petclinic spec: to: kind: Service name: petclinic port: targetPort: web ================================================ FILE: infrastructures/openshift/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n target/log/log.log %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: infrastructures/pom.xml ================================================ 4.0.0 che-server org.eclipse.che 7.118.0-SNAPSHOT ../pom.xml org.eclipse.che.infrastructure che-infrastructures-parent pom Infrastructure :: Infrastructures Parent openshift kubernetes infrastructure-metrics infrastructure-distributed infrastructure-permission infrastructure-factory ================================================ FILE: make-release.sh ================================================ #!/bin/bash # Release process automation script. # Used to create branch/tag, update versions in pom.xml # build and push maven artifacts and docker images to Quay.io REGISTRY="quay.io" ORGANIZATION="eclipse" IMAGE="quay.io/eclipse/che-server" BUILD_PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64,linux/s390x" sed_in_place() { SHORT_UNAME=$(uname -s) if [ "$(uname)" == "Darwin" ]; then sed -i '' "$@" elif [ "${SHORT_UNAME:0:5}" == "Linux" ]; then sed -i "$@" fi } loadMvnSettingsGpgKey() { set +x mkdir $HOME/.m2 #prepare settings.xml for maven and sonatype (central maven repository) echo $CHE_MAVEN_SETTINGS | base64 -d > $HOME/.m2/settings.xml #load GPG key for sign artifacts echo $CHE_OSS_SONATYPE_GPG_KEY | base64 -d > $HOME/.m2/gpg.key #load SSH key for release process echo ${#CHE_OSS_SONATYPE_GPG_KEY} mkdir $HOME/.ssh/ echo $CHE_GITHUB_SSH_KEY | base64 -d > $HOME/.ssh/id_rsa chmod 0400 $HOME/.ssh/id_rsa ssh-keyscan github.com >> ~/.ssh/known_hosts set -x export GPG_TTY=$(tty) gpg --import $HOME/.m2/gpg.key # gpg --import --batch $HOME/.m2/gpg.key gpg --version } evaluateCheVariables() { echo "Che version: ${CHE_VERSION}" # derive branch from version BRANCH=${CHE_VERSION%.*}.x echo "Branch: ${BRANCH}" if [[ ${CHE_VERSION} == *".0" ]]; then BASEBRANCH="main" else BASEBRANCH="${BRANCH}" fi echo "Basebranch: ${BASEBRANCH}" } checkoutProjects() { checkoutProject git@github.com:eclipse-che/che-server } checkoutProject() { PROJECT="${1##*/}" echo "checking out project $PROJECT with ${BRANCH} branch" if [[ ! -d ${PROJECT} ]]; then echo "project not found in ${PROJECT} directory, performing 'git clone'" git clone $1 fi cd $PROJECT git checkout ${BASEBRANCH} set -x set +e if [[ "${BASEBRANCH}" != "${BRANCH}" ]]; then git branch "${BRANCH}" || git checkout "${BRANCH}" && git pull origin "${BRANCH}" git push origin "${BRANCH}" git fetch origin "${BRANCH}:${BRANCH}" git checkout "${BRANCH}" fi set -e set +x cd .. } checkoutTags() { cd che-server git checkout ${CHE_VERSION} cd .. } # check for build errors, since we're using set +e above to NOT fail the build for network errors checkLogForErrors () { tmplog="$1" errors_in_log="$(grep -E "FAILURE \[|BUILD FAILURE|Failed to execute goal" $tmplog || true)" if [[ ${errors_in_log} ]]; then echo "${errors_in_log}" exit 1 fi } # TODO change it to someone else? setupGitconfig() { git config --global user.name "Mykhailo Kuznietsov" git config --global user.email mkuznets@redhat.com # hub CLI configuration git config --global pull.rebase true git config --global push.default matching # replace default GITHUB_TOKEN, that is used by GitHub export GITHUB_TOKEN="${CHE_BOT_GITHUB_TOKEN}" } commitChangeOrCreatePR() { set +e aVERSION="$1" aBRANCH="$2" PR_BRANCH="$3" COMMIT_MSG="chore: Bump to ${aVERSION} in ${aBRANCH}" # commit change into branch git commit -asm "${COMMIT_MSG}" git pull origin "${aBRANCH}" PUSH_TRY="$(git push origin "${aBRANCH}")" # shellcheck disable=SC2181 if [[ $? -gt 0 ]] || [[ $PUSH_TRY == *"protected branch hook declined"* ]]; then # create pull request for main branch, as branch is restricted git branch "${PR_BRANCH}" git checkout "${PR_BRANCH}" git pull origin "${PR_BRANCH}" || true git push origin "${PR_BRANCH}" gh pr create -f -B "${aBRANCH}" -H "${PR_BRANCH}" fi set -e } createTags() { tagAndCommit che-server } tagAndCommit() { cd $1 # this branch isn't meant to be pushed git checkout -b release-${CHE_VERSION} git commit -asm "chore: Release version ${CHE_VERSION}" if [ $(git tag -l "$CHE_VERSION") ]; then echo "tag ${CHE_VERSION} already exists! recreating ..." git tag -d ${CHE_VERSION} git push origin :${CHE_VERSION} git tag "${CHE_VERSION}" else echo "[INFO] creating new tag ${CHE_VERSION}" git tag "${CHE_VERSION}" fi git push --tags echo "[INFO] tag created and pushed for $1" cd .. } prepareRelease() { pushd che-server >/dev/null mvn versions:set -DgenerateBackupPoms=false -DallowSnapshots=false -DnewVersion=${CHE_VERSION} echo "[INFO] Che Server version has been updated to ${CHE_VERSION} " # Replace dependencies in che-server parent sed -i -e "s#.*<\/che.version>#${CHE_VERSION}<\/che.version>#" pom.xml echo "[INFO] Dependencies updated in che-server parent" # TODO pull parent pom version from VERSION file, instead of being hardcoded pushd typescript-dto >/dev/null sed -i -e "s#.*<\/che.version>#${CHE_VERSION}<\/che.version>#" dto-pom.xml echo "[INFO] Dependencies updated in che typescript DTO (che server = ${CHE_VERSION})" popd >/dev/null # run mvn license format, in case some files that have old license headers have been updated mvn license:format popd >/dev/null } releaseCheServer() { set -x tmpmvnlog=/tmp/mvn.log.txt pushd che-server >/dev/null rm -f $tmpmvnlog || true set +e mvn clean install -U -Pcodenvy-release -Dgpg.passphrase=$CHE_OSS_SONATYPE_PASSPHRASE | tee $tmpmvnlog EXIT_CODE=$? set -e # try maven build again if we receive a server error if grep -q -E "502 - Bad Gateway" $tmpmvnlog; then rm -f $tmpmvnlog || true mvn clean install -U -Pcodenvy-release -Dgpg.passphrase=$CHE_OSS_SONATYPE_PASSPHRASE | tee $tmpmvnlog EXIT_CODE=$? fi # check log for errors if build successful; if failed, no need to check (already failed) if [ $EXIT_CODE -eq 0 ]; then checkLogForErrors $tmpmvnlog echo 'Build of che-server: Success!' else echo '[ERROR] 2. Build of che-server: Failed!' exit $EXIT_CODE fi set +x popd >/dev/null } releaseTypescriptDto() { pushd che-server/typescript-dto >/dev/null ./build.sh popd >/dev/null } buildAndPushImages() { echo "Going to build docker images" set -e set -o pipefail TAG=$1 # stop / rm all containers if [[ $(podman ps -aq) != "" ]];then podman rm -f "$(podman ps -aq)" fi # BUILD AND PUSH IMAGES bash "$(pwd)/che-server/build/build.sh" --tag:${TAG} --latest-tag --build-platforms:${BUILD_PLATFORMS} --builder:podman --push-image if [[ $? -ne 0 ]]; then echo "ERROR:" echo "build of che-server image $TAG is failed!" exit 1 fi } bumpVersions() { # infer project version + commit change into ${BASEBRANCH} branch echo "${BASEBRANCH} ${BRANCH}" if [[ "${BASEBRANCH}" != "${BRANCH}" ]]; then # bump the y digit [[ ${BRANCH} =~ ^([0-9]+)\.([0-9]+)\.x ]] && BASE=${BASH_REMATCH[1]}; NEXT=${BASH_REMATCH[2]}; (( NEXT=NEXT+1 )) # for BRANCH=7.10.x, get BASE=7, NEXT=11 NEXTVERSION_Y="${BASE}.${NEXT}.0-SNAPSHOT" bumpVersion ${NEXTVERSION_Y} ${BASEBRANCH} fi # bump the z digit [[ ${CHE_VERSION} =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]] && BASE="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"; NEXT="${BASH_REMATCH[3]}"; (( NEXT=NEXT+1 )) # for VERSION=7.7.1, get BASE=7.7, NEXT=2 NEXTVERSION_Z="${BASE}.${NEXT}-SNAPSHOT" bumpVersion ${NEXTVERSION_Z} ${BRANCH} } bumpVersion() { set -x echo "[info]bumping to version $1 in branch $2" pushd che-server >/dev/null git checkout $2 # compute current version of root pom current_root_pom_version=$(grep "" pom.xml | sed -r -e "s#.+([^<>]+).*#\1#") mvn versions:set -DgenerateBackupPoms=false -DallowSnapshots=true -DnewVersion=$1 sed -i -e "s#.*<\/che.version>#$1<\/che.version>#" pom.xml pushd typescript-dto >/dev/null sed -i -e "s#.*<\/che.version>#${1}<\/che.version>#" dto-pom.xml popd >/dev/null # update integration tests to new root pom version find . -name "pom.xml" -exec sed -i {} -r -e "s@${current_root_pom_version}@$1@g" \; # run mvn license format, in case some files that have old license headers have been updated mvn license:format commitChangeOrCreatePR $1 $2 "pr-${2}-to-${1}" popd >/dev/null set +x } updateImageTagsInCheServer() { pushd che-server >/dev/null git checkout ${BRANCH} plugin_version="latest" sed_in_place -r -e "s#che.factory.default_editor=eclipse/che-theia/.*#che.factory.default_editor=eclipse/che-theia/$plugin_version#g" assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties sed_in_place -r -e "s#che.workspace.devfile.default_editor=eclipse/che-theia/.*#che.workspace.devfile.default_editor=eclipse/che-theia/$plugin_version#g" assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties if [[ $(git diff --stat) != '' ]]; then git commit -asm "chore: Set ${CHE_VERSION} release image tags" git pull origin "${BRANCH}" || true git push origin "${BRANCH}" fi popd >/dev/null } loadMvnSettingsGpgKey set -x setupGitconfig evaluateCheVariables checkoutProjects if [[ "${BUMP_NEXT_VERSION}" = "true" ]]; then bumpVersions updateImageTagsInCheServer # checkout back to branches to make release from checkoutProjects fi if [[ "${REBUILD_FROM_EXISTING_TAGS}" = "true" ]]; then echo "[INFO] Checking out from existing ${CHE_VERSION} tag" checkoutTags else echo "[INFO] Creating a new ${CHE_VERSION} tag" prepareRelease createTags fi releaseCheServer releaseTypescriptDto if [[ "${BUILD_AND_PUSH_IMAGES}" = "true" ]]; then buildAndPushImages ${CHE_VERSION} fi ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/pom.xml ================================================ 4.0.0 che-multiuser-api org.eclipse.che.multiuser 7.118.0-SNAPSHOT che-multiuser-api-authentication-commons jar Che Multiuser :: API :: Authentication Commons com.google.inject guice jakarta.inject jakarta.inject-api jakarta.servlet jakarta.servlet-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-core org.slf4j slf4j-api org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/main/java/org/eclipse/che/multiuser/api/authentication/commons/Constants.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons; /** Auth-related constants. */ public class Constants { /** Name of the subject attribute in the Http session */ public static final String CHE_SUBJECT_ATTRIBUTE = "che_subject"; } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/main/java/org/eclipse/che/multiuser/api/authentication/commons/DestroySessionListener.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons; import static org.eclipse.che.multiuser.api.authentication.commons.Constants.CHE_SUBJECT_ATTRIBUTE; import com.google.inject.Injector; import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpSessionEvent; import jakarta.servlet.http.HttpSessionListener; import java.util.Optional; import org.eclipse.che.commons.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Purges deleted sessions from sessions cache store. */ public class DestroySessionListener implements HttpSessionListener { private static final Logger LOG = LoggerFactory.getLogger(DestroySessionListener.class); @Override public final void sessionCreated(HttpSessionEvent sessionEvent) {} @Override public void sessionDestroyed(HttpSessionEvent sessionEvent) { ServletContext servletContext = sessionEvent.getSession().getServletContext(); Optional sessionStoreOptional = getSessionStoreInstance(servletContext); if (!sessionStoreOptional.isPresent()) { LOG.error( "Unable to remove session from store. Session store is not configured in servlet context."); return; } SessionStore sessionStore = sessionStoreOptional.get(); Subject subject = (Subject) sessionEvent.getSession().getAttribute(CHE_SUBJECT_ATTRIBUTE); if (subject != null) { sessionStore.remove(subject.getUserId()); } } /** Searches session store component in servlet context when with help of guice injector. */ private Optional getSessionStoreInstance(ServletContext servletContext) { String attributeName = SessionStore.class.getName(); SessionStore result = (SessionStore) servletContext.getAttribute(attributeName); if (result == null) { Injector injector = (Injector) servletContext.getAttribute(Injector.class.getName()); if (injector != null) { result = injector.getInstance(SessionStore.class); if (result != null) { servletContext.setAttribute(attributeName, result); } } } return Optional.ofNullable(result); } } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/main/java/org/eclipse/che/multiuser/api/authentication/commons/SessionStore.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons; import jakarta.servlet.http.HttpSession; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import javax.inject.Singleton; /** * Thread safe {@link HttpSession} storage based on {@link ConcurrentHashMap}. Sessions are stored * per user Id, and should be externally aligned with catalina session manager using {@link * jakarta.servlet.http.HttpSessionListener} etc. */ @Singleton public class SessionStore { private final ConcurrentHashMap userIdToSession; public SessionStore() { this.userIdToSession = new ConcurrentHashMap<>(); } /** Fetches stored session if present or creates new using provided function */ public HttpSession getSession( String userId, Function createSessionFunction) { return userIdToSession.computeIfAbsent(userId, createSessionFunction); } /** Fetches stored session if present or returns {@code null} otherwise */ public HttpSession getSession(String userId) { return userIdToSession.get(userId); } /** Removes stored session */ public void remove(String userId) { userIdToSession.remove(userId); } } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/main/java/org/eclipse/che/multiuser/api/authentication/commons/SubjectHttpRequestWrapper.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import java.security.Principal; import org.eclipse.che.commons.subject.Subject; /** * Wraps {@link HttpServletRequest} and overrides {@link HttpServletRequest#getRemoteUser} and * {@link HttpServletRequest#getUserPrincipal} to return an values based on current {@link Subject} * which is calculated during request authentication process. */ public class SubjectHttpRequestWrapper extends HttpServletRequestWrapper { private final Principal principal; public SubjectHttpRequestWrapper(HttpServletRequest request, Subject subject) { super(request); this.subject = subject; this.principal = subject::getUserName; } private final Subject subject; @Override public String getRemoteUser() { return subject.getUserName(); } @Override public Principal getUserPrincipal() { return principal; } } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/main/java/org/eclipse/che/multiuser/api/authentication/commons/filter/MultiUserEnvironmentInitializationFilter.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons.filter; import static org.eclipse.che.multiuser.api.authentication.commons.Constants.CHE_SUBJECT_ATTRIBUTE; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Optional; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.multiuser.api.authentication.commons.SessionStore; import org.eclipse.che.multiuser.api.authentication.commons.SubjectHttpRequestWrapper; import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Performs basic environment initialization actions as follows: * *

      *
    • Extracts token from request *
    • Checks token for validity and fetch user Id (implementation specific) *
    • Fetch cached {@link HttpSession} or requests to create new one *
    • Gets {@link Subject} stored in session or construct new (implementation specific)) *
    • Set subject for current request into {@link EnvironmentContext} *
    * *

    {@link MultiUserEnvironmentInitializationFilter#UNAUTHORIZED_ENDPOINT_PATHS} is list of * unauthenticated paths, that are allowed without token. * * @param the type of intermediary type used for conversion from a string token to a Subject * @author Max Shaposhnyk (mshaposh@redhat.com) */ public abstract class MultiUserEnvironmentInitializationFilter implements Filter { private static final Logger LOG = LoggerFactory.getLogger(MultiUserEnvironmentInitializationFilter.class); private static final List UNAUTHORIZED_ENDPOINT_PATHS = Collections.singletonList("/system/state"); private final SessionStore sessionStore; private final RequestTokenExtractor tokenExtractor; public MultiUserEnvironmentInitializationFilter( SessionStore sessionStore, RequestTokenExtractor tokenExtractor) { this.sessionStore = sessionStore; this.tokenExtractor = tokenExtractor; } /** * Wraps {@link HttpServletRequest} to handle HTTP session creation requests and return cached one * if possible, so the same user will always have single session despite the fact he is using * different kind of tokens or several tokens of the same kind (for example logged in in different * browsers) */ protected class SessionCachedHttpRequest extends HttpServletRequestWrapper { private final String userId; /** * Constructs a request object wrapping the given request. * * @throws IllegalArgumentException if the request is null */ public SessionCachedHttpRequest(ServletRequest request, String userId) { super((HttpServletRequest) request); this.userId = userId; } @Override public HttpSession getSession() { return getOrCreateSession(true); } @Override public HttpSession getSession(boolean create) { return getOrCreateSession(create); } /* Finds cached session or creates new if allowed */ private HttpSession getOrCreateSession(boolean createNew) { HttpSession session = super.getSession(false); if (session != null) { return session; } if (!createNew) { return sessionStore.getSession(userId); } else { return sessionStore.getSession( userId, s -> SessionCachedHttpRequest.super.getSession(true)); } } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Subject sessionSubject; HttpServletRequest httpRequest = (HttpServletRequest) request; final String token = tokenExtractor.getToken(httpRequest); if (token == null) { handleMissingToken(request, response, chain); return; } Optional maybeProcessedToken = processToken(token); if (maybeProcessedToken.isEmpty()) { handleMissingToken(request, response, chain); return; } T processedToken = maybeProcessedToken.get(); String userId = getUserId(processedToken); // retrieve cached session if any or create new httpRequest = new SessionCachedHttpRequest(request, userId); HttpSession session = httpRequest.getSession(true); // retrieve and check / create new subject sessionSubject = (Subject) session.getAttribute(CHE_SUBJECT_ATTRIBUTE); if (sessionSubject == null) { sessionSubject = extractSubject(token, processedToken); session.setAttribute(CHE_SUBJECT_ATTRIBUTE, sessionSubject); } else if (!sessionSubject.getUserId().equals(userId)) { LOG.debug( "Invalidating session with mismatched user IDs: old was '{}', new is '{}'.", sessionSubject.getUserId(), userId); session.invalidate(); HttpSession new_session = httpRequest.getSession(true); sessionSubject = extractSubject(token, processedToken); new_session.setAttribute(CHE_SUBJECT_ATTRIBUTE, sessionSubject); } else if (!sessionSubject.getToken().equals(token)) { sessionSubject = extractSubject(token, processedToken); session.setAttribute(CHE_SUBJECT_ATTRIBUTE, sessionSubject); } // set current subject try { EnvironmentContext.getCurrent().setSubject(sessionSubject); chain.doFilter(new SubjectHttpRequestWrapper(httpRequest, sessionSubject), response); } finally { EnvironmentContext.reset(); } } /** * Processes the token and creates implementation-specific intermediary type using which the * subclasses can extract different kinds of information like user ID or subject. * *

    This may also imply verification of token signature or encryption for * encrypted/signed tokens (like JWT etc). * * @param token the token to process * @return a processed token or null if the token could not be processed for valid reasons (like * expiry or not matching any user). */ protected abstract Optional processToken(String token); /** * Retrieves the id of the user from given authentication token. * * @param processedToken the processed authentication string * @return user id given token belongs to */ protected abstract String getUserId(T processedToken); /** * Calculates user {@link Subject} from given authentication token. * * @param token the original authentication token string * @param processedToken the processed authentication string * @return constructed subject */ protected abstract Subject extractSubject(String token, T processedToken) throws ServletException; /** * Describes behavior when the token is missed. In case if performing authentication by given * implementing filter isn't required, it may be invocation of {@link FilterChain#doFilter} or * throwing of appropriate exception otherwise. * * @param request http request * @param response http response * @param chain filter chain * @throws IOException inherited from {@link FilterChain#doFilter} * @throws ServletException inherited from {@link FilterChain#doFilter} */ protected void handleMissingToken( ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // if request path is in unauthorized endpoints, continue if (request instanceof HttpServletRequest) { HttpServletRequest httpRequest = (HttpServletRequest) request; String path = httpRequest.getServletPath(); if (UNAUTHORIZED_ENDPOINT_PATHS.contains(path)) { LOG.debug("Allowing request to '{}' without authorization header.", path); chain.doFilter(request, response); return; } } LOG.error("Rejecting the request due to missing/expired token in Authorization header."); sendError(response, 401, "Authorization token is missing or expired"); } /** * Sends appropriate error status code and message into response. * * @param res response to send error message * @param errorCode status code to send * @param message sessage to send * @throws IOException inherited from {@link HttpServletResponse#sendError} */ protected void sendError(ServletResponse res, int errorCode, String message) throws IOException { HttpServletResponse response = (HttpServletResponse) res; response.sendError(errorCode, message); } } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/main/java/org/eclipse/che/multiuser/api/authentication/commons/token/ChainedTokenExtractor.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons.token; import jakarta.servlet.http.HttpServletRequest; /** * Try to extract token from request in 3 steps. 1. From query parameter. 2. From header. 3. From * cookie. * * @author Sergii Kabashniuk */ public class ChainedTokenExtractor implements RequestTokenExtractor { private final HeaderRequestTokenExtractor headerRequestTokenExtractor; private final QueryRequestTokenExtractor queryRequestTokenExtractor; public ChainedTokenExtractor() { headerRequestTokenExtractor = new HeaderRequestTokenExtractor(); queryRequestTokenExtractor = new QueryRequestTokenExtractor(); } @Override public String getToken(HttpServletRequest req) { String token; if ((token = queryRequestTokenExtractor.getToken(req)) == null) { token = headerRequestTokenExtractor.getToken(req); } return token; } } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/main/java/org/eclipse/che/multiuser/api/authentication/commons/token/HeaderRequestTokenExtractor.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons.token; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.core.HttpHeaders; /** Extract sso token from request headers. */ public class HeaderRequestTokenExtractor implements RequestTokenExtractor { @Override public String getToken(HttpServletRequest req) { if (req.getHeader(HttpHeaders.AUTHORIZATION) == null) { return null; } if (req.getHeader(HttpHeaders.AUTHORIZATION).toLowerCase().startsWith("bearer")) { String[] parts = req.getHeader(HttpHeaders.AUTHORIZATION).split(" "); if (parts.length != 2) { throw new BadRequestException("Invalid authorization header format."); } return parts[1]; } else { return req.getHeader(HttpHeaders.AUTHORIZATION); } } } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/main/java/org/eclipse/che/multiuser/api/authentication/commons/token/QueryRequestTokenExtractor.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons.token; import jakarta.servlet.http.HttpServletRequest; /** * @author Max Shaposhnik (mshaposh@redhat.com) */ public class QueryRequestTokenExtractor implements RequestTokenExtractor { @Override public String getToken(HttpServletRequest req) { String query = req.getQueryString(); if (query != null) { int start = query.indexOf("&token="); if (start != -1 || query.startsWith("token=")) { int end = query.indexOf('&', start + 7); if (end == -1) { end = query.length(); } if (end != start + 7) { return query.substring(start + 7, end); } } } return null; } } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/main/java/org/eclipse/che/multiuser/api/authentication/commons/token/RequestTokenExtractor.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons.token; import jakarta.servlet.http.HttpServletRequest; /** Allows to extract sso token from request. */ public interface RequestTokenExtractor { /** * Extract token from request. * * @param req - request object. * @return - token if it was found, null otherwise. */ String getToken(HttpServletRequest req); } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/test/java/org/eclipse/che/multiuser/api/authentication/commons/SessionStoreTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons; import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import jakarta.servlet.http.HttpSession; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class SessionStoreTest { private final String userId = "userId"; private SessionStore sessionStore; @BeforeMethod public void setUp() { this.sessionStore = new SessionStore(); } @Test public void shouldCreateSessionOnlyIfAbsent() { HttpSession sessionMock = mock(HttpSession.class); HttpSession result1 = sessionStore.getSession(userId, s -> sessionMock); assertEquals(result1, sessionMock); HttpSession result2 = sessionStore.getSession(userId, s -> null); assertEquals(result2, sessionMock); } @Test public void shouldGetSessionById() { HttpSession sessionMock = mock(HttpSession.class); sessionStore.getSession(userId, s -> sessionMock); HttpSession result = sessionStore.getSession(userId); assertEquals(result, sessionMock); } @Test public void shouldRemoveSessionById() { HttpSession sessionMock = mock(HttpSession.class); sessionStore.getSession(userId, s -> sessionMock); sessionStore.remove(userId); HttpSession result = sessionStore.getSession(userId); assertNull(result); } } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/test/java/org/eclipse/che/multiuser/api/authentication/commons/SubjectHttpRequestWrapperTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import jakarta.servlet.http.HttpServletRequest; import java.security.Principal; import org.eclipse.che.commons.subject.Subject; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(value = MockitoTestNGListener.class) public class SubjectHttpRequestWrapperTest { private final String userName = "userName"; @Mock private Subject subject; @Mock private HttpServletRequest servletRequest; private SubjectHttpRequestWrapper subjectHttpRequestWrapper; @BeforeMethod public void setUp() throws Exception { when(subject.getUserName()).thenReturn(userName); this.subjectHttpRequestWrapper = new SubjectHttpRequestWrapper(servletRequest, subject); } @Test public void shouldReturnRemoteUserFromSubject() { String user = subjectHttpRequestWrapper.getRemoteUser(); verify(subject).getUserName(); verifyNoMoreInteractions(subject); assertEquals(user, userName); } @Test public void shouldConstructPrincipalFromSubject() { Principal principal = subjectHttpRequestWrapper.getUserPrincipal(); assertEquals(principal.getName(), userName); verify(subject).getUserName(); verifyNoMoreInteractions(subject); } } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/test/java/org/eclipse/che/multiuser/api/authentication/commons/filter/MultiUserEnvironmentInitializationFilterTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons.filter; import static org.eclipse.che.multiuser.api.authentication.commons.Constants.CHE_SUBJECT_ATTRIBUTE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import java.io.IOException; import java.util.Collections; import java.util.Optional; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.multiuser.api.authentication.commons.SessionStore; import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(value = MockitoTestNGListener.class) public class MultiUserEnvironmentInitializationFilterTest { @Mock private SessionStore sessionStore; @Mock private RequestTokenExtractor tokenExtractor; @Mock private HttpServletRequest request; @Mock private HttpServletResponse response; @Mock private HttpSession session; @Mock private FilterChain chain; private final String userId = "user-abc-123"; private final String token = "token-abc123"; private final Subject subject = new SubjectImpl("user", Collections.emptyList(), userId, token, false); private MultiUserEnvironmentInitializationFilter filter; @BeforeMethod @SuppressWarnings("unchecked") public void setUp() throws Exception { filter = mock( MultiUserEnvironmentInitializationFilter.class, withSettings() .defaultAnswer(Mockito.CALLS_REAL_METHODS) .useConstructor(sessionStore, tokenExtractor)); lenient().when(filter.getUserId(any())).thenReturn(userId); lenient().when(filter.extractSubject(anyString(), any())).thenReturn(subject); // pretend like we successfully processed the token unless defined otherwise in the tests lenient().when(filter.processToken(anyString())).thenReturn(Optional.of(new Object())); } @Test public void shouldCallHandleMissingTokenIfTokenIsNull() throws Exception { // given when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn(null); // when filter.doFilter(request, response, chain); // then verify(tokenExtractor).getToken(eq(request)); verify(filter).handleMissingToken(eq(request), eq(response), eq(chain)); verify(request).getServletPath(); verifyNoMoreInteractions(request); verify(filter, never()).getUserId(any()); verify(filter, never()).extractSubject(anyString(), any()); } @Test public void shouldCallHandleMissingTokenIfTokenCannotBeProcessed() throws Exception { // given when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("abc"); when(filter.processToken(anyString())).thenReturn(Optional.empty()); // when filter.doFilter(request, response, chain); // then verify(tokenExtractor).getToken(eq(request)); verify(filter).handleMissingToken(eq(request), eq(response), eq(chain)); verify(request).getServletPath(); verifyNoMoreInteractions(request); verify(filter, never()).getUserId(any()); verify(filter, never()).extractSubject(anyString(), any()); } @Test public void shouldGetSessionFromStoreWithCorrectUserId() throws Exception { when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn(token); when(sessionStore.getSession(eq(userId), any())).thenReturn(session); // when filter.doFilter(request, response, chain); // then verify(request).getSession(eq(false)); verify(tokenExtractor).getToken(eq(request)); verify(filter).getUserId(any()); verify(sessionStore).getSession(eq(userId), any()); } @Test public void shouldCreateSubjectIfSessionDidNotContainOne() throws Exception { when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn(token); when(sessionStore.getSession(eq(userId), any())).thenReturn(session); when(session.getAttribute(eq(CHE_SUBJECT_ATTRIBUTE))).thenReturn(null); // when filter.doFilter(request, response, chain); // then verify(filter).extractSubject(eq(token), any()); } @Test public void shouldReCreateSubjectIfTokensDidNotMatch() throws Exception { Subject otherSubject = new SubjectImpl( subject.getUserId(), Collections.emptyList(), subject.getUserName(), "token111", false); when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn(token); when(sessionStore.getSession(eq(userId), any())).thenReturn(session); when(session.getAttribute(eq(CHE_SUBJECT_ATTRIBUTE))).thenReturn(otherSubject); // when filter.doFilter(request, response, chain); // then verify(filter).extractSubject(eq(token), any()); } @Test public void shouldInvalidateSessionIfUserChanged() throws Exception { Subject otherSubject = new SubjectImpl("another_user", Collections.emptyList(), "user987", "token111", false); when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn(token); when(sessionStore.getSession(eq(userId), any())).thenReturn(session); when(session.getAttribute(eq(CHE_SUBJECT_ATTRIBUTE))).thenReturn(otherSubject); // when filter.doFilter(request, response, chain); // then verify(session).invalidate(); verify(filter).extractSubject(eq(token), any()); } @Test public void shouldSetSubjectIntoEnvironmentContext() throws Exception { EnvironmentContext context = spy(EnvironmentContext.getCurrent()); EnvironmentContext.setCurrent(context); when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn(token); when(sessionStore.getSession(eq(userId), any())).thenReturn(session); when(session.getAttribute(eq(CHE_SUBJECT_ATTRIBUTE))).thenReturn(subject); // when filter.doFilter(request, response, chain); // then verify(context).setSubject(eq(subject)); } @Test public void handleMissingTokenShouldAllowUnauthorizedEndpoint() throws ServletException, IOException { when(request.getServletPath()).thenReturn("/system/state"); filter.handleMissingToken(request, response, chain); verify(chain).doFilter(request, response); } @Test public void handleMissingTokenShouldRejectRequest() throws ServletException, IOException { when(request.getServletPath()).thenReturn("blabol"); filter.handleMissingToken(request, response, chain); verify(response).sendError(eq(401), anyString()); } } ================================================ FILE: multiuser/api/che-multiuser-api-authentication-commons/src/test/java/org/eclipse/che/multiuser/api/authentication/commons/token/HeaderRequestTokenExtractorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.authentication.commons.token; import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.BadRequestException; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class HeaderRequestTokenExtractorTest { private HeaderRequestTokenExtractor tokenExtractor = new HeaderRequestTokenExtractor(); @Mock HttpServletRequest servletRequest; @Test(dataProvider = "validHeadersProvider") public void shouldExtractTokensFromValidHeaders(String headerValue, String expectedToken) { when(servletRequest.getHeader(eq(AUTHORIZATION))).thenReturn(headerValue); // when String token = tokenExtractor.getToken(servletRequest); // then assertEquals(token, expectedToken); } @Test( dataProvider = "invalidHeadersProvider", expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Invalid authorization header format.") public void shouldThrowExceptionOnInvalidToken(String headerValue) { when(servletRequest.getHeader(eq(AUTHORIZATION))).thenReturn(headerValue); // when tokenExtractor.getToken(servletRequest); } @DataProvider private Object[][] validHeadersProvider() { return new Object[][] { {"token123", "token123"}, {"bearer token123", "token123"}, {"Bearer token123", "token123"}, }; } @DataProvider private Object[][] invalidHeadersProvider() { return new Object[][] {{"bearertoken123"}, {"bearer token123"}, {"bearer token 123"}}; } } ================================================ FILE: multiuser/api/che-multiuser-api-authorization/pom.xml ================================================ 4.0.0 che-multiuser-api org.eclipse.che.multiuser 7.118.0-SNAPSHOT che-multiuser-api-authorization jar Che Multiuser :: API :: Authorization ${project.build.directory}/generated-sources/dto/ com.google.code.gson gson com.google.guava guava jakarta.inject jakarta.inject-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.multiuser che-multiuser-api-permission-shared org.slf4j slf4j-api ch.qos.logback logback-classic test org.eclipse.che.multiuser che-multiuser-api-permission test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test src/main/java src/main/resources ${dto-generator-out-directory} org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} process-sources generate org.eclipse.che.multiuser che-multiuser-api-permission-shared ${project.version} org.eclipse.che.api.permission.shared.dto ${dto-generator-out-directory} org.eclipse.che.multiuser.api.permission.server.dto.DtoServerImpls server maven-compiler-plugin pre-compile generate-sources compile org.codehaus.mojo build-helper-maven-plugin add-domain process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} ================================================ FILE: multiuser/api/che-multiuser-api-authorization/src/main/java/org/eclipse/che/multiuser/api/permission/server/AuthorizedSubject.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.server; import static java.lang.String.format; import java.util.List; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.commons.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Sets up implementation of {@link Subject} that can check permissions. * * @author Sergii Leschenko */ public class AuthorizedSubject implements Subject { private static final Logger LOG = LoggerFactory.getLogger(AuthorizedSubject.class); private final Subject baseSubject; private final PermissionChecker permissionChecker; public AuthorizedSubject(Subject baseSubject, PermissionChecker permissionChecker) { this.baseSubject = baseSubject; this.permissionChecker = permissionChecker; } @Override public String getUserName() { return baseSubject.getUserName(); } @Override public List getGroups() { return baseSubject.getGroups(); } @Override public boolean hasPermission(String domain, String instance, String action) { try { return permissionChecker.hasPermission(getUserId(), domain, instance, action); } catch (NotFoundException nfe) { return false; } catch (ServerException | ConflictException e) { LOG.error( format( "Can't check permissions for user '%s' and instance '%s' of domain '%s'", getUserId(), domain, instance), e); throw new RuntimeException("Can't check user's permissions", e); } } @Override public void checkPermission(String domain, String instance, String action) throws ForbiddenException { if (!hasPermission(domain, instance, action)) { String message = "User is not authorized to perform " + action + " of " + domain; if (instance != null) { message += " with id '" + instance + "'"; } throw new ForbiddenException(message); } } @Override public String getToken() { return baseSubject.getToken(); } @Override public String getUserId() { return baseSubject.getUserId(); } @Override public boolean isTemporary() { return baseSubject.isTemporary(); } } ================================================ FILE: multiuser/api/che-multiuser-api-authorization/src/main/java/org/eclipse/che/multiuser/api/permission/server/HttpPermissionCheckerImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.server; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import jakarta.ws.rs.core.UriBuilder; import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; import org.eclipse.che.multiuser.api.permission.shared.dto.PermissionsDto; /** * Implementation of {@link PermissionChecker} that load permissions by http requests to {@link * PermissionsService} * *

    It also caches permissions to avoid frequently requests to workspace master. * * @author Sergii Leschenko */ @Singleton public class HttpPermissionCheckerImpl implements PermissionChecker { private final LoadingCache> permissionsCache; @Inject public HttpPermissionCheckerImpl( @Named("che.api") String apiEndpoint, HttpJsonRequestFactory requestFactory) { // TODO mb make configurable size of cache and expiration time this.permissionsCache = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(1, TimeUnit.MINUTES) .build( new CacheLoader>() { @Override public Set load(Key key) throws Exception { UriBuilder currentUsersPermissions = UriBuilder.fromUri(apiEndpoint).path("permissions/" + key.domain); if (key.instance != null) { currentUsersPermissions.queryParam("instance", key.instance); } String userPermissionsUrl = currentUsersPermissions.build().toString(); try { PermissionsDto usersPermissions = requestFactory .fromUrl(userPermissionsUrl) .useGetMethod() .request() .asDto(PermissionsDto.class); return new HashSet<>(usersPermissions.getActions()); } catch (NotFoundException e) { // user doesn't have permissions return new HashSet<>(); } } }); } @Override public boolean hasPermission(String user, String domain, String instance, String action) throws ServerException { try { return permissionsCache.get(new Key(user, domain, instance)).contains(action); } catch (Exception e) { throw new ServerException(e.getMessage(), e); } } private static final class Key { private final String user; private final String domain; private final String instance; private Key(String user, String domain, String instance) { this.user = user; this.domain = domain; this.instance = instance; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Key)) { return false; } final Key other = (Key) obj; return Objects.equals(user, other.user) && Objects.equals(domain, other.domain) && Objects.equals(instance, other.instance); } @Override public int hashCode() { int hash = 7; hash = hash * 31 + Objects.hashCode(user); hash = hash * 31 + Objects.hashCode(domain); hash = hash * 31 + Objects.hashCode(instance); return hash; } } } ================================================ FILE: multiuser/api/che-multiuser-api-authorization/src/main/java/org/eclipse/che/multiuser/api/permission/server/PermissionChecker.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.server; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; /** * Checks user's permission to perform some action with particular instance of given domain. * * @author Sergii Leschenko */ public interface PermissionChecker { /** * Checks user's permission to perform some action with particular instance. * * @param user user id * @param domain domain id * @param instance instance id * @param action action name * @return true if the user has given permission * @throws NotFoundException when given domain is unsupported * @throws ConflictException when given domain requires non nullable value for instance but it is * null * @throws ServerException when any other error occurs during permission existence checking */ boolean hasPermission(String user, String domain, String instance, String action) throws ServerException, NotFoundException, ConflictException; } ================================================ FILE: multiuser/api/che-multiuser-api-authorization-impl/pom.xml ================================================ 4.0.0 che-multiuser-api org.eclipse.che.multiuser 7.118.0-SNAPSHOT che-multiuser-api-authorization-impl jar Che Multiuser :: API :: Authorization Impl jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-core org.eclipse.che.multiuser che-multiuser-api-authorization org.eclipse.che.multiuser che-multiuser-api-permission ch.qos.logback logback-classic test jakarta.ws.rs jakarta.ws.rs-api test org.eclipse.che.core che-core-api-dto test org.eclipse.che.multiuser che-multiuser-api-permission-shared test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test ================================================ FILE: multiuser/api/che-multiuser-api-authorization-impl/src/main/java/org/eclipse/che/multiuser/api/permission/server/PermissionCheckerImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.server; import javax.inject.Inject; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; /** * Implementation of {@link PermissionChecker} that use {@link PermissionsManager} for checking. * * @author Sergii Leschenko */ public class PermissionCheckerImpl implements PermissionChecker { private final PermissionsManager permissionsManager; @Inject public PermissionCheckerImpl(PermissionsManager permissionsManager) { this.permissionsManager = permissionsManager; } @Override public boolean hasPermission(String user, String domain, String instance, String action) throws ServerException, NotFoundException, ConflictException { return permissionsManager.exists(user, domain, instance, action) || permissionsManager.exists("*", domain, instance, action); } } ================================================ FILE: multiuser/api/che-multiuser-api-authorization-impl/src/test/java/org/eclipse/che/multiuser/api/permission/server/PermissionCheckerImplTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.server; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class PermissionCheckerImplTest { @Mock private PermissionsManager permissionsManager; @InjectMocks private PermissionCheckerImpl permissionChecker; @Test public void shouldCheckExistingDirectUsersPermissions() throws Exception { when(permissionsManager.exists(anyString(), anyString(), anyString(), anyString())) .thenReturn(true); boolean hasPermission = permissionChecker.hasPermission("user123", "domain123", "instance123", "test"); assertEquals(hasPermission, true); verify(permissionsManager).exists("user123", "domain123", "instance123", "test"); } @Test public void shouldCheckExistingPublicPermissionsIfThereIsNoDirectUsersPermissions() throws Exception { doReturn(false) .when(permissionsManager) .exists(eq("user123"), anyString(), anyString(), anyString()); doReturn(true).when(permissionsManager).exists(eq("*"), anyString(), anyString(), anyString()); boolean hasPermission = permissionChecker.hasPermission("user123", "domain123", "instance123", "test"); assertEquals(hasPermission, true); verify(permissionsManager).exists("user123", "domain123", "instance123", "test"); verify(permissionsManager).exists("*", "domain123", "instance123", "test"); } } ================================================ FILE: multiuser/api/che-multiuser-api-authorization-impl/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n target/log/authorization-impl.log %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: multiuser/api/che-multiuser-api-permission/pom.xml ================================================ 4.0.0 che-multiuser-api org.eclipse.che.multiuser 7.118.0-SNAPSHOT che-multiuser-api-permission jar Che Multiuser :: Permissions API ${project.build.directory}/generated-sources/dto/ com.google.code.gson gson com.google.guava guava com.google.inject guice jakarta.inject jakarta.inject-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-user org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang org.eclipse.che.core che-core-commons-test org.eclipse.che.multiuser che-multiuser-api-permission-shared org.everrest everrest-core com.google.inject.extensions guice-persist provided org.eclipse.persistence jakarta.persistence provided ch.qos.logback logback-classic test commons-fileupload commons-fileupload test io.rest-assured rest-assured test org.eclipse.che.core che-core-api-account test org.eclipse.persistence org.eclipse.persistence.core test org.eclipse.persistence org.eclipse.persistence.jpa test org.everrest everrest-assured test org.flywaydb flyway-core test org.hamcrest hamcrest test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} process-sources generate org.eclipse.che.multiuser che-multiuser-api-permission-shared ${project.version} org.eclipse.che.multiuser.api.permission.shared.dto ${dto-generator-out-directory} org.eclipse.che.api.multiuser.permission.server.dto.DtoServerImpls server maven-compiler-plugin pre-compile generate-sources compile org.codehaus.mojo build-helper-maven-plugin add-domain process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} org.apache.maven.plugins maven-jar-plugin test-jar **/spi/tck/*.* org.apache.maven.plugins maven-dependency-plugin resource-dependencies process-test-resources unpack-dependencies ================================================ FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/AbstractPermissionsDomain.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.server; import com.google.common.collect.ImmutableList; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions; import org.eclipse.che.multiuser.api.permission.shared.model.PermissionsDomain; /** * Abstract implementation for {@link PermissionsDomain} * *

    Note: It supports "setPermission" by default * * @author Sergii Leschenko */ public abstract class AbstractPermissionsDomain implements PermissionsDomain { public static final String SET_PERMISSIONS = "setPermissions"; private final String id; private final List allowedActions; private final boolean requiresInstance; protected AbstractPermissionsDomain(String id, List allowedActions) { this(id, allowedActions, true); } protected AbstractPermissionsDomain( String id, List allowedActions, boolean requiresInstance) { this.id = id; Set resultActions = new HashSet<>(allowedActions); resultActions.add(SET_PERMISSIONS); this.allowedActions = ImmutableList.copyOf(resultActions); this.requiresInstance = requiresInstance; } @Override public String getId() { return id; } @Override public List getAllowedActions() { return allowedActions; } @Override public Boolean isInstanceRequired() { return requiresInstance; } /** * Creates new instance of the entity related to this domain. * * @return new entity instance related to this domain * @throws IllegalArgumentException when instance id is null when it's required */ public T newInstance(String userId, String instanceId, List allowedActions) { if (isInstanceRequired() && instanceId == null) { throw new IllegalArgumentException("Given domain requires non nullable value for instanceId"); } return doCreateInstance(userId, instanceId, allowedActions); } protected abstract T doCreateInstance( String userId, String instanceId, List allowedActions); @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof AbstractPermissionsDomain)) return false; final AbstractPermissionsDomain other = (AbstractPermissionsDomain) obj; return Objects.equals(id, other.id) && Objects.equals(allowedActions, other.allowedActions); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(allowedActions); return hash; } @Override public String toString() { return "PermissionsDomain{" + "id='" + id + '\'' + ", allowedActions=" + allowedActions + "}"; } } ================================================ FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/InstanceParameterValidator.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.server; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.commons.annotation.Nullable; /** * Validates that provided instance parameter is valid * * @author Sergii Leschenko */ @Singleton public class InstanceParameterValidator { private final PermissionsManager permissionsManager; @Inject public InstanceParameterValidator(PermissionsManager permissionsManager) { this.permissionsManager = permissionsManager; } /** * Validates that provided instance parameter is valid for specified domain * * @param domain the domain of specified {@code instance} * @param instance the instance to check * @throws BadRequestException if specified {@code domain} is null * @throws BadRequestException if specified {@code instance} is not valid * @throws NotFoundException if specified {@code domain} is unsupported */ public void validate(String domain, @Nullable String instance) throws BadRequestException, NotFoundException { checkArgument(domain != null, "Domain id required"); if (permissionsManager.getDomain(domain).isInstanceRequired() && instance == null) { throw new BadRequestException("Specified domain requires non nullable value for instance"); } } private void checkArgument(boolean expression, String message) throws BadRequestException { if (!expression) { throw new BadRequestException(message); } } } ================================================ FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/PermissionsManager.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.server; import static com.google.common.base.MoreObjects.firstNonNull; import static org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain.SET_PERMISSIONS; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.Pages; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.concurrent.StripedLocks; import org.eclipse.che.commons.lang.concurrent.Unlocker; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.multiuser.api.permission.server.event.PermissionsCreatedEvent; import org.eclipse.che.multiuser.api.permission.server.event.PermissionsRemovedEvent; import org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions; import org.eclipse.che.multiuser.api.permission.server.spi.PermissionsDao; import org.eclipse.che.multiuser.api.permission.shared.model.Permissions; /** * Facade for Permissions related operations. * * @author gazarenkov * @author Sergii Leschenko * @author Anton Korneta */ @Singleton public class PermissionsManager { private final EventService eventService; private final List> domains; private final Map> domainToDao; private final StripedLocks updateLocks; @Inject public PermissionsManager(EventService eventService) throws ServerException { this.eventService = eventService; final Map> domainToDao = new HashMap<>(); final List> domains = new ArrayList<>(); this.domains = ImmutableList.copyOf(domains); this.domainToDao = ImmutableMap.copyOf(domainToDao); this.updateLocks = new StripedLocks(16); } /** * Stores (adds or updates) permissions. * * @param permissions permission to store * @throws NotFoundException when permissions have unsupported domain * @throws ConflictException when new permissions remove last 'setPermissions' of given instance * @throws ServerException when any other error occurs during permissions storing */ public void storePermission(Permissions permissions) throws ServerException, ConflictException, NotFoundException { final String domainId = permissions.getDomainId(); final String instanceId = permissions.getInstanceId(); final String userId = permissions.getUserId(); try (@SuppressWarnings("unused") Unlocker unlocker = updateLocks.writeLock(firstNonNull(instanceId, domainId))) { final PermissionsDao permissionsDao = getPermissionsDao(domainId); if (!permissions.getActions().contains(SET_PERMISSIONS) && userHasLastSetPermissions(permissionsDao, userId, instanceId)) { throw new ConflictException( "Can't edit permissions because there is not any another user " + "with permission 'setPermissions'"); } store(permissionsDao, userId, instanceId, permissions); } } /** * Returns user's permissions for specified instance * * @param userId user id * @param domainId domain id * @param instanceId instance id * @return userId's permissions for specified instanceId * @throws NotFoundException when given domainId is unsupported * @throws NotFoundException when permissions with given userId and domainId and instanceId was * not found * @throws ServerException when any other error occurs during permissions fetching */ public AbstractPermissions get(String userId, String domainId, String instanceId) throws ServerException, NotFoundException, ConflictException { return getPermissionsDao(domainId).get(userId, instanceId); } /** * Returns users' permissions for specified instance * * @param domainId domain id * @param instanceId instance id * @param maxItems the maximum number of permissions to return * @param skipCount the number of permissions to skip * @return set of permissions * @throws NotFoundException when given domainId is unsupported * @throws ServerException when any other error occurs during permissions fetching */ @SuppressWarnings("unchecked") public Page getByInstance( String domainId, String instanceId, int maxItems, long skipCount) throws ServerException, NotFoundException { return (Page) getPermissionsDao(domainId).getByInstance(instanceId, maxItems, skipCount); } /** * Removes permissions of userId related to the particular instanceId of specified domainId * * @param userId user id * @param domainId domain id * @param instanceId instance id * @throws NotFoundException when given domainId is unsupported * @throws ConflictException when removes last 'setPermissions' of given instanceId * @throws ServerException when any other error occurs during permissions removing */ public void remove(String userId, String domainId, String instanceId) throws ConflictException, ServerException, NotFoundException { final PermissionsDao permissionsDao = getPermissionsDao(domainId); Permissions permissions; try (@SuppressWarnings("unused") Unlocker unlocker = updateLocks.writeLock(firstNonNull(instanceId, domainId))) { if (userHasLastSetPermissions(permissionsDao, userId, instanceId)) { throw new ConflictException( "Can't remove permissions because there is not any another user " + "with permission 'setPermissions'"); } permissions = permissionsDao.get(userId, instanceId); permissionsDao.remove(userId, instanceId); } final String initiator = EnvironmentContext.getCurrent().getSubject().getUserName(); eventService.publish(new PermissionsRemovedEvent(initiator, permissions)); } /** * Checks existence of user's permission for specified instance * * @param userId user id * @param domainId domain id * @param instanceId instance id * @param action action name * @return true if the permission exists * @throws NotFoundException when given domain is unsupported * @throws ServerException when any other error occurs during permission existence checking */ public boolean exists(String userId, String domainId, String instanceId, String action) throws ServerException, NotFoundException, ConflictException { return getDomain(domainId).getAllowedActions().contains(action) && getPermissionsDao(domainId).exists(userId, instanceId, action); } /** * Checks supporting all specified actions by domain with specified id. * * @param domainId domain id to check supporting * @param actions actions to check * @throws NotFoundException when domain with specified id is unsupported * @throws ConflictException when actions contain unsupported value */ public void checkActionsSupporting(String domainId, List actions) throws NotFoundException, ConflictException { checkActionsSupporting(getDomain(domainId), actions); } /** Returns supported domains */ public List getDomains() { return new ArrayList<>(domains); } /** * Returns supported domain * * @throws NotFoundException when given domain is unsupported */ public AbstractPermissionsDomain getDomain(String domain) throws NotFoundException { return getPermissionsDao(domain).getDomain(); } private void store( PermissionsDao dao, String userId, String instanceId, Permissions permissions) throws ConflictException, ServerException { final AbstractPermissionsDomain permissionsDomain = dao.getDomain(); final T permission = permissionsDomain.newInstance(userId, instanceId, permissions.getActions()); checkActionsSupporting(permissionsDomain, permission.getActions()); final Optional existing = dao.store(permission); if (!existing.isPresent()) { Subject subject = EnvironmentContext.getCurrent().getSubject(); final String initiator = subject.isAnonymous() ? null : subject.getUserName(); eventService.publish(new PermissionsCreatedEvent(initiator, permissions)); } } private void checkActionsSupporting(AbstractPermissionsDomain domain, List actions) throws ConflictException { final Set allowedActions = new HashSet<>(domain.getAllowedActions()); final Set unsupportedActions = actions.stream() .filter(action -> !allowedActions.contains(action)) .collect(Collectors.toSet()); if (!unsupportedActions.isEmpty()) { throw new ConflictException( "Domain with id '" + domain.getId() + "' doesn't support following action(s): " + unsupportedActions.stream().collect(Collectors.joining(", "))); } } private PermissionsDao getPermissionsDao(String domain) throws NotFoundException { final PermissionsDao permissionsStorage = domainToDao.get(domain); if (permissionsStorage == null) { throw new NotFoundException("Requested unsupported domain '" + domain + "'"); } return permissionsStorage; } private boolean userHasLastSetPermissions( PermissionsDao storage, String userId, String instanceId) throws ServerException, ConflictException, NotFoundException { if (!storage.exists(userId, instanceId, SET_PERMISSIONS)) { return false; } for (AbstractPermissions permissions : Pages.iterateLazily( (maxItems, skipCount) -> storage.getByInstance(instanceId, maxItems, skipCount))) { if (!permissions.getUserId().equals(userId) && permissions.getActions().contains(SET_PERMISSIONS)) { return false; } } return true; } } ================================================ FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/PermissionsModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.server; import com.google.inject.AbstractModule; import com.google.inject.multibindings.MapBinder; import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; import org.eclipse.che.multiuser.api.permission.server.account.AccountPermissionsChecker; import org.eclipse.che.multiuser.api.permission.server.filter.GetPermissionsFilter; import org.eclipse.che.multiuser.api.permission.server.filter.RemovePermissionsFilter; import org.eclipse.che.multiuser.api.permission.server.filter.SetPermissionsFilter; import org.eclipse.che.multiuser.api.permission.server.filter.check.RemovePermissionsChecker; import org.eclipse.che.multiuser.api.permission.server.filter.check.SetPermissionsChecker; import org.eclipse.che.multiuser.api.permission.server.jsonrpc.RemoteSubscriptionPermissionManager; /** * @author Sergii Leschenko */ public class PermissionsModule extends AbstractModule { @Override protected void configure() { bind(SetPermissionsFilter.class); bind(RemovePermissionsFilter.class); bind(GetPermissionsFilter.class); bind(RemoteSubscriptionPermissionManager.class).asEagerSingleton(); // Creates empty multibinder to avoid error during container starting Multibinder.newSetBinder( binder(), String.class, Names.named(SystemDomain.SYSTEM_DOMAIN_ACTIONS)); // initialize empty set binder Multibinder.newSetBinder(binder(), AccountPermissionsChecker.class); MapBinder.newMapBinder(binder(), String.class, SetPermissionsChecker.class); MapBinder.newMapBinder(binder(), String.class, RemovePermissionsChecker.class); } } ================================================ FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/SuperPrivilegesChecker.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.server; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.multiuser.api.permission.shared.model.PermissionsDomain; /** * Checks that current subject has privileges to perform some operation without required * permissions. * *

    Super privileges is designed to give some extra abilities for users who have permission to * perform {@link SystemDomain#MANAGE_SYSTEM_ACTION manage system}.
    * Super privileges are optional, they can be disabled by configuration. * *

    User has super privileges if he has {@link SystemDomain#MANAGE_SYSTEM_ACTION manage system} * permission and system configuration property {@link #SYSTEM_SUPER_PRIVILEGED_MODE} is true. * *

    It is required to perform {@link #hasSuperPrivileges()} checks manually before permissions * checking if user should be able to perform some operation. * *

     * public class ExamplePermissionsFilter extends CheMethodInvokerFilter {
     *     @Inject
     *     private SuperPrivilegesChecker superPrivilegesChecker;
     *
     *     @Override
     *     protected void filter(GenericResourceMethod genericMethodResource, Object[] arguments) throws ApiException {
     *         if (superPrivilegesChecker.hasSuperPrivileges()) {
     *             return;
     *         }
     *         EnvironmentContext.getCurrent().getSubject().checkPermissions("domain", "domain123", "action");
     *     }
     * }
     * 
    * * If user should be able to manage permissions for some permission domain then this domain should * be present in multibinder named with {@link #SUPER_PRIVILEGED_DOMAINS}.
    * Binding example: * *
     * public class ExampleModule extends AbstractModule {
     *     @Override
     *     protected void configure() {
     *         Multibinder.newSetBinder(binder(), PermissionsDomain.class, Names.named(SuperPrivilegesChecker.SUPER_PRIVILEGED_DOMAINS))
     *                    .addBinding().to(ExampleDomain.class);
     *     }
     * }
     * 
    * * @author Sergii Leschenko */ public class SuperPrivilegesChecker { /** * Configuration parameter that indicates extended abilities for users who have {@link * SystemDomain#MANAGE_SYSTEM_ACTION manageSytem} permission. */ public static final String SYSTEM_SUPER_PRIVILEGED_MODE = "che.system.super_privileged_mode"; /** Permissions of these domains can be managed by any user who has super privileges. */ public static final String SUPER_PRIVILEGED_DOMAINS = "system.super_privileged_domains"; private final boolean superPrivilegedMode; private final Set privilegesDomainsIds; @Inject public SuperPrivilegesChecker( @Named(SYSTEM_SUPER_PRIVILEGED_MODE) boolean superPrivilegedMode, @Named(SUPER_PRIVILEGED_DOMAINS) Set domains) { this.superPrivilegedMode = superPrivilegedMode; this.privilegesDomainsIds = domains.stream().map(PermissionsDomain::getId).collect(Collectors.toSet()); } /** * Checks that current subject has super privileges. * * @return true if current subject has super privileges, false otherwise */ public boolean hasSuperPrivileges() { return superPrivilegedMode && EnvironmentContext.getCurrent() .getSubject() .hasPermission(SystemDomain.DOMAIN_ID, null, SystemDomain.MANAGE_SYSTEM_ACTION); } /** * Checks that current subject is privileged to manage permissions of specified domain. * * @return true if current subject is privileged to manage permissions of specified domain, false * otherwise */ public boolean isPrivilegedToManagePermissions(String domainId) { return superPrivilegedMode && privilegesDomainsIds.contains(domainId) && EnvironmentContext.getCurrent() .getSubject() .hasPermission(SystemDomain.DOMAIN_ID, null, SystemDomain.MANAGE_SYSTEM_ACTION); } } ================================================ FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/SystemDomain.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.server; import static java.util.stream.Collectors.toList; import java.util.List; import java.util.Set; import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.multiuser.api.permission.server.model.impl.SystemPermissionsImpl; /** * Domain for storing actions that are used for managing system e.g. user management, configuration properties management. * *

    The list of supported actions by system domain can be configured by following lines *

     *   Multibinder binder = Multibinder.newSetBinder(binder(), String.class, Names.named(SystemDomain.SYSTEM_DOMAIN_ACTIONS));
     *   binder.addBinding().toInstance("customAction");
     * 
     *
     * @author Sergii Leschenko
     */
    public class SystemDomain extends AbstractPermissionsDomain {
      public static final String SYSTEM_DOMAIN_ACTIONS = "system.domain.actions";
      public static final String DOMAIN_ID = "system";
      public static final String MANAGE_SYSTEM_ACTION = "manageSystem";
      public static final String MONITOR_SYSTEM_ACTION = "monitorSystem";
    
      @Inject
      public SystemDomain(@Named(SYSTEM_DOMAIN_ACTIONS) Set allowedActions) {
        super(
            DOMAIN_ID,
            Stream.concat(
                    allowedActions.stream(), Stream.of(MANAGE_SYSTEM_ACTION, MONITOR_SYSTEM_ACTION))
                .collect(toList()),
            false);
      }
    
      @Override
      public SystemPermissionsImpl doCreateInstance(
          String userId, String instanceId, List allowedActions) {
        return new SystemPermissionsImpl(userId, allowedActions);
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/account/AccountOperation.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.account;
    
    /**
     * Actions that can be performed by users in accounts.
     *
     * @author Sergii Leshchenko
     */
    public enum AccountOperation {
      /** When user creates workspace that will belong to account. */
      CREATE_WORKSPACE,
    
      /** When user does any operation with existing workspace. */
      MANAGE_WORKSPACES,
    
      /** When user retrieves information about account resources(like available, total, etc). */
      SEE_RESOURCE_INFORMATION
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/account/AccountPermissionsChecker.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.account;
    
    import org.eclipse.che.api.core.ForbiddenException;
    
    /**
     * Defines permissions checking for accounts with some type.
     *
     * @author Sergii Leshchenko
     */
    public interface AccountPermissionsChecker {
      /**
       * Checks that current subject is authorized to perform given operation with specified account
       *
       * @param accountId account to check
       * @param operation operation that is going to be performed
       * @throws ForbiddenException when user doesn't have permissions to perform specified operation
       */
      void checkPermissions(String accountId, AccountOperation operation) throws ForbiddenException;
    
      /** Returns account type for which this class tracks check resources permissions. */
      String getAccountType();
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/event/PermissionsCreatedEvent.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.event;
    
    import static org.eclipse.che.multiuser.api.permission.shared.event.EventType.PERMISSIONS_ADDED;
    
    import org.eclipse.che.commons.annotation.Nullable;
    import org.eclipse.che.multiuser.api.permission.shared.event.EventType;
    import org.eclipse.che.multiuser.api.permission.shared.event.PermissionsEvent;
    import org.eclipse.che.multiuser.api.permission.shared.model.Permissions;
    
    /**
     * Defines permissions added events.
     *
     * @author Anton Korneta
     */
    public class PermissionsCreatedEvent implements PermissionsEvent {
    
      private final String initiator;
      private final Permissions permissions;
    
      public PermissionsCreatedEvent(String initiator, Permissions permissions) {
        this.initiator = initiator;
        this.permissions = permissions;
      }
    
      @Override
      public EventType getType() {
        return PERMISSIONS_ADDED;
      }
    
      @Override
      public Permissions getPermissions() {
        return permissions;
      }
    
      @Nullable
      @Override
      public String getInitiator() {
        return initiator;
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/event/PermissionsRemovedEvent.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.event;
    
    import static org.eclipse.che.multiuser.api.permission.shared.event.EventType.PERMISSIONS_REMOVED;
    
    import org.eclipse.che.commons.annotation.Nullable;
    import org.eclipse.che.multiuser.api.permission.shared.event.EventType;
    import org.eclipse.che.multiuser.api.permission.shared.event.PermissionsEvent;
    import org.eclipse.che.multiuser.api.permission.shared.model.Permissions;
    
    /**
     * Defines permissions added events.
     *
     * @author Anton Korneta
     */
    public class PermissionsRemovedEvent implements PermissionsEvent {
    
      private final String initiator;
      private final Permissions permissions;
    
      public PermissionsRemovedEvent(String initiator, Permissions permissions) {
        this.initiator = initiator;
        this.permissions = permissions;
      }
    
      @Override
      public EventType getType() {
        return PERMISSIONS_REMOVED;
      }
    
      @Override
      public Permissions getPermissions() {
        return permissions;
      }
    
      @Nullable
      @Override
      public String getInitiator() {
        return initiator;
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/filter/GetPermissionsFilter.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.filter;
    
    import jakarta.ws.rs.Path;
    import jakarta.ws.rs.PathParam;
    import jakarta.ws.rs.QueryParam;
    import javax.inject.Inject;
    import org.eclipse.che.api.core.BadRequestException;
    import org.eclipse.che.api.core.ConflictException;
    import org.eclipse.che.api.core.ForbiddenException;
    import org.eclipse.che.api.core.NotFoundException;
    import org.eclipse.che.api.core.ServerException;
    import org.eclipse.che.commons.env.EnvironmentContext;
    import org.eclipse.che.everrest.CheMethodInvokerFilter;
    import org.eclipse.che.multiuser.api.permission.server.InstanceParameterValidator;
    import org.eclipse.che.multiuser.api.permission.server.PermissionsManager;
    import org.eclipse.che.multiuser.api.permission.server.SuperPrivilegesChecker;
    import org.everrest.core.Filter;
    import org.everrest.core.resource.GenericResourceMethod;
    
    /**
     * Restricts access to reading permissions of instance by users' readPermissions permission
     *
     * @author Sergii Leschenko
     */
    @Filter
    @Path("/permissions/{domain}/all")
    public class GetPermissionsFilter extends CheMethodInvokerFilter {
      @PathParam("domain")
      private String domain;
    
      @QueryParam("instance")
      private String instance;
    
      @Inject private PermissionsManager permissionsManager;
    
      @Inject private SuperPrivilegesChecker superPrivilegesChecker;
    
      @Inject private InstanceParameterValidator instanceValidator;
    
      @Override
      public void filter(GenericResourceMethod genericResourceMethod, Object[] arguments)
          throws BadRequestException,
              NotFoundException,
              ConflictException,
              ForbiddenException,
              ServerException {
    
        final String methodName = genericResourceMethod.getMethod().getName();
        if (methodName.equals("getUsersPermissions")) {
          instanceValidator.validate(domain, instance);
          if (superPrivilegesChecker.isPrivilegedToManagePermissions(domain)) {
            return;
          }
          final String userId = EnvironmentContext.getCurrent().getSubject().getUserId();
          try {
            permissionsManager.get(userId, domain, instance);
            // user should have ability to see another users' permissions if he has any permission there
          } catch (NotFoundException e) {
            throw new ForbiddenException("User is not authorized to perform this operation");
          }
        }
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/filter/RemovePermissionsFilter.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.filter;
    
    import jakarta.ws.rs.Path;
    import jakarta.ws.rs.PathParam;
    import jakarta.ws.rs.QueryParam;
    import javax.inject.Inject;
    import org.eclipse.che.api.core.BadRequestException;
    import org.eclipse.che.api.core.ForbiddenException;
    import org.eclipse.che.api.core.NotFoundException;
    import org.eclipse.che.api.core.ServerException;
    import org.eclipse.che.commons.env.EnvironmentContext;
    import org.eclipse.che.commons.subject.Subject;
    import org.eclipse.che.everrest.CheMethodInvokerFilter;
    import org.eclipse.che.multiuser.api.permission.server.InstanceParameterValidator;
    import org.eclipse.che.multiuser.api.permission.server.SuperPrivilegesChecker;
    import org.eclipse.che.multiuser.api.permission.server.filter.check.DomainsPermissionsCheckers;
    import org.everrest.core.Filter;
    import org.everrest.core.resource.GenericResourceMethod;
    
    /**
     * Restricts access to removing permissions of instance by users' setPermissions permission
     *
     * @author Sergii Leschenko
     */
    @Filter
    @Path("/permissions/{domain}")
    public class RemovePermissionsFilter extends CheMethodInvokerFilter {
      @PathParam("domain")
      private String domain;
    
      @QueryParam("instance")
      private String instance;
    
      @QueryParam("user")
      private String user;
    
      @Inject private SuperPrivilegesChecker superPrivilegesChecker;
    
      @Inject private InstanceParameterValidator instanceValidator;
    
      @Inject private DomainsPermissionsCheckers domainsPermissionsCheckers;
    
      @Override
      public void filter(GenericResourceMethod genericResourceMethod, Object[] args)
          throws BadRequestException, ForbiddenException, NotFoundException, ServerException {
        if (genericResourceMethod.getMethod().getName().equals("removePermissions")) {
          instanceValidator.validate(domain, instance);
          final Subject currentSubject = EnvironmentContext.getCurrent().getSubject();
          if (currentSubject.getUserId().equals(user)
              || superPrivilegesChecker.isPrivilegedToManagePermissions(domain)) {
            return;
          }
          domainsPermissionsCheckers.getRemoveChecker(domain).check(user, domain, instance);
        }
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/filter/SetPermissionsFilter.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.filter;
    
    import static com.google.common.base.Strings.isNullOrEmpty;
    
    import jakarta.ws.rs.Path;
    import javax.inject.Inject;
    import org.eclipse.che.api.core.BadRequestException;
    import org.eclipse.che.api.core.ForbiddenException;
    import org.eclipse.che.api.core.NotFoundException;
    import org.eclipse.che.api.core.ServerException;
    import org.eclipse.che.everrest.CheMethodInvokerFilter;
    import org.eclipse.che.multiuser.api.permission.server.InstanceParameterValidator;
    import org.eclipse.che.multiuser.api.permission.server.SuperPrivilegesChecker;
    import org.eclipse.che.multiuser.api.permission.server.filter.check.DomainsPermissionsCheckers;
    import org.eclipse.che.multiuser.api.permission.shared.dto.PermissionsDto;
    import org.everrest.core.Filter;
    import org.everrest.core.resource.GenericResourceMethod;
    
    /**
     * Restricts access to setting permissions of instance by users' setPermissions permission
     *
     * @author Sergii Leschenko
     */
    @Filter
    @Path("/permissions/")
    public class SetPermissionsFilter extends CheMethodInvokerFilter {
      @Inject private SuperPrivilegesChecker superPrivilegesChecker;
    
      @Inject private InstanceParameterValidator instanceValidator;
    
      @Inject private DomainsPermissionsCheckers domainsPermissionsChecker;
    
      @Override
      public void filter(GenericResourceMethod genericResourceMethod, Object[] args)
          throws BadRequestException, ForbiddenException, NotFoundException, ServerException {
        if (genericResourceMethod.getMethod().getName().equals("storePermissions")) {
          final PermissionsDto permissions = (PermissionsDto) args[0];
          checkArgument(permissions != null, "Permissions descriptor required");
          final String domain = permissions.getDomainId();
          checkArgument(!isNullOrEmpty(domain), "Domain required");
          instanceValidator.validate(domain, permissions.getInstanceId());
          if (superPrivilegesChecker.isPrivilegedToManagePermissions(permissions.getDomainId())) {
            return;
          }
          domainsPermissionsChecker.getSetChecker(domain).check(permissions);
        }
      }
    
      private void checkArgument(boolean expression, String message) throws BadRequestException {
        if (!expression) {
          throw new BadRequestException(message);
        }
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/filter/check/DefaultRemovePermissionsChecker.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.filter.check;
    
    import static org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain.SET_PERMISSIONS;
    
    import javax.inject.Singleton;
    import org.eclipse.che.api.core.ForbiddenException;
    import org.eclipse.che.commons.env.EnvironmentContext;
    
    /**
     * Common checks while removing permissions.
     *
     * @author Anton Korneta
     */
    @Singleton
    public class DefaultRemovePermissionsChecker implements RemovePermissionsChecker {
    
      @Override
      public void check(String user, String domainId, String instance) throws ForbiddenException {
        if (!EnvironmentContext.getCurrent()
            .getSubject()
            .hasPermission(domainId, instance, SET_PERMISSIONS)) {
          throw new ForbiddenException("User can't edit permissions for this instance");
        }
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/filter/check/DefaultSetPermissionsChecker.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.filter.check;
    
    import static org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain.SET_PERMISSIONS;
    
    import javax.inject.Singleton;
    import org.eclipse.che.api.core.ForbiddenException;
    import org.eclipse.che.commons.env.EnvironmentContext;
    import org.eclipse.che.multiuser.api.permission.shared.model.Permissions;
    
    /**
     * Common checks while setting permissions.
     *
     * @author Anton Korneta
     */
    @Singleton
    public class DefaultSetPermissionsChecker implements SetPermissionsChecker {
    
      @Override
      public void check(Permissions permissions) throws ForbiddenException {
        if (!EnvironmentContext.getCurrent()
            .getSubject()
            .hasPermission(permissions.getDomainId(), permissions.getInstanceId(), SET_PERMISSIONS)) {
          throw new ForbiddenException("User can't edit permissions for this instance");
        }
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/filter/check/DomainsPermissionsCheckers.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.filter.check;
    
    import java.util.Map;
    import javax.inject.Inject;
    import javax.inject.Singleton;
    
    /**
     * Represents a set of domain-specific permissions checkers.
     *
     * @author Anton Korneta
     */
    @Singleton
    public class DomainsPermissionsCheckers {
    
      private final Map domain2setPermissionsChecker;
      private final DefaultSetPermissionsChecker defaultSetPermissionsChecker;
      private final Map domain2removePermissionsChecker;
      private final DefaultRemovePermissionsChecker defaultRemovePermissionsChecker;
    
      @Inject
      public DomainsPermissionsCheckers(
          Map domain2setPermissionsChecker,
          DefaultSetPermissionsChecker defaultPermissionsChecker,
          Map domain2removePermissionsChecker,
          DefaultRemovePermissionsChecker defaultRemovePermissionsChecker) {
        this.domain2setPermissionsChecker = domain2setPermissionsChecker;
        this.defaultSetPermissionsChecker = defaultPermissionsChecker;
        this.domain2removePermissionsChecker = domain2removePermissionsChecker;
        this.defaultRemovePermissionsChecker = defaultRemovePermissionsChecker;
      }
    
      public SetPermissionsChecker getSetChecker(String domain) {
        if (domain2setPermissionsChecker.containsKey(domain)) {
          return domain2setPermissionsChecker.get(domain);
        }
        return defaultSetPermissionsChecker;
      }
    
      public RemovePermissionsChecker getRemoveChecker(String domain) {
        if (domain2removePermissionsChecker.containsKey(domain)) {
          return domain2removePermissionsChecker.get(domain);
        }
        return defaultRemovePermissionsChecker;
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/filter/check/RemovePermissionsChecker.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.filter.check;
    
    import org.eclipse.che.api.core.ForbiddenException;
    
    /**
     * Defines contract for domain specific checks, before remove permissions.
     *
     * @author Anton Korneta
     */
    public interface RemovePermissionsChecker {
    
      /**
       * Checks if the current user is allowed to remove permissions.
       *
       * @param user user identifier
       * @param domainId permissions domain
       * @param instance instance associated with the permissions to be removed
       * @throws ForbiddenException when it is not allowed to remove permissions
       */
      void check(String user, String domainId, String instance) throws ForbiddenException;
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/filter/check/SetPermissionsChecker.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.filter.check;
    
    import org.eclipse.che.api.core.ForbiddenException;
    import org.eclipse.che.multiuser.api.permission.shared.model.Permissions;
    
    /**
     * Defines contract for domain specific checks, before set permissions.
     *
     * @author Anton Korneta
     */
    public interface SetPermissionsChecker {
    
      /**
       * Checks if the current user is allowed to set permissions.
       *
       * @param permissions permission to set
       * @throws ForbiddenException when it is not allowed to set {@code permissions}
       */
      void check(Permissions permissions) throws ForbiddenException;
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/jsonrpc/JsonRpcPermissionsFilterAdapter.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.jsonrpc;
    
    import org.eclipse.che.api.core.ForbiddenException;
    import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcException;
    import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcMethodInvokerFilter;
    
    /**
     * Purpose: Provides an implementation of JsonRpcMethodInvokerFilter that allow to throw
     * {@link ForbiddenException} that will be wrapped into {@link JsonRpcException}.
     *
     * @author Sergii Leshchenko
     */
    public abstract class JsonRpcPermissionsFilterAdapter implements JsonRpcMethodInvokerFilter {
    
      @Override
      public void accept(String method, Object... params) throws JsonRpcException {
        try {
          doAccept(method, params);
        } catch (ForbiddenException e) {
          throw new JsonRpcException(403, e.getMessage());
        }
      }
    
      protected abstract void doAccept(String method, Object... params) throws ForbiddenException;
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/jsonrpc/RemoteSubscriptionPermissionCheck.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.jsonrpc;
    
    import java.util.Map;
    import org.eclipse.che.api.core.ForbiddenException;
    
    /**
     * Check that should be performed before remote subscribing.
     *
     * @author Sergii Leshchenko
     * @see org.eclipse.che.api.core.notification.RemoteSubscriptionManager
     */
    public interface RemoteSubscriptionPermissionCheck {
    
      /**
       * Check that the current subject is allowed to listen to the specified method events
       *
       * @param methodName method name to subscribe
       * @param scope scope of subscription
       * @throws ForbiddenException if the current subject does not have needed permissions
       * @throws ForbiddenException if any other exception occurred during permissions check
       */
      void check(String methodName, Map scope) throws ForbiddenException;
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/jsonrpc/RemoteSubscriptionPermissionManager.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.jsonrpc;
    
    import static java.lang.String.format;
    import static org.eclipse.che.api.core.notification.RemoteSubscriptionManager.SUBSCRIBE_JSON_RPC_METHOD;
    
    import java.util.HashMap;
    import java.util.Map;
    import javax.inject.Inject;
    import javax.inject.Singleton;
    import org.eclipse.che.api.core.ForbiddenException;
    import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerManager;
    import org.eclipse.che.api.core.notification.dto.EventSubscription;
    
    /**
     * Filters invocation of {@link
     * org.eclipse.che.api.core.notification.RemoteSubscriptionManager#SUBSCRIBE_JSON_RPC_METHOD} and
     * performs the corresponding {@link RemoteSubscriptionPermissionCheck} to make sure that user is
     * authorized to listen to requested events.
     *
     * @author Sergii Leshchenko
     */
    @Singleton
    public class RemoteSubscriptionPermissionManager {
    
      private final Map methodToCheck;
    
      public RemoteSubscriptionPermissionManager() {
        this.methodToCheck = new HashMap<>();
      }
    
      @Inject
      void register(RequestHandlerManager requestHandlerManager) {
        requestHandlerManager.registerMethodInvokerFilter(
            new RemoteSubscriptionFilter(), SUBSCRIBE_JSON_RPC_METHOD);
      }
    
      /**
       * Registers permissions check for the specified methods
       *
       * @param permissionCheck permissions check that should be invoked before subscription
       * @param methods methods to filter
       * @throws IllegalStateException if any of specified methods already has registered check
       */
      public synchronized void registerCheck(
          RemoteSubscriptionPermissionCheck permissionCheck, String... methods) {
        for (String method : methods) {
          if (methodToCheck.containsKey(method)) {
            throw new IllegalStateException(
                format("Permissions check is already registered for method '%s'", method));
          }
    
          methodToCheck.put(method, permissionCheck);
        }
      }
    
      private class RemoteSubscriptionFilter extends JsonRpcPermissionsFilterAdapter {
        @Override
        protected void doAccept(String method, Object... params) throws ForbiddenException {
          EventSubscription param = (EventSubscription) params[0];
    
          RemoteSubscriptionPermissionCheck permissionCheck = methodToCheck.get(param.getMethod());
          if (permissionCheck != null) {
            permissionCheck.check(param.getMethod(), param.getScope());
          }
        }
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/model/impl/AbstractPermissions.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.model.impl;
    
    import java.util.List;
    import java.util.Objects;
    import javax.persistence.Column;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.MappedSuperclass;
    import javax.persistence.OneToOne;
    import javax.persistence.PostLoad;
    import javax.persistence.PrePersist;
    import javax.persistence.PreUpdate;
    import javax.persistence.Transient;
    import org.eclipse.che.api.user.server.model.impl.UserImpl;
    import org.eclipse.che.multiuser.api.permission.shared.model.Permissions;
    
    /**
     * Represents user's permissions to access to some resources
     *
     * @author Sergii Leschenko
     */
    @MappedSuperclass
    public abstract class AbstractPermissions implements Permissions {
    
      @Id
      @GeneratedValue
      @Column(name = "id")
      protected String id;
    
      @Column(name = "user_id")
      protected String userId;
    
      @OneToOne
      @JoinColumn(name = "user_id", insertable = false, updatable = false)
      private UserImpl user;
    
      @Transient private String userIdHolder;
    
      public AbstractPermissions() {}
    
      public AbstractPermissions(Permissions permissions) {
        this(permissions.getUserId());
      }
    
      public AbstractPermissions(String userId) {
        this.userIdHolder = userId;
        this.userId = userId;
      }
    
      /** Returns used id */
      @Override
      public String getUserId() {
        return userIdHolder;
      }
    
      public void setUserId(String userId) {
        this.userIdHolder = userId;
      }
    
      /** Returns instance id */
      @Override
      public abstract String getInstanceId();
    
      /** Returns domain id */
      @Override
      public abstract String getDomainId();
    
      /** List of actions which user can perform for particular instance */
      @Override
      public abstract List getActions();
    
      @PreUpdate
      @PrePersist
      private void prePersist() {
        if ("*".equals(userIdHolder)) {
          userId = null;
        } else {
          userId = userIdHolder;
        }
      }
    
      @PostLoad
      private void postLoad() {
        if (userId == null) {
          userIdHolder = "*";
        } else {
          userIdHolder = userId;
        }
      }
    
      @Override
      public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof AbstractPermissions)) return false;
        final AbstractPermissions other = (AbstractPermissions) obj;
        return Objects.equals(id, other.id)
            && Objects.equals(getUserId(), other.getUserId())
            && Objects.equals(getInstanceId(), other.getInstanceId())
            && Objects.equals(getDomainId(), other.getDomainId())
            && Objects.equals(getActions(), other.getActions());
      }
    
      @Override
      public int hashCode() {
        int hash = 7;
        hash = 31 * hash + Objects.hashCode(id);
        hash = 31 * hash + Objects.hashCode(getUserId());
        hash = 31 * hash + Objects.hashCode(getInstanceId());
        hash = 31 * hash + Objects.hashCode(getDomainId());
        hash = 31 * hash + Objects.hashCode(getActions());
        return hash;
      }
    
      @Override
      public String toString() {
        return "AbstractPermissions{" + "id='" + id + '\'' + ", user=" + user + '}';
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/model/impl/SystemPermissionsImpl.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.model.impl;
    
    import java.util.ArrayList;
    import java.util.List;
    import javax.persistence.CollectionTable;
    import javax.persistence.Column;
    import javax.persistence.ElementCollection;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.JoinColumn;
    import javax.persistence.NamedQueries;
    import javax.persistence.NamedQuery;
    import javax.persistence.Table;
    import org.eclipse.che.multiuser.api.permission.server.SystemDomain;
    
    /**
     * System permissions data object.
     *
     * @author Max Shaposhnik
     */
    @Entity(name = "SystemPermissions")
    @NamedQueries({
      @NamedQuery(
          name = "SystemPermissions.getByUserId",
          query =
              "SELECT permissions "
                  + "FROM SystemPermissions permissions "
                  + "WHERE permissions.userId = :userId "),
      @NamedQuery(
          name = "SystemPermissions.getAll",
          query = "SELECT permissions " + "FROM SystemPermissions permissions "),
      @NamedQuery(
          name = "SystemPermissions.getTotalCount",
          query = "SELECT COUNT(permissions) " + "FROM SystemPermissions permissions ")
    })
    @Table(name = "che_system_permissions")
    public class SystemPermissionsImpl extends AbstractPermissions {
    
      public SystemPermissionsImpl() {}
    
      public SystemPermissionsImpl(String userId, List actions) {
        super(userId);
        if (actions != null) {
          this.actions = new ArrayList<>(actions);
        }
      }
    
      public SystemPermissionsImpl(SystemPermissionsImpl permissions) {
        this(permissions.getUserId(), permissions.getActions());
      }
    
      @ElementCollection(fetch = FetchType.EAGER)
      @Column(name = "actions")
      @CollectionTable(
          name = "che_system_permissions_actions",
          joinColumns = @JoinColumn(name = "system_permissions_id"))
      protected List actions;
    
      @Override
      public String getInstanceId() {
        return null;
      }
    
      @Override
      public String getDomainId() {
        return SystemDomain.DOMAIN_ID;
      }
    
      @Override
      public List getActions() {
        return actions;
      }
    
      @Override
      public String toString() {
        return "SystemPermissions{" + "user='" + getUserId() + '\'' + ", actions=" + actions + '}';
      }
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission/src/main/java/org/eclipse/che/multiuser/api/permission/server/spi/PermissionsDao.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.server.spi;
    
    import java.util.List;
    import java.util.Optional;
    import org.eclipse.che.api.core.NotFoundException;
    import org.eclipse.che.api.core.Page;
    import org.eclipse.che.api.core.ServerException;
    import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain;
    import org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions;
    
    /**
     * General contract of storage for permissions. Single Storage may maintain one or more Domains (it
     * is responsibility of system on top to make the choice consistent) It actually defines CRUD
     * methods with some specific such as: - processing list of permissions - checking for existence but
     * not returning fully qualified stored permission
     *
     * @author gazarenkov
     * @author Sergii Leschenko
     */
    public interface PermissionsDao {
    
      /**
       * @return store of domains this storage is able to maintain
       */
      AbstractPermissionsDomain getDomain();
    
      /**
       * Stores (adds or updates) permissions.
       *
       * @param permissions permission to store
       * @return optional with updated permissions, other way empty optional must be returned
       * @throws ServerException when any other error occurs during permissions storing
       */
      Optional store(T permissions) throws ServerException;
    
      /**
       * @param userId user id
       * @param instanceId instance id
       * @return user's permissions for specified instance
       * @throws NullPointerException when instance id is null and domain requires it
       * @throws NullPointerException when user id is null
       * @throws NotFoundException when permissions with given user and domain and instance was not
       *     found
       * @throws ServerException when any other error occurs during permissions fetching
       */
      T get(String userId, String instanceId) throws ServerException, NotFoundException;
    
      /**
       * @param instanceId instance id
       * @param maxItems the maximum number of permissions to return
       * @param skipCount the number of permissions to skip
       * @return set of permissions
       * @throws NotFoundException when given instance was not found
       * @throws ServerException when any other error occurs during permissions fetching
       */
      Page getByInstance(String instanceId, int maxItems, long skipCount)
          throws ServerException, NotFoundException;
    
      /**
       * @param userId user id
       * @return set of permissions
       * @throws NotFoundException when given instance was not found
       * @throws ServerException when any other error occurs during permissions fetching
       */
      List getByUser(String userId) throws ServerException, NotFoundException;
    
      /**
       * @param userId user id
       * @param instanceId instance id
       * @param action action name
       * @return true if the permission exists
       * @throws ServerException when any other error occurs during permission existence checking
       */
      boolean exists(String userId, String instanceId, String action) throws ServerException;
    
      /**
       * Removes permissions of user related to the particular instance of specified domain
       *
       * @param userId user id
       * @param instanceId instance id
       * @throws NotFoundException when permissions with given user and domain and instance was not
       *     found
       * @throws ServerException when any other error occurs during permissions removing
       */
      void remove(String userId, String instanceId) throws ServerException, NotFoundException;
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission-shared/pom.xml
    ================================================
    
    
    
        4.0.0
        
            che-multiuser-api
            org.eclipse.che.multiuser
            7.118.0-SNAPSHOT
        
        che-multiuser-api-permission-shared
        jar
        Che Multiuser :: Permissions :: Shared
        
            
                org.eclipse.che.core
                che-core-api-dto
            
            
                org.eclipse.che.core
                che-core-commons-annotations
            
        
    
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission-shared/src/main/java/org/eclipse/che/multiuser/api/permission/shared/dto/DomainDto.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.shared.dto;
    
    import java.util.List;
    import org.eclipse.che.dto.shared.DTO;
    import org.eclipse.che.multiuser.api.permission.shared.model.PermissionsDomain;
    
    /**
     * @author Sergii Leschenko
     */
    @DTO
    public interface DomainDto extends PermissionsDomain {
      @Override
      String getId();
    
      void setId(String id);
    
      DomainDto withId(String id);
    
      @Override
      List getAllowedActions();
    
      void setAllowedActions(List allowedActions);
    
      DomainDto withAllowedActions(List allowedActions);
    
      @Override
      Boolean isInstanceRequired();
    
      void setInstanceRequired(Boolean isInstanceRequired);
    
      DomainDto withInstanceRequired(Boolean isInstanceRequired);
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission-shared/src/main/java/org/eclipse/che/multiuser/api/permission/shared/dto/PermissionsDto.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.shared.dto;
    
    import java.util.List;
    import org.eclipse.che.dto.shared.DTO;
    import org.eclipse.che.multiuser.api.permission.shared.model.Permissions;
    
    /**
     * @author Sergii Leschenko
     */
    @DTO
    public interface PermissionsDto extends Permissions {
      @Override
      String getUserId();
    
      void setUserId(String userId);
    
      PermissionsDto withUserId(String userId);
    
      @Override
      String getDomainId();
    
      void setDomainId(String domainId);
    
      PermissionsDto withDomainId(String domainId);
    
      @Override
      String getInstanceId();
    
      void setInstanceId(String instanceId);
    
      PermissionsDto withInstanceId(String instanceId);
    
      @Override
      List getActions();
    
      void setActions(List actions);
    
      PermissionsDto withActions(List actions);
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission-shared/src/main/java/org/eclipse/che/multiuser/api/permission/shared/event/EventType.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.shared.event;
    
    /**
     * Defines list of event types related to permissions.
     *
     * @author Anton Korneta
     */
    public enum EventType {
      PERMISSIONS_ADDED,
    
      PERMISSIONS_REMOVED
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission-shared/src/main/java/org/eclipse/che/multiuser/api/permission/shared/event/PermissionsEvent.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.shared.event;
    
    import org.eclipse.che.commons.annotation.Nullable;
    import org.eclipse.che.multiuser.api.permission.shared.model.Permissions;
    
    /**
     * The base interface for all events related to permissions.
     *
     * @author Anton Korneta
     */
    public interface PermissionsEvent {
    
      /** Returns the permissions related to this event. */
      Permissions getPermissions();
    
      /** Returns concrete event type, see {@link EventType}. */
      EventType getType();
    
      /** Returns name of user who acted with permission or null if user is undefined. */
      @Nullable
      String getInitiator();
    }
    
    
    ================================================
    FILE: multiuser/api/che-multiuser-api-permission-shared/src/main/java/org/eclipse/che/multiuser/api/permission/shared/model/Permissions.java
    ================================================
    /*
     * Copyright (c) 2012-2025 Red Hat, Inc.
     * This program and the accompanying materials are made
     * available under the terms of the Eclipse Public License 2.0
     * which is available at https://www.eclipse.org/legal/epl-2.0/
     *
     * SPDX-License-Identifier: EPL-2.0
     *
     * Contributors:
     *   Red Hat, Inc. - initial API and implementation
     */
    package org.eclipse.che.multiuser.api.permission.shared.model;
    
    import java.util.List;
    import org.eclipse.che.commons.annotation.Nullable;
    
    /**
     * Represents users' permissions to access to some resources
     *
     * @author Sergii Leschenko
     */
    public interface Permissions {
      /**
       * Returns user id
       *
       * 

    Note: also supported '*' for marking all users */ String getUserId(); /** Returns domain id */ String getDomainId(); /** * Returns instance id. It is optional and can be null if domain supports it * * @see PermissionsDomain#isInstanceRequired() */ @Nullable String getInstanceId(); /** List of actions which user can perform for particular instance */ List getActions(); } ================================================ FILE: multiuser/api/che-multiuser-api-permission-shared/src/main/java/org/eclipse/che/multiuser/api/permission/shared/model/PermissionsDomain.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.api.permission.shared.model; import java.util.List; /** * Describes permissions domain * * @author Sergii Leschenko * @author gazarenkov */ public interface PermissionsDomain { /** * @return id of permissions domain */ String getId(); /** * @return true if domain requires non nullable value for instance field or false otherwise */ Boolean isInstanceRequired(); /** * @return list actions which are allowed for domain */ List getAllowedActions(); } ================================================ FILE: multiuser/api/pom.xml ================================================ 4.0.0 che-multiuser-parent org.eclipse.che.multiuser 7.118.0-SNAPSHOT ../pom.xml che-multiuser-api pom Che Multiuser :: API Parent che-multiuser-api-authentication-commons che-multiuser-api-permission-shared che-multiuser-api-permission che-multiuser-api-authorization che-multiuser-api-authorization-impl ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/pom.xml ================================================ 4.0.0 che-multiuser-machine-auth org.eclipse.che.multiuser 7.118.0-SNAPSHOT che-multiuser-machine-authentication jar Che Multiuser :: Machine Authentication ${project.build.directory}/generated-sources/dto/ com.google.code.gson gson com.google.guava guava com.google.inject guice com.google.inject.extensions guice-persist io.jsonwebtoken jjwt-api jakarta.annotation jakarta.annotation-api jakarta.inject jakarta.inject-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-api-user org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.core che-core-commons-lang org.eclipse.che.multiuser che-multiuser-api-authentication-commons org.eclipse.che.multiuser che-multiuser-api-authorization org.eclipse.che.multiuser che-multiuser-api-permission org.eclipse.che.multiuser che-multiuser-machine-authentication-shared org.eclipse.persistence jakarta.persistence org.everrest everrest-core org.slf4j slf4j-api jakarta.servlet jakarta.servlet-api provided ch.qos.logback logback-classic test io.jsonwebtoken jjwt-impl test io.jsonwebtoken jjwt-jackson test io.rest-assured rest-assured test org.eclipse.che.core che-core-api-account test org.eclipse.che.core che-core-commons-json test org.eclipse.che.core che-core-commons-test test org.eclipse.che.core che-core-sql-schema test org.eclipse.persistence org.eclipse.persistence.jpa test org.everrest everrest-assured test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test org.apache.maven.plugins maven-dependency-plugin analyze org.eclipse.che.multiuser:che-multiuser-api-permission org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} maven-compiler-plugin pre-compile generate-sources compile org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} server process-sources generate org.eclipse.che.multiuser.machine.authentication.shared.dto ${dto-generator-out-directory} org.eclipse.che.multiuser.machine.authentication.server.DtoServerImpls server org.eclipse.che.multiuser che-multiuser-machine-authentication-shared ${project.version} org.apache.maven.plugins maven-jar-plugin test-jar **/spi/tck/*.* ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/MachineAuthModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; import org.eclipse.che.api.workspace.server.spi.provision.env.EnvVarProvider; import org.eclipse.che.api.workspace.server.token.MachineTokenProvider; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureAlgorithmEnvProvider; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignaturePublicKeyEnvProvider; import org.eclipse.che.multiuser.machine.authentication.server.signature.jpa.JpaSignatureKeyDao; import org.eclipse.che.multiuser.machine.authentication.server.signature.spi.SignatureKeyDao; /** * Machine auth module. * * @author Max Shaposhnik * @author Sergii Leshchenko */ public class MachineAuthModule extends AbstractModule { @Override protected void configure() { bind(MachineSessionInvalidator.class).asEagerSingleton(); bind(MachineTokenProvider.class).to(MachineTokenProviderImpl.class); bind(MachineTokenAccessFilter.class); bind(SignatureKeyManager.class); bind(SignatureKeyDao.class).to(JpaSignatureKeyDao.class); final Multibinder envVarProviders = Multibinder.newSetBinder(binder(), EnvVarProvider.class); envVarProviders.addBinding().to(SignaturePublicKeyEnvProvider.class); envVarProviders.addBinding().to(SignatureAlgorithmEnvProvider.class); final Multibinder machineAuthenticatedResources = Multibinder.newSetBinder(binder(), MachineAuthenticatedResource.class); machineAuthenticatedResources .addBinding() .toInstance( new MachineAuthenticatedResource( "/workspace", "getByKey", "getSettings", "update", "stop")); machineAuthenticatedResources .addBinding() .toInstance( new MachineAuthenticatedResource( "/ssh", "getPair", "generatePair", "createPair", "getPairs", "removePair")); machineAuthenticatedResources .addBinding() .toInstance(new MachineAuthenticatedResource("/factory", "resolveFactory")); machineAuthenticatedResources .addBinding() .toInstance( new MachineAuthenticatedResource( "/preferences", "find", "save", "update", "removePreferences")); machineAuthenticatedResources .addBinding() .toInstance(new MachineAuthenticatedResource("/activity", "active")); machineAuthenticatedResources .addBinding() .toInstance(new MachineAuthenticatedResource("project-template", "getProjectTemplates")); machineAuthenticatedResources .addBinding() .toInstance(new MachineAuthenticatedResource("/installer", "getInstallers")); bindConstant().annotatedWith(Names.named("che.auth.signature_key_size")).to(2048); bindConstant().annotatedWith(Names.named("che.auth.signature_key_algorithm")).to("RSA"); } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/MachineAuthenticatedResource.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server; import static java.util.Arrays.asList; import java.util.HashSet; import java.util.Set; /** * Describes resource which can be accessible using machine token. Consists of rest service path * with preliminary slash and set of method names. * * @author Max Shaposhnyk */ public class MachineAuthenticatedResource { private final String servicePath; private final Set methodNames; public MachineAuthenticatedResource(String servicePath, String... methodNames) { this.servicePath = servicePath; this.methodNames = new HashSet(asList(methodNames)); } public String getServicePath() { return servicePath; } public Set getMethodNames() { return methodNames; } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/MachineLoginFilter.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server; import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; import static java.lang.String.format; import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.USER_ID_CLAIM; import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.WORKSPACE_ID_CLAIM; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import java.io.IOException; import java.util.Collections; import java.util.Optional; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.multiuser.api.authentication.commons.SessionStore; import org.eclipse.che.multiuser.api.authentication.commons.filter.MultiUserEnvironmentInitializationFilter; import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor; import org.eclipse.che.multiuser.api.permission.server.PermissionChecker; /** * Handles requests that comes from machines with specific machine token. * * @author Max Shaposhnik (mshaposhnik@codenvy.com) * @author Anton Korneta */ @Singleton public class MachineLoginFilter extends MultiUserEnvironmentInitializationFilter { private final UserManager userManager; private final JwtParser jwtParser; private final PermissionChecker permissionChecker; @Inject public MachineLoginFilter( SessionStore sessionStore, RequestTokenExtractor tokenExtractor, UserManager userManager, MachineSigningKeyResolver machineKeyResolver, PermissionChecker permissionChecker) { super(sessionStore, tokenExtractor); this.userManager = userManager; this.jwtParser = Jwts.parser().setSigningKeyResolver(machineKeyResolver).build(); this.permissionChecker = permissionChecker; } @Override public void init(FilterConfig filterConfig) {} @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { try { super.doFilter(request, response, filterChain); } catch (NotMachineTokenJwtException mte) { filterChain.doFilter(request, response); } catch (JwtException e) { sendError( response, SC_UNAUTHORIZED, format("Machine token authentication failed: %s", e.getMessage())); return; } } @Override protected Optional processToken(String token) { return Optional.ofNullable(jwtParser.parseClaimsJws(token).getBody()); } @Override public Subject extractSubject(String token, Claims claims) { try { final String userId = claims.get(USER_ID_CLAIM, String.class); // check if user with such id exists final String userName = userManager.getById(userId).getName(); final String workspaceId = claims.get(WORKSPACE_ID_CLAIM, String.class); return new MachineTokenAuthorizedSubject( new SubjectImpl(userName, Collections.emptyList(), userId, token, false), permissionChecker, workspaceId); } catch (NotFoundException e) { throw new JwtException("Corresponding user doesn't exist."); } catch (ServerException | JwtException e) { throw new JwtException(e.getMessage(), e); } } @Override protected String getUserId(Claims claims) { return claims.get(USER_ID_CLAIM, String.class); } @Override protected void handleMissingToken( ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response); } @Override public void destroy() {} } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/MachineSessionInvalidator.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; /** * Clears all machine tokens associated with stopped workspace. * * @author Max Shaposhnyk */ @Singleton public class MachineSessionInvalidator implements EventSubscriber { private final MachineTokenRegistry tokenRegistry; @Inject public MachineSessionInvalidator(MachineTokenRegistry tokenRegistry) { this.tokenRegistry = tokenRegistry; } @Override public void onEvent(WorkspaceStatusEvent event) { if (WorkspaceStatus.STOPPED.equals(event.getStatus())) { tokenRegistry.removeTokens(event.getWorkspaceId()); } } @Inject private void subscribe(EventService eventService) { eventService.subscribe(this); } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/MachineSigningKeyResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server; import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.MACHINE_TOKEN_KIND; import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.WORKSPACE_ID_CLAIM; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.SigningKeyResolverAdapter; import java.security.Key; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManagerException; /** Resolves signing key pair based on workspace Id claim of token. */ @Singleton public class MachineSigningKeyResolver extends SigningKeyResolverAdapter { private final SignatureKeyManager keyManager; @Inject public MachineSigningKeyResolver(SignatureKeyManager keyManager) { this.keyManager = keyManager; } @Override public Key resolveSigningKey(JwsHeader header, Claims claims) { if (!MACHINE_TOKEN_KIND.equals(header.get("kind"))) { throw new NotMachineTokenJwtException(); } String wsId = claims.get(WORKSPACE_ID_CLAIM, String.class); if (wsId == null) { throw new JwtException( "Unable to fetch signature key pair: no workspace id present in token"); } try { return keyManager.getOrCreateKeyPair(wsId).getPublic(); } catch (SignatureKeyManagerException e) { throw new JwtException("Unable to fetch signature key pair:" + e.getMessage(), e); } } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/MachineTokenAccessFilter.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server; import com.google.common.collect.HashMultimap; import com.google.common.collect.SetMultimap; import jakarta.ws.rs.Path; import java.util.Set; import javax.inject.Inject; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.everrest.CheMethodInvokerFilter; import org.everrest.core.Filter; import org.everrest.core.resource.GenericResourceMethod; /** * Limits set of methods which can be invoked using machine token signed requests. * * @author Max Shaposhnik (mshaposh@redhat.com) */ @Filter @Path("/{path:.*}") public class MachineTokenAccessFilter extends CheMethodInvokerFilter { private final SetMultimap allowedMethodsByPath = HashMultimap.create(); @Inject public MachineTokenAccessFilter(Set resources) { for (MachineAuthenticatedResource resource : resources) { allowedMethodsByPath.putAll(resource.getServicePath(), resource.getMethodNames()); } } @Override protected void filter(GenericResourceMethod genericMethodResource, Object[] arguments) throws ForbiddenException { if (!(EnvironmentContext.getCurrent().getSubject() instanceof MachineTokenAuthorizedSubject)) { return; } if (!allowedMethodsByPath .get(genericMethodResource.getParentResource().getPathValue().getPath()) .contains(genericMethodResource.getMethod().getName())) { throw new ForbiddenException("This operation cannot be performed using machine token."); } } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/MachineTokenAuthorizedSubject.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.multiuser.api.permission.server.AuthorizedSubject; import org.eclipse.che.multiuser.api.permission.server.PermissionChecker; /** * An implementation of {@link Subject} which should be used when request was signed by machine * token. This implementation limits all workspace related permissions to the single workspace for * which the machine token was issued. * * @author Max Shaposhnik (mshaposh@redhat.com) */ public class MachineTokenAuthorizedSubject extends AuthorizedSubject { private final String claimsWorkspaceId; public MachineTokenAuthorizedSubject( Subject baseSubject, PermissionChecker permissionChecker, String claimsWorkspaceId) { super(baseSubject, permissionChecker); this.claimsWorkspaceId = claimsWorkspaceId; } @Override public boolean hasPermission(String domain, String instance, String action) { if (domain.equals("workspace") && !instance.equals(claimsWorkspaceId)) { return false; } return super.hasPermission(domain, instance, action); } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/MachineTokenProviderImpl.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server; import static java.lang.String.format; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.token.MachineTokenException; import org.eclipse.che.api.workspace.server.token.MachineTokenProvider; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.multiuser.api.permission.server.PermissionChecker; /** * Provides machine token from {@link MachineTokenRegistry}. * *

    Note that {@link MachineTokenRegistry} provides different tokens for different users. Token of * current user will be provided for agents. * * @author Sergii Leshchenko */ @Singleton public class MachineTokenProviderImpl implements MachineTokenProvider { private final PermissionChecker permissionChecker; private final MachineTokenRegistry tokenRegistry; @Inject public MachineTokenProviderImpl( PermissionChecker permissionChecker, MachineTokenRegistry tokenRegistry) { this.permissionChecker = permissionChecker; this.tokenRegistry = tokenRegistry; } @Override public String getToken(String workspaceId) throws MachineTokenException { final Subject subject = EnvironmentContext.getCurrent().getSubject(); if (subject.isAnonymous()) { throw new IllegalStateException( format( "Unable to get machine token of the workspace '%s' " + "because it does not exist for an anonymous user.", workspaceId)); } return getToken(subject.getUserId(), workspaceId); } @Override public String getToken(String userId, String workspaceId) throws MachineTokenException { // try { // if (!permissionChecker.hasPermission(userId, DOMAIN_ID, workspaceId, USE)) { // throw new MachineAccessForbidden( // format( // "The user `%s` doesn't have the required `use` permission for workspace `%s`", // userId, workspaceId)); // } // } catch (ServerException | NotFoundException | ConflictException e) { // throw new MachineTokenException(e.getMessage(), e); // } return tokenRegistry.getOrCreateToken(userId, workspaceId); } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/MachineTokenRegistry.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server; import static io.jsonwebtoken.SignatureAlgorithm.RS256; import static java.lang.String.format; import static java.time.temporal.ChronoUnit.DAYS; import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.MACHINE_TOKEN_KIND; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import java.security.PrivateKey; import java.time.Instant; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.api.workspace.server.token.MachineTokenException; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManagerException; import org.eclipse.che.multiuser.machine.authentication.shared.Constants; /** * Table-based storage of machine security tokens. Table rows is workspace id's, columns - user * id's. Table is synchronized externally as required by its javadoc. * * @author Max Shaposhnik (mshaposhnik@codenvy.com) * @see HashBasedTable */ @Singleton public class MachineTokenRegistry { private final SignatureKeyManager signatureKeyManager; private final UserManager userManager; private final Table tokens; private final ReadWriteLock lock; @Inject public MachineTokenRegistry(SignatureKeyManager signatureKeyManager, UserManager userManager) { this.signatureKeyManager = signatureKeyManager; this.userManager = userManager; this.tokens = HashBasedTable.create(); this.lock = new ReentrantReadWriteLock(); } /** * Gets or creates machine security token for user and workspace. For running workspace, there is * always at least one token for user who performed start. * * @param userId id of user to get token * @param workspaceId id of workspace to get token * @return machine security token for for given user and workspace * @throws MachineTokenException when user with given id not found or any errors occurs */ public String getOrCreateToken(String userId, String workspaceId) throws MachineTokenException { lock.writeLock().lock(); try { final Map wsRow = tokens.row(workspaceId); String token = wsRow.get(userId); if (token == null) { token = createToken(userId, workspaceId); } return token; } finally { lock.writeLock().unlock(); } } /** Creates new token with given data. */ private String createToken(String userId, String workspaceId) throws MachineTokenException { try { final PrivateKey privateKey = signatureKeyManager.getOrCreateKeyPair(workspaceId).getPrivate(); final User user = userManager.getById(userId); final Map header = new HashMap<>(2); header.put("kind", MACHINE_TOKEN_KIND); header.put("kid", workspaceId); final Map claims = new HashMap<>(); // to ensure that each token is unique claims.put(Claims.ID, UUID.randomUUID().toString()); claims.put(Constants.USER_ID_CLAIM, userId); claims.put(Constants.USER_NAME_CLAIM, user.getName()); claims.put(Constants.WORKSPACE_ID_CLAIM, workspaceId); // jwtproxy required claims claims.put(Claims.ISSUER, "wsmaster"); claims.put(Claims.AUDIENCE, workspaceId); claims.put(Claims.EXPIRATION, Instant.now().plus(365, DAYS).getEpochSecond()); claims.put(Claims.NOT_BEFORE, -1); // always claims.put(Claims.ISSUED_AT, Instant.now().getEpochSecond()); final String token = Jwts.builder().setClaims(claims).setHeader(header).signWith(RS256, privateKey).compact(); tokens.put(workspaceId, userId, token); return token; } catch (SignatureKeyManagerException | NotFoundException | ServerException ex) { throw new MachineTokenException( format( "Failed to generate machine token for user '%s' and workspace '%s'. Cause: '%s'", userId, workspaceId, ex.getMessage()), ex); } } /** * Invalidates machine security tokens for all users of given workspace. * * @param workspaceId workspace to invalidate tokens * @return the copy of the tokens row, where row is a map where key is user id and value is token */ public Map removeTokens(String workspaceId) { lock.writeLock().lock(); try { final Map rowCopy = new HashMap<>(tokens.row(workspaceId)); tokens.row(workspaceId).clear(); return rowCopy; } finally { lock.writeLock().unlock(); } } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/NotMachineTokenJwtException.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server; import io.jsonwebtoken.JwtException; public class NotMachineTokenJwtException extends JwtException { public NotMachineTokenJwtException() { super("This is not a machine token"); } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/signature/SignatureAlgorithmEnvProvider.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server.signature; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.provision.env.EnvVarProvider; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.multiuser.machine.authentication.shared.Constants; /** * Provides signature algorithm into machine environment. * * @author Anton Korneta */ public class SignatureAlgorithmEnvProvider implements EnvVarProvider { private final String algorithm; @Inject public SignatureAlgorithmEnvProvider( @Named("che.auth.signature_key_algorithm") String algorithm) { this.algorithm = algorithm; } @Override public Pair get(RuntimeIdentity runtimeIdentity) { return Pair.of(Constants.SIGNATURE_ALGORITHM_ENV, algorithm); } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/signature/SignatureKey.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server.signature; import com.google.common.annotations.Beta; /** * Defines signature key interface. * * @author Anton Korneta */ @Beta public interface SignatureKey { /** Returns algorithm of this signature key. */ String getAlgorithm(); /** Returns encoding format of this signature key. */ String getFormat(); /** Returns encoded value of this signature key. */ byte[] getEncoded(); } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/signature/SignatureKeyManager.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server.signature; import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPED; import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import jakarta.annotation.PostConstruct; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl; import org.eclipse.che.multiuser.machine.authentication.server.signature.spi.SignatureKeyDao; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Manages signature keys. * * @author Anton Korneta */ @Beta @Singleton public class SignatureKeyManager { public static final String PKCS_8 = "PKCS#8"; public static final String X_509 = "X.509"; private static final Logger LOG = LoggerFactory.getLogger(SignatureKeyManager.class); private final int keySize; private final String algorithm; private final SignatureKeyDao signatureKeyDao; private final EventService eventService; private final EventSubscriber workspaceEventsSubscriber; @Inject public SignatureKeyManager( @Named("che.auth.signature_key_size") int keySize, @Named("che.auth.signature_key_algorithm") String algorithm, EventService eventService, SignatureKeyDao signatureKeyDao) { this.keySize = keySize; this.algorithm = algorithm; this.eventService = eventService; this.signatureKeyDao = signatureKeyDao; this.workspaceEventsSubscriber = new EventSubscriber() { @Override public void onEvent(WorkspaceStatusEvent event) { if (event.getStatus() == STOPPED) { removeKeyPair(event.getWorkspaceId()); } } }; } /** * Returns instance of {@link KeyPair} for given workspace. * * @throws SignatureKeyManagerException when stored keypair is incorrect (e.g. has bad algorithm * or keyspec) or other error */ public KeyPair getOrCreateKeyPair(String workspaceId) throws SignatureKeyManagerException { SignatureKeyPair keyPair; try { try { keyPair = signatureKeyDao.get(workspaceId); } catch (NotFoundException e) { keyPair = generateKeyPair(workspaceId); } } catch (NoSuchAlgorithmException | ServerException | ConflictException ex) { LOG.error( "Failed to load signature keys for ws {}. Cause: {}", workspaceId, ex.getMessage()); throw new SignatureKeyManagerException(ex.getMessage(), ex); } return toJavaKeyPair(keyPair); } /** Removes key pair from cache and DB. */ @VisibleForTesting void removeKeyPair(String workspaceId) { try { signatureKeyDao.remove(workspaceId); LOG.debug("Removed signature key pair for ws id {}.", workspaceId); } catch (ServerException e) { LOG.error( "Unable to cleanup machine token signature keypairs for ws {}. Cause: {}", workspaceId, e.getMessage()); } } @VisibleForTesting SignatureKeyPair generateKeyPair(String workspaceId) throws NoSuchAlgorithmException, ServerException, ConflictException { try { KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm); kpg.initialize(keySize); final KeyPair pair = kpg.generateKeyPair(); final SignatureKeyPairImpl kp = new SignatureKeyPairImpl(workspaceId, pair.getPublic(), pair.getPrivate()); LOG.debug( "Generated signature key pair with ws id {} and algorithm {}.", kp.getWorkspaceId(), algorithm); return signatureKeyDao.create(kp); } catch (NoSuchAlgorithmException | ConflictException | ServerException ex) { LOG.error( "Unable to generate signature keypair for ws {}. Cause: {}", workspaceId, ex.getMessage()); throw ex; } } /** Returns key spec by key format and encoded data. */ private EncodedKeySpec getKeySpec(SignatureKey key) { switch (key.getFormat()) { case PKCS_8: return new PKCS8EncodedKeySpec(key.getEncoded()); case X_509: return new X509EncodedKeySpec(key.getEncoded()); default: throw new IllegalArgumentException( String.format("Unsupported key spec '%s' for signature keys", key.getFormat())); } } private KeyPair toJavaKeyPair(SignatureKeyPair keyPair) throws SignatureKeyManagerException { try { final PrivateKey privateKey = KeyFactory.getInstance(keyPair.getPrivateKey().getAlgorithm()) .generatePrivate(getKeySpec(keyPair.getPrivateKey())); final PublicKey publicKey = KeyFactory.getInstance(keyPair.getPublicKey().getAlgorithm()) .generatePublic(getKeySpec(keyPair.getPublicKey())); return new KeyPair(publicKey, privateKey); } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { LOG.error("Failed to convert signature key pair to Java keys. Cause: {}", ex.getMessage()); throw new SignatureKeyManagerException("Failed to convert signature key pair to Java keys."); } } @VisibleForTesting @PostConstruct void subscribe() { eventService.subscribe(workspaceEventsSubscriber); } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/signature/SignatureKeyManagerException.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server.signature; public class SignatureKeyManagerException extends Exception { public SignatureKeyManagerException(String message) { super(message); } public SignatureKeyManagerException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/signature/SignatureKeyPair.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server.signature; import com.google.common.annotations.Beta; /** * Represents signature key pair with public and private part. * * @see SignatureKey * @author Anton Korneta */ @Beta public interface SignatureKeyPair { /** Returns workspace identifier for this sign key pair. */ String getWorkspaceId(); /** Returns public part for this sign key pair. */ SignatureKey getPublicKey(); /** Returns private part for this sign key pair. */ SignatureKey getPrivateKey(); } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/signature/SignaturePublicKeyEnvProvider.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server.signature; import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.SIGNATURE_PUBLIC_KEY_ENV; import java.util.Base64; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.provision.env.EnvVarProvider; import org.eclipse.che.commons.lang.Pair; /** * Provides a public part of a signature key into machine environment. * * @author Anton Korneta */ public class SignaturePublicKeyEnvProvider implements EnvVarProvider { private final SignatureKeyManager keyManager; @Inject public SignaturePublicKeyEnvProvider(SignatureKeyManager keyManager) { this.keyManager = keyManager; } @Override public Pair get(RuntimeIdentity runtimeIdentity) throws InfrastructureException { try { return Pair.of( SIGNATURE_PUBLIC_KEY_ENV, new String( Base64.getEncoder() .encode( keyManager .getOrCreateKeyPair(runtimeIdentity.getWorkspaceId()) .getPublic() .getEncoded()))); } catch (SignatureKeyManagerException e) { throw new InfrastructureException( "Signature key pair for machine authentication cannot be retrieved. Reason: " + e.getMessage()); } } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/signature/jpa/JpaSignatureKeyDao.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server.signature.jpa; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import com.google.inject.persist.Transactional; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl; import org.eclipse.che.multiuser.machine.authentication.server.signature.spi.SignatureKeyDao; /** * JPA based implementation of {@link SignatureKeyDao}. * * @author Anton Korneta */ @Singleton public class JpaSignatureKeyDao implements SignatureKeyDao { private final Provider managerProvider; @Inject public JpaSignatureKeyDao(Provider managerProvider) { this.managerProvider = managerProvider; } @Override public SignatureKeyPairImpl create(SignatureKeyPairImpl keyPair) throws ConflictException, ServerException { requireNonNull(keyPair, "Required non-null key pair"); try { doCreate(keyPair); } catch (RuntimeException ex) { throw new ServerException(ex.getMessage(), ex); } return new SignatureKeyPairImpl(keyPair); } @Transactional protected void doCreate(SignatureKeyPairImpl key) { final EntityManager manager = managerProvider.get(); manager.persist(key); manager.flush(); } @Override public void remove(String workspaceId) throws ServerException { requireNonNull(workspaceId, "Required non-null workspace Id"); try { doRemove(workspaceId); } catch (RuntimeException ex) { throw new ServerException(ex.getMessage(), ex); } } @Transactional protected void doRemove(String workspaceId) { final EntityManager manager = managerProvider.get(); final SignatureKeyPairImpl keyPair = manager.find(SignatureKeyPairImpl.class, workspaceId); if (keyPair != null) { manager.remove(keyPair); manager.flush(); } } @Override @Transactional public SignatureKeyPairImpl get(String workspaceId) throws NotFoundException, ServerException { final EntityManager manager = managerProvider.get(); try { return new SignatureKeyPairImpl( manager .createNamedQuery("SignKeyPair.getAll", SignatureKeyPairImpl.class) .setParameter("workspaceId", workspaceId) .getSingleResult()); } catch (NoResultException x) { throw new NotFoundException( format("Signature key pair for workspace '%s' doesn't exist", workspaceId)); } catch (RuntimeException ex) { throw new ServerException(ex.getMessage(), ex); } } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/signature/model/impl/SignatureKeyImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl; import java.security.Key; import java.util.Arrays; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKey; /** * @author Anton Korneta */ @Entity(name = "SignKey") @Table(name = "che_sign_key") public class SignatureKeyImpl implements SignatureKey { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "algorithm") private String algorithm; @Column(name = "encoding_format") private String format; @Column(name = "encoded_value") private byte[] encoded; public SignatureKeyImpl() {} public SignatureKeyImpl(Key publicKey) { this(publicKey.getEncoded(), publicKey.getAlgorithm(), publicKey.getFormat()); } public SignatureKeyImpl(byte[] encoded, String algorithm, String format) { this.encoded = encoded; this.algorithm = algorithm; this.format = format; } @Override public String getAlgorithm() { return algorithm; } @Override public String getFormat() { return format; } @Override public byte[] getEncoded() { return encoded; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof SignatureKeyImpl)) { return false; } final SignatureKeyImpl that = (SignatureKeyImpl) obj; return Objects.equals(id, that.id) && Objects.equals(algorithm, that.algorithm) && Objects.equals(format, that.format) && Arrays.equals(encoded, that.encoded); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(algorithm); hash = 31 * hash + Objects.hashCode(format); hash = 31 * hash + Arrays.hashCode(encoded); return hash; } @Override public String toString() { return "SignatureKeyImpl{" + "id=" + id + ", algorithm='" + algorithm + '\'' + ", format='" + format + '\'' + ", encoded=" + Arrays.toString(encoded) + '}'; } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/signature/model/impl/SignatureKeyPairImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Objects; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToOne; import javax.persistence.Table; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyPair; /** * @author Anton Korneta */ @Entity(name = "SignKeyPair") @Table(name = "che_sign_key_pair") @NamedQueries({ @NamedQuery( name = "SignKeyPair.getAll", query = "SELECT kp FROM SignKeyPair kp WHERE kp.workspaceId = :workspaceId"), }) public class SignatureKeyPairImpl implements SignatureKeyPair { @Id @Column(name = "workspace_id") private String workspaceId; @OneToOne @JoinColumn(name = "workspace_id", insertable = false, updatable = false) private WorkspaceImpl workspace; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "public_key") private SignatureKeyImpl publicKey; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "private_key") private SignatureKeyImpl privateKey; public SignatureKeyPairImpl() {} public SignatureKeyPairImpl(SignatureKeyPairImpl keyPair) { this(keyPair.getWorkspaceId(), keyPair.getPublicKey(), keyPair.getPrivateKey()); } public SignatureKeyPairImpl(String workspaceId, PublicKey publicKey, PrivateKey privateKey) { this(workspaceId, new SignatureKeyImpl(publicKey), new SignatureKeyImpl(privateKey)); } public SignatureKeyPairImpl( String workspaceId, SignatureKeyImpl publicKey, SignatureKeyImpl privateKey) { this.workspaceId = workspaceId; this.publicKey = publicKey; this.privateKey = privateKey; } @Override public String getWorkspaceId() { return workspaceId; } public void setWorkspaceId(String workspaceId) { this.workspaceId = workspaceId; } @Override public SignatureKeyImpl getPublicKey() { return publicKey; } public void setPublicKey(SignatureKeyImpl publicKey) { this.publicKey = publicKey; } @Override public SignatureKeyImpl getPrivateKey() { return privateKey; } public void setPrivateKey(SignatureKeyImpl privateKey) { this.privateKey = privateKey; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof SignatureKeyPairImpl)) { return false; } final SignatureKeyPairImpl that = (SignatureKeyPairImpl) obj; return Objects.equals(workspaceId, that.workspaceId) && Objects.equals(publicKey, that.publicKey) && Objects.equals(privateKey, that.privateKey); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(workspaceId); hash = 31 * hash + Objects.hashCode(publicKey); hash = 31 * hash + Objects.hashCode(privateKey); return hash; } @Override public String toString() { return "SignatureKeyPairImpl{" + "workspaceId='" + workspaceId + '\'' + ", publicKey=" + publicKey + ", privateKey=" + privateKey + '}'; } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/main/java/org/eclipse/che/multiuser/machine/authentication/server/signature/spi/SignatureKeyDao.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server.signature.spi; import com.google.common.annotations.Beta; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl; /** * Defines data access object for {@link SignatureKeyPairImpl}. * * @author Anton Korneta */ @Beta public interface SignatureKeyDao { /** * Creates signature key pair. * * @param keyPair signature key pair to create * @throws ConflictException when signature key pair with given id already exists * @throws ServerException when any errors occur while creating signature key pair */ SignatureKeyPairImpl create(SignatureKeyPairImpl keyPair) throws ConflictException, ServerException; /** * Removes signature key pair with given workspace id. * * @param workspaceId workspace identifier to remove keypair from * @throws ServerException when any errors occur while removing signature key pair */ void remove(String workspaceId) throws ServerException; /** * Returns signature key pair for given workspace id. * * @param workspaceId identifier of workspace which key pair belongs to * @return signature key pair for the given workspace * @throws NotFoundException when any errors occur while fetching the key pairs */ SignatureKeyPairImpl get(String workspaceId) throws NotFoundException, ServerException; } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/test/java/org/eclipse/che/multiuser/machine/authentication/server/MachineLoginFilterTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server; import static io.jsonwebtoken.SignatureAlgorithm.RS512; import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.MACHINE_TOKEN_KIND; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ClaimsBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.impl.DefaultClaims; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.multiuser.api.authentication.commons.SessionStore; import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor; import org.eclipse.che.multiuser.api.permission.server.PermissionChecker; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager; import org.eclipse.che.multiuser.machine.authentication.shared.Constants; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link MachineLoginFilter}. * * @author Anton Korneta */ @Listeners(MockitoTestNGListener.class) public class MachineLoginFilterTest { private static final int KEY_SIZE = 2048; private static final String REQUEST_SCHEME = "https"; private static final String SIGNATURE_ALGORITHM = "RSA"; private static final String WORKSPACE_ID = "workspace31"; private static final Subject SUBJECT = new SubjectImpl("test_user", Collections.emptyList(), "test_user31", "userToken", false); private static final Map HEADER = new HashMap<>(); private static final ClaimsBuilder CLAIMS_BUILDER = Jwts.claims(); static { HEADER.put("kind", MACHINE_TOKEN_KIND); CLAIMS_BUILDER.add(Constants.WORKSPACE_ID_CLAIM, WORKSPACE_ID); CLAIMS_BUILDER.add(Constants.USER_ID_CLAIM, SUBJECT.getUserId()); CLAIMS_BUILDER.add(Constants.USER_NAME_CLAIM, SUBJECT.getUserName()); CLAIMS_BUILDER.add(Claims.ID, "84123-132-fn31"); } @Mock private UserManager userManagerMock; @Mock private RequestTokenExtractor tokenExtractorMock; @Mock private SignatureKeyManager keyManagerMock; @Mock private PermissionChecker permissionCheckerMock; @Mock private FilterChain chainMock; @Mock private HttpSession sessionMock; @Mock private HttpServletResponse responseMock; private MachineLoginFilter machineLoginFilter; @BeforeMethod private void setUp() throws Exception { final User userMock = mock(User.class); final KeyPairGenerator kpg = KeyPairGenerator.getInstance(SIGNATURE_ALGORITHM); kpg.initialize(KEY_SIZE); final KeyPair keyPair = kpg.generateKeyPair(); final String token = Jwts.builder() .setClaims(CLAIMS_BUILDER.build()) .setHeader(HEADER) .signWith(RS512, keyPair.getPrivate()) .compact(); machineLoginFilter = new MachineLoginFilter( new SessionStore(), tokenExtractorMock, userManagerMock, new MachineSigningKeyResolver(keyManagerMock), permissionCheckerMock); lenient().when(tokenExtractorMock.getToken(any(HttpServletRequest.class))).thenReturn(token); lenient().when(keyManagerMock.getOrCreateKeyPair(eq(WORKSPACE_ID))).thenReturn(keyPair); lenient().when(userMock.getName()).thenReturn(SUBJECT.getUserName()); lenient().when(userManagerMock.getById(SUBJECT.getUserId())).thenReturn(userMock); } @Test public void testProcessRequestWithValidToken() throws Exception { machineLoginFilter.doFilter(getRequestMock(), responseMock, chainMock); verify(keyManagerMock, atLeastOnce()).getOrCreateKeyPair(eq(WORKSPACE_ID)); verify(userManagerMock).getById(anyString()); verifyNoMoreInteractions(responseMock); } @Test public void testNotProceedRequestWhenSignatureCheckIsFailed() throws Exception { final HttpServletRequest requestMock = getRequestMock(); final KeyPairGenerator kpg = KeyPairGenerator.getInstance(SIGNATURE_ALGORITHM); kpg.initialize(KEY_SIZE); final KeyPair pair = kpg.generateKeyPair(); when(keyManagerMock.getOrCreateKeyPair(eq(WORKSPACE_ID))).thenReturn(pair); machineLoginFilter.doFilter(requestMock, responseMock, chainMock); verify(tokenExtractorMock, atLeastOnce()).getToken(any(HttpServletRequest.class)); verify(responseMock) .sendError( 401, "Machine token authentication failed: JWT signature does not match locally computed signature." + " JWT validity cannot be asserted and should not be trusted."); } @Test public void testNotProceedRequestWhenNoWorkspaceIdClaim() throws Exception { final HttpServletRequest requestMock = getRequestMock(); final KeyPairGenerator kpg = KeyPairGenerator.getInstance(SIGNATURE_ALGORITHM); kpg.initialize(KEY_SIZE); final KeyPair pair = kpg.generateKeyPair(); final Claims badClaims = new DefaultClaims( Map.of(Constants.USER_ID_CLAIM, SUBJECT.getUserId(), Claims.ID, "84123-132-fn31")); final String token = Jwts.builder() .setClaims(badClaims) .setHeader(HEADER) .signWith(RS512, pair.getPrivate()) .compact(); when(tokenExtractorMock.getToken(any(HttpServletRequest.class))).thenReturn(token); machineLoginFilter.doFilter(requestMock, responseMock, chainMock); verify(tokenExtractorMock, atLeastOnce()).getToken(any(HttpServletRequest.class)); verify(responseMock) .sendError( 401, "Machine token authentication failed: Unable to fetch signature key pair: no workspace id present in token"); } @Test public void testProceedRequestWhenEmptyTokenProvided() throws Exception { final HttpServletRequest requestMock = getRequestMock(); when(tokenExtractorMock.getToken(any(HttpServletRequest.class))).thenReturn(null); machineLoginFilter.doFilter(requestMock, responseMock, chainMock); verify(tokenExtractorMock, atLeastOnce()).getToken(any(HttpServletRequest.class)); verify(chainMock).doFilter(requestMock, responseMock); verifyNoMoreInteractions(keyManagerMock); verifyNoMoreInteractions(userManagerMock); verifyNoMoreInteractions(responseMock); } @Test public void testSetErrorInResponseWhenNoUserFoundForProvidedToken() throws Exception { when(userManagerMock.getById(anyString())).thenThrow(new NotFoundException("User not found")); machineLoginFilter.doFilter(getRequestMock(), responseMock, chainMock); verify(keyManagerMock, atLeastOnce()).getOrCreateKeyPair(eq(WORKSPACE_ID)); verify(userManagerMock).getById(anyString()); verify(responseMock) .sendError(401, "Machine token authentication failed: Corresponding user doesn't exist."); } @Test public void testSetErrorInResponseWhenUnableToGetUserForProvidedToken() throws Exception { when(userManagerMock.getById(anyString())).thenThrow(new ServerException("err")); machineLoginFilter.doFilter(getRequestMock(), responseMock, chainMock); verify(keyManagerMock, atLeastOnce()).getOrCreateKeyPair(eq(WORKSPACE_ID)); verify(userManagerMock).getById(anyString()); verify(responseMock) .sendError(eq(401), argThat(s -> s.startsWith("Machine token authentication failed:"))); } private HttpServletRequest getRequestMock() { final HttpServletRequest request = mock(HttpServletRequest.class); lenient().when(request.getSession(true)).thenReturn(sessionMock); lenient().when(request.getScheme()).thenReturn(REQUEST_SCHEME); return request; } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/test/java/org/eclipse/che/multiuser/machine/authentication/server/signature/SignatureKeyManagerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.server.signature; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import java.security.Key; import java.security.KeyPair; import java.security.KeyPairGenerator; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyImpl; import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl; import org.eclipse.che.multiuser.machine.authentication.server.signature.spi.SignatureKeyDao; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link SignatureKeyManager}. * * @author Anton Korneta */ @Listeners(MockitoTestNGListener.class) public class SignatureKeyManagerTest { private static final int KEY_SIZE = 512; private static final String ALGORITHM = "RSA"; @Mock SignatureKeyDao signatureKeyDao; @Mock EventService eventService; @Captor private ArgumentCaptor> captor; private KeyPairGenerator kpg; private SignatureKeyManager signatureKeyManager; @BeforeMethod public void createEntities() throws Exception { kpg = KeyPairGenerator.getInstance(ALGORITHM); kpg.initialize(KEY_SIZE); signatureKeyManager = new SignatureKeyManager(KEY_SIZE, ALGORITHM, eventService, signatureKeyDao); } @Test public void shouldRemoveKeyPairOnWorkspaceStop() throws Exception { final String wsId = "ws123"; signatureKeyManager.subscribe(); verify(eventService).subscribe(captor.capture()); final EventSubscriber subscriber = captor.getValue(); subscriber.onEvent( DtoFactory.newDto(WorkspaceStatusEvent.class) .withStatus(WorkspaceStatus.STOPPED) .withWorkspaceId(wsId)); verify(signatureKeyDao, times(1)).remove(eq(wsId)); } @Test(expectedExceptions = SignatureKeyManagerException.class) public void shouldThrowsExceptionWhenAlgorithmIsNotSupported() throws Exception { final SignatureKeyImpl publicKey = new SignatureKeyImpl(new byte[] {}, "ECDH", "PKCS#15"); final SignatureKeyImpl privateKey = new SignatureKeyImpl(new byte[] {}, "ECDH", "PKCS#3"); final SignatureKeyPairImpl kp = new SignatureKeyPairImpl("id_" + 1, publicKey, privateKey); doReturn(kp).when(signatureKeyDao).get(anyString()); signatureKeyManager.getOrCreateKeyPair("ws1"); verify(signatureKeyDao).get(anyString()); } @Test public void shouldReturnSignatureKeys() throws Exception { String wsId = "WS_id_1"; final SignatureKeyPairImpl kp = newKeyPair(wsId); when(signatureKeyDao.get(anyString())).thenReturn(kp); final KeyPair cachedPair = signatureKeyManager.getOrCreateKeyPair(wsId); assertNotNull(cachedPair); assertKeys(cachedPair.getPublic(), kp.getPublicKey()); assertKeys(cachedPair.getPrivate(), kp.getPrivateKey()); } private SignatureKeyPairImpl newKeyPair(String id) { final KeyPair pair = kpg.generateKeyPair(); return new SignatureKeyPairImpl(id, pair.getPublic(), pair.getPrivate()); } private static void assertKeys(Key key, SignatureKey signatureKey) { assertEquals(key.getAlgorithm(), signatureKey.getAlgorithm()); assertEquals(key.getEncoded(), signatureKey.getEncoded()); assertEquals(key.getFormat(), signatureKey.getFormat()); } } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule ================================================ org.eclipse.che.multiuser.machine.authentication.server.signature.jpa.SignatureKeyTckModule ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication-shared/pom.xml ================================================ 4.0.0 che-multiuser-machine-auth org.eclipse.che.multiuser 7.118.0-SNAPSHOT che-multiuser-machine-authentication-shared jar Che Multiuser :: Machine Authentication Shared org.eclipse.che.core che-core-api-dto ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication-shared/src/main/java/org/eclipse/che/multiuser/machine/authentication/shared/Constants.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.shared; /** * @author Anton Korneta */ public class Constants { public static final String USER_ID_CLAIM = "uid"; public static final String USER_NAME_CLAIM = "uname"; public static final String WORKSPACE_ID_CLAIM = "wsid"; public static final String MACHINE_TOKEN_KIND = "machine_token"; public static final String SIGNATURE_ALGORITHM_ENV = "CHE_MACHINE_AUTH_SIGNATURE__ALGORITHM"; public static final String SIGNATURE_PUBLIC_KEY_ENV = "CHE_MACHINE_AUTH_SIGNATURE__PUBLIC__KEY"; private Constants() {} } ================================================ FILE: multiuser/machine-auth/che-multiuser-machine-authentication-shared/src/main/java/org/eclipse/che/multiuser/machine/authentication/shared/dto/MachineTokenDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.machine.authentication.shared.dto; import org.eclipse.che.dto.shared.DTO; /** * Representation of machine token, that needed for communication between workspace master and * workspace agents. * * @author Max Shaposhnik (mshaposhnik@codenvy.com) */ @DTO public interface MachineTokenDto { String getUserId(); void setUserId(String userId); MachineTokenDto withUserId(String userId); String getWorkspaceId(); void setWorkspaceId(String workspaceId); MachineTokenDto withWorkspaceId(String workspaceId); String getMachineToken(); void setMachineToken(String machineToken); MachineTokenDto withMachineToken(String machineToken); } ================================================ FILE: multiuser/machine-auth/pom.xml ================================================ 4.0.0 che-multiuser-parent org.eclipse.che.multiuser 7.118.0-SNAPSHOT ../pom.xml che-multiuser-machine-auth pom Che Multiuser :: Machine Auth Parent che-multiuser-machine-authentication-shared che-multiuser-machine-authentication ================================================ FILE: multiuser/oidc/pom.xml ================================================ 4.0.0 che-multiuser-parent org.eclipse.che.multiuser 7.118.0-SNAPSHOT che-multiuser-oidc jar Che Multiuser :: OIDC com.auth0 jwks-rsa com.fasterxml.jackson.core jackson-core com.fasterxml.jackson.core jackson-databind com.google.guava guava io.jsonwebtoken jjwt-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-inject org.eclipse.che.multiuser che-multiuser-api-authentication-commons org.eclipse.che.multiuser che-multiuser-api-authorization org.slf4j slf4j-api jakarta.inject jakarta.inject-api provided jakarta.servlet jakarta.servlet-api provided org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test ================================================ FILE: multiuser/oidc/src/main/java/org/eclipse/che/multiuser/oidc/OIDCInfo.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.oidc; import java.util.Optional; import java.util.StringJoiner; /** OIDCInfo - POJO object to store information about OIDC api. */ public class OIDCInfo { private final String tokenPublicEndpoint; private final String endSessionPublicEndpoint; private final String userInfoPublicEndpoint; private final String userInfoInternalEndpoint; private final String jwksPublicUri; private final String jwksInternalUri; private final String authServerURL; private final String authServerPublicURL; public OIDCInfo( String tokenPublicEndpoint, String endSessionPublicEndpoint, String userInfoPublicEndpoint, String userInfoInternalEndpoint, String jwksPublicUri, String jwksInternalUri, String authServerURL, String authServerPublicURL) { this.tokenPublicEndpoint = tokenPublicEndpoint; this.endSessionPublicEndpoint = endSessionPublicEndpoint; this.userInfoPublicEndpoint = userInfoPublicEndpoint; this.userInfoInternalEndpoint = userInfoInternalEndpoint; this.jwksPublicUri = jwksPublicUri; this.jwksInternalUri = jwksInternalUri; this.authServerURL = authServerURL; this.authServerPublicURL = authServerPublicURL; } /** * @return public url to retrieve token */ public String getTokenPublicEndpoint() { return tokenPublicEndpoint; } /** * @return public url to get user profile information. */ public String getUserInfoPublicEndpoint() { return userInfoPublicEndpoint; } /** * @return internal network url to get user profile information. */ public String getUserInfoInternalEndpoint() { return userInfoInternalEndpoint; } /** * @return public url to retrieve JWK public key for token validation. */ public String getJwksPublicUri() { return jwksPublicUri; } /** * @return internal network url to retrieve JWK public key for token validation. */ public String getJwksInternalUri() { return jwksInternalUri; } /** * @return OIDC auth endpoint url. Url will be internal if internal network enabled, otherwise url * will be public. */ public String getAuthServerURL() { return authServerURL; } /** * @return public OIDC auth endpoint url. */ public String getAuthServerPublicURL() { return authServerPublicURL; } @Override public String toString() { return new StringJoiner(", ", OIDCInfo.class.getSimpleName() + "[", "]") .add("tokenPublicEndpoint='" + tokenPublicEndpoint + "'") .add("userInfoPublicEndpoint='" + userInfoPublicEndpoint + "'") .add("userInfoInternalEndpoint='" + userInfoInternalEndpoint + "'") .add("jwksPublicUri='" + jwksPublicUri + "'") .add("jwksInternalUri='" + jwksInternalUri + "'") .add("authServerURL='" + authServerURL + "'") .add("authServerPublicURL='" + authServerPublicURL + "'") .toString(); } public Optional getEndSessionPublicEndpoint() { return Optional.ofNullable(endSessionPublicEndpoint); } } ================================================ FILE: multiuser/oidc/src/main/java/org/eclipse/che/multiuser/oidc/OIDCInfoProvider.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.oidc; import static com.google.common.base.MoreObjects.firstNonNull; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.proxy.ProxyAuthenticator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * OIDCInfoProvider retrieves OpenID Connect (OIDC) configuration for well-known endpoint. This * information is useful to provide access to the OIDC api. */ public class OIDCInfoProvider implements Provider { private static final Logger LOG = LoggerFactory.getLogger(OIDCInfoProvider.class); private static final String OIDC_SETTING_PREFIX = "che.oidc."; public static final String AUTH_SERVER_URL_SETTING = OIDC_SETTING_PREFIX + "auth_server_url"; public static final String AUTH_SERVER_URL_INTERNAL_SETTING = OIDC_SETTING_PREFIX + "auth_internal_server_url"; public static final String OIDC_PROVIDER_SETTING = OIDC_SETTING_PREFIX + "oidc_provider"; public static final String OIDC_USERNAME_CLAIM_SETTING = OIDC_SETTING_PREFIX + "username_claim"; public static final String OIDC_USERNAME_PREFIX_SETTING = OIDC_SETTING_PREFIX + "username_prefix"; public static final String OIDC_EMAIL_CLAIM_SETTING = OIDC_SETTING_PREFIX + "email_claim"; public static final String OIDC_GROUPS_CLAIM_SETTING = OIDC_SETTING_PREFIX + "groups_claim"; public static final String OIDC_GROUP_PREFIX_SETTING = OIDC_SETTING_PREFIX + "group_prefix"; public static final String OIDC_ALLOWED_CLOCK_SKEW_SEC = OIDC_SETTING_PREFIX + "allowed_clock_skew_sec"; protected String serverURL; protected String serverInternalURL; protected String oidcProviderUrl; @Inject public OIDCInfoProvider( @Nullable @Named(AUTH_SERVER_URL_SETTING) String serverURL, @Nullable @Named(AUTH_SERVER_URL_INTERNAL_SETTING) String serverInternalURL, @Nullable @Named(OIDC_PROVIDER_SETTING) String oidcProviderUrl) { this.serverURL = serverURL; this.serverInternalURL = serverInternalURL; this.oidcProviderUrl = oidcProviderUrl; } /** * @return OIDCInfo with OIDC settings information. */ @Override public OIDCInfo get() { this.validate(); String serverAuthUrl = (serverInternalURL != null) ? serverInternalURL : serverURL; String wellKnownEndpoint = this.getWellKnownEndpoint(serverAuthUrl); LOG.info("Retrieving OpenId configuration from endpoint: {}", wellKnownEndpoint); ProxyAuthenticator.initAuthenticator(wellKnownEndpoint); try (InputStream inputStream = new URL(wellKnownEndpoint).openStream()) { final JsonParser parser = new JsonFactory().createParser(inputStream); final TypeReference> typeReference = new TypeReference<>() {}; Map openIdConfiguration = new ObjectMapper().reader().readValue(parser, typeReference); LOG.info("openid configuration = {}", openIdConfiguration); String tokenPublicEndPoint = setPublicUrl((String) openIdConfiguration.get("token_endpoint")); String userInfoPublicEndpoint = setPublicUrl((String) openIdConfiguration.get("userinfo_endpoint")); String endSessionPublicEndpoint = openIdConfiguration.containsKey("end_session_endpoint") ? setPublicUrl((String) openIdConfiguration.get("end_session_endpoint")) : null; String jwksPublicUri = setPublicUrl((String) openIdConfiguration.get("jwks_uri")); String jwksInternalUri = setInternalUrl(jwksPublicUri); String userInfoInternalEndpoint = setInternalUrl(userInfoPublicEndpoint); return new OIDCInfo( tokenPublicEndPoint, endSessionPublicEndpoint, userInfoPublicEndpoint, userInfoInternalEndpoint, jwksPublicUri, jwksInternalUri, serverAuthUrl, serverURL); } catch (IOException e) { throw new RuntimeException( "Exception while retrieving OpenId configuration from endpoint: " + wellKnownEndpoint, e); } finally { ProxyAuthenticator.resetAuthenticator(); } } private String getWellKnownEndpoint(String serverAuthUrl) { String wellKnownEndpoint = firstNonNull(oidcProviderUrl, constructServerAuthUrl(serverAuthUrl)); if (!wellKnownEndpoint.endsWith("/")) { wellKnownEndpoint = wellKnownEndpoint + "/"; } wellKnownEndpoint += ".well-known/openid-configuration"; return wellKnownEndpoint; } protected String constructServerAuthUrl(String serverAuthUrl) { return serverAuthUrl; } protected void validate() { if (serverURL == null && serverInternalURL == null && oidcProviderUrl == null) { throw new RuntimeException( "Either the '" + AUTH_SERVER_URL_SETTING + "' or '" + AUTH_SERVER_URL_INTERNAL_SETTING + "' or '" + OIDC_PROVIDER_SETTING + "' property should be set"); } } private String setInternalUrl(String endpointUrl) { if (serverURL != null && serverInternalURL != null) { return endpointUrl.replace(serverURL, serverInternalURL); } return null; } private String setPublicUrl(String endpointUrl) { if (serverURL != null && serverInternalURL != null) { return endpointUrl.replace(serverInternalURL, serverURL); } return endpointUrl; } } ================================================ FILE: multiuser/oidc/src/main/java/org/eclipse/che/multiuser/oidc/OIDCJwkProvider.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.oidc; import com.auth0.jwk.GuavaCachedJwkProvider; import com.auth0.jwk.JwkProvider; import com.auth0.jwk.UrlJwkProvider; import com.google.common.base.Strings; import java.net.MalformedURLException; import java.net.URL; import javax.inject.Inject; import javax.inject.Provider; import org.eclipse.che.inject.ConfigurationException; /** Constructs {@link UrlJwkProvider} based on Jwk endpoint from keycloak settings */ public class OIDCJwkProvider implements Provider { private final JwkProvider jwkProvider; @Inject public OIDCJwkProvider(OIDCInfo oidcInfo) throws MalformedURLException { final String jwksUrl = Strings.isNullOrEmpty(oidcInfo.getJwksInternalUri()) ? oidcInfo.getJwksPublicUri() : oidcInfo.getJwksInternalUri(); if (jwksUrl == null) { throw new ConfigurationException("Jwks endpoint url not found in keycloak settings"); } this.jwkProvider = new GuavaCachedJwkProvider(new UrlJwkProvider(new URL(jwksUrl))); } @Override public JwkProvider get() { return jwkProvider; } } ================================================ FILE: multiuser/oidc/src/main/java/org/eclipse/che/multiuser/oidc/OIDCJwtParserProvider.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.oidc; import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_ALLOWED_CLOCK_SKEW_SEC; import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SigningKeyResolver; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; /** Provides instance of {@link JwtParser} */ @Singleton public class OIDCJwtParserProvider implements Provider { private final JwtParser jwtParser; @Inject public OIDCJwtParserProvider( @Named(OIDC_ALLOWED_CLOCK_SKEW_SEC) long allowedClockSkewSec, SigningKeyResolver signingKeyResolver) { this.jwtParser = Jwts.parser() .setSigningKeyResolver(signingKeyResolver) .setAllowedClockSkewSeconds(allowedClockSkewSec) .build(); } @Override public JwtParser get() { return jwtParser; } } ================================================ FILE: multiuser/oidc/src/main/java/org/eclipse/che/multiuser/oidc/OIDCSigningKeyResolver.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.oidc; import com.auth0.jwk.JwkException; import com.auth0.jwk.JwkProvider; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.SigningKeyResolverAdapter; import java.security.Key; import java.security.PublicKey; import javax.inject.Inject; import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Resolves signing key based on id from JWT header */ @Singleton public class OIDCSigningKeyResolver extends SigningKeyResolverAdapter { private final JwkProvider jwkProvider; private static final Logger LOG = LoggerFactory.getLogger(OIDCSigningKeyResolver.class); @Inject protected OIDCSigningKeyResolver(JwkProvider jwkProvider) { this.jwkProvider = jwkProvider; } @Override public Key resolveSigningKey(JwsHeader header, byte[] content) { return getJwtPublicKey(header); } @Override public Key resolveSigningKey(JwsHeader header, Claims claims) { return getJwtPublicKey(header); } protected synchronized PublicKey getJwtPublicKey(JwsHeader header) { String kid = header.getKeyId(); if (kid == null) { LOG.warn( "'kid' is missing in the JWT token header. This is not possible to validate the token with OIDC provider keys"); throw new JwtException("'kid' is missing in the JWT token header."); } try { return jwkProvider.get(kid).getPublicKey(); } catch (JwkException e) { throw new JwtException( "Error during the retrieval of the public key during JWT token validation", e); } } } ================================================ FILE: multiuser/oidc/src/main/java/org/eclipse/che/multiuser/oidc/filter/OidcTokenInitializationFilter.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.oidc.filter; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_EMAIL_CLAIM_SETTING; import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_GROUPS_CLAIM_SETTING; import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_GROUP_PREFIX_SETTING; import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_USERNAME_CLAIM_SETTING; import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_USERNAME_PREFIX_SETTING; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.JwtParser; import java.util.ArrayList; import java.util.List; import java.util.Optional; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.multiuser.api.authentication.commons.SessionStore; import org.eclipse.che.multiuser.api.authentication.commons.filter.MultiUserEnvironmentInitializationFilter; import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor; import org.eclipse.che.multiuser.api.permission.server.AuthorizedSubject; import org.eclipse.che.multiuser.api.permission.server.PermissionChecker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This filter uses given token directly. It's used for native Kubernetes user authentication. * Requests without token or with invalid token are rejected. * *

    It also makes sure that User is present in Che database. If not, it will create the User from * JWT token claims. The username claim is configured with {@link * org.eclipse.che.multiuser.oidc.OIDCInfoProvider#OIDC_USERNAME_CLAIM_SETTING}. The email claim is * configured with {@link org.eclipse.che.multiuser.oidc.OIDCInfoProvider#OIDC_EMAIL_CLAIM_SETTING}. */ @Singleton public class OidcTokenInitializationFilter extends MultiUserEnvironmentInitializationFilter> { private static final Logger LOG = LoggerFactory.getLogger(OidcTokenInitializationFilter.class); private static final String EMAIL_CLAIM = "email"; private static final String NAME_CLAIM = "name"; private static final String GROUPS_CLAIM = "groups"; protected static final String DEFAULT_USERNAME_CLAIM = NAME_CLAIM; protected static final String DEFAULT_EMAIL_CLAIM = EMAIL_CLAIM; protected static final String DEFAULT_GROUPS_CLAIM = GROUPS_CLAIM; private final JwtParser jwtParser; private final PermissionChecker permissionChecker; private final String usernameClaim; private final String usernamePrefix; private final String groupsClaim; private final String groupPrefix; private final String emailClaim; @Inject public OidcTokenInitializationFilter( PermissionChecker permissionChecker, JwtParser jwtParser, SessionStore sessionStore, RequestTokenExtractor tokenExtractor, @Nullable @Named(OIDC_USERNAME_CLAIM_SETTING) String usernameClaim, @Nullable @Named(OIDC_USERNAME_PREFIX_SETTING) String usernamePrefix, @Nullable @Named(OIDC_GROUPS_CLAIM_SETTING) String groupsClaim, @Nullable @Named(OIDC_GROUP_PREFIX_SETTING) String groupPrefix, @Nullable @Named(OIDC_EMAIL_CLAIM_SETTING) String emailClaim) { super(sessionStore, tokenExtractor); this.permissionChecker = permissionChecker; this.jwtParser = jwtParser; this.emailClaim = isNullOrEmpty(emailClaim) ? DEFAULT_EMAIL_CLAIM : emailClaim; this.usernameClaim = isNullOrEmpty(usernameClaim) ? DEFAULT_USERNAME_CLAIM : usernameClaim; this.usernamePrefix = isNullOrEmpty(usernamePrefix) ? "" : usernamePrefix; this.groupsClaim = isNullOrEmpty(groupsClaim) ? DEFAULT_GROUPS_CLAIM : groupsClaim; this.groupPrefix = isNullOrEmpty(groupPrefix) ? "" : groupPrefix; } @Override protected Optional> processToken(String token) { return Optional.ofNullable(jwtParser.parseClaimsJws(token)); } @Override protected String getUserId(Jws processedToken) { return processedToken.getBody().getSubject(); } @Override protected Subject extractSubject(String token, Jws processedToken) { Claims claims = processedToken.getBody(); String email = claims.get(emailClaim, String.class); String username = usernamePrefix + claims.get(usernameClaim, String.class); List groups = new ArrayList<>(); Object groupClaim = claims.get(this.groupsClaim); if (groupClaim != null) { if (groupClaim instanceof Iterable) { for (Object group : ((Iterable) groupClaim)) { groups.add(groupPrefix + group); } } else if (groupClaim instanceof String) { groups.add(groupPrefix + groupClaim); } else { LOG.warn( "Groups claim '{}' is not a String or Iterable, skipping groups", this.groupsClaim); } } return new AuthorizedSubject( new SubjectImpl(username, groups, claims.getSubject(), token, false), permissionChecker); } } ================================================ FILE: multiuser/oidc/src/test/java/org/eclipse/che/multiuser/oidc/filter/OidcTokenInitializationFilterTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.multiuser.oidc.filter; import static org.eclipse.che.multiuser.oidc.filter.OidcTokenInitializationFilter.DEFAULT_EMAIL_CLAIM; import static org.eclipse.che.multiuser.oidc.filter.OidcTokenInitializationFilter.DEFAULT_GROUPS_CLAIM; import static org.eclipse.che.multiuser.oidc.filter.OidcTokenInitializationFilter.DEFAULT_USERNAME_CLAIM; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.JwtParser; import java.util.Arrays; import java.util.List; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.multiuser.api.authentication.commons.SessionStore; import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor; import org.eclipse.che.multiuser.api.permission.server.PermissionChecker; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class OidcTokenInitializationFilterTest { @Mock private PermissionChecker permissionsChecker; @Mock private JwtParser jwtParser; @Mock private SessionStore sessionStore; @Mock private RequestTokenExtractor tokenExtractor; private final String usernameClaim = "userClaim"; private final String usernamePrefix = "oidc-user:"; private final String groupsClaim = "groupClaim"; private final String groupPrefix = "oidc-group:"; private final String emailClaim = "emailClaim"; private final String TEST_USERNAME = "jondoe"; private final String TEST_USER_EMAIL = "jon@doe"; private final String TEST_USERID = "jon1337"; private final List TEST_GROUPS = Arrays.asList("group1", "group2"); private final String TEST_TOKEN = "abcToken"; @Mock private Jws jwsClaims; @Mock private Claims claims; OidcTokenInitializationFilter tokenInitializationFilter; @BeforeMethod public void setUp() { tokenInitializationFilter = new OidcTokenInitializationFilter( permissionsChecker, jwtParser, sessionStore, tokenExtractor, usernameClaim, usernamePrefix, groupsClaim, groupPrefix, emailClaim); lenient().when(jwsClaims.getBody()).thenReturn(claims); lenient().when(claims.getSubject()).thenReturn(TEST_USERID); lenient().when(claims.get(emailClaim, String.class)).thenReturn(TEST_USER_EMAIL); lenient().when(claims.get(usernameClaim, String.class)).thenReturn(TEST_USERNAME); lenient().when(claims.get(groupsClaim)).thenReturn(TEST_GROUPS); } @Test public void testProcessToken() { when(jwtParser.parseClaimsJws(TEST_TOKEN)).thenReturn(jwsClaims); var returnedClaims = tokenInitializationFilter.processToken(TEST_TOKEN).get(); assertEquals(returnedClaims, jwsClaims); verify(jwtParser).parseClaimsJws(TEST_TOKEN); } @Test public void testProcessEmptyToken() { var returnedClaims = tokenInitializationFilter.processToken(""); assertTrue(returnedClaims.isEmpty()); } @Test public void testGetUserId() { var userId = tokenInitializationFilter.getUserId(jwsClaims); assertEquals(userId, TEST_USERID); } @Test public void testExtractSubject() throws ServerException, ConflictException { var subject = tokenInitializationFilter.extractSubject(TEST_TOKEN, jwsClaims); assertEquals(subject.getUserId(), TEST_USERID); assertEquals(subject.getUserName(), usernamePrefix + TEST_USERNAME); assertEquals(subject.getGroups(), Arrays.asList("oidc-group:group1", "oidc-group:group2")); assertEquals(subject.getToken(), TEST_TOKEN); } @Test(dataProvider = "usernameClaims") public void testDefaultUsernameClaimWhenEmpty(String customUsernameClaim) throws ServerException, ConflictException { tokenInitializationFilter = new OidcTokenInitializationFilter( permissionsChecker, jwtParser, sessionStore, tokenExtractor, customUsernameClaim, usernamePrefix, groupsClaim, groupPrefix, emailClaim); when(claims.get(DEFAULT_USERNAME_CLAIM, String.class)).thenReturn(TEST_USERNAME); var subject = tokenInitializationFilter.extractSubject(TEST_TOKEN, jwsClaims); assertEquals(subject.getUserId(), TEST_USERID); assertEquals(subject.getUserName(), usernamePrefix + TEST_USERNAME); assertEquals(subject.getGroups(), Arrays.asList("oidc-group:group1", "oidc-group:group2")); assertEquals(subject.getToken(), TEST_TOKEN); verify(claims).get(DEFAULT_USERNAME_CLAIM, String.class); verify(claims, never()).get(usernameClaim, String.class); } @Test(dataProvider = "emailClaims") public void testDefaultEmailClaimWhenEmpty(String customEmailClaim) throws ServerException, ConflictException { tokenInitializationFilter = new OidcTokenInitializationFilter( permissionsChecker, jwtParser, sessionStore, tokenExtractor, usernameClaim, usernamePrefix, groupsClaim, groupPrefix, customEmailClaim); when(claims.get(DEFAULT_EMAIL_CLAIM, String.class)).thenReturn(TEST_USER_EMAIL); var subject = tokenInitializationFilter.extractSubject(TEST_TOKEN, jwsClaims); assertEquals(subject.getUserId(), TEST_USERID); assertEquals(subject.getUserName(), usernamePrefix + TEST_USERNAME); assertEquals(subject.getToken(), TEST_TOKEN); assertEquals(subject.getGroups(), Arrays.asList("oidc-group:group1", "oidc-group:group2")); verify(claims).get(DEFAULT_EMAIL_CLAIM, String.class); verify(claims, never()).get(emailClaim, String.class); } @Test(dataProvider = "groupsClaims") public void testDefaultGroupClaimWhenEmpty(String customGroupsClaim) throws ServerException, ConflictException { tokenInitializationFilter = new OidcTokenInitializationFilter( permissionsChecker, jwtParser, sessionStore, tokenExtractor, usernameClaim, usernamePrefix, customGroupsClaim, groupPrefix, emailClaim); when(claims.get(DEFAULT_GROUPS_CLAIM)).thenReturn(TEST_GROUPS); var subject = tokenInitializationFilter.extractSubject(TEST_TOKEN, jwsClaims); assertEquals(subject.getUserId(), TEST_USERID); assertEquals(subject.getUserName(), usernamePrefix + TEST_USERNAME); assertEquals(subject.getGroups(), Arrays.asList("oidc-group:group1", "oidc-group:group2")); assertEquals(subject.getToken(), TEST_TOKEN); verify(claims).get(DEFAULT_GROUPS_CLAIM); verify(claims, never()).get(groupsClaim); } @DataProvider public static Object[][] usernameClaims() { return new Object[][] {{""}, {null}}; } @DataProvider public static Object[][] groupsClaims() { return new Object[][] {{""}, {null}}; } @DataProvider public static Object[][] emailClaims() { return new Object[][] {{""}, {null}}; } } ================================================ FILE: multiuser/pom.xml ================================================ 4.0.0 che-server org.eclipse.che 7.118.0-SNAPSHOT ../pom.xml org.eclipse.che.multiuser che-multiuser-parent pom Che Multiuser :: Parent api machine-auth oidc ================================================ FILE: pom.xml ================================================ 4.0.0 org.eclipse.che che-server 7.118.0-SNAPSHOT pom Che Server Eclipse Che Server 2012 Red Hat, Inc. http://www.redhat.com Eclipse Public License 2.0 https://www.eclipse.org/legal/epl-2.0/ repo core wsmaster multiuser infrastructures assembly scm:git:git@github.com:eclipse/che.git scm:git:git@github.com:eclipse/che.git HEAD https://github.com/eclipse/che 1.0 1.5.28 0.22.1 2.21.0 1.7.1 5.0.1 2.42.0 32.1.2-jre 2.1.0 1.34.1 2.13.2 0.1.55 3.16.4 1.6.0 2.14.0 2.6 false 1.78.0 quay.io/eclipse/che--centos--mysql-57-centos7:latest-e08ee4d43b7356607685b69bde6335e27cf20c020f345b6c6c59400183882764 quay.io/eclipse/che--centos--postgresql-13-centos7:1-71b24684d64da46f960682cc4216222a7e4ed8b1a31dd5a865b3e71afdea20d2 7.6.1 0.2.2 1.8.1 1.8.1 0.13.0 1.11.5 4.2.12.Final 0.6.0 0.4.0 0.3.0 0.2.15 0.1.8 0.33.0 0.4.1 0.16.0 6.0.0 2.2.16 4.5.24 2.1.4 3.0.0 1.0.5 2.0.1 2.0.2 6.1.0 3.0.2 2.0.0 4.0.0 11 ${java.target.version} 4.13.2 Red Hat, Inc. - initial API and implementation ${project.version} 7.118.0-SNAPSHOT 1.0-beta2 Red Hat, Inc. 7.4 3.2.0 3.8.0 3.6.1 3.3.0 3.5.0 3.15.0 3.9.0 3.0.0 3.6.3 0.48.1 3.5.4 2.29 1.27.0 3.1.4 0.8.14 3.5.0 5.0.0 3.15.2 3.5.0 2.3.1 3.4.0 [3.6.2,) 3.5.5 3.5.1 8.0.33 4.1.0 4.3.4 3.20.0 4.5.14 4.4.16 3.5.0 3.15.2 3.9.12 11.0.20 4.0.2 11.0.26 4.0.8 2.2.3 0.9.0.M2 1.15.0 11.20.2 3.0 3.30.2-GA 1.0.13.Final 4.1.9.Final 3.1.0 0.5.4 3.11.2 42.7.9 0.9.9 2.0.17 7.12.0 3.0.1 aopalliance aopalliance ${aopalliance.version} ch.qos.logback logback-classic ${ch.qos.logback.version} ch.qos.logback logback-core ${ch.qos.logback.version} com.auth0 jwks-rsa ${com.auth0.jwks-rsa.version} com.fasterxml.jackson.dataformat jackson-dataformat-yaml ${com.fasterxml.jackson.version} com.github.tomakehurst wiremock-jre8-standalone ${org.wiremock.version} com.google.code.gson gson ${com.googlecode.gson.version} com.google.guava guava ${com.google.guava.version} jsr305 com.google.code.findbugs error_prone_annotations com.google.errorprone com.google.http-client google-http-client ${com.google.http-client.version} httpclient org.apache.httpcomponents jsr305 com.google.code.findbugs j2objc-annotations com.google.j2objc grpc-context io.grpc error_prone_annotations com.google.errorprone com.google.http-client google-http-client-jackson2 ${com.google.http-client.version} com.google.inject guice ${com.google.code.guice.version} javax.inject javax.inject guava com.google.guava com.google.inject.extensions guice-assistedinject ${com.google.code.guice.version} com.google.inject.extensions guice-persist ${com.google.code.guice.version} com.google.oauth-client google-oauth-client ${com.google.oauth-client.version} jsr305 com.google.code.findbugs com.jcraft jsch ${com.jcraft.jsch.version} com.squareup.okio okio ${com.squareup.okio.version} com.sun.mail jakarta.mail ${jakarta.mail.version} jakarta.activation com.sun.activation commons-fileupload commons-fileupload ${commons-fileupload.version} servlet-api javax.servlet commons-io commons-io ${commons-io.version} commons-lang commons-lang ${commons-lang.version} io.github.mweirauch micrometer-jvm-extras ${io.github.mweirauch.micrometer-jvm-extras.version} io.grpc grpc-api ${grpc-api.version} io.jaegertracing jaeger-client ${io.jaegertracing.version} * org.apache.httpcomponents jaeger-thrift io.jaegertracing io.jaegertracing jaeger-core ${io.jaegertracing.version} io.jaegertracing jaeger-micrometer ${io.jaegertracing.micrometer.version} io.jaegertracing jaeger-tracerresolver ${io.jaegertracing.version} io.jsonwebtoken jjwt-api ${io.jsonwebtoken.jjwt.version} io.jsonwebtoken jjwt-impl ${io.jsonwebtoken.jjwt.version} io.jsonwebtoken jjwt-jackson ${io.jsonwebtoken.jjwt.version} io.micrometer micrometer-core ${io.micrometer.version} io.micrometer micrometer-registry-prometheus ${io.micrometer.version} io.netty netty-codec-dns ${io.netty.version} io.netty netty-codec-http ${io.netty.version} io.netty netty-codec-http2 ${io.netty.version} io.netty netty-handler ${io.netty.version} io.netty netty-handler-proxy ${io.netty.version} io.opentracing opentracing-api ${io.opentracing.version} io.opentracing opentracing-noop ${io.opentracing.version} io.opentracing opentracing-util ${io.opentracing.version} io.opentracing.contrib opentracing-api-extensions ${io.opentracing.api.extensions.version} io.opentracing.contrib opentracing-api-extensions-tracer ${io.opentracing.api.extensions.version} opentracing-noop io.opentracing io.opentracing.contrib opentracing-concurrent ${io.opentracing.concurrent.version} io.opentracing.contrib opentracing-jdbc ${io.opentracing.jdbc.version} io.opentracing.contrib opentracing-metrics ${io.opentracing.contrib.metrics.version} io.opentracing.contrib opentracing-metrics-micrometer ${io.opentracing.contrib.metrics.version} io.opentracing.contrib opentracing-tracerresolver ${io.opentracing.tracerresolver.version} io.opentracing.contrib opentracing-web-servlet-filter ${io.opentracing.web-servlet-filter.version} io.prometheus simpleclient ${io.prometheus.simpleclient.version} io.prometheus simpleclient_httpserver ${io.prometheus.simpleclient.version} io.rest-assured rest-assured ${io.rest-assured.version} io.swagger.core.v3 swagger-annotations-jakarta ${io.swagger.version} io.swagger.core.v3 swagger-jaxrs2-jakarta ${io.swagger.version} jackson-jaxrs-json-provider com.fasterxml.jackson.jaxrs io.vertx vertx-core ${io.vertx.version} jakarta.activation jakarta.activation-api ${jakarta.activation.version} jakarta.annotation jakarta.annotation-api ${jakarta.annotation.version} jakarta.inject jakarta.inject-api ${jakarta.inject.version} jakarta.servlet jakarta.servlet-api ${jakarta.servlet.version} jakarta.validation jakarta.validation-api ${jakarta.validatio-api.version} jakarta.websocket jakarta.websocket-api ${jakarta.websocket.version} jakarta.ws.rs jakarta.ws.rs-api ${jakarta.ws.rs.version} mysql mysql-connector-java ${mysql.connector.version} net.logstash.logback logstash-logback-encoder ${logstash.logback.encoder.version} org.antlr ST4 ${org.antlr.st4.version} org.apache.commons commons-lang3 ${org.apache.commons.lang3.version} org.apache.httpcomponents httpclient ${org.apache.httpcomponents.httpclient.version} org.apache.httpcomponents httpcore ${org.apache.httpcomponents.httpcore.version} org.apache.maven maven-artifact ${org.apache.maven.version} org.apache.maven maven-compat ${org.apache.maven.version} org.apache.maven maven-core ${org.apache.maven.version} org.apache.maven maven-model ${org.apache.maven.version} org.apache.maven maven-plugin-api ${org.apache.maven.version} jsr250-api jakarta.annotation org.apache.maven.plugin-tools maven-plugin-annotations ${org.apache.maven.plugin-tools.plugin-annotations.version} org.apache.tomcat tomcat-catalina ${org.apache.tomcat.version} org.apache.tomcat tomcat-coyote ${org.apache.tomcat.version} org.apache.tomcat tomcat-dbcp ${org.apache.tomcat.version} org.apache.tomcat tomcat-servlet-api ${org.apache.tomcat.version} org.codehaus.plexus plexus-utils ${org.codehaus.plexus.utils.version} org.codehaus.plexus plexus-xml ${org.codehaus.plexus.utils.version} org.eclipse.che assembly-che-tomcat ${che.version} zip org.eclipse.che assembly-dashboard-war ${che.version} war org.eclipse.che assembly-main ${che.version} org.eclipse.che assembly-root-war ${che.version} war org.eclipse.che assembly-swagger-war ${che.version} war org.eclipse.che assembly-wsmaster-war ${che.version} war org.eclipse.che che-multiuser-keycloak-shared ${che.version} org.eclipse.che.core che-core-api-account ${che.version} org.eclipse.che.core che-core-api-account ${che.version} tests org.eclipse.che.core che-core-api-auth ${che.version} org.eclipse.che.core che-core-api-auth-azure-devops ${che.version} org.eclipse.che.core che-core-api-auth-bitbucket ${che.version} org.eclipse.che.core che-core-api-auth-github ${che.version} org.eclipse.che.core che-core-api-auth-github-common ${che.version} org.eclipse.che.core che-core-api-auth-gitlab ${che.version} org.eclipse.che.core che-core-api-auth-gitlab-common ${che.version} org.eclipse.che.core che-core-api-auth-openshift ${che.version} org.eclipse.che.core che-core-api-auth-shared ${che.version} org.eclipse.che.core che-core-api-core ${che.version} org.eclipse.che.core che-core-api-devfile ${che.version} org.eclipse.che.core che-core-api-devfile ${che.version} tests org.eclipse.che.core che-core-api-devfile-shared ${che.version} org.eclipse.che.core che-core-api-dto ${che.version} org.eclipse.che.core che-core-api-dto-maven-plugin ${che.version} org.eclipse.che.core che-core-api-factory ${che.version} org.eclipse.che.core che-core-api-factory ${che.version} tests org.eclipse.che.core che-core-api-factory-azure-devops ${che.version} org.eclipse.che.core che-core-api-factory-bitbucket ${che.version} org.eclipse.che.core che-core-api-factory-bitbucket-server ${che.version} org.eclipse.che.core che-core-api-factory-git-ssh ${che.version} org.eclipse.che.core che-core-api-factory-github ${che.version} org.eclipse.che.core che-core-api-factory-github-common ${che.version} org.eclipse.che.core che-core-api-factory-gitlab ${che.version} org.eclipse.che.core che-core-api-factory-gitlab-common ${che.version} org.eclipse.che.core che-core-api-factory-shared ${che.version} org.eclipse.che.core che-core-api-git ${che.version} org.eclipse.che.core che-core-api-git ${che.version} tests org.eclipse.che.core che-core-api-git-shared ${che.version} org.eclipse.che.core che-core-api-infrastructure-local ${che.version} org.eclipse.che.core che-core-api-installer-shared ${che.version} org.eclipse.che.core che-core-api-logger ${che.version} org.eclipse.che.core che-core-api-logger-shared ${che.version} org.eclipse.che.core che-core-api-metrics ${che.version} org.eclipse.che.core che-core-api-model ${che.version} org.eclipse.che.core che-core-api-oauth ${che.version} org.eclipse.che.core che-core-api-ssh ${che.version} org.eclipse.che.core che-core-api-ssh ${che.version} tests org.eclipse.che.core che-core-api-ssh-shared ${che.version} org.eclipse.che.core che-core-api-system ${che.version} org.eclipse.che.core che-core-api-system-shared ${che.version} org.eclipse.che.core che-core-api-testing ${che.version} org.eclipse.che.core che-core-api-testing-shared ${che.version} org.eclipse.che.core che-core-api-user ${che.version} tests org.eclipse.che.core che-core-api-user ${che.version} org.eclipse.che.core che-core-api-user-shared ${che.version} org.eclipse.che.core che-core-api-workspace ${che.version} tests org.eclipse.che.core che-core-api-workspace ${che.version} org.eclipse.che.core che-core-api-workspace-shared ${che.version} org.eclipse.che.core che-core-commons-annotations ${che.version} org.eclipse.che.core che-core-commons-inject ${che.version} org.eclipse.che.core che-core-commons-j2ee ${che.version} org.eclipse.che.core che-core-commons-json ${che.version} org.eclipse.che.core che-core-commons-lang ${che.version} org.eclipse.che.core che-core-commons-observability ${che.version} org.eclipse.che.core che-core-commons-schedule ${che.version} org.eclipse.che.core che-core-commons-tracing ${che.version} org.eclipse.che.core che-core-db ${che.version} org.eclipse.che.core che-core-db-vendor-h2 ${che.version} org.eclipse.che.core che-core-db-vendor-mysql ${che.version} org.eclipse.che.core che-core-db-vendor-postgresql ${che.version} org.eclipse.che.core che-core-logback ${che.version} org.eclipse.che.core che-core-metrics-core ${che.version} org.eclipse.che.core che-core-sql-schema ${che.version} org.eclipse.che.core che-core-tracing-core ${che.version} org.eclipse.che.core che-core-tracing-metrics ${che.version} org.eclipse.che.core che-core-tracing-web ${che.version} org.eclipse.che.core che-core-workspace-activity-server ${che.version} org.eclipse.che.core wsmaster-local ${che.version} org.eclipse.che.dashboard che-dashboard-war ${che.dashboard.version} war org.eclipse.che.infrastructure infrastructure-distributed ${che.version} org.eclipse.che.infrastructure infrastructure-factory ${che.version} org.eclipse.che.infrastructure infrastructure-kubernetes ${che.version} org.eclipse.che.infrastructure infrastructure-kubernetes ${che.version} tests org.eclipse.che.infrastructure infrastructure-metrics ${che.version} org.eclipse.che.infrastructure infrastructure-openshift ${che.version} org.eclipse.che.infrastructure infrastructure-permission ${che.version} org.eclipse.che.multiuser che-multiuser-api-authentication-commons ${che.version} org.eclipse.che.multiuser che-multiuser-api-authorization ${che.version} org.eclipse.che.multiuser che-multiuser-api-authorization-impl ${che.version} org.eclipse.che.multiuser che-multiuser-api-organization ${che.version} org.eclipse.che.multiuser che-multiuser-api-organization ${che.version} tests org.eclipse.che.multiuser che-multiuser-api-organization-shared ${che.version} org.eclipse.che.multiuser che-multiuser-api-permission ${che.version} org.eclipse.che.multiuser che-multiuser-api-permission ${che.version} tests org.eclipse.che.multiuser che-multiuser-api-permission-shared ${che.version} org.eclipse.che.multiuser che-multiuser-api-resource ${che.version} org.eclipse.che.multiuser che-multiuser-api-resource ${che.version} tests org.eclipse.che.multiuser che-multiuser-api-resource-shared ${che.version} org.eclipse.che.multiuser che-multiuser-keycloak-server ${che.version} org.eclipse.che.multiuser che-multiuser-keycloak-shared ${che.version} org.eclipse.che.multiuser che-multiuser-keycloak-user-remover ${che.version} org.eclipse.che.multiuser che-multiuser-machine-authentication ${che.version} org.eclipse.che.multiuser che-multiuser-machine-authentication ${che.version} tests org.eclipse.che.multiuser che-multiuser-machine-authentication-shared ${che.version} org.eclipse.che.multiuser che-multiuser-oidc ${che.version} org.eclipse.che.multiuser che-multiuser-permission-devfile ${che.version} org.eclipse.che.multiuser multiuser-infrastructure-openshift ${che.version} org.eclipse.persistence jakarta.persistence ${org.eclipse.persistence.version} org.eclipse.persistence org.eclipse.persistence.core ${org.eclipse.persistence.eclipselink.version} org.eclipse.persistence org.eclipse.persistence.extension ${org.eclipse.persistence.eclipselink.version} org.eclipse.persistence org.eclipse.persistence.jpa ${org.eclipse.persistence.eclipselink.version} org.eclipse.sisu org.eclipse.sisu.plexus ${org.eclipse.sisu.version} org.everrest everrest-assured ${org.everrest.version} org.everrest everrest-core ${org.everrest.version} everrest-commons-fileupload org.everrest org.everrest everrest-guice-servlet ${org.everrest.version} org.everrest everrest-integration-guice ${org.everrest.version} org.everrest everrest-test ${org.everrest.version} org.flywaydb flyway-core ${org.flyway.version} org.glassfish jakarta.json ${jakarta.json.version} org.hamcrest hamcrest ${org.hamcrest.version} org.hamcrest hamcrest-core ${org.hamcrest.version} org.hamcrest hamcrest-library ${org.hamcrest.version} org.javassist javassist ${org.javassist.version} org.jgroups jgroups ${org.jgroups.version} org.jgroups.kubernetes jgroups-kubernetes ${org.jgroups.kubernetes.version} org.leadpony.justify justify ${org.leadpony.justify.version} org.mockito mockito-core ${org.mockito.version} hamcrest-core org.hamcrest org.mockito mockito-testng ${org.mockito.mockito-testng.version} mockito-core org.mockito testng org.testng org.postgresql postgresql ${org.postgresql.version} checker-qual org.checkerframework org.reflections reflections ${org.reflections.version} dom4j dom4j annotations com.google.code.findbugs javassist org.javassist org.slf4j jcl-over-slf4j ${org.slf4j.version} org.slf4j jul-to-slf4j ${org.slf4j.version} org.slf4j log4j-over-slf4j ${org.slf4j.version} org.slf4j slf4j-api ${org.slf4j.version} org.slf4j slf4j-simple ${org.slf4j.version} org.testng testng ${org.testng.version} org.wiremock wiremock-standalone ${org.wiremock.version} com.fasterxml.jackson jackson-bom ${com.fasterxml.jackson.version} pom import io.fabric8 kubernetes-client-api ${io.fabric8.kubernetes-client.version} pom import com.github.kirviq dumbster ${com.github.kirviq.dumbster.version} test junit junit ${junit.junit.version} test org.apache.maven.plugin-testing maven-plugin-testing-harness ${org.apache.maven.plugin-testing.maven-plugin-testing-harness.version} test org.eclipse.che.core che-core-commons-test ${che.version} test org.eclipse.jetty jetty-security ${org.eclipse.jetty.version} test org.eclipse.jetty jetty-servlet ${org.eclipse.jetty.version} test org.eclipse.jetty jetty-util ${org.eclipse.jetty.version} test true fail ossrh central public snapshots https://oss.sonatype.org/content/repositories/snapshots/ ossrh central public snapshots https://oss.sonatype.org/content/repositories/snapshots/ io.fabric8 docker-maven-plugin ${maven.fabric8io.docker.plugin.version} org.apache.maven.plugins maven-failsafe-plugin ${maven.failsafe.plugin.version} --illegal-access=permit org.apache.maven.plugins maven-antrun-plugin ${maven.antrun.plugin.version} org.apache.maven.plugins maven-clean-plugin ${maven.clean.plugin.version} org.codehaus.mojo build-helper-maven-plugin ${maven.buildhelper.plugin.version} org.codehaus.mojo exec-maven-plugin ${maven.exec.plugin.version} org.apache.maven.plugins maven-plugin-plugin ${maven.plugin.plugin.version} org.eclipse.che.core che-core-dynamodule-maven-plugin ${che.version} org.codehaus.mojo buildnumber-maven-plugin ${maven.buildnumber.plugin.version} maven-compiler-plugin ${maven.compiler.plugin.version} ${java.target.version} true true com.google.code.sortpom maven-sortpom-plugin ${maven.sortpom.plugin.version} false 4 false true scope,groupId,artifactId Stop org.apache.maven.plugins maven-dependency-plugin ${maven.dependency.plugin.version} true org.apache.maven.plugins maven-assembly-plugin ${maven.assembly.plugin.version} gnu true ${maven.assembly.recompressZippedFiles} maven-jar-plugin ${maven.jar.plugin.version} true true ${maven.build.timestamp} ${project.url} ${project.scm.connection} ${buildNumber} ${product.name} ${specification.version} ${project.organization.name} ${project.name} ${project.version} ${project.organization.name} maven-javadoc-plugin ${maven.javadoc.plugin.version} com.mycila license-maven-plugin ${maven.mycila.license.plugin.version} com.mycila license-maven-plugin-git ${maven.mycila.license.plugin.version} org.eclipse.che.dev che-eclipse-license-resource-bundle 20

    license-header.txt
    ${project.inceptionYear} ${currentYear} ${license_copyrightOwner} ${license_years} ${license_contributor} ${license_contributor2} true true src/** pom.xml false **/*~ **/#*# **/.#* **/%*% **/._* **/.repository/** **/CVS **/CVS/** **/.cvsignore **/RCS **/RCS/** **/SCCS **/SCCS/** **/vssver.scc **/.svn **/.svn/** **/.arch-ids **/.arch-ids/** **/.bzr **/.bzr/** **/.MySCMServerInfo **/.DS_Store **/.metadata **/.metadata/** **/.hg **/.hg/** **/.git **/.git/** **/.gitignore **/BitKeeper **/BitKeeper/** **/ChangeSet **/ChangeSet/** **/_darcs **/_darcs/** **/.darcsrepo **/.darcsrepo/** **/-darcs-backup* **/.darcs-temp-mail **/target/** **/test-output/** **/release.properties **/dependency-reduced-pom.xml **/cobertura.ser **/.clover/** **/.classpath **/.project **/.settings/** **/*.iml **/*.ipr **/*.iws **/.idea/** **/MANIFEST.MF **/*.jpg **/*.png **/*.gif **/*.ico **/*.bmp **/*.tiff **/*.tif **/*.cr2 **/*.xcf **/*.class **/*.exe **/*.md5 **/*.sha1 **/*.jar **/*.zip **/*.rar **/*.tar **/*.tar.gz **/*.tar.bz2 **/*.gz **/*.xls **/META-INF/services/** **/*.pem **/*.txt **/*.log **/README **/server.xml **/*.json **/catalina.properties **/logging.properties **/jaas.conf **/jmxremote.access **/jmxremote.password **/tomcat-users.xml **/rewrite.config XML_STYLE SLASHSTAR_STYLE DOUBLESLASH_STYLE SLASHSTAR_STYLE maven-source-plugin ${maven.source.plugin.version} true true ${maven.build.timestamp} ${project.url} ${project.scm.connection} ${buildNumber} ${product.name} ${parsedVersion.majorVersion}.0 ${project.organization.name} ${project.name} ${project.version} ${project.organization.name} org.apache.maven.plugins maven-war-plugin ${maven.war.plugin.version} true true ${maven.build.timestamp} ${project.url} ${project.scm.connection} ${buildNumber} ${product.name} ${parsedVersion.majorVersion}.0 ${project.organization.name} ${project.name} ${project.version} ${project.organization.name} ${archiveClasses} ${maven.war.attachClasses} ${useCache} ${maven.war.recompressZippedFiles} maven-surefire-plugin ${maven.surefire.plugin.version} maven-resources-plugin ${maven.resources.plugin.version} maven-install-plugin ${maven.install.plugin.version} org.jacoco jacoco-maven-plugin ${maven.jacoco.plugin.version} org.apache.maven.plugins maven-enforcer-plugin ${maven.enforcer.plugin.version} com.ceilfors.maven.plugin enforcer-rules 1.2.0 org.codehaus.mojo build-helper-maven-plugin parse-version parse-version org.codehaus.mojo buildnumber-maven-plugin validate create buildNumber no_revision true yyyyMMdd org.apache.maven.plugins maven-enforcer-plugin ${maven.enforcer.plugin.version} enforce enforce You are trying to use artifacts that are not in the list of allowed artifacts included in dependencyManagement in root pom or have different version. true runtime [${java.target.version},) ${maven.supported-versions} javax.activation:activation javax.activation:javax.activation-api javax.enterprise:cdi-api javax.inject:javax.inject javax.json:javax.json-api javax.json.bind:javax.json.bind-api org.glassfish:javax.json org.glassfish:javax.el javax.persistence:javax.persistence-api javax.persistence:persistence-api javax.security.enterprise:javax.security.enterprise-api javax.servlet:servlet-api javax.servlet:javax.servlet-api javax.transaction:jta javax.transaction:javax.transaction-api javax.validation:validation-api javax.xml.bind:jaxb-api javax.websocket:javax.websocket-api javax.ws.rs:javax.ws.rs-api com.sun.activation:jakarta.activation commons-logging:commons-logging-api concurrent:concurrent com.google.code.findbugs:annotations com.google.code.findbugs:jsr305 jboss:jboss-logging-spi org.apache.xalan:serializer org.apache.xalan:xalan org.slf4j:slf4j-nop org.slf4j:slf4j-jdk14 org.slf4j:slf4j-log4j12 org.slf4j:slf4j-log4j13 stax:stax-api sun-jaxb:jaxb-api trove:trove woodstox:wstx-lgpl xml-apis:xml-apis com.google.inject.extensions:guice-servlet com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider org.apache.logging.log4j:log4j log4j:log4j ${enforcer.skip} fast true true true true true validate-sources !skip-validate-sources org.apache.maven.plugins maven-compiler-plugin ${maven.compiler.plugin.version} ${java.target.version} -XDcompilePolicy=simple --should-stop=ifError=FLOW -Xplugin:ErrorProne com.google.errorprone error_prone_core ${com.google.errorprone} com.google.code.sortpom maven-sortpom-plugin validate verify org.apache.maven.plugins maven-dependency-plugin analyze analyze-only true true true com.spotify.fmt fmt-maven-plugin ${maven.fmt.plugin.version} check com.google.googlejavaformat google-java-format ${maven.google-java-format.version} com.mycila license-maven-plugin check-headers validate check ================================================ FILE: typescript-dto/.gitignore ================================================ index.d.ts target/ ================================================ FILE: typescript-dto/.yarn/releases/yarn-4.12.0.cjs ================================================ #!/usr/bin/env node /* eslint-disable */ //prettier-ignore (()=>{var xGe=Object.create;var mU=Object.defineProperty;var kGe=Object.getOwnPropertyDescriptor;var QGe=Object.getOwnPropertyNames;var TGe=Object.getPrototypeOf,RGe=Object.prototype.hasOwnProperty;var Ie=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var Xe=(t,e)=>()=>(t&&(e=t(t=0)),e);var _=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),Vt=(t,e)=>{for(var r in e)mU(t,r,{get:e[r],enumerable:!0})},FGe=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of QGe(e))!RGe.call(t,a)&&a!==r&&mU(t,a,{get:()=>e[a],enumerable:!(s=kGe(e,a))||s.enumerable});return t};var ut=(t,e,r)=>(r=t!=null?xGe(TGe(t)):{},FGe(e||!t||!t.__esModule?mU(r,"default",{value:t,enumerable:!0}):r,t));var fi={};Vt(fi,{SAFE_TIME:()=>WZ,S_IFDIR:()=>JP,S_IFLNK:()=>KP,S_IFMT:()=>Mf,S_IFREG:()=>N2});var Mf,JP,N2,KP,WZ,YZ=Xe(()=>{Mf=61440,JP=16384,N2=32768,KP=40960,WZ=456789e3});var or={};Vt(or,{EBADF:()=>Mo,EBUSY:()=>NGe,EEXIST:()=>HGe,EINVAL:()=>LGe,EISDIR:()=>_Ge,ENOENT:()=>MGe,ENOSYS:()=>OGe,ENOTDIR:()=>UGe,ENOTEMPTY:()=>GGe,EOPNOTSUPP:()=>qGe,EROFS:()=>jGe,ERR_DIR_CLOSED:()=>yU});function Cc(t,e){return Object.assign(new Error(`${t}: ${e}`),{code:t})}function NGe(t){return Cc("EBUSY",t)}function OGe(t,e){return Cc("ENOSYS",`${t}, ${e}`)}function LGe(t){return Cc("EINVAL",`invalid argument, ${t}`)}function Mo(t){return Cc("EBADF",`bad file descriptor, ${t}`)}function MGe(t){return Cc("ENOENT",`no such file or directory, ${t}`)}function UGe(t){return Cc("ENOTDIR",`not a directory, ${t}`)}function _Ge(t){return Cc("EISDIR",`illegal operation on a directory, ${t}`)}function HGe(t){return Cc("EEXIST",`file already exists, ${t}`)}function jGe(t){return Cc("EROFS",`read-only filesystem, ${t}`)}function GGe(t){return Cc("ENOTEMPTY",`directory not empty, ${t}`)}function qGe(t){return Cc("EOPNOTSUPP",`operation not supported, ${t}`)}function yU(){return Cc("ERR_DIR_CLOSED","Directory handle was closed")}var zP=Xe(()=>{});var $a={};Vt($a,{BigIntStatsEntry:()=>iE,DEFAULT_MODE:()=>CU,DirEntry:()=>EU,StatEntry:()=>nE,areStatsEqual:()=>wU,clearStats:()=>XP,convertToBigIntStats:()=>YGe,makeDefaultStats:()=>VZ,makeEmptyStats:()=>WGe});function VZ(){return new nE}function WGe(){return XP(VZ())}function XP(t){for(let e in t)if(Object.hasOwn(t,e)){let r=t[e];typeof r=="number"?t[e]=0:typeof r=="bigint"?t[e]=BigInt(0):IU.types.isDate(r)&&(t[e]=new Date(0))}return t}function YGe(t){let e=new iE;for(let r in t)if(Object.hasOwn(t,r)){let s=t[r];typeof s=="number"?e[r]=BigInt(s):IU.types.isDate(s)&&(e[r]=new Date(s))}return e.atimeNs=e.atimeMs*BigInt(1e6),e.mtimeNs=e.mtimeMs*BigInt(1e6),e.ctimeNs=e.ctimeMs*BigInt(1e6),e.birthtimeNs=e.birthtimeMs*BigInt(1e6),e}function wU(t,e){if(t.atimeMs!==e.atimeMs||t.birthtimeMs!==e.birthtimeMs||t.blksize!==e.blksize||t.blocks!==e.blocks||t.ctimeMs!==e.ctimeMs||t.dev!==e.dev||t.gid!==e.gid||t.ino!==e.ino||t.isBlockDevice()!==e.isBlockDevice()||t.isCharacterDevice()!==e.isCharacterDevice()||t.isDirectory()!==e.isDirectory()||t.isFIFO()!==e.isFIFO()||t.isFile()!==e.isFile()||t.isSocket()!==e.isSocket()||t.isSymbolicLink()!==e.isSymbolicLink()||t.mode!==e.mode||t.mtimeMs!==e.mtimeMs||t.nlink!==e.nlink||t.rdev!==e.rdev||t.size!==e.size||t.uid!==e.uid)return!1;let r=t,s=e;return!(r.atimeNs!==s.atimeNs||r.mtimeNs!==s.mtimeNs||r.ctimeNs!==s.ctimeNs||r.birthtimeNs!==s.birthtimeNs)}var IU,CU,EU,nE,iE,BU=Xe(()=>{IU=ut(Ie("util")),CU=33188,EU=class{constructor(){this.name="";this.path="";this.mode=0}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&61440)===16384}isFIFO(){return!1}isFile(){return(this.mode&61440)===32768}isSocket(){return!1}isSymbolicLink(){return(this.mode&61440)===40960}},nE=class{constructor(){this.uid=0;this.gid=0;this.size=0;this.blksize=0;this.atimeMs=0;this.mtimeMs=0;this.ctimeMs=0;this.birthtimeMs=0;this.atime=new Date(0);this.mtime=new Date(0);this.ctime=new Date(0);this.birthtime=new Date(0);this.dev=0;this.ino=0;this.mode=CU;this.nlink=1;this.rdev=0;this.blocks=1}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&61440)===16384}isFIFO(){return!1}isFile(){return(this.mode&61440)===32768}isSocket(){return!1}isSymbolicLink(){return(this.mode&61440)===40960}},iE=class{constructor(){this.uid=BigInt(0);this.gid=BigInt(0);this.size=BigInt(0);this.blksize=BigInt(0);this.atimeMs=BigInt(0);this.mtimeMs=BigInt(0);this.ctimeMs=BigInt(0);this.birthtimeMs=BigInt(0);this.atimeNs=BigInt(0);this.mtimeNs=BigInt(0);this.ctimeNs=BigInt(0);this.birthtimeNs=BigInt(0);this.atime=new Date(0);this.mtime=new Date(0);this.ctime=new Date(0);this.birthtime=new Date(0);this.dev=BigInt(0);this.ino=BigInt(0);this.mode=BigInt(CU);this.nlink=BigInt(1);this.rdev=BigInt(0);this.blocks=BigInt(1)}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&BigInt(61440))===BigInt(16384)}isFIFO(){return!1}isFile(){return(this.mode&BigInt(61440))===BigInt(32768)}isSocket(){return!1}isSymbolicLink(){return(this.mode&BigInt(61440))===BigInt(40960)}}});function XGe(t){let e,r;if(e=t.match(KGe))t=e[1];else if(r=t.match(zGe))t=`\\\\${r[1]?".\\":""}${r[2]}`;else return t;return t.replace(/\//g,"\\")}function ZGe(t){t=t.replace(/\\/g,"/");let e,r;return(e=t.match(VGe))?t=`/${e[1]}`:(r=t.match(JGe))&&(t=`/unc/${r[1]?".dot/":""}${r[2]}`),t}function ZP(t,e){return t===fe?KZ(e):vU(e)}var O2,vt,Er,fe,J,JZ,VGe,JGe,KGe,zGe,vU,KZ,el=Xe(()=>{O2=ut(Ie("path")),vt={root:"/",dot:".",parent:".."},Er={home:"~",nodeModules:"node_modules",manifest:"package.json",lockfile:"yarn.lock",virtual:"__virtual__",pnpJs:".pnp.js",pnpCjs:".pnp.cjs",pnpData:".pnp.data.json",pnpEsmLoader:".pnp.loader.mjs",rc:".yarnrc.yml",env:".env"},fe=Object.create(O2.default),J=Object.create(O2.default.posix);fe.cwd=()=>process.cwd();J.cwd=process.platform==="win32"?()=>vU(process.cwd()):process.cwd;process.platform==="win32"&&(J.resolve=(...t)=>t.length>0&&J.isAbsolute(t[0])?O2.default.posix.resolve(...t):O2.default.posix.resolve(J.cwd(),...t));JZ=function(t,e,r){return e=t.normalize(e),r=t.normalize(r),e===r?".":(e.endsWith(t.sep)||(e=e+t.sep),r.startsWith(e)?r.slice(e.length):null)};fe.contains=(t,e)=>JZ(fe,t,e);J.contains=(t,e)=>JZ(J,t,e);VGe=/^([a-zA-Z]:.*)$/,JGe=/^\/\/(\.\/)?(.*)$/,KGe=/^\/([a-zA-Z]:.*)$/,zGe=/^\/unc\/(\.dot\/)?(.*)$/;vU=process.platform==="win32"?ZGe:t=>t,KZ=process.platform==="win32"?XGe:t=>t;fe.fromPortablePath=KZ;fe.toPortablePath=vU});async function $P(t,e){let r="0123456789abcdef";await t.mkdirPromise(e.indexPath,{recursive:!0});let s=[];for(let a of r)for(let n of r)s.push(t.mkdirPromise(t.pathUtils.join(e.indexPath,`${a}${n}`),{recursive:!0}));return await Promise.all(s),e.indexPath}async function zZ(t,e,r,s,a){let n=t.pathUtils.normalize(e),c=r.pathUtils.normalize(s),f=[],p=[],{atime:h,mtime:E}=a.stableTime?{atime:dd,mtime:dd}:await r.lstatPromise(c);await t.mkdirpPromise(t.pathUtils.dirname(e),{utimes:[h,E]}),await SU(f,p,t,n,r,c,{...a,didParentExist:!0});for(let C of f)await C();await Promise.all(p.map(C=>C()))}async function SU(t,e,r,s,a,n,c){let f=c.didParentExist?await XZ(r,s):null,p=await a.lstatPromise(n),{atime:h,mtime:E}=c.stableTime?{atime:dd,mtime:dd}:p,C;switch(!0){case p.isDirectory():C=await e5e(t,e,r,s,f,a,n,p,c);break;case p.isFile():C=await n5e(t,e,r,s,f,a,n,p,c);break;case p.isSymbolicLink():C=await i5e(t,e,r,s,f,a,n,p,c);break;default:throw new Error(`Unsupported file type (${p.mode})`)}return(c.linkStrategy?.type!=="HardlinkFromIndex"||!p.isFile())&&((C||f?.mtime?.getTime()!==E.getTime()||f?.atime?.getTime()!==h.getTime())&&(e.push(()=>r.lutimesPromise(s,h,E)),C=!0),(f===null||(f.mode&511)!==(p.mode&511))&&(e.push(()=>r.chmodPromise(s,p.mode&511)),C=!0)),C}async function XZ(t,e){try{return await t.lstatPromise(e)}catch{return null}}async function e5e(t,e,r,s,a,n,c,f,p){if(a!==null&&!a.isDirectory())if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;let h=!1;a===null&&(t.push(async()=>{try{await r.mkdirPromise(s,{mode:f.mode})}catch(S){if(S.code!=="EEXIST")throw S}}),h=!0);let E=await n.readdirPromise(c),C=p.didParentExist&&!a?{...p,didParentExist:!1}:p;if(p.stableSort)for(let S of E.sort())await SU(t,e,r,r.pathUtils.join(s,S),n,n.pathUtils.join(c,S),C)&&(h=!0);else(await Promise.all(E.map(async P=>{await SU(t,e,r,r.pathUtils.join(s,P),n,n.pathUtils.join(c,P),C)}))).some(P=>P)&&(h=!0);return h}async function t5e(t,e,r,s,a,n,c,f,p,h){let E=await n.checksumFilePromise(c,{algorithm:"sha1"}),C=420,S=f.mode&511,P=`${E}${S!==C?S.toString(8):""}`,I=r.pathUtils.join(h.indexPath,E.slice(0,2),`${P}.dat`),R;(le=>(le[le.Lock=0]="Lock",le[le.Rename=1]="Rename"))(R||={});let N=1,U=await XZ(r,I);if(a){let ie=U&&a.dev===U.dev&&a.ino===U.ino,ue=U?.mtimeMs!==$Ge;if(ie&&ue&&h.autoRepair&&(N=0,U=null),!ie)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1}let W=!U&&N===1?`${I}.${Math.floor(Math.random()*4294967296).toString(16).padStart(8,"0")}`:null,ee=!1;return t.push(async()=>{if(!U&&(N===0&&await r.lockPromise(I,async()=>{let ie=await n.readFilePromise(c);await r.writeFilePromise(I,ie)}),N===1&&W)){let ie=await n.readFilePromise(c);await r.writeFilePromise(W,ie);try{await r.linkPromise(W,I)}catch(ue){if(ue.code==="EEXIST")ee=!0,await r.unlinkPromise(W);else throw ue}}a||await r.linkPromise(I,s)}),e.push(async()=>{U||(await r.lutimesPromise(I,dd,dd),S!==C&&await r.chmodPromise(I,S)),W&&!ee&&await r.unlinkPromise(W)}),!1}async function r5e(t,e,r,s,a,n,c,f,p){if(a!==null)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;return t.push(async()=>{let h=await n.readFilePromise(c);await r.writeFilePromise(s,h)}),!0}async function n5e(t,e,r,s,a,n,c,f,p){return p.linkStrategy?.type==="HardlinkFromIndex"?t5e(t,e,r,s,a,n,c,f,p,p.linkStrategy):r5e(t,e,r,s,a,n,c,f,p)}async function i5e(t,e,r,s,a,n,c,f,p){if(a!==null)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;return t.push(async()=>{await r.symlinkPromise(ZP(r.pathUtils,await n.readlinkPromise(c)),s)}),!0}var dd,$Ge,DU=Xe(()=>{el();dd=new Date(456789e3*1e3),$Ge=dd.getTime()});function ex(t,e,r,s){let a=()=>{let n=r.shift();if(typeof n>"u")return null;let c=t.pathUtils.join(e,n);return Object.assign(t.statSync(c),{name:n,path:void 0})};return new L2(e,a,s)}var L2,ZZ=Xe(()=>{zP();L2=class{constructor(e,r,s={}){this.path=e;this.nextDirent=r;this.opts=s;this.closed=!1}throwIfClosed(){if(this.closed)throw yU()}async*[Symbol.asyncIterator](){try{let e;for(;(e=await this.read())!==null;)yield e}finally{await this.close()}}read(e){let r=this.readSync();return typeof e<"u"?e(null,r):Promise.resolve(r)}readSync(){return this.throwIfClosed(),this.nextDirent()}close(e){return this.closeSync(),typeof e<"u"?e(null):Promise.resolve()}closeSync(){this.throwIfClosed(),this.opts.onClose?.(),this.closed=!0}}});function $Z(t,e){if(t!==e)throw new Error(`Invalid StatWatcher status: expected '${e}', got '${t}'`)}var e$,tx,t$=Xe(()=>{e$=Ie("events");BU();tx=class t extends e$.EventEmitter{constructor(r,s,{bigint:a=!1}={}){super();this.status="ready";this.changeListeners=new Map;this.startTimeout=null;this.fakeFs=r,this.path=s,this.bigint=a,this.lastStats=this.stat()}static create(r,s,a){let n=new t(r,s,a);return n.start(),n}start(){$Z(this.status,"ready"),this.status="running",this.startTimeout=setTimeout(()=>{this.startTimeout=null,this.fakeFs.existsSync(this.path)||this.emit("change",this.lastStats,this.lastStats)},3)}stop(){$Z(this.status,"running"),this.status="stopped",this.startTimeout!==null&&(clearTimeout(this.startTimeout),this.startTimeout=null),this.emit("stop")}stat(){try{return this.fakeFs.statSync(this.path,{bigint:this.bigint})}catch{let r=this.bigint?new iE:new nE;return XP(r)}}makeInterval(r){let s=setInterval(()=>{let a=this.stat(),n=this.lastStats;wU(a,n)||(this.lastStats=a,this.emit("change",a,n))},r.interval);return r.persistent?s:s.unref()}registerChangeListener(r,s){this.addListener("change",r),this.changeListeners.set(r,this.makeInterval(s))}unregisterChangeListener(r){this.removeListener("change",r);let s=this.changeListeners.get(r);typeof s<"u"&&clearInterval(s),this.changeListeners.delete(r)}unregisterAllChangeListeners(){for(let r of this.changeListeners.keys())this.unregisterChangeListener(r)}hasChangeListeners(){return this.changeListeners.size>0}ref(){for(let r of this.changeListeners.values())r.ref();return this}unref(){for(let r of this.changeListeners.values())r.unref();return this}}});function sE(t,e,r,s){let a,n,c,f;switch(typeof r){case"function":a=!1,n=!0,c=5007,f=r;break;default:({bigint:a=!1,persistent:n=!0,interval:c=5007}=r),f=s;break}let p=rx.get(t);typeof p>"u"&&rx.set(t,p=new Map);let h=p.get(e);return typeof h>"u"&&(h=tx.create(t,e,{bigint:a}),p.set(e,h)),h.registerChangeListener(f,{persistent:n,interval:c}),h}function md(t,e,r){let s=rx.get(t);if(typeof s>"u")return;let a=s.get(e);typeof a>"u"||(typeof r>"u"?a.unregisterAllChangeListeners():a.unregisterChangeListener(r),a.hasChangeListeners()||(a.stop(),s.delete(e)))}function yd(t){let e=rx.get(t);if(!(typeof e>"u"))for(let r of e.keys())md(t,r)}var rx,bU=Xe(()=>{t$();rx=new WeakMap});function s5e(t){let e=t.match(/\r?\n/g);if(e===null)return n$.EOL;let r=e.filter(a=>a===`\r `).length,s=e.length-r;return r>s?`\r `:` `}function Ed(t,e){return e.replace(/\r?\n/g,s5e(t))}var r$,n$,mp,Uf,Id=Xe(()=>{r$=Ie("crypto"),n$=Ie("os");DU();el();mp=class{constructor(e){this.pathUtils=e}async*genTraversePromise(e,{stableSort:r=!1}={}){let s=[e];for(;s.length>0;){let a=s.shift();if((await this.lstatPromise(a)).isDirectory()){let c=await this.readdirPromise(a);if(r)for(let f of c.sort())s.push(this.pathUtils.join(a,f));else throw new Error("Not supported")}else yield a}}async checksumFilePromise(e,{algorithm:r="sha512"}={}){let s=await this.openPromise(e,"r");try{let n=Buffer.allocUnsafeSlow(65536),c=(0,r$.createHash)(r),f=0;for(;(f=await this.readPromise(s,n,0,65536))!==0;)c.update(f===65536?n:n.slice(0,f));return c.digest("hex")}finally{await this.closePromise(s)}}async removePromise(e,{recursive:r=!0,maxRetries:s=5}={}){let a;try{a=await this.lstatPromise(e)}catch(n){if(n.code==="ENOENT")return;throw n}if(a.isDirectory()){if(r){let n=await this.readdirPromise(e);await Promise.all(n.map(c=>this.removePromise(this.pathUtils.resolve(e,c))))}for(let n=0;n<=s;n++)try{await this.rmdirPromise(e);break}catch(c){if(c.code!=="EBUSY"&&c.code!=="ENOTEMPTY")throw c;nsetTimeout(f,n*100))}}else await this.unlinkPromise(e)}removeSync(e,{recursive:r=!0}={}){let s;try{s=this.lstatSync(e)}catch(a){if(a.code==="ENOENT")return;throw a}if(s.isDirectory()){if(r)for(let a of this.readdirSync(e))this.removeSync(this.pathUtils.resolve(e,a));this.rmdirSync(e)}else this.unlinkSync(e)}async mkdirpPromise(e,{chmod:r,utimes:s}={}){if(e=this.resolve(e),e===this.pathUtils.dirname(e))return;let a=e.split(this.pathUtils.sep),n;for(let c=2;c<=a.length;++c){let f=a.slice(0,c).join(this.pathUtils.sep);if(!this.existsSync(f)){try{await this.mkdirPromise(f)}catch(p){if(p.code==="EEXIST")continue;throw p}if(n??=f,r!=null&&await this.chmodPromise(f,r),s!=null)await this.utimesPromise(f,s[0],s[1]);else{let p=await this.statPromise(this.pathUtils.dirname(f));await this.utimesPromise(f,p.atime,p.mtime)}}}return n}mkdirpSync(e,{chmod:r,utimes:s}={}){if(e=this.resolve(e),e===this.pathUtils.dirname(e))return;let a=e.split(this.pathUtils.sep),n;for(let c=2;c<=a.length;++c){let f=a.slice(0,c).join(this.pathUtils.sep);if(!this.existsSync(f)){try{this.mkdirSync(f)}catch(p){if(p.code==="EEXIST")continue;throw p}if(n??=f,r!=null&&this.chmodSync(f,r),s!=null)this.utimesSync(f,s[0],s[1]);else{let p=this.statSync(this.pathUtils.dirname(f));this.utimesSync(f,p.atime,p.mtime)}}}return n}async copyPromise(e,r,{baseFs:s=this,overwrite:a=!0,stableSort:n=!1,stableTime:c=!1,linkStrategy:f=null}={}){return await zZ(this,e,s,r,{overwrite:a,stableSort:n,stableTime:c,linkStrategy:f})}copySync(e,r,{baseFs:s=this,overwrite:a=!0}={}){let n=s.lstatSync(r),c=this.existsSync(e);if(n.isDirectory()){this.mkdirpSync(e);let p=s.readdirSync(r);for(let h of p)this.copySync(this.pathUtils.join(e,h),s.pathUtils.join(r,h),{baseFs:s,overwrite:a})}else if(n.isFile()){if(!c||a){c&&this.removeSync(e);let p=s.readFileSync(r);this.writeFileSync(e,p)}}else if(n.isSymbolicLink()){if(!c||a){c&&this.removeSync(e);let p=s.readlinkSync(r);this.symlinkSync(ZP(this.pathUtils,p),e)}}else throw new Error(`Unsupported file type (file: ${r}, mode: 0o${n.mode.toString(8).padStart(6,"0")})`);let f=n.mode&511;this.chmodSync(e,f)}async changeFilePromise(e,r,s={}){return Buffer.isBuffer(r)?this.changeFileBufferPromise(e,r,s):this.changeFileTextPromise(e,r,s)}async changeFileBufferPromise(e,r,{mode:s}={}){let a=Buffer.alloc(0);try{a=await this.readFilePromise(e)}catch{}Buffer.compare(a,r)!==0&&await this.writeFilePromise(e,r,{mode:s})}async changeFileTextPromise(e,r,{automaticNewlines:s,mode:a}={}){let n="";try{n=await this.readFilePromise(e,"utf8")}catch{}let c=s?Ed(n,r):r;n!==c&&await this.writeFilePromise(e,c,{mode:a})}changeFileSync(e,r,s={}){return Buffer.isBuffer(r)?this.changeFileBufferSync(e,r,s):this.changeFileTextSync(e,r,s)}changeFileBufferSync(e,r,{mode:s}={}){let a=Buffer.alloc(0);try{a=this.readFileSync(e)}catch{}Buffer.compare(a,r)!==0&&this.writeFileSync(e,r,{mode:s})}changeFileTextSync(e,r,{automaticNewlines:s=!1,mode:a}={}){let n="";try{n=this.readFileSync(e,"utf8")}catch{}let c=s?Ed(n,r):r;n!==c&&this.writeFileSync(e,c,{mode:a})}async movePromise(e,r){try{await this.renamePromise(e,r)}catch(s){if(s.code==="EXDEV")await this.copyPromise(r,e),await this.removePromise(e);else throw s}}moveSync(e,r){try{this.renameSync(e,r)}catch(s){if(s.code==="EXDEV")this.copySync(r,e),this.removeSync(e);else throw s}}async lockPromise(e,r){let s=`${e}.flock`,a=1e3/60,n=Date.now(),c=null,f=async()=>{let p;try{[p]=await this.readJsonPromise(s)}catch{return Date.now()-n<500}try{return process.kill(p,0),!0}catch{return!1}};for(;c===null;)try{c=await this.openPromise(s,"wx")}catch(p){if(p.code==="EEXIST"){if(!await f())try{await this.unlinkPromise(s);continue}catch{}if(Date.now()-n<60*1e3)await new Promise(h=>setTimeout(h,a));else throw new Error(`Couldn't acquire a lock in a reasonable time (via ${s})`)}else throw p}await this.writePromise(c,JSON.stringify([process.pid]));try{return await r()}finally{try{await this.closePromise(c),await this.unlinkPromise(s)}catch{}}}async readJsonPromise(e){let r=await this.readFilePromise(e,"utf8");try{return JSON.parse(r)}catch(s){throw s.message+=` (in ${e})`,s}}readJsonSync(e){let r=this.readFileSync(e,"utf8");try{return JSON.parse(r)}catch(s){throw s.message+=` (in ${e})`,s}}async writeJsonPromise(e,r,{compact:s=!1}={}){let a=s?0:2;return await this.writeFilePromise(e,`${JSON.stringify(r,null,a)} `)}writeJsonSync(e,r,{compact:s=!1}={}){let a=s?0:2;return this.writeFileSync(e,`${JSON.stringify(r,null,a)} `)}async preserveTimePromise(e,r){let s=await this.lstatPromise(e),a=await r();typeof a<"u"&&(e=a),await this.lutimesPromise(e,s.atime,s.mtime)}async preserveTimeSync(e,r){let s=this.lstatSync(e),a=r();typeof a<"u"&&(e=a),this.lutimesSync(e,s.atime,s.mtime)}},Uf=class extends mp{constructor(){super(J)}}});var _s,yp=Xe(()=>{Id();_s=class extends mp{getExtractHint(e){return this.baseFs.getExtractHint(e)}resolve(e){return this.mapFromBase(this.baseFs.resolve(this.mapToBase(e)))}getRealPath(){return this.mapFromBase(this.baseFs.getRealPath())}async openPromise(e,r,s){return this.baseFs.openPromise(this.mapToBase(e),r,s)}openSync(e,r,s){return this.baseFs.openSync(this.mapToBase(e),r,s)}async opendirPromise(e,r){return Object.assign(await this.baseFs.opendirPromise(this.mapToBase(e),r),{path:e})}opendirSync(e,r){return Object.assign(this.baseFs.opendirSync(this.mapToBase(e),r),{path:e})}async readPromise(e,r,s,a,n){return await this.baseFs.readPromise(e,r,s,a,n)}readSync(e,r,s,a,n){return this.baseFs.readSync(e,r,s,a,n)}async writePromise(e,r,s,a,n){return typeof r=="string"?await this.baseFs.writePromise(e,r,s):await this.baseFs.writePromise(e,r,s,a,n)}writeSync(e,r,s,a,n){return typeof r=="string"?this.baseFs.writeSync(e,r,s):this.baseFs.writeSync(e,r,s,a,n)}async closePromise(e){return this.baseFs.closePromise(e)}closeSync(e){this.baseFs.closeSync(e)}createReadStream(e,r){return this.baseFs.createReadStream(e!==null?this.mapToBase(e):e,r)}createWriteStream(e,r){return this.baseFs.createWriteStream(e!==null?this.mapToBase(e):e,r)}async realpathPromise(e){return this.mapFromBase(await this.baseFs.realpathPromise(this.mapToBase(e)))}realpathSync(e){return this.mapFromBase(this.baseFs.realpathSync(this.mapToBase(e)))}async existsPromise(e){return this.baseFs.existsPromise(this.mapToBase(e))}existsSync(e){return this.baseFs.existsSync(this.mapToBase(e))}accessSync(e,r){return this.baseFs.accessSync(this.mapToBase(e),r)}async accessPromise(e,r){return this.baseFs.accessPromise(this.mapToBase(e),r)}async statPromise(e,r){return this.baseFs.statPromise(this.mapToBase(e),r)}statSync(e,r){return this.baseFs.statSync(this.mapToBase(e),r)}async fstatPromise(e,r){return this.baseFs.fstatPromise(e,r)}fstatSync(e,r){return this.baseFs.fstatSync(e,r)}lstatPromise(e,r){return this.baseFs.lstatPromise(this.mapToBase(e),r)}lstatSync(e,r){return this.baseFs.lstatSync(this.mapToBase(e),r)}async fchmodPromise(e,r){return this.baseFs.fchmodPromise(e,r)}fchmodSync(e,r){return this.baseFs.fchmodSync(e,r)}async chmodPromise(e,r){return this.baseFs.chmodPromise(this.mapToBase(e),r)}chmodSync(e,r){return this.baseFs.chmodSync(this.mapToBase(e),r)}async fchownPromise(e,r,s){return this.baseFs.fchownPromise(e,r,s)}fchownSync(e,r,s){return this.baseFs.fchownSync(e,r,s)}async chownPromise(e,r,s){return this.baseFs.chownPromise(this.mapToBase(e),r,s)}chownSync(e,r,s){return this.baseFs.chownSync(this.mapToBase(e),r,s)}async renamePromise(e,r){return this.baseFs.renamePromise(this.mapToBase(e),this.mapToBase(r))}renameSync(e,r){return this.baseFs.renameSync(this.mapToBase(e),this.mapToBase(r))}async copyFilePromise(e,r,s=0){return this.baseFs.copyFilePromise(this.mapToBase(e),this.mapToBase(r),s)}copyFileSync(e,r,s=0){return this.baseFs.copyFileSync(this.mapToBase(e),this.mapToBase(r),s)}async appendFilePromise(e,r,s){return this.baseFs.appendFilePromise(this.fsMapToBase(e),r,s)}appendFileSync(e,r,s){return this.baseFs.appendFileSync(this.fsMapToBase(e),r,s)}async writeFilePromise(e,r,s){return this.baseFs.writeFilePromise(this.fsMapToBase(e),r,s)}writeFileSync(e,r,s){return this.baseFs.writeFileSync(this.fsMapToBase(e),r,s)}async unlinkPromise(e){return this.baseFs.unlinkPromise(this.mapToBase(e))}unlinkSync(e){return this.baseFs.unlinkSync(this.mapToBase(e))}async utimesPromise(e,r,s){return this.baseFs.utimesPromise(this.mapToBase(e),r,s)}utimesSync(e,r,s){return this.baseFs.utimesSync(this.mapToBase(e),r,s)}async lutimesPromise(e,r,s){return this.baseFs.lutimesPromise(this.mapToBase(e),r,s)}lutimesSync(e,r,s){return this.baseFs.lutimesSync(this.mapToBase(e),r,s)}async mkdirPromise(e,r){return this.baseFs.mkdirPromise(this.mapToBase(e),r)}mkdirSync(e,r){return this.baseFs.mkdirSync(this.mapToBase(e),r)}async rmdirPromise(e,r){return this.baseFs.rmdirPromise(this.mapToBase(e),r)}rmdirSync(e,r){return this.baseFs.rmdirSync(this.mapToBase(e),r)}async rmPromise(e,r){return this.baseFs.rmPromise(this.mapToBase(e),r)}rmSync(e,r){return this.baseFs.rmSync(this.mapToBase(e),r)}async linkPromise(e,r){return this.baseFs.linkPromise(this.mapToBase(e),this.mapToBase(r))}linkSync(e,r){return this.baseFs.linkSync(this.mapToBase(e),this.mapToBase(r))}async symlinkPromise(e,r,s){let a=this.mapToBase(r);if(this.pathUtils.isAbsolute(e))return this.baseFs.symlinkPromise(this.mapToBase(e),a,s);let n=this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(r),e)),c=this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(a),n);return this.baseFs.symlinkPromise(c,a,s)}symlinkSync(e,r,s){let a=this.mapToBase(r);if(this.pathUtils.isAbsolute(e))return this.baseFs.symlinkSync(this.mapToBase(e),a,s);let n=this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(r),e)),c=this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(a),n);return this.baseFs.symlinkSync(c,a,s)}async readFilePromise(e,r){return this.baseFs.readFilePromise(this.fsMapToBase(e),r)}readFileSync(e,r){return this.baseFs.readFileSync(this.fsMapToBase(e),r)}readdirPromise(e,r){return this.baseFs.readdirPromise(this.mapToBase(e),r)}readdirSync(e,r){return this.baseFs.readdirSync(this.mapToBase(e),r)}async readlinkPromise(e){return this.mapFromBase(await this.baseFs.readlinkPromise(this.mapToBase(e)))}readlinkSync(e){return this.mapFromBase(this.baseFs.readlinkSync(this.mapToBase(e)))}async truncatePromise(e,r){return this.baseFs.truncatePromise(this.mapToBase(e),r)}truncateSync(e,r){return this.baseFs.truncateSync(this.mapToBase(e),r)}async ftruncatePromise(e,r){return this.baseFs.ftruncatePromise(e,r)}ftruncateSync(e,r){return this.baseFs.ftruncateSync(e,r)}watch(e,r,s){return this.baseFs.watch(this.mapToBase(e),r,s)}watchFile(e,r,s){return this.baseFs.watchFile(this.mapToBase(e),r,s)}unwatchFile(e,r){return this.baseFs.unwatchFile(this.mapToBase(e),r)}fsMapToBase(e){return typeof e=="number"?e:this.mapToBase(e)}}});var _f,i$=Xe(()=>{yp();_f=class extends _s{constructor(e,{baseFs:r,pathUtils:s}){super(s),this.target=e,this.baseFs=r}getRealPath(){return this.target}getBaseFs(){return this.baseFs}mapFromBase(e){return e}mapToBase(e){return e}}});function s$(t){let e=t;return typeof t.path=="string"&&(e.path=fe.toPortablePath(t.path)),e}var o$,Yn,Cd=Xe(()=>{o$=ut(Ie("fs"));Id();el();Yn=class extends Uf{constructor(e=o$.default){super(),this.realFs=e}getExtractHint(){return!1}getRealPath(){return vt.root}resolve(e){return J.resolve(e)}async openPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.open(fe.fromPortablePath(e),r,s,this.makeCallback(a,n))})}openSync(e,r,s){return this.realFs.openSync(fe.fromPortablePath(e),r,s)}async opendirPromise(e,r){return await new Promise((s,a)=>{typeof r<"u"?this.realFs.opendir(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.opendir(fe.fromPortablePath(e),this.makeCallback(s,a))}).then(s=>{let a=s;return Object.defineProperty(a,"path",{value:e,configurable:!0,writable:!0}),a})}opendirSync(e,r){let a=typeof r<"u"?this.realFs.opendirSync(fe.fromPortablePath(e),r):this.realFs.opendirSync(fe.fromPortablePath(e));return Object.defineProperty(a,"path",{value:e,configurable:!0,writable:!0}),a}async readPromise(e,r,s=0,a=0,n=-1){return await new Promise((c,f)=>{this.realFs.read(e,r,s,a,n,(p,h)=>{p?f(p):c(h)})})}readSync(e,r,s,a,n){return this.realFs.readSync(e,r,s,a,n)}async writePromise(e,r,s,a,n){return await new Promise((c,f)=>typeof r=="string"?this.realFs.write(e,r,s,this.makeCallback(c,f)):this.realFs.write(e,r,s,a,n,this.makeCallback(c,f)))}writeSync(e,r,s,a,n){return typeof r=="string"?this.realFs.writeSync(e,r,s):this.realFs.writeSync(e,r,s,a,n)}async closePromise(e){await new Promise((r,s)=>{this.realFs.close(e,this.makeCallback(r,s))})}closeSync(e){this.realFs.closeSync(e)}createReadStream(e,r){let s=e!==null?fe.fromPortablePath(e):e;return this.realFs.createReadStream(s,r)}createWriteStream(e,r){let s=e!==null?fe.fromPortablePath(e):e;return this.realFs.createWriteStream(s,r)}async realpathPromise(e){return await new Promise((r,s)=>{this.realFs.realpath(fe.fromPortablePath(e),{},this.makeCallback(r,s))}).then(r=>fe.toPortablePath(r))}realpathSync(e){return fe.toPortablePath(this.realFs.realpathSync(fe.fromPortablePath(e),{}))}async existsPromise(e){return await new Promise(r=>{this.realFs.exists(fe.fromPortablePath(e),r)})}accessSync(e,r){return this.realFs.accessSync(fe.fromPortablePath(e),r)}async accessPromise(e,r){return await new Promise((s,a)=>{this.realFs.access(fe.fromPortablePath(e),r,this.makeCallback(s,a))})}existsSync(e){return this.realFs.existsSync(fe.fromPortablePath(e))}async statPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.stat(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.stat(fe.fromPortablePath(e),this.makeCallback(s,a))})}statSync(e,r){return r?this.realFs.statSync(fe.fromPortablePath(e),r):this.realFs.statSync(fe.fromPortablePath(e))}async fstatPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.fstat(e,r,this.makeCallback(s,a)):this.realFs.fstat(e,this.makeCallback(s,a))})}fstatSync(e,r){return r?this.realFs.fstatSync(e,r):this.realFs.fstatSync(e)}async lstatPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.lstat(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.lstat(fe.fromPortablePath(e),this.makeCallback(s,a))})}lstatSync(e,r){return r?this.realFs.lstatSync(fe.fromPortablePath(e),r):this.realFs.lstatSync(fe.fromPortablePath(e))}async fchmodPromise(e,r){return await new Promise((s,a)=>{this.realFs.fchmod(e,r,this.makeCallback(s,a))})}fchmodSync(e,r){return this.realFs.fchmodSync(e,r)}async chmodPromise(e,r){return await new Promise((s,a)=>{this.realFs.chmod(fe.fromPortablePath(e),r,this.makeCallback(s,a))})}chmodSync(e,r){return this.realFs.chmodSync(fe.fromPortablePath(e),r)}async fchownPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.fchown(e,r,s,this.makeCallback(a,n))})}fchownSync(e,r,s){return this.realFs.fchownSync(e,r,s)}async chownPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.chown(fe.fromPortablePath(e),r,s,this.makeCallback(a,n))})}chownSync(e,r,s){return this.realFs.chownSync(fe.fromPortablePath(e),r,s)}async renamePromise(e,r){return await new Promise((s,a)=>{this.realFs.rename(fe.fromPortablePath(e),fe.fromPortablePath(r),this.makeCallback(s,a))})}renameSync(e,r){return this.realFs.renameSync(fe.fromPortablePath(e),fe.fromPortablePath(r))}async copyFilePromise(e,r,s=0){return await new Promise((a,n)=>{this.realFs.copyFile(fe.fromPortablePath(e),fe.fromPortablePath(r),s,this.makeCallback(a,n))})}copyFileSync(e,r,s=0){return this.realFs.copyFileSync(fe.fromPortablePath(e),fe.fromPortablePath(r),s)}async appendFilePromise(e,r,s){return await new Promise((a,n)=>{let c=typeof e=="string"?fe.fromPortablePath(e):e;s?this.realFs.appendFile(c,r,s,this.makeCallback(a,n)):this.realFs.appendFile(c,r,this.makeCallback(a,n))})}appendFileSync(e,r,s){let a=typeof e=="string"?fe.fromPortablePath(e):e;s?this.realFs.appendFileSync(a,r,s):this.realFs.appendFileSync(a,r)}async writeFilePromise(e,r,s){return await new Promise((a,n)=>{let c=typeof e=="string"?fe.fromPortablePath(e):e;s?this.realFs.writeFile(c,r,s,this.makeCallback(a,n)):this.realFs.writeFile(c,r,this.makeCallback(a,n))})}writeFileSync(e,r,s){let a=typeof e=="string"?fe.fromPortablePath(e):e;s?this.realFs.writeFileSync(a,r,s):this.realFs.writeFileSync(a,r)}async unlinkPromise(e){return await new Promise((r,s)=>{this.realFs.unlink(fe.fromPortablePath(e),this.makeCallback(r,s))})}unlinkSync(e){return this.realFs.unlinkSync(fe.fromPortablePath(e))}async utimesPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.utimes(fe.fromPortablePath(e),r,s,this.makeCallback(a,n))})}utimesSync(e,r,s){this.realFs.utimesSync(fe.fromPortablePath(e),r,s)}async lutimesPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.lutimes(fe.fromPortablePath(e),r,s,this.makeCallback(a,n))})}lutimesSync(e,r,s){this.realFs.lutimesSync(fe.fromPortablePath(e),r,s)}async mkdirPromise(e,r){return await new Promise((s,a)=>{this.realFs.mkdir(fe.fromPortablePath(e),r,this.makeCallback(s,a))})}mkdirSync(e,r){return this.realFs.mkdirSync(fe.fromPortablePath(e),r)}async rmdirPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.rmdir(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.rmdir(fe.fromPortablePath(e),this.makeCallback(s,a))})}rmdirSync(e,r){return this.realFs.rmdirSync(fe.fromPortablePath(e),r)}async rmPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.rm(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.rm(fe.fromPortablePath(e),this.makeCallback(s,a))})}rmSync(e,r){return this.realFs.rmSync(fe.fromPortablePath(e),r)}async linkPromise(e,r){return await new Promise((s,a)=>{this.realFs.link(fe.fromPortablePath(e),fe.fromPortablePath(r),this.makeCallback(s,a))})}linkSync(e,r){return this.realFs.linkSync(fe.fromPortablePath(e),fe.fromPortablePath(r))}async symlinkPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.symlink(fe.fromPortablePath(e.replace(/\/+$/,"")),fe.fromPortablePath(r),s,this.makeCallback(a,n))})}symlinkSync(e,r,s){return this.realFs.symlinkSync(fe.fromPortablePath(e.replace(/\/+$/,"")),fe.fromPortablePath(r),s)}async readFilePromise(e,r){return await new Promise((s,a)=>{let n=typeof e=="string"?fe.fromPortablePath(e):e;this.realFs.readFile(n,r,this.makeCallback(s,a))})}readFileSync(e,r){let s=typeof e=="string"?fe.fromPortablePath(e):e;return this.realFs.readFileSync(s,r)}async readdirPromise(e,r){return await new Promise((s,a)=>{r?r.recursive&&process.platform==="win32"?r.withFileTypes?this.realFs.readdir(fe.fromPortablePath(e),r,this.makeCallback(n=>s(n.map(s$)),a)):this.realFs.readdir(fe.fromPortablePath(e),r,this.makeCallback(n=>s(n.map(fe.toPortablePath)),a)):this.realFs.readdir(fe.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.readdir(fe.fromPortablePath(e),this.makeCallback(s,a))})}readdirSync(e,r){return r?r.recursive&&process.platform==="win32"?r.withFileTypes?this.realFs.readdirSync(fe.fromPortablePath(e),r).map(s$):this.realFs.readdirSync(fe.fromPortablePath(e),r).map(fe.toPortablePath):this.realFs.readdirSync(fe.fromPortablePath(e),r):this.realFs.readdirSync(fe.fromPortablePath(e))}async readlinkPromise(e){return await new Promise((r,s)=>{this.realFs.readlink(fe.fromPortablePath(e),this.makeCallback(r,s))}).then(r=>fe.toPortablePath(r))}readlinkSync(e){return fe.toPortablePath(this.realFs.readlinkSync(fe.fromPortablePath(e)))}async truncatePromise(e,r){return await new Promise((s,a)=>{this.realFs.truncate(fe.fromPortablePath(e),r,this.makeCallback(s,a))})}truncateSync(e,r){return this.realFs.truncateSync(fe.fromPortablePath(e),r)}async ftruncatePromise(e,r){return await new Promise((s,a)=>{this.realFs.ftruncate(e,r,this.makeCallback(s,a))})}ftruncateSync(e,r){return this.realFs.ftruncateSync(e,r)}watch(e,r,s){return this.realFs.watch(fe.fromPortablePath(e),r,s)}watchFile(e,r,s){return this.realFs.watchFile(fe.fromPortablePath(e),r,s)}unwatchFile(e,r){return this.realFs.unwatchFile(fe.fromPortablePath(e),r)}makeCallback(e,r){return(s,a)=>{s?r(s):e(a)}}}});var Sn,a$=Xe(()=>{Cd();yp();el();Sn=class extends _s{constructor(e,{baseFs:r=new Yn}={}){super(J),this.target=this.pathUtils.normalize(e),this.baseFs=r}getRealPath(){return this.pathUtils.resolve(this.baseFs.getRealPath(),this.target)}resolve(e){return this.pathUtils.isAbsolute(e)?J.normalize(e):this.baseFs.resolve(J.join(this.target,e))}mapFromBase(e){return e}mapToBase(e){return this.pathUtils.isAbsolute(e)?e:this.pathUtils.join(this.target,e)}}});var l$,Hf,c$=Xe(()=>{Cd();yp();el();l$=vt.root,Hf=class extends _s{constructor(e,{baseFs:r=new Yn}={}){super(J),this.target=this.pathUtils.resolve(vt.root,e),this.baseFs=r}getRealPath(){return this.pathUtils.resolve(this.baseFs.getRealPath(),this.pathUtils.relative(vt.root,this.target))}getTarget(){return this.target}getBaseFs(){return this.baseFs}mapToBase(e){let r=this.pathUtils.normalize(e);if(this.pathUtils.isAbsolute(e))return this.pathUtils.resolve(this.target,this.pathUtils.relative(l$,e));if(r.match(/^\.\.\/?/))throw new Error(`Resolving this path (${e}) would escape the jail`);return this.pathUtils.resolve(this.target,e)}mapFromBase(e){return this.pathUtils.resolve(l$,this.pathUtils.relative(this.target,e))}}});var oE,u$=Xe(()=>{yp();oE=class extends _s{constructor(r,s){super(s);this.instance=null;this.factory=r}get baseFs(){return this.instance||(this.instance=this.factory()),this.instance}set baseFs(r){this.instance=r}mapFromBase(r){return r}mapToBase(r){return r}}});var wd,tl,e0,f$=Xe(()=>{wd=Ie("fs");Id();Cd();bU();zP();el();tl=4278190080,e0=class extends Uf{constructor({baseFs:r=new Yn,filter:s=null,magicByte:a=42,maxOpenFiles:n=1/0,useCache:c=!0,maxAge:f=5e3,typeCheck:p=wd.constants.S_IFREG,getMountPoint:h,factoryPromise:E,factorySync:C}){if(Math.floor(a)!==a||!(a>1&&a<=127))throw new Error("The magic byte must be set to a round value between 1 and 127 included");super();this.fdMap=new Map;this.nextFd=3;this.isMount=new Set;this.notMount=new Set;this.realPaths=new Map;this.limitOpenFilesTimeout=null;this.baseFs=r,this.mountInstances=c?new Map:null,this.factoryPromise=E,this.factorySync=C,this.filter=s,this.getMountPoint=h,this.magic=a<<24,this.maxAge=f,this.maxOpenFiles=n,this.typeCheck=p}getExtractHint(r){return this.baseFs.getExtractHint(r)}getRealPath(){return this.baseFs.getRealPath()}saveAndClose(){if(yd(this),this.mountInstances)for(let[r,{childFs:s}]of this.mountInstances.entries())s.saveAndClose?.(),this.mountInstances.delete(r)}discardAndClose(){if(yd(this),this.mountInstances)for(let[r,{childFs:s}]of this.mountInstances.entries())s.discardAndClose?.(),this.mountInstances.delete(r)}resolve(r){return this.baseFs.resolve(r)}remapFd(r,s){let a=this.nextFd++|this.magic;return this.fdMap.set(a,[r,s]),a}async openPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.openPromise(r,s,a),async(n,{subPath:c})=>this.remapFd(n,await n.openPromise(c,s,a)))}openSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.openSync(r,s,a),(n,{subPath:c})=>this.remapFd(n,n.openSync(c,s,a)))}async opendirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.opendirPromise(r,s),async(a,{subPath:n})=>await a.opendirPromise(n,s),{requireSubpath:!1})}opendirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.opendirSync(r,s),(a,{subPath:n})=>a.opendirSync(n,s),{requireSubpath:!1})}async readPromise(r,s,a,n,c){if((r&tl)!==this.magic)return await this.baseFs.readPromise(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Mo("read");let[p,h]=f;return await p.readPromise(h,s,a,n,c)}readSync(r,s,a,n,c){if((r&tl)!==this.magic)return this.baseFs.readSync(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Mo("readSync");let[p,h]=f;return p.readSync(h,s,a,n,c)}async writePromise(r,s,a,n,c){if((r&tl)!==this.magic)return typeof s=="string"?await this.baseFs.writePromise(r,s,a):await this.baseFs.writePromise(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Mo("write");let[p,h]=f;return typeof s=="string"?await p.writePromise(h,s,a):await p.writePromise(h,s,a,n,c)}writeSync(r,s,a,n,c){if((r&tl)!==this.magic)return typeof s=="string"?this.baseFs.writeSync(r,s,a):this.baseFs.writeSync(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Mo("writeSync");let[p,h]=f;return typeof s=="string"?p.writeSync(h,s,a):p.writeSync(h,s,a,n,c)}async closePromise(r){if((r&tl)!==this.magic)return await this.baseFs.closePromise(r);let s=this.fdMap.get(r);if(typeof s>"u")throw Mo("close");this.fdMap.delete(r);let[a,n]=s;return await a.closePromise(n)}closeSync(r){if((r&tl)!==this.magic)return this.baseFs.closeSync(r);let s=this.fdMap.get(r);if(typeof s>"u")throw Mo("closeSync");this.fdMap.delete(r);let[a,n]=s;return a.closeSync(n)}createReadStream(r,s){return r===null?this.baseFs.createReadStream(r,s):this.makeCallSync(r,()=>this.baseFs.createReadStream(r,s),(a,{archivePath:n,subPath:c})=>{let f=a.createReadStream(c,s);return f.path=fe.fromPortablePath(this.pathUtils.join(n,c)),f})}createWriteStream(r,s){return r===null?this.baseFs.createWriteStream(r,s):this.makeCallSync(r,()=>this.baseFs.createWriteStream(r,s),(a,{subPath:n})=>a.createWriteStream(n,s))}async realpathPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.realpathPromise(r),async(s,{archivePath:a,subPath:n})=>{let c=this.realPaths.get(a);return typeof c>"u"&&(c=await this.baseFs.realpathPromise(a),this.realPaths.set(a,c)),this.pathUtils.join(c,this.pathUtils.relative(vt.root,await s.realpathPromise(n)))})}realpathSync(r){return this.makeCallSync(r,()=>this.baseFs.realpathSync(r),(s,{archivePath:a,subPath:n})=>{let c=this.realPaths.get(a);return typeof c>"u"&&(c=this.baseFs.realpathSync(a),this.realPaths.set(a,c)),this.pathUtils.join(c,this.pathUtils.relative(vt.root,s.realpathSync(n)))})}async existsPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.existsPromise(r),async(s,{subPath:a})=>await s.existsPromise(a))}existsSync(r){return this.makeCallSync(r,()=>this.baseFs.existsSync(r),(s,{subPath:a})=>s.existsSync(a))}async accessPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.accessPromise(r,s),async(a,{subPath:n})=>await a.accessPromise(n,s))}accessSync(r,s){return this.makeCallSync(r,()=>this.baseFs.accessSync(r,s),(a,{subPath:n})=>a.accessSync(n,s))}async statPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.statPromise(r,s),async(a,{subPath:n})=>await a.statPromise(n,s))}statSync(r,s){return this.makeCallSync(r,()=>this.baseFs.statSync(r,s),(a,{subPath:n})=>a.statSync(n,s))}async fstatPromise(r,s){if((r&tl)!==this.magic)return this.baseFs.fstatPromise(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("fstat");let[n,c]=a;return n.fstatPromise(c,s)}fstatSync(r,s){if((r&tl)!==this.magic)return this.baseFs.fstatSync(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("fstatSync");let[n,c]=a;return n.fstatSync(c,s)}async lstatPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.lstatPromise(r,s),async(a,{subPath:n})=>await a.lstatPromise(n,s))}lstatSync(r,s){return this.makeCallSync(r,()=>this.baseFs.lstatSync(r,s),(a,{subPath:n})=>a.lstatSync(n,s))}async fchmodPromise(r,s){if((r&tl)!==this.magic)return this.baseFs.fchmodPromise(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("fchmod");let[n,c]=a;return n.fchmodPromise(c,s)}fchmodSync(r,s){if((r&tl)!==this.magic)return this.baseFs.fchmodSync(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("fchmodSync");let[n,c]=a;return n.fchmodSync(c,s)}async chmodPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.chmodPromise(r,s),async(a,{subPath:n})=>await a.chmodPromise(n,s))}chmodSync(r,s){return this.makeCallSync(r,()=>this.baseFs.chmodSync(r,s),(a,{subPath:n})=>a.chmodSync(n,s))}async fchownPromise(r,s,a){if((r&tl)!==this.magic)return this.baseFs.fchownPromise(r,s,a);let n=this.fdMap.get(r);if(typeof n>"u")throw Mo("fchown");let[c,f]=n;return c.fchownPromise(f,s,a)}fchownSync(r,s,a){if((r&tl)!==this.magic)return this.baseFs.fchownSync(r,s,a);let n=this.fdMap.get(r);if(typeof n>"u")throw Mo("fchownSync");let[c,f]=n;return c.fchownSync(f,s,a)}async chownPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.chownPromise(r,s,a),async(n,{subPath:c})=>await n.chownPromise(c,s,a))}chownSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.chownSync(r,s,a),(n,{subPath:c})=>n.chownSync(c,s,a))}async renamePromise(r,s){return await this.makeCallPromise(r,async()=>await this.makeCallPromise(s,async()=>await this.baseFs.renamePromise(r,s),async()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})}),async(a,{subPath:n})=>await this.makeCallPromise(s,async()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})},async(c,{subPath:f})=>{if(a!==c)throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"});return await a.renamePromise(n,f)}))}renameSync(r,s){return this.makeCallSync(r,()=>this.makeCallSync(s,()=>this.baseFs.renameSync(r,s),()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})}),(a,{subPath:n})=>this.makeCallSync(s,()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})},(c,{subPath:f})=>{if(a!==c)throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"});return a.renameSync(n,f)}))}async copyFilePromise(r,s,a=0){let n=async(c,f,p,h)=>{if(a&wd.constants.COPYFILE_FICLONE_FORCE)throw Object.assign(new Error(`EXDEV: cross-device clone not permitted, copyfile '${f}' -> ${h}'`),{code:"EXDEV"});if(a&wd.constants.COPYFILE_EXCL&&await this.existsPromise(f))throw Object.assign(new Error(`EEXIST: file already exists, copyfile '${f}' -> '${h}'`),{code:"EEXIST"});let E;try{E=await c.readFilePromise(f)}catch{throw Object.assign(new Error(`EINVAL: invalid argument, copyfile '${f}' -> '${h}'`),{code:"EINVAL"})}await p.writeFilePromise(h,E)};return await this.makeCallPromise(r,async()=>await this.makeCallPromise(s,async()=>await this.baseFs.copyFilePromise(r,s,a),async(c,{subPath:f})=>await n(this.baseFs,r,c,f)),async(c,{subPath:f})=>await this.makeCallPromise(s,async()=>await n(c,f,this.baseFs,s),async(p,{subPath:h})=>c!==p?await n(c,f,p,h):await c.copyFilePromise(f,h,a)))}copyFileSync(r,s,a=0){let n=(c,f,p,h)=>{if(a&wd.constants.COPYFILE_FICLONE_FORCE)throw Object.assign(new Error(`EXDEV: cross-device clone not permitted, copyfile '${f}' -> ${h}'`),{code:"EXDEV"});if(a&wd.constants.COPYFILE_EXCL&&this.existsSync(f))throw Object.assign(new Error(`EEXIST: file already exists, copyfile '${f}' -> '${h}'`),{code:"EEXIST"});let E;try{E=c.readFileSync(f)}catch{throw Object.assign(new Error(`EINVAL: invalid argument, copyfile '${f}' -> '${h}'`),{code:"EINVAL"})}p.writeFileSync(h,E)};return this.makeCallSync(r,()=>this.makeCallSync(s,()=>this.baseFs.copyFileSync(r,s,a),(c,{subPath:f})=>n(this.baseFs,r,c,f)),(c,{subPath:f})=>this.makeCallSync(s,()=>n(c,f,this.baseFs,s),(p,{subPath:h})=>c!==p?n(c,f,p,h):c.copyFileSync(f,h,a)))}async appendFilePromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.appendFilePromise(r,s,a),async(n,{subPath:c})=>await n.appendFilePromise(c,s,a))}appendFileSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.appendFileSync(r,s,a),(n,{subPath:c})=>n.appendFileSync(c,s,a))}async writeFilePromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.writeFilePromise(r,s,a),async(n,{subPath:c})=>await n.writeFilePromise(c,s,a))}writeFileSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.writeFileSync(r,s,a),(n,{subPath:c})=>n.writeFileSync(c,s,a))}async unlinkPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.unlinkPromise(r),async(s,{subPath:a})=>await s.unlinkPromise(a))}unlinkSync(r){return this.makeCallSync(r,()=>this.baseFs.unlinkSync(r),(s,{subPath:a})=>s.unlinkSync(a))}async utimesPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.utimesPromise(r,s,a),async(n,{subPath:c})=>await n.utimesPromise(c,s,a))}utimesSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.utimesSync(r,s,a),(n,{subPath:c})=>n.utimesSync(c,s,a))}async lutimesPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.lutimesPromise(r,s,a),async(n,{subPath:c})=>await n.lutimesPromise(c,s,a))}lutimesSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.lutimesSync(r,s,a),(n,{subPath:c})=>n.lutimesSync(c,s,a))}async mkdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.mkdirPromise(r,s),async(a,{subPath:n})=>await a.mkdirPromise(n,s))}mkdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.mkdirSync(r,s),(a,{subPath:n})=>a.mkdirSync(n,s))}async rmdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.rmdirPromise(r,s),async(a,{subPath:n})=>await a.rmdirPromise(n,s))}rmdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.rmdirSync(r,s),(a,{subPath:n})=>a.rmdirSync(n,s))}async rmPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.rmPromise(r,s),async(a,{subPath:n})=>await a.rmPromise(n,s))}rmSync(r,s){return this.makeCallSync(r,()=>this.baseFs.rmSync(r,s),(a,{subPath:n})=>a.rmSync(n,s))}async linkPromise(r,s){return await this.makeCallPromise(s,async()=>await this.baseFs.linkPromise(r,s),async(a,{subPath:n})=>await a.linkPromise(r,n))}linkSync(r,s){return this.makeCallSync(s,()=>this.baseFs.linkSync(r,s),(a,{subPath:n})=>a.linkSync(r,n))}async symlinkPromise(r,s,a){return await this.makeCallPromise(s,async()=>await this.baseFs.symlinkPromise(r,s,a),async(n,{subPath:c})=>await n.symlinkPromise(r,c))}symlinkSync(r,s,a){return this.makeCallSync(s,()=>this.baseFs.symlinkSync(r,s,a),(n,{subPath:c})=>n.symlinkSync(r,c))}async readFilePromise(r,s){return this.makeCallPromise(r,async()=>await this.baseFs.readFilePromise(r,s),async(a,{subPath:n})=>await a.readFilePromise(n,s))}readFileSync(r,s){return this.makeCallSync(r,()=>this.baseFs.readFileSync(r,s),(a,{subPath:n})=>a.readFileSync(n,s))}async readdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.readdirPromise(r,s),async(a,{subPath:n})=>await a.readdirPromise(n,s),{requireSubpath:!1})}readdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.readdirSync(r,s),(a,{subPath:n})=>a.readdirSync(n,s),{requireSubpath:!1})}async readlinkPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.readlinkPromise(r),async(s,{subPath:a})=>await s.readlinkPromise(a))}readlinkSync(r){return this.makeCallSync(r,()=>this.baseFs.readlinkSync(r),(s,{subPath:a})=>s.readlinkSync(a))}async truncatePromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.truncatePromise(r,s),async(a,{subPath:n})=>await a.truncatePromise(n,s))}truncateSync(r,s){return this.makeCallSync(r,()=>this.baseFs.truncateSync(r,s),(a,{subPath:n})=>a.truncateSync(n,s))}async ftruncatePromise(r,s){if((r&tl)!==this.magic)return this.baseFs.ftruncatePromise(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("ftruncate");let[n,c]=a;return n.ftruncatePromise(c,s)}ftruncateSync(r,s){if((r&tl)!==this.magic)return this.baseFs.ftruncateSync(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Mo("ftruncateSync");let[n,c]=a;return n.ftruncateSync(c,s)}watch(r,s,a){return this.makeCallSync(r,()=>this.baseFs.watch(r,s,a),(n,{subPath:c})=>n.watch(c,s,a))}watchFile(r,s,a){return this.makeCallSync(r,()=>this.baseFs.watchFile(r,s,a),()=>sE(this,r,s,a))}unwatchFile(r,s){return this.makeCallSync(r,()=>this.baseFs.unwatchFile(r,s),()=>md(this,r,s))}async makeCallPromise(r,s,a,{requireSubpath:n=!0}={}){if(typeof r!="string")return await s();let c=this.resolve(r),f=this.findMount(c);return f?n&&f.subPath==="/"?await s():await this.getMountPromise(f.archivePath,async p=>await a(p,f)):await s()}makeCallSync(r,s,a,{requireSubpath:n=!0}={}){if(typeof r!="string")return s();let c=this.resolve(r),f=this.findMount(c);return!f||n&&f.subPath==="/"?s():this.getMountSync(f.archivePath,p=>a(p,f))}findMount(r){if(this.filter&&!this.filter.test(r))return null;let s="";for(;;){let a=r.substring(s.length),n=this.getMountPoint(a,s);if(!n)return null;if(s=this.pathUtils.join(s,n),!this.isMount.has(s)){if(this.notMount.has(s))continue;try{if(this.typeCheck!==null&&(this.baseFs.statSync(s).mode&wd.constants.S_IFMT)!==this.typeCheck){this.notMount.add(s);continue}}catch{return null}this.isMount.add(s)}return{archivePath:s,subPath:this.pathUtils.join(vt.root,r.substring(s.length))}}}limitOpenFiles(r){if(this.mountInstances===null)return;let s=Date.now(),a=s+this.maxAge,n=r===null?0:this.mountInstances.size-r;for(let[c,{childFs:f,expiresAt:p,refCount:h}]of this.mountInstances.entries())if(!(h!==0||f.hasOpenFileHandles?.())){if(s>=p){f.saveAndClose?.(),this.mountInstances.delete(c),n-=1;continue}else if(r===null||n<=0){a=p;break}f.saveAndClose?.(),this.mountInstances.delete(c),n-=1}this.limitOpenFilesTimeout===null&&(r===null&&this.mountInstances.size>0||r!==null)&&isFinite(a)&&(this.limitOpenFilesTimeout=setTimeout(()=>{this.limitOpenFilesTimeout=null,this.limitOpenFiles(null)},a-s).unref())}async getMountPromise(r,s){if(this.mountInstances){let a=this.mountInstances.get(r);if(!a){let n=await this.factoryPromise(this.baseFs,r);a=this.mountInstances.get(r),a||(a={childFs:n(),expiresAt:0,refCount:0})}this.mountInstances.delete(r),this.limitOpenFiles(this.maxOpenFiles-1),this.mountInstances.set(r,a),a.expiresAt=Date.now()+this.maxAge,a.refCount+=1;try{return await s(a.childFs)}finally{a.refCount-=1}}else{let a=(await this.factoryPromise(this.baseFs,r))();try{return await s(a)}finally{a.saveAndClose?.()}}}getMountSync(r,s){if(this.mountInstances){let a=this.mountInstances.get(r);return a||(a={childFs:this.factorySync(this.baseFs,r),expiresAt:0,refCount:0}),this.mountInstances.delete(r),this.limitOpenFiles(this.maxOpenFiles-1),this.mountInstances.set(r,a),a.expiresAt=Date.now()+this.maxAge,s(a.childFs)}else{let a=this.factorySync(this.baseFs,r);try{return s(a)}finally{a.saveAndClose?.()}}}}});var er,nx,A$=Xe(()=>{Id();el();er=()=>Object.assign(new Error("ENOSYS: unsupported filesystem access"),{code:"ENOSYS"}),nx=class t extends mp{static{this.instance=new t}constructor(){super(J)}getExtractHint(){throw er()}getRealPath(){throw er()}resolve(){throw er()}async openPromise(){throw er()}openSync(){throw er()}async opendirPromise(){throw er()}opendirSync(){throw er()}async readPromise(){throw er()}readSync(){throw er()}async writePromise(){throw er()}writeSync(){throw er()}async closePromise(){throw er()}closeSync(){throw er()}createWriteStream(){throw er()}createReadStream(){throw er()}async realpathPromise(){throw er()}realpathSync(){throw er()}async readdirPromise(){throw er()}readdirSync(){throw er()}async existsPromise(e){throw er()}existsSync(e){throw er()}async accessPromise(){throw er()}accessSync(){throw er()}async statPromise(){throw er()}statSync(){throw er()}async fstatPromise(e){throw er()}fstatSync(e){throw er()}async lstatPromise(e){throw er()}lstatSync(e){throw er()}async fchmodPromise(){throw er()}fchmodSync(){throw er()}async chmodPromise(){throw er()}chmodSync(){throw er()}async fchownPromise(){throw er()}fchownSync(){throw er()}async chownPromise(){throw er()}chownSync(){throw er()}async mkdirPromise(){throw er()}mkdirSync(){throw er()}async rmdirPromise(){throw er()}rmdirSync(){throw er()}async rmPromise(){throw er()}rmSync(){throw er()}async linkPromise(){throw er()}linkSync(){throw er()}async symlinkPromise(){throw er()}symlinkSync(){throw er()}async renamePromise(){throw er()}renameSync(){throw er()}async copyFilePromise(){throw er()}copyFileSync(){throw er()}async appendFilePromise(){throw er()}appendFileSync(){throw er()}async writeFilePromise(){throw er()}writeFileSync(){throw er()}async unlinkPromise(){throw er()}unlinkSync(){throw er()}async utimesPromise(){throw er()}utimesSync(){throw er()}async lutimesPromise(){throw er()}lutimesSync(){throw er()}async readFilePromise(){throw er()}readFileSync(){throw er()}async readlinkPromise(){throw er()}readlinkSync(){throw er()}async truncatePromise(){throw er()}truncateSync(){throw er()}async ftruncatePromise(e,r){throw er()}ftruncateSync(e,r){throw er()}watch(){throw er()}watchFile(){throw er()}unwatchFile(){throw er()}}});var t0,p$=Xe(()=>{yp();el();t0=class extends _s{constructor(e){super(fe),this.baseFs=e}mapFromBase(e){return fe.fromPortablePath(e)}mapToBase(e){return fe.toPortablePath(e)}}});var o5e,PU,a5e,uo,h$=Xe(()=>{Cd();yp();el();o5e=/^[0-9]+$/,PU=/^(\/(?:[^/]+\/)*?(?:\$\$virtual|__virtual__))((?:\/((?:[^/]+-)?[a-f0-9]+)(?:\/([^/]+))?)?((?:\/.*)?))$/,a5e=/^([^/]+-)?[a-f0-9]+$/,uo=class t extends _s{static makeVirtualPath(e,r,s){if(J.basename(e)!=="__virtual__")throw new Error('Assertion failed: Virtual folders must be named "__virtual__"');if(!J.basename(r).match(a5e))throw new Error("Assertion failed: Virtual components must be ended by an hexadecimal hash");let n=J.relative(J.dirname(e),s).split("/"),c=0;for(;c{xU=ut(Ie("buffer")),g$=Ie("url"),d$=Ie("util");yp();el();ix=class extends _s{constructor(e){super(fe),this.baseFs=e}mapFromBase(e){return e}mapToBase(e){if(typeof e=="string")return e;if(e instanceof URL)return(0,g$.fileURLToPath)(e);if(Buffer.isBuffer(e)){let r=e.toString();if(!l5e(e,r))throw new Error("Non-utf8 buffers are not supported at the moment. Please upvote the following issue if you encounter this error: https://github.com/yarnpkg/berry/issues/4942");return r}throw new Error(`Unsupported path type: ${(0,d$.inspect)(e)}`)}}});var w$,Uo,Ep,r0,sx,ox,aE,Ru,Fu,y$,E$,I$,C$,M2,B$=Xe(()=>{w$=Ie("readline"),Uo=Symbol("kBaseFs"),Ep=Symbol("kFd"),r0=Symbol("kClosePromise"),sx=Symbol("kCloseResolve"),ox=Symbol("kCloseReject"),aE=Symbol("kRefs"),Ru=Symbol("kRef"),Fu=Symbol("kUnref"),M2=class{constructor(e,r){this[C$]=1;this[I$]=void 0;this[E$]=void 0;this[y$]=void 0;this[Uo]=r,this[Ep]=e}get fd(){return this[Ep]}async appendFile(e,r){try{this[Ru](this.appendFile);let s=(typeof r=="string"?r:r?.encoding)??void 0;return await this[Uo].appendFilePromise(this.fd,e,s?{encoding:s}:void 0)}finally{this[Fu]()}}async chown(e,r){try{return this[Ru](this.chown),await this[Uo].fchownPromise(this.fd,e,r)}finally{this[Fu]()}}async chmod(e){try{return this[Ru](this.chmod),await this[Uo].fchmodPromise(this.fd,e)}finally{this[Fu]()}}createReadStream(e){return this[Uo].createReadStream(null,{...e,fd:this.fd})}createWriteStream(e){return this[Uo].createWriteStream(null,{...e,fd:this.fd})}datasync(){throw new Error("Method not implemented.")}sync(){throw new Error("Method not implemented.")}async read(e,r,s,a){try{this[Ru](this.read);let n,c;return ArrayBuffer.isView(e)?typeof r=="object"&&r!==null?(n=e,c=r?.offset??0,s=r?.length??n.byteLength-c,a=r?.position??null):(n=e,c=r??0,s??=0):(n=e?.buffer??Buffer.alloc(16384),c=e?.offset??0,s=e?.length??n.byteLength-c,a=e?.position??null),s===0?{bytesRead:s,buffer:n}:{bytesRead:await this[Uo].readPromise(this.fd,Buffer.isBuffer(n)?n:Buffer.from(n.buffer,n.byteOffset,n.byteLength),c,s,a),buffer:n}}finally{this[Fu]()}}async readFile(e){try{this[Ru](this.readFile);let r=(typeof e=="string"?e:e?.encoding)??void 0;return await this[Uo].readFilePromise(this.fd,r)}finally{this[Fu]()}}readLines(e){return(0,w$.createInterface)({input:this.createReadStream(e),crlfDelay:1/0})}async stat(e){try{return this[Ru](this.stat),await this[Uo].fstatPromise(this.fd,e)}finally{this[Fu]()}}async truncate(e){try{return this[Ru](this.truncate),await this[Uo].ftruncatePromise(this.fd,e)}finally{this[Fu]()}}utimes(e,r){throw new Error("Method not implemented.")}async writeFile(e,r){try{this[Ru](this.writeFile);let s=(typeof r=="string"?r:r?.encoding)??void 0;await this[Uo].writeFilePromise(this.fd,e,s)}finally{this[Fu]()}}async write(...e){try{if(this[Ru](this.write),ArrayBuffer.isView(e[0])){let[r,s,a,n]=e;return{bytesWritten:await this[Uo].writePromise(this.fd,r,s??void 0,a??void 0,n??void 0),buffer:r}}else{let[r,s,a]=e;return{bytesWritten:await this[Uo].writePromise(this.fd,r,s,a),buffer:r}}}finally{this[Fu]()}}async writev(e,r){try{this[Ru](this.writev);let s=0;if(typeof r<"u")for(let a of e){let n=await this.write(a,void 0,void 0,r);s+=n.bytesWritten,r+=n.bytesWritten}else for(let a of e){let n=await this.write(a);s+=n.bytesWritten}return{buffers:e,bytesWritten:s}}finally{this[Fu]()}}readv(e,r){throw new Error("Method not implemented.")}close(){if(this[Ep]===-1)return Promise.resolve();if(this[r0])return this[r0];if(this[aE]--,this[aE]===0){let e=this[Ep];this[Ep]=-1,this[r0]=this[Uo].closePromise(e).finally(()=>{this[r0]=void 0})}else this[r0]=new Promise((e,r)=>{this[sx]=e,this[ox]=r}).finally(()=>{this[r0]=void 0,this[ox]=void 0,this[sx]=void 0});return this[r0]}[(Uo,Ep,C$=aE,I$=r0,E$=sx,y$=ox,Ru)](e){if(this[Ep]===-1){let r=new Error("file closed");throw r.code="EBADF",r.syscall=e.name,r}this[aE]++}[Fu](){if(this[aE]--,this[aE]===0){let e=this[Ep];this[Ep]=-1,this[Uo].closePromise(e).then(this[sx],this[ox])}}}});function U2(t,e){e=new ix(e);let r=(s,a,n)=>{let c=s[a];s[a]=n,typeof c?.[lE.promisify.custom]<"u"&&(n[lE.promisify.custom]=c[lE.promisify.custom])};{r(t,"exists",(s,...a)=>{let c=typeof a[a.length-1]=="function"?a.pop():()=>{};process.nextTick(()=>{e.existsPromise(s).then(f=>{c(f)},()=>{c(!1)})})}),r(t,"read",(...s)=>{let[a,n,c,f,p,h]=s;if(s.length<=3){let E={};s.length<3?h=s[1]:(E=s[1],h=s[2]),{buffer:n=Buffer.alloc(16384),offset:c=0,length:f=n.byteLength,position:p}=E}if(c==null&&(c=0),f|=0,f===0){process.nextTick(()=>{h(null,0,n)});return}p==null&&(p=-1),process.nextTick(()=>{e.readPromise(a,n,c,f,p).then(E=>{h(null,E,n)},E=>{h(E,0,n)})})});for(let s of v$){let a=s.replace(/Promise$/,"");if(typeof t[a]>"u")continue;let n=e[s];if(typeof n>"u")continue;r(t,a,(...f)=>{let h=typeof f[f.length-1]=="function"?f.pop():()=>{};process.nextTick(()=>{n.apply(e,f).then(E=>{h(null,E)},E=>{h(E)})})})}t.realpath.native=t.realpath}{r(t,"existsSync",s=>{try{return e.existsSync(s)}catch{return!1}}),r(t,"readSync",(...s)=>{let[a,n,c,f,p]=s;return s.length<=3&&({offset:c=0,length:f=n.byteLength,position:p}=s[2]||{}),c==null&&(c=0),f|=0,f===0?0:(p==null&&(p=-1),e.readSync(a,n,c,f,p))});for(let s of c5e){let a=s;if(typeof t[a]>"u")continue;let n=e[s];typeof n>"u"||r(t,a,n.bind(e))}t.realpathSync.native=t.realpathSync}{let s=t.promises;for(let a of v$){let n=a.replace(/Promise$/,"");if(typeof s[n]>"u")continue;let c=e[a];typeof c>"u"||a!=="open"&&r(s,n,(f,...p)=>f instanceof M2?f[n].apply(f,p):c.call(e,f,...p))}r(s,"open",async(...a)=>{let n=await e.openPromise(...a);return new M2(n,e)})}t.read[lE.promisify.custom]=async(s,a,...n)=>({bytesRead:await e.readPromise(s,a,...n),buffer:a}),t.write[lE.promisify.custom]=async(s,a,...n)=>({bytesWritten:await e.writePromise(s,a,...n),buffer:a})}function ax(t,e){let r=Object.create(t);return U2(r,e),r}var lE,c5e,v$,S$=Xe(()=>{lE=Ie("util");m$();B$();c5e=new Set(["accessSync","appendFileSync","createReadStream","createWriteStream","chmodSync","fchmodSync","chownSync","fchownSync","closeSync","copyFileSync","linkSync","lstatSync","fstatSync","lutimesSync","mkdirSync","openSync","opendirSync","readlinkSync","readFileSync","readdirSync","readlinkSync","realpathSync","renameSync","rmdirSync","rmSync","statSync","symlinkSync","truncateSync","ftruncateSync","unlinkSync","unwatchFile","utimesSync","watch","watchFile","writeFileSync","writeSync"]),v$=new Set(["accessPromise","appendFilePromise","fchmodPromise","chmodPromise","fchownPromise","chownPromise","closePromise","copyFilePromise","linkPromise","fstatPromise","lstatPromise","lutimesPromise","mkdirPromise","openPromise","opendirPromise","readdirPromise","realpathPromise","readFilePromise","readdirPromise","readlinkPromise","renamePromise","rmdirPromise","rmPromise","statPromise","symlinkPromise","truncatePromise","ftruncatePromise","unlinkPromise","utimesPromise","writeFilePromise","writeSync"])});function D$(t){let e=Math.ceil(Math.random()*4294967296).toString(16).padStart(8,"0");return`${t}${e}`}function b$(){if(kU)return kU;let t=fe.toPortablePath(P$.default.tmpdir()),e=ce.realpathSync(t);return process.once("exit",()=>{ce.rmtempSync()}),kU={tmpdir:t,realTmpdir:e}}var P$,Nu,kU,ce,x$=Xe(()=>{P$=ut(Ie("os"));Cd();el();Nu=new Set,kU=null;ce=Object.assign(new Yn,{detachTemp(t){Nu.delete(t)},mktempSync(t){let{tmpdir:e,realTmpdir:r}=b$();for(;;){let s=D$("xfs-");try{this.mkdirSync(J.join(e,s))}catch(n){if(n.code==="EEXIST")continue;throw n}let a=J.join(r,s);if(Nu.add(a),typeof t>"u")return a;try{return t(a)}finally{if(Nu.has(a)){Nu.delete(a);try{this.removeSync(a)}catch{}}}}},async mktempPromise(t){let{tmpdir:e,realTmpdir:r}=b$();for(;;){let s=D$("xfs-");try{await this.mkdirPromise(J.join(e,s))}catch(n){if(n.code==="EEXIST")continue;throw n}let a=J.join(r,s);if(Nu.add(a),typeof t>"u")return a;try{return await t(a)}finally{if(Nu.has(a)){Nu.delete(a);try{await this.removePromise(a)}catch{}}}}},async rmtempPromise(){await Promise.all(Array.from(Nu.values()).map(async t=>{try{await ce.removePromise(t,{maxRetries:0}),Nu.delete(t)}catch{}}))},rmtempSync(){for(let t of Nu)try{ce.removeSync(t),Nu.delete(t)}catch{}}})});var _2={};Vt(_2,{AliasFS:()=>_f,BasePortableFakeFS:()=>Uf,CustomDir:()=>L2,CwdFS:()=>Sn,FakeFS:()=>mp,Filename:()=>Er,JailFS:()=>Hf,LazyFS:()=>oE,MountFS:()=>e0,NoFS:()=>nx,NodeFS:()=>Yn,PortablePath:()=>vt,PosixFS:()=>t0,ProxiedFS:()=>_s,VirtualFS:()=>uo,constants:()=>fi,errors:()=>or,extendFs:()=>ax,normalizeLineEndings:()=>Ed,npath:()=>fe,opendir:()=>ex,patchFs:()=>U2,ppath:()=>J,setupCopyIndex:()=>$P,statUtils:()=>$a,unwatchAllFiles:()=>yd,unwatchFile:()=>md,watchFile:()=>sE,xfs:()=>ce});var Dt=Xe(()=>{YZ();zP();BU();DU();ZZ();bU();Id();el();el();i$();Id();a$();c$();u$();f$();A$();Cd();p$();yp();h$();S$();x$()});var F$=_((Dkt,R$)=>{R$.exports=T$;T$.sync=f5e;var k$=Ie("fs");function u5e(t,e){var r=e.pathExt!==void 0?e.pathExt:process.env.PATHEXT;if(!r||(r=r.split(";"),r.indexOf("")!==-1))return!0;for(var s=0;s{M$.exports=O$;O$.sync=A5e;var N$=Ie("fs");function O$(t,e,r){N$.stat(t,function(s,a){r(s,s?!1:L$(a,e))})}function A5e(t,e){return L$(N$.statSync(t),e)}function L$(t,e){return t.isFile()&&p5e(t,e)}function p5e(t,e){var r=t.mode,s=t.uid,a=t.gid,n=e.uid!==void 0?e.uid:process.getuid&&process.getuid(),c=e.gid!==void 0?e.gid:process.getgid&&process.getgid(),f=parseInt("100",8),p=parseInt("010",8),h=parseInt("001",8),E=f|p,C=r&h||r&p&&a===c||r&f&&s===n||r&E&&n===0;return C}});var H$=_((xkt,_$)=>{var Pkt=Ie("fs"),lx;process.platform==="win32"||global.TESTING_WINDOWS?lx=F$():lx=U$();_$.exports=QU;QU.sync=h5e;function QU(t,e,r){if(typeof e=="function"&&(r=e,e={}),!r){if(typeof Promise!="function")throw new TypeError("callback not provided");return new Promise(function(s,a){QU(t,e||{},function(n,c){n?a(n):s(c)})})}lx(t,e||{},function(s,a){s&&(s.code==="EACCES"||e&&e.ignoreErrors)&&(s=null,a=!1),r(s,a)})}function h5e(t,e){try{return lx.sync(t,e||{})}catch(r){if(e&&e.ignoreErrors||r.code==="EACCES")return!1;throw r}}});var J$=_((kkt,V$)=>{var cE=process.platform==="win32"||process.env.OSTYPE==="cygwin"||process.env.OSTYPE==="msys",j$=Ie("path"),g5e=cE?";":":",G$=H$(),q$=t=>Object.assign(new Error(`not found: ${t}`),{code:"ENOENT"}),W$=(t,e)=>{let r=e.colon||g5e,s=t.match(/\//)||cE&&t.match(/\\/)?[""]:[...cE?[process.cwd()]:[],...(e.path||process.env.PATH||"").split(r)],a=cE?e.pathExt||process.env.PATHEXT||".EXE;.CMD;.BAT;.COM":"",n=cE?a.split(r):[""];return cE&&t.indexOf(".")!==-1&&n[0]!==""&&n.unshift(""),{pathEnv:s,pathExt:n,pathExtExe:a}},Y$=(t,e,r)=>{typeof e=="function"&&(r=e,e={}),e||(e={});let{pathEnv:s,pathExt:a,pathExtExe:n}=W$(t,e),c=[],f=h=>new Promise((E,C)=>{if(h===s.length)return e.all&&c.length?E(c):C(q$(t));let S=s[h],P=/^".*"$/.test(S)?S.slice(1,-1):S,I=j$.join(P,t),R=!P&&/^\.[\\\/]/.test(t)?t.slice(0,2)+I:I;E(p(R,h,0))}),p=(h,E,C)=>new Promise((S,P)=>{if(C===a.length)return S(f(E+1));let I=a[C];G$(h+I,{pathExt:n},(R,N)=>{if(!R&&N)if(e.all)c.push(h+I);else return S(h+I);return S(p(h,E,C+1))})});return r?f(0).then(h=>r(null,h),r):f(0)},d5e=(t,e)=>{e=e||{};let{pathEnv:r,pathExt:s,pathExtExe:a}=W$(t,e),n=[];for(let c=0;c{"use strict";var K$=(t={})=>{let e=t.env||process.env;return(t.platform||process.platform)!=="win32"?"PATH":Object.keys(e).reverse().find(s=>s.toUpperCase()==="PATH")||"Path"};TU.exports=K$;TU.exports.default=K$});var eee=_((Tkt,$$)=>{"use strict";var X$=Ie("path"),m5e=J$(),y5e=z$();function Z$(t,e){let r=t.options.env||process.env,s=process.cwd(),a=t.options.cwd!=null,n=a&&process.chdir!==void 0&&!process.chdir.disabled;if(n)try{process.chdir(t.options.cwd)}catch{}let c;try{c=m5e.sync(t.command,{path:r[y5e({env:r})],pathExt:e?X$.delimiter:void 0})}catch{}finally{n&&process.chdir(s)}return c&&(c=X$.resolve(a?t.options.cwd:"",c)),c}function E5e(t){return Z$(t)||Z$(t,!0)}$$.exports=E5e});var tee=_((Rkt,FU)=>{"use strict";var RU=/([()\][%!^"`<>&|;, *?])/g;function I5e(t){return t=t.replace(RU,"^$1"),t}function C5e(t,e){return t=`${t}`,t=t.replace(/(?=(\\+?)?)\1"/g,'$1$1\\"'),t=t.replace(/(?=(\\+?)?)\1$/,"$1$1"),t=`"${t}"`,t=t.replace(RU,"^$1"),e&&(t=t.replace(RU,"^$1")),t}FU.exports.command=I5e;FU.exports.argument=C5e});var nee=_((Fkt,ree)=>{"use strict";ree.exports=/^#!(.*)/});var see=_((Nkt,iee)=>{"use strict";var w5e=nee();iee.exports=(t="")=>{let e=t.match(w5e);if(!e)return null;let[r,s]=e[0].replace(/#! ?/,"").split(" "),a=r.split("/").pop();return a==="env"?s:s?`${a} ${s}`:a}});var aee=_((Okt,oee)=>{"use strict";var NU=Ie("fs"),B5e=see();function v5e(t){let r=Buffer.alloc(150),s;try{s=NU.openSync(t,"r"),NU.readSync(s,r,0,150,0),NU.closeSync(s)}catch{}return B5e(r.toString())}oee.exports=v5e});var fee=_((Lkt,uee)=>{"use strict";var S5e=Ie("path"),lee=eee(),cee=tee(),D5e=aee(),b5e=process.platform==="win32",P5e=/\.(?:com|exe)$/i,x5e=/node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;function k5e(t){t.file=lee(t);let e=t.file&&D5e(t.file);return e?(t.args.unshift(t.file),t.command=e,lee(t)):t.file}function Q5e(t){if(!b5e)return t;let e=k5e(t),r=!P5e.test(e);if(t.options.forceShell||r){let s=x5e.test(e);t.command=S5e.normalize(t.command),t.command=cee.command(t.command),t.args=t.args.map(n=>cee.argument(n,s));let a=[t.command].concat(t.args).join(" ");t.args=["/d","/s","/c",`"${a}"`],t.command=process.env.comspec||"cmd.exe",t.options.windowsVerbatimArguments=!0}return t}function T5e(t,e,r){e&&!Array.isArray(e)&&(r=e,e=null),e=e?e.slice(0):[],r=Object.assign({},r);let s={command:t,args:e,options:r,file:void 0,original:{command:t,args:e}};return r.shell?s:Q5e(s)}uee.exports=T5e});var hee=_((Mkt,pee)=>{"use strict";var OU=process.platform==="win32";function LU(t,e){return Object.assign(new Error(`${e} ${t.command} ENOENT`),{code:"ENOENT",errno:"ENOENT",syscall:`${e} ${t.command}`,path:t.command,spawnargs:t.args})}function R5e(t,e){if(!OU)return;let r=t.emit;t.emit=function(s,a){if(s==="exit"){let n=Aee(a,e);if(n)return r.call(t,"error",n)}return r.apply(t,arguments)}}function Aee(t,e){return OU&&t===1&&!e.file?LU(e.original,"spawn"):null}function F5e(t,e){return OU&&t===1&&!e.file?LU(e.original,"spawnSync"):null}pee.exports={hookChildProcess:R5e,verifyENOENT:Aee,verifyENOENTSync:F5e,notFoundError:LU}});var _U=_((Ukt,uE)=>{"use strict";var gee=Ie("child_process"),MU=fee(),UU=hee();function dee(t,e,r){let s=MU(t,e,r),a=gee.spawn(s.command,s.args,s.options);return UU.hookChildProcess(a,s),a}function N5e(t,e,r){let s=MU(t,e,r),a=gee.spawnSync(s.command,s.args,s.options);return a.error=a.error||UU.verifyENOENTSync(a.status,s),a}uE.exports=dee;uE.exports.spawn=dee;uE.exports.sync=N5e;uE.exports._parse=MU;uE.exports._enoent=UU});var yee=_((_kt,mee)=>{"use strict";function O5e(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function Bd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Bd)}O5e(Bd,Error);Bd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;C>",b=ur(">>",!1),y=">&",F=ur(">&",!1),z=">",X=ur(">",!1),$="<<<",oe=ur("<<<",!1),xe="<&",Te=ur("<&",!1),lt="<",Ct=ur("<",!1),qt=function(O){return{type:"argument",segments:[].concat(...O)}},ir=function(O){return O},Pt="$'",gn=ur("$'",!1),Pr="'",Ir=ur("'",!1),Or=function(O){return[{type:"text",text:O}]},on='""',ai=ur('""',!1),Io=function(){return{type:"text",text:""}},rs='"',$s=ur('"',!1),Co=function(O){return O},ji=function(O){return{type:"arithmetic",arithmetic:O,quoted:!0}},eo=function(O){return{type:"shell",shell:O,quoted:!0}},wo=function(O){return{type:"variable",...O,quoted:!0}},QA=function(O){return{type:"text",text:O}},Af=function(O){return{type:"arithmetic",arithmetic:O,quoted:!1}},dh=function(O){return{type:"shell",shell:O,quoted:!1}},mh=function(O){return{type:"variable",...O,quoted:!1}},to=function(O){return{type:"glob",pattern:O}},jn=/^[^']/,Ts=zi(["'"],!0,!1),ro=function(O){return O.join("")},ou=/^[^$"]/,au=zi(["$",'"'],!0,!1),lu=`\\ `,TA=ur(`\\ `,!1),RA=function(){return""},oa="\\",aa=ur("\\",!1),FA=/^[\\$"`]/,gr=zi(["\\","$",'"',"`"],!1,!1),Bo=function(O){return O},Me="\\a",cu=ur("\\a",!1),Cr=function(){return"a"},pf="\\b",NA=ur("\\b",!1),OA=function(){return"\b"},uu=/^[Ee]/,fu=zi(["E","e"],!1,!1),oc=function(){return"\x1B"},ve="\\f",Nt=ur("\\f",!1),ac=function(){return"\f"},Oi="\\n",no=ur("\\n",!1),Rt=function(){return` `},xn="\\r",la=ur("\\r",!1),Gi=function(){return"\r"},Li="\\t",Na=ur("\\t",!1),dn=function(){return" "},Kn="\\v",Au=ur("\\v",!1),yh=function(){return"\v"},Oa=/^[\\'"?]/,La=zi(["\\","'",'"',"?"],!1,!1),Ma=function(O){return String.fromCharCode(parseInt(O,16))},$e="\\x",Ua=ur("\\x",!1),hf="\\u",lc=ur("\\u",!1),wn="\\U",ca=ur("\\U",!1),LA=function(O){return String.fromCodePoint(parseInt(O,16))},MA=/^[0-7]/,ua=zi([["0","7"]],!1,!1),Bl=/^[0-9a-fA-f]/,Mt=zi([["0","9"],["a","f"],["A","f"]],!1,!1),kn=yf(),fa="{}",Ha=ur("{}",!1),ns=function(){return"{}"},cc="-",pu=ur("-",!1),uc="+",ja=ur("+",!1),Mi=".",Is=ur(".",!1),vl=function(O,K,re){return{type:"number",value:(O==="-"?-1:1)*parseFloat(K.join("")+"."+re.join(""))}},gf=function(O,K){return{type:"number",value:(O==="-"?-1:1)*parseInt(K.join(""))}},fc=function(O){return{type:"variable",...O}},wi=function(O){return{type:"variable",name:O}},Qn=function(O){return O},Ac="*",Ke=ur("*",!1),st="/",St=ur("/",!1),lr=function(O,K,re){return{type:K==="*"?"multiplication":"division",right:re}},te=function(O,K){return K.reduce((re,de)=>({left:re,...de}),O)},Ee=function(O,K,re){return{type:K==="+"?"addition":"subtraction",right:re}},Oe="$((",dt=ur("$((",!1),Et="))",bt=ur("))",!1),tr=function(O){return O},An="$(",li=ur("$(",!1),qi=function(O){return O},Tn="${",Ga=ur("${",!1),my=":-",Z1=ur(":-",!1),vo=function(O,K){return{name:O,defaultValue:K}},yy=":-}",Eh=ur(":-}",!1),$1=function(O){return{name:O,defaultValue:[]}},So=":+",Ih=ur(":+",!1),Ch=function(O,K){return{name:O,alternativeValue:K}},hu=":+}",wh=ur(":+}",!1),Fg=function(O){return{name:O,alternativeValue:[]}},Ng=function(O){return{name:O}},Og="$",Ey=ur("$",!1),df=function(O){return e.isGlobPattern(O)},Do=function(O){return O},Sl=/^[a-zA-Z0-9_]/,Bh=zi([["a","z"],["A","Z"],["0","9"],"_"],!1,!1),Lg=function(){return By()},Dl=/^[$@*?#a-zA-Z0-9_\-]/,bl=zi(["$","@","*","?","#",["a","z"],["A","Z"],["0","9"],"_","-"],!1,!1),Iy=/^[()}<>$|&; \t"']/,UA=zi(["(",")","}","<",">","$","|","&",";"," "," ",'"',"'"],!1,!1),Cy=/^[<>&; \t"']/,wy=zi(["<",">","&",";"," "," ",'"',"'"],!1,!1),_A=/^[ \t]/,HA=zi([" "," "],!1,!1),Y=0,xt=0,jA=[{line:1,column:1}],bo=0,mf=[],yt=0,gu;if("startRule"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');a=s[e.startRule]}function By(){return t.substring(xt,Y)}function Mg(){return Ef(xt,Y)}function e2(O,K){throw K=K!==void 0?K:Ef(xt,Y),GA([Ug(O)],t.substring(xt,Y),K)}function vh(O,K){throw K=K!==void 0?K:Ef(xt,Y),di(O,K)}function ur(O,K){return{type:"literal",text:O,ignoreCase:K}}function zi(O,K,re){return{type:"class",parts:O,inverted:K,ignoreCase:re}}function yf(){return{type:"any"}}function qa(){return{type:"end"}}function Ug(O){return{type:"other",description:O}}function du(O){var K=jA[O],re;if(K)return K;for(re=O-1;!jA[re];)re--;for(K=jA[re],K={line:K.line,column:K.column};rebo&&(bo=Y,mf=[]),mf.push(O))}function di(O,K){return new Bd(O,null,null,K)}function GA(O,K,re){return new Bd(Bd.buildMessage(O,K),O,K,re)}function Wa(){var O,K,re;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();return K!==r?(re=Aa(),re===r&&(re=null),re!==r?(xt=O,K=n(re),O=K):(Y=O,O=r)):(Y=O,O=r),O}function Aa(){var O,K,re,de,Je;if(O=Y,K=Sh(),K!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();re!==r?(de=_g(),de!==r?(Je=Ya(),Je===r&&(Je=null),Je!==r?(xt=O,K=c(K,de,Je),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;if(O===r)if(O=Y,K=Sh(),K!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();re!==r?(de=_g(),de===r&&(de=null),de!==r?(xt=O,K=f(K,de),O=K):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;return O}function Ya(){var O,K,re,de,Je;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r)if(re=Aa(),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();de!==r?(xt=O,K=p(re),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;return O}function _g(){var O;return t.charCodeAt(Y)===59?(O=h,Y++):(O=r,yt===0&&wt(E)),O===r&&(t.charCodeAt(Y)===38?(O=C,Y++):(O=r,yt===0&&wt(S))),O}function Sh(){var O,K,re;return O=Y,K=qA(),K!==r?(re=Hg(),re===r&&(re=null),re!==r?(xt=O,K=P(K,re),O=K):(Y=O,O=r)):(Y=O,O=r),O}function Hg(){var O,K,re,de,Je,At,dr;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r)if(re=vy(),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();if(de!==r)if(Je=Sh(),Je!==r){for(At=[],dr=kt();dr!==r;)At.push(dr),dr=kt();At!==r?(xt=O,K=I(re,Je),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;return O}function vy(){var O;return t.substr(Y,2)===R?(O=R,Y+=2):(O=r,yt===0&&wt(N)),O===r&&(t.substr(Y,2)===U?(O=U,Y+=2):(O=r,yt===0&&wt(W))),O}function qA(){var O,K,re;return O=Y,K=If(),K!==r?(re=jg(),re===r&&(re=null),re!==r?(xt=O,K=ee(K,re),O=K):(Y=O,O=r)):(Y=O,O=r),O}function jg(){var O,K,re,de,Je,At,dr;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r)if(re=mu(),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();if(de!==r)if(Je=qA(),Je!==r){for(At=[],dr=kt();dr!==r;)At.push(dr),dr=kt();At!==r?(xt=O,K=ie(re,Je),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;return O}function mu(){var O;return t.substr(Y,2)===ue?(O=ue,Y+=2):(O=r,yt===0&&wt(le)),O===r&&(t.charCodeAt(Y)===124?(O=me,Y++):(O=r,yt===0&&wt(pe))),O}function yu(){var O,K,re,de,Je,At;if(O=Y,K=Ph(),K!==r)if(t.charCodeAt(Y)===61?(re=Be,Y++):(re=r,yt===0&&wt(Ce)),re!==r)if(de=WA(),de!==r){for(Je=[],At=kt();At!==r;)Je.push(At),At=kt();Je!==r?(xt=O,K=g(K,de),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;else Y=O,O=r;if(O===r)if(O=Y,K=Ph(),K!==r)if(t.charCodeAt(Y)===61?(re=Be,Y++):(re=r,yt===0&&wt(Ce)),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();de!==r?(xt=O,K=we(K),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;return O}function If(){var O,K,re,de,Je,At,dr,vr,Un,mi,Cs;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r)if(t.charCodeAt(Y)===40?(re=ye,Y++):(re=r,yt===0&&wt(Ae)),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();if(de!==r)if(Je=Aa(),Je!==r){for(At=[],dr=kt();dr!==r;)At.push(dr),dr=kt();if(At!==r)if(t.charCodeAt(Y)===41?(dr=se,Y++):(dr=r,yt===0&&wt(Z)),dr!==r){for(vr=[],Un=kt();Un!==r;)vr.push(Un),Un=kt();if(vr!==r){for(Un=[],mi=Gn();mi!==r;)Un.push(mi),mi=Gn();if(Un!==r){for(mi=[],Cs=kt();Cs!==r;)mi.push(Cs),Cs=kt();mi!==r?(xt=O,K=De(Je,Un),O=K):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;if(O===r){for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r)if(t.charCodeAt(Y)===123?(re=Re,Y++):(re=r,yt===0&&wt(mt)),re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();if(de!==r)if(Je=Aa(),Je!==r){for(At=[],dr=kt();dr!==r;)At.push(dr),dr=kt();if(At!==r)if(t.charCodeAt(Y)===125?(dr=j,Y++):(dr=r,yt===0&&wt(rt)),dr!==r){for(vr=[],Un=kt();Un!==r;)vr.push(Un),Un=kt();if(vr!==r){for(Un=[],mi=Gn();mi!==r;)Un.push(mi),mi=Gn();if(Un!==r){for(mi=[],Cs=kt();Cs!==r;)mi.push(Cs),Cs=kt();mi!==r?(xt=O,K=Fe(Je,Un),O=K):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;if(O===r){for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r){for(re=[],de=yu();de!==r;)re.push(de),de=yu();if(re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();if(de!==r){if(Je=[],At=Eu(),At!==r)for(;At!==r;)Je.push(At),At=Eu();else Je=r;if(Je!==r){for(At=[],dr=kt();dr!==r;)At.push(dr),dr=kt();At!==r?(xt=O,K=Ne(re,Je),O=K):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;if(O===r){for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r){if(re=[],de=yu(),de!==r)for(;de!==r;)re.push(de),de=yu();else re=r;if(re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();de!==r?(xt=O,K=Pe(re),O=K):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}}}return O}function Rs(){var O,K,re,de,Je;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r){if(re=[],de=Pi(),de!==r)for(;de!==r;)re.push(de),de=Pi();else re=r;if(re!==r){for(de=[],Je=kt();Je!==r;)de.push(Je),Je=kt();de!==r?(xt=O,K=Ve(re),O=K):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r;return O}function Eu(){var O,K,re;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();if(K!==r?(re=Gn(),re!==r?(xt=O,K=ke(re),O=K):(Y=O,O=r)):(Y=O,O=r),O===r){for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();K!==r?(re=Pi(),re!==r?(xt=O,K=ke(re),O=K):(Y=O,O=r)):(Y=O,O=r)}return O}function Gn(){var O,K,re,de,Je;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();return K!==r?(it.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Ue)),re===r&&(re=null),re!==r?(de=is(),de!==r?(Je=Pi(),Je!==r?(xt=O,K=x(re,de,Je),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function is(){var O;return t.substr(Y,2)===w?(O=w,Y+=2):(O=r,yt===0&&wt(b)),O===r&&(t.substr(Y,2)===y?(O=y,Y+=2):(O=r,yt===0&&wt(F)),O===r&&(t.charCodeAt(Y)===62?(O=z,Y++):(O=r,yt===0&&wt(X)),O===r&&(t.substr(Y,3)===$?(O=$,Y+=3):(O=r,yt===0&&wt(oe)),O===r&&(t.substr(Y,2)===xe?(O=xe,Y+=2):(O=r,yt===0&&wt(Te)),O===r&&(t.charCodeAt(Y)===60?(O=lt,Y++):(O=r,yt===0&&wt(Ct))))))),O}function Pi(){var O,K,re;for(O=Y,K=[],re=kt();re!==r;)K.push(re),re=kt();return K!==r?(re=WA(),re!==r?(xt=O,K=ke(re),O=K):(Y=O,O=r)):(Y=O,O=r),O}function WA(){var O,K,re;if(O=Y,K=[],re=Cf(),re!==r)for(;re!==r;)K.push(re),re=Cf();else K=r;return K!==r&&(xt=O,K=qt(K)),O=K,O}function Cf(){var O,K;return O=Y,K=mn(),K!==r&&(xt=O,K=ir(K)),O=K,O===r&&(O=Y,K=Gg(),K!==r&&(xt=O,K=ir(K)),O=K,O===r&&(O=Y,K=qg(),K!==r&&(xt=O,K=ir(K)),O=K,O===r&&(O=Y,K=ss(),K!==r&&(xt=O,K=ir(K)),O=K))),O}function mn(){var O,K,re,de;return O=Y,t.substr(Y,2)===Pt?(K=Pt,Y+=2):(K=r,yt===0&&wt(gn)),K!==r?(re=yn(),re!==r?(t.charCodeAt(Y)===39?(de=Pr,Y++):(de=r,yt===0&&wt(Ir)),de!==r?(xt=O,K=Or(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function Gg(){var O,K,re,de;return O=Y,t.charCodeAt(Y)===39?(K=Pr,Y++):(K=r,yt===0&&wt(Ir)),K!==r?(re=wf(),re!==r?(t.charCodeAt(Y)===39?(de=Pr,Y++):(de=r,yt===0&&wt(Ir)),de!==r?(xt=O,K=Or(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function qg(){var O,K,re,de;if(O=Y,t.substr(Y,2)===on?(K=on,Y+=2):(K=r,yt===0&&wt(ai)),K!==r&&(xt=O,K=Io()),O=K,O===r)if(O=Y,t.charCodeAt(Y)===34?(K=rs,Y++):(K=r,yt===0&&wt($s)),K!==r){for(re=[],de=Pl();de!==r;)re.push(de),de=Pl();re!==r?(t.charCodeAt(Y)===34?(de=rs,Y++):(de=r,yt===0&&wt($s)),de!==r?(xt=O,K=Co(re),O=K):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;return O}function ss(){var O,K,re;if(O=Y,K=[],re=Po(),re!==r)for(;re!==r;)K.push(re),re=Po();else K=r;return K!==r&&(xt=O,K=Co(K)),O=K,O}function Pl(){var O,K;return O=Y,K=Zr(),K!==r&&(xt=O,K=ji(K)),O=K,O===r&&(O=Y,K=bh(),K!==r&&(xt=O,K=eo(K)),O=K,O===r&&(O=Y,K=VA(),K!==r&&(xt=O,K=wo(K)),O=K,O===r&&(O=Y,K=Bf(),K!==r&&(xt=O,K=QA(K)),O=K))),O}function Po(){var O,K;return O=Y,K=Zr(),K!==r&&(xt=O,K=Af(K)),O=K,O===r&&(O=Y,K=bh(),K!==r&&(xt=O,K=dh(K)),O=K,O===r&&(O=Y,K=VA(),K!==r&&(xt=O,K=mh(K)),O=K,O===r&&(O=Y,K=Sy(),K!==r&&(xt=O,K=to(K)),O=K,O===r&&(O=Y,K=Dh(),K!==r&&(xt=O,K=QA(K)),O=K)))),O}function wf(){var O,K,re;for(O=Y,K=[],jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Ts));re!==r;)K.push(re),jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Ts));return K!==r&&(xt=O,K=ro(K)),O=K,O}function Bf(){var O,K,re;if(O=Y,K=[],re=xl(),re===r&&(ou.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(au))),re!==r)for(;re!==r;)K.push(re),re=xl(),re===r&&(ou.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(au)));else K=r;return K!==r&&(xt=O,K=ro(K)),O=K,O}function xl(){var O,K,re;return O=Y,t.substr(Y,2)===lu?(K=lu,Y+=2):(K=r,yt===0&&wt(TA)),K!==r&&(xt=O,K=RA()),O=K,O===r&&(O=Y,t.charCodeAt(Y)===92?(K=oa,Y++):(K=r,yt===0&&wt(aa)),K!==r?(FA.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(gr)),re!==r?(xt=O,K=Bo(re),O=K):(Y=O,O=r)):(Y=O,O=r)),O}function yn(){var O,K,re;for(O=Y,K=[],re=xo(),re===r&&(jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Ts)));re!==r;)K.push(re),re=xo(),re===r&&(jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Ts)));return K!==r&&(xt=O,K=ro(K)),O=K,O}function xo(){var O,K,re;return O=Y,t.substr(Y,2)===Me?(K=Me,Y+=2):(K=r,yt===0&&wt(cu)),K!==r&&(xt=O,K=Cr()),O=K,O===r&&(O=Y,t.substr(Y,2)===pf?(K=pf,Y+=2):(K=r,yt===0&&wt(NA)),K!==r&&(xt=O,K=OA()),O=K,O===r&&(O=Y,t.charCodeAt(Y)===92?(K=oa,Y++):(K=r,yt===0&&wt(aa)),K!==r?(uu.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(fu)),re!==r?(xt=O,K=oc(),O=K):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===ve?(K=ve,Y+=2):(K=r,yt===0&&wt(Nt)),K!==r&&(xt=O,K=ac()),O=K,O===r&&(O=Y,t.substr(Y,2)===Oi?(K=Oi,Y+=2):(K=r,yt===0&&wt(no)),K!==r&&(xt=O,K=Rt()),O=K,O===r&&(O=Y,t.substr(Y,2)===xn?(K=xn,Y+=2):(K=r,yt===0&&wt(la)),K!==r&&(xt=O,K=Gi()),O=K,O===r&&(O=Y,t.substr(Y,2)===Li?(K=Li,Y+=2):(K=r,yt===0&&wt(Na)),K!==r&&(xt=O,K=dn()),O=K,O===r&&(O=Y,t.substr(Y,2)===Kn?(K=Kn,Y+=2):(K=r,yt===0&&wt(Au)),K!==r&&(xt=O,K=yh()),O=K,O===r&&(O=Y,t.charCodeAt(Y)===92?(K=oa,Y++):(K=r,yt===0&&wt(aa)),K!==r?(Oa.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(La)),re!==r?(xt=O,K=Bo(re),O=K):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Iu()))))))))),O}function Iu(){var O,K,re,de,Je,At,dr,vr,Un,mi,Cs,JA;return O=Y,t.charCodeAt(Y)===92?(K=oa,Y++):(K=r,yt===0&&wt(aa)),K!==r?(re=pa(),re!==r?(xt=O,K=Ma(re),O=K):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===$e?(K=$e,Y+=2):(K=r,yt===0&&wt(Ua)),K!==r?(re=Y,de=Y,Je=pa(),Je!==r?(At=Fs(),At!==r?(Je=[Je,At],de=Je):(Y=de,de=r)):(Y=de,de=r),de===r&&(de=pa()),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,K=Ma(re),O=K):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===hf?(K=hf,Y+=2):(K=r,yt===0&&wt(lc)),K!==r?(re=Y,de=Y,Je=Fs(),Je!==r?(At=Fs(),At!==r?(dr=Fs(),dr!==r?(vr=Fs(),vr!==r?(Je=[Je,At,dr,vr],de=Je):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,K=Ma(re),O=K):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===wn?(K=wn,Y+=2):(K=r,yt===0&&wt(ca)),K!==r?(re=Y,de=Y,Je=Fs(),Je!==r?(At=Fs(),At!==r?(dr=Fs(),dr!==r?(vr=Fs(),vr!==r?(Un=Fs(),Un!==r?(mi=Fs(),mi!==r?(Cs=Fs(),Cs!==r?(JA=Fs(),JA!==r?(Je=[Je,At,dr,vr,Un,mi,Cs,JA],de=Je):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,K=LA(re),O=K):(Y=O,O=r)):(Y=O,O=r)))),O}function pa(){var O;return MA.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,yt===0&&wt(ua)),O}function Fs(){var O;return Bl.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,yt===0&&wt(Mt)),O}function Dh(){var O,K,re,de,Je;if(O=Y,K=[],re=Y,t.charCodeAt(Y)===92?(de=oa,Y++):(de=r,yt===0&&wt(aa)),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r),re===r&&(re=Y,t.substr(Y,2)===fa?(de=fa,Y+=2):(de=r,yt===0&&wt(Ha)),de!==r&&(xt=re,de=ns()),re=de,re===r&&(re=Y,de=Y,yt++,Je=Dy(),yt--,Je===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r))),re!==r)for(;re!==r;)K.push(re),re=Y,t.charCodeAt(Y)===92?(de=oa,Y++):(de=r,yt===0&&wt(aa)),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r),re===r&&(re=Y,t.substr(Y,2)===fa?(de=fa,Y+=2):(de=r,yt===0&&wt(Ha)),de!==r&&(xt=re,de=ns()),re=de,re===r&&(re=Y,de=Y,yt++,Je=Dy(),yt--,Je===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r)));else K=r;return K!==r&&(xt=O,K=ro(K)),O=K,O}function YA(){var O,K,re,de,Je,At;if(O=Y,t.charCodeAt(Y)===45?(K=cc,Y++):(K=r,yt===0&&wt(pu)),K===r&&(t.charCodeAt(Y)===43?(K=uc,Y++):(K=r,yt===0&&wt(ja))),K===r&&(K=null),K!==r){if(re=[],it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,yt===0&&wt(Ue)),de!==r)for(;de!==r;)re.push(de),it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,yt===0&&wt(Ue));else re=r;if(re!==r)if(t.charCodeAt(Y)===46?(de=Mi,Y++):(de=r,yt===0&&wt(Is)),de!==r){if(Je=[],it.test(t.charAt(Y))?(At=t.charAt(Y),Y++):(At=r,yt===0&&wt(Ue)),At!==r)for(;At!==r;)Je.push(At),it.test(t.charAt(Y))?(At=t.charAt(Y),Y++):(At=r,yt===0&&wt(Ue));else Je=r;Je!==r?(xt=O,K=vl(K,re,Je),O=K):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;if(O===r){if(O=Y,t.charCodeAt(Y)===45?(K=cc,Y++):(K=r,yt===0&&wt(pu)),K===r&&(t.charCodeAt(Y)===43?(K=uc,Y++):(K=r,yt===0&&wt(ja))),K===r&&(K=null),K!==r){if(re=[],it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,yt===0&&wt(Ue)),de!==r)for(;de!==r;)re.push(de),it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,yt===0&&wt(Ue));else re=r;re!==r?(xt=O,K=gf(K,re),O=K):(Y=O,O=r)}else Y=O,O=r;if(O===r&&(O=Y,K=VA(),K!==r&&(xt=O,K=fc(K)),O=K,O===r&&(O=Y,K=pc(),K!==r&&(xt=O,K=wi(K)),O=K,O===r)))if(O=Y,t.charCodeAt(Y)===40?(K=ye,Y++):(K=r,yt===0&&wt(Ae)),K!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();if(re!==r)if(de=io(),de!==r){for(Je=[],At=kt();At!==r;)Je.push(At),At=kt();Je!==r?(t.charCodeAt(Y)===41?(At=se,Y++):(At=r,yt===0&&wt(Z)),At!==r?(xt=O,K=Qn(de),O=K):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r}return O}function vf(){var O,K,re,de,Je,At,dr,vr;if(O=Y,K=YA(),K!==r){for(re=[],de=Y,Je=[],At=kt();At!==r;)Je.push(At),At=kt();if(Je!==r)if(t.charCodeAt(Y)===42?(At=Ac,Y++):(At=r,yt===0&&wt(Ke)),At===r&&(t.charCodeAt(Y)===47?(At=st,Y++):(At=r,yt===0&&wt(St))),At!==r){for(dr=[],vr=kt();vr!==r;)dr.push(vr),vr=kt();dr!==r?(vr=YA(),vr!==r?(xt=de,Je=lr(K,At,vr),de=Je):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r;for(;de!==r;){for(re.push(de),de=Y,Je=[],At=kt();At!==r;)Je.push(At),At=kt();if(Je!==r)if(t.charCodeAt(Y)===42?(At=Ac,Y++):(At=r,yt===0&&wt(Ke)),At===r&&(t.charCodeAt(Y)===47?(At=st,Y++):(At=r,yt===0&&wt(St))),At!==r){for(dr=[],vr=kt();vr!==r;)dr.push(vr),vr=kt();dr!==r?(vr=YA(),vr!==r?(xt=de,Je=lr(K,At,vr),de=Je):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r}re!==r?(xt=O,K=te(K,re),O=K):(Y=O,O=r)}else Y=O,O=r;return O}function io(){var O,K,re,de,Je,At,dr,vr;if(O=Y,K=vf(),K!==r){for(re=[],de=Y,Je=[],At=kt();At!==r;)Je.push(At),At=kt();if(Je!==r)if(t.charCodeAt(Y)===43?(At=uc,Y++):(At=r,yt===0&&wt(ja)),At===r&&(t.charCodeAt(Y)===45?(At=cc,Y++):(At=r,yt===0&&wt(pu))),At!==r){for(dr=[],vr=kt();vr!==r;)dr.push(vr),vr=kt();dr!==r?(vr=vf(),vr!==r?(xt=de,Je=Ee(K,At,vr),de=Je):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r;for(;de!==r;){for(re.push(de),de=Y,Je=[],At=kt();At!==r;)Je.push(At),At=kt();if(Je!==r)if(t.charCodeAt(Y)===43?(At=uc,Y++):(At=r,yt===0&&wt(ja)),At===r&&(t.charCodeAt(Y)===45?(At=cc,Y++):(At=r,yt===0&&wt(pu))),At!==r){for(dr=[],vr=kt();vr!==r;)dr.push(vr),vr=kt();dr!==r?(vr=vf(),vr!==r?(xt=de,Je=Ee(K,At,vr),de=Je):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r}re!==r?(xt=O,K=te(K,re),O=K):(Y=O,O=r)}else Y=O,O=r;return O}function Zr(){var O,K,re,de,Je,At;if(O=Y,t.substr(Y,3)===Oe?(K=Oe,Y+=3):(K=r,yt===0&&wt(dt)),K!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();if(re!==r)if(de=io(),de!==r){for(Je=[],At=kt();At!==r;)Je.push(At),At=kt();Je!==r?(t.substr(Y,2)===Et?(At=Et,Y+=2):(At=r,yt===0&&wt(bt)),At!==r?(xt=O,K=tr(de),O=K):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;return O}function bh(){var O,K,re,de;return O=Y,t.substr(Y,2)===An?(K=An,Y+=2):(K=r,yt===0&&wt(li)),K!==r?(re=Aa(),re!==r?(t.charCodeAt(Y)===41?(de=se,Y++):(de=r,yt===0&&wt(Z)),de!==r?(xt=O,K=qi(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function VA(){var O,K,re,de,Je,At;return O=Y,t.substr(Y,2)===Tn?(K=Tn,Y+=2):(K=r,yt===0&&wt(Ga)),K!==r?(re=pc(),re!==r?(t.substr(Y,2)===my?(de=my,Y+=2):(de=r,yt===0&&wt(Z1)),de!==r?(Je=Rs(),Je!==r?(t.charCodeAt(Y)===125?(At=j,Y++):(At=r,yt===0&&wt(rt)),At!==r?(xt=O,K=vo(re,Je),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(K=Tn,Y+=2):(K=r,yt===0&&wt(Ga)),K!==r?(re=pc(),re!==r?(t.substr(Y,3)===yy?(de=yy,Y+=3):(de=r,yt===0&&wt(Eh)),de!==r?(xt=O,K=$1(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(K=Tn,Y+=2):(K=r,yt===0&&wt(Ga)),K!==r?(re=pc(),re!==r?(t.substr(Y,2)===So?(de=So,Y+=2):(de=r,yt===0&&wt(Ih)),de!==r?(Je=Rs(),Je!==r?(t.charCodeAt(Y)===125?(At=j,Y++):(At=r,yt===0&&wt(rt)),At!==r?(xt=O,K=Ch(re,Je),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(K=Tn,Y+=2):(K=r,yt===0&&wt(Ga)),K!==r?(re=pc(),re!==r?(t.substr(Y,3)===hu?(de=hu,Y+=3):(de=r,yt===0&&wt(wh)),de!==r?(xt=O,K=Fg(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(K=Tn,Y+=2):(K=r,yt===0&&wt(Ga)),K!==r?(re=pc(),re!==r?(t.charCodeAt(Y)===125?(de=j,Y++):(de=r,yt===0&&wt(rt)),de!==r?(xt=O,K=Ng(re),O=K):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.charCodeAt(Y)===36?(K=Og,Y++):(K=r,yt===0&&wt(Ey)),K!==r?(re=pc(),re!==r?(xt=O,K=Ng(re),O=K):(Y=O,O=r)):(Y=O,O=r)))))),O}function Sy(){var O,K,re;return O=Y,K=Wg(),K!==r?(xt=Y,re=df(K),re?re=void 0:re=r,re!==r?(xt=O,K=Do(K),O=K):(Y=O,O=r)):(Y=O,O=r),O}function Wg(){var O,K,re,de,Je;if(O=Y,K=[],re=Y,de=Y,yt++,Je=xh(),yt--,Je===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r),re!==r)for(;re!==r;)K.push(re),re=Y,de=Y,yt++,Je=xh(),yt--,Je===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Je=t.charAt(Y),Y++):(Je=r,yt===0&&wt(kn)),Je!==r?(xt=re,de=Bo(Je),re=de):(Y=re,re=r)):(Y=re,re=r);else K=r;return K!==r&&(xt=O,K=ro(K)),O=K,O}function Ph(){var O,K,re;if(O=Y,K=[],Sl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Bh)),re!==r)for(;re!==r;)K.push(re),Sl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(Bh));else K=r;return K!==r&&(xt=O,K=Lg()),O=K,O}function pc(){var O,K,re;if(O=Y,K=[],Dl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(bl)),re!==r)for(;re!==r;)K.push(re),Dl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,yt===0&&wt(bl));else K=r;return K!==r&&(xt=O,K=Lg()),O=K,O}function Dy(){var O;return Iy.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,yt===0&&wt(UA)),O}function xh(){var O;return Cy.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,yt===0&&wt(wy)),O}function kt(){var O,K;if(O=[],_A.test(t.charAt(Y))?(K=t.charAt(Y),Y++):(K=r,yt===0&&wt(HA)),K!==r)for(;K!==r;)O.push(K),_A.test(t.charAt(Y))?(K=t.charAt(Y),Y++):(K=r,yt===0&&wt(HA));else O=r;return O}if(gu=a(),gu!==r&&Y===t.length)return gu;throw gu!==r&&Y!1}){try{return(0,Eee.parse)(t,e)}catch(r){throw r.location&&(r.message=r.message.replace(/(\.)?$/,` (line ${r.location.start.line}, column ${r.location.start.column})$1`)),r}}function fE(t,{endSemicolon:e=!1}={}){return t.map(({command:r,type:s},a)=>`${fx(r)}${s===";"?a!==t.length-1||e?";":"":" &"}`).join(" ")}function fx(t){return`${AE(t.chain)}${t.then?` ${HU(t.then)}`:""}`}function HU(t){return`${t.type} ${fx(t.line)}`}function AE(t){return`${GU(t)}${t.then?` ${jU(t.then)}`:""}`}function jU(t){return`${t.type} ${AE(t.chain)}`}function GU(t){switch(t.type){case"command":return`${t.envs.length>0?`${t.envs.map(e=>cx(e)).join(" ")} `:""}${t.args.map(e=>qU(e)).join(" ")}`;case"subshell":return`(${fE(t.subshell)})${t.args.length>0?` ${t.args.map(e=>H2(e)).join(" ")}`:""}`;case"group":return`{ ${fE(t.group,{endSemicolon:!0})} }${t.args.length>0?` ${t.args.map(e=>H2(e)).join(" ")}`:""}`;case"envs":return t.envs.map(e=>cx(e)).join(" ");default:throw new Error(`Unsupported command type: "${t.type}"`)}}function cx(t){return`${t.name}=${t.args[0]?vd(t.args[0]):""}`}function qU(t){switch(t.type){case"redirection":return H2(t);case"argument":return vd(t);default:throw new Error(`Unsupported argument type: "${t.type}"`)}}function H2(t){return`${t.subtype} ${t.args.map(e=>vd(e)).join(" ")}`}function vd(t){return t.segments.map(e=>WU(e)).join("")}function WU(t){let e=(s,a)=>a?`"${s}"`:s,r=s=>s===""?"''":s.match(/[()}<>$|&;"'\n\t ]/)?s.match(/['\t\p{C}]/u)?s.match(/'/)?`"${s.replace(/["$\t\p{C}]/u,U5e)}"`:`$'${s.replace(/[\t\p{C}]/u,Cee)}'`:`'${s}'`:s;switch(t.type){case"text":return r(t.text);case"glob":return t.pattern;case"shell":return e(`$(${fE(t.shell)})`,t.quoted);case"variable":return e(typeof t.defaultValue>"u"?typeof t.alternativeValue>"u"?`\${${t.name}}`:t.alternativeValue.length===0?`\${${t.name}:+}`:`\${${t.name}:+${t.alternativeValue.map(s=>vd(s)).join(" ")}}`:t.defaultValue.length===0?`\${${t.name}:-}`:`\${${t.name}:-${t.defaultValue.map(s=>vd(s)).join(" ")}}`,t.quoted);case"arithmetic":return`$(( ${Ax(t.arithmetic)} ))`;default:throw new Error(`Unsupported argument segment type: "${t.type}"`)}}function Ax(t){let e=a=>{switch(a){case"addition":return"+";case"subtraction":return"-";case"multiplication":return"*";case"division":return"/";default:throw new Error(`Can't extract operator from arithmetic expression of type "${a}"`)}},r=(a,n)=>n?`( ${a} )`:a,s=a=>r(Ax(a),!["number","variable"].includes(a.type));switch(t.type){case"number":return String(t.value);case"variable":return t.name;default:return`${s(t.left)} ${e(t.type)} ${s(t.right)}`}}var Eee,Iee,M5e,Cee,U5e,wee=Xe(()=>{Eee=ut(yee());Iee=new Map([["\f","\\f"],[` `,"\\n"],["\r","\\r"],[" ","\\t"],["\v","\\v"],["\0","\\0"]]),M5e=new Map([["\\","\\\\"],["$","\\$"],['"','\\"'],...Array.from(Iee,([t,e])=>[t,`"$'${e}'"`])]),Cee=t=>Iee.get(t)??`\\x${t.charCodeAt(0).toString(16).padStart(2,"0")}`,U5e=t=>M5e.get(t)??`"$'${Cee(t)}'"`});var vee=_((eQt,Bee)=>{"use strict";function _5e(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function Sd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Sd)}_5e(Sd,Error);Sd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;Cue&&(ue=W,le=[]),le.push(Ue))}function rt(Ue,x){return new Sd(Ue,null,null,x)}function Fe(Ue,x,w){return new Sd(Sd.buildMessage(Ue,x),Ue,x,w)}function Ne(){var Ue,x,w,b;return Ue=W,x=Pe(),x!==r?(t.charCodeAt(W)===47?(w=n,W++):(w=r,me===0&&j(c)),w!==r?(b=Pe(),b!==r?(ee=Ue,x=f(x,b),Ue=x):(W=Ue,Ue=r)):(W=Ue,Ue=r)):(W=Ue,Ue=r),Ue===r&&(Ue=W,x=Pe(),x!==r&&(ee=Ue,x=p(x)),Ue=x),Ue}function Pe(){var Ue,x,w,b;return Ue=W,x=Ve(),x!==r?(t.charCodeAt(W)===64?(w=h,W++):(w=r,me===0&&j(E)),w!==r?(b=it(),b!==r?(ee=Ue,x=C(x,b),Ue=x):(W=Ue,Ue=r)):(W=Ue,Ue=r)):(W=Ue,Ue=r),Ue===r&&(Ue=W,x=Ve(),x!==r&&(ee=Ue,x=S(x)),Ue=x),Ue}function Ve(){var Ue,x,w,b,y;return Ue=W,t.charCodeAt(W)===64?(x=h,W++):(x=r,me===0&&j(E)),x!==r?(w=ke(),w!==r?(t.charCodeAt(W)===47?(b=n,W++):(b=r,me===0&&j(c)),b!==r?(y=ke(),y!==r?(ee=Ue,x=P(),Ue=x):(W=Ue,Ue=r)):(W=Ue,Ue=r)):(W=Ue,Ue=r)):(W=Ue,Ue=r),Ue===r&&(Ue=W,x=ke(),x!==r&&(ee=Ue,x=P()),Ue=x),Ue}function ke(){var Ue,x,w;if(Ue=W,x=[],I.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(R)),w!==r)for(;w!==r;)x.push(w),I.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(R));else x=r;return x!==r&&(ee=Ue,x=P()),Ue=x,Ue}function it(){var Ue,x,w;if(Ue=W,x=[],N.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(U)),w!==r)for(;w!==r;)x.push(w),N.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(U));else x=r;return x!==r&&(ee=Ue,x=P()),Ue=x,Ue}if(pe=a(),pe!==r&&W===t.length)return pe;throw pe!==r&&W{See=ut(vee())});var bd=_((rQt,Dd)=>{"use strict";function bee(t){return typeof t>"u"||t===null}function j5e(t){return typeof t=="object"&&t!==null}function G5e(t){return Array.isArray(t)?t:bee(t)?[]:[t]}function q5e(t,e){var r,s,a,n;if(e)for(n=Object.keys(e),r=0,s=n.length;r{"use strict";function j2(t,e){Error.call(this),this.name="YAMLException",this.reason=t,this.mark=e,this.message=(this.reason||"(unknown reason)")+(this.mark?" "+this.mark.toString():""),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack||""}j2.prototype=Object.create(Error.prototype);j2.prototype.constructor=j2;j2.prototype.toString=function(e){var r=this.name+": ";return r+=this.reason||"(unknown reason)",!e&&this.mark&&(r+=" "+this.mark.toString()),r};Pee.exports=j2});var Qee=_((iQt,kee)=>{"use strict";var xee=bd();function YU(t,e,r,s,a){this.name=t,this.buffer=e,this.position=r,this.line=s,this.column=a}YU.prototype.getSnippet=function(e,r){var s,a,n,c,f;if(!this.buffer)return null;for(e=e||4,r=r||75,s="",a=this.position;a>0&&`\0\r \x85\u2028\u2029`.indexOf(this.buffer.charAt(a-1))===-1;)if(a-=1,this.position-a>r/2-1){s=" ... ",a+=5;break}for(n="",c=this.position;cr/2-1){n=" ... ",c-=5;break}return f=this.buffer.slice(a,c),xee.repeat(" ",e)+s+f+n+` `+xee.repeat(" ",e+this.position-a+s.length)+"^"};YU.prototype.toString=function(e){var r,s="";return this.name&&(s+='in "'+this.name+'" '),s+="at line "+(this.line+1)+", column "+(this.column+1),e||(r=this.getSnippet(),r&&(s+=`: `+r)),s};kee.exports=YU});var Ss=_((sQt,Ree)=>{"use strict";var Tee=pE(),V5e=["kind","resolve","construct","instanceOf","predicate","represent","defaultStyle","styleAliases"],J5e=["scalar","sequence","mapping"];function K5e(t){var e={};return t!==null&&Object.keys(t).forEach(function(r){t[r].forEach(function(s){e[String(s)]=r})}),e}function z5e(t,e){if(e=e||{},Object.keys(e).forEach(function(r){if(V5e.indexOf(r)===-1)throw new Tee('Unknown option "'+r+'" is met in definition of "'+t+'" YAML type.')}),this.tag=t,this.kind=e.kind||null,this.resolve=e.resolve||function(){return!0},this.construct=e.construct||function(r){return r},this.instanceOf=e.instanceOf||null,this.predicate=e.predicate||null,this.represent=e.represent||null,this.defaultStyle=e.defaultStyle||null,this.styleAliases=K5e(e.styleAliases||null),J5e.indexOf(this.kind)===-1)throw new Tee('Unknown kind "'+this.kind+'" is specified for "'+t+'" YAML type.')}Ree.exports=z5e});var Pd=_((oQt,Nee)=>{"use strict";var Fee=bd(),gx=pE(),X5e=Ss();function VU(t,e,r){var s=[];return t.include.forEach(function(a){r=VU(a,e,r)}),t[e].forEach(function(a){r.forEach(function(n,c){n.tag===a.tag&&n.kind===a.kind&&s.push(c)}),r.push(a)}),r.filter(function(a,n){return s.indexOf(n)===-1})}function Z5e(){var t={scalar:{},sequence:{},mapping:{},fallback:{}},e,r;function s(a){t[a.kind][a.tag]=t.fallback[a.tag]=a}for(e=0,r=arguments.length;e{"use strict";var $5e=Ss();Oee.exports=new $5e("tag:yaml.org,2002:str",{kind:"scalar",construct:function(t){return t!==null?t:""}})});var Uee=_((lQt,Mee)=>{"use strict";var eqe=Ss();Mee.exports=new eqe("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(t){return t!==null?t:[]}})});var Hee=_((cQt,_ee)=>{"use strict";var tqe=Ss();_ee.exports=new tqe("tag:yaml.org,2002:map",{kind:"mapping",construct:function(t){return t!==null?t:{}}})});var dx=_((uQt,jee)=>{"use strict";var rqe=Pd();jee.exports=new rqe({explicit:[Lee(),Uee(),Hee()]})});var qee=_((fQt,Gee)=>{"use strict";var nqe=Ss();function iqe(t){if(t===null)return!0;var e=t.length;return e===1&&t==="~"||e===4&&(t==="null"||t==="Null"||t==="NULL")}function sqe(){return null}function oqe(t){return t===null}Gee.exports=new nqe("tag:yaml.org,2002:null",{kind:"scalar",resolve:iqe,construct:sqe,predicate:oqe,represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})});var Yee=_((AQt,Wee)=>{"use strict";var aqe=Ss();function lqe(t){if(t===null)return!1;var e=t.length;return e===4&&(t==="true"||t==="True"||t==="TRUE")||e===5&&(t==="false"||t==="False"||t==="FALSE")}function cqe(t){return t==="true"||t==="True"||t==="TRUE"}function uqe(t){return Object.prototype.toString.call(t)==="[object Boolean]"}Wee.exports=new aqe("tag:yaml.org,2002:bool",{kind:"scalar",resolve:lqe,construct:cqe,predicate:uqe,represent:{lowercase:function(t){return t?"true":"false"},uppercase:function(t){return t?"TRUE":"FALSE"},camelcase:function(t){return t?"True":"False"}},defaultStyle:"lowercase"})});var Jee=_((pQt,Vee)=>{"use strict";var fqe=bd(),Aqe=Ss();function pqe(t){return 48<=t&&t<=57||65<=t&&t<=70||97<=t&&t<=102}function hqe(t){return 48<=t&&t<=55}function gqe(t){return 48<=t&&t<=57}function dqe(t){if(t===null)return!1;var e=t.length,r=0,s=!1,a;if(!e)return!1;if(a=t[r],(a==="-"||a==="+")&&(a=t[++r]),a==="0"){if(r+1===e)return!0;if(a=t[++r],a==="b"){for(r++;r=0?"0b"+t.toString(2):"-0b"+t.toString(2).slice(1)},octal:function(t){return t>=0?"0"+t.toString(8):"-0"+t.toString(8).slice(1)},decimal:function(t){return t.toString(10)},hexadecimal:function(t){return t>=0?"0x"+t.toString(16).toUpperCase():"-0x"+t.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})});var Xee=_((hQt,zee)=>{"use strict";var Kee=bd(),Eqe=Ss(),Iqe=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");function Cqe(t){return!(t===null||!Iqe.test(t)||t[t.length-1]==="_")}function wqe(t){var e,r,s,a;return e=t.replace(/_/g,"").toLowerCase(),r=e[0]==="-"?-1:1,a=[],"+-".indexOf(e[0])>=0&&(e=e.slice(1)),e===".inf"?r===1?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:e===".nan"?NaN:e.indexOf(":")>=0?(e.split(":").forEach(function(n){a.unshift(parseFloat(n,10))}),e=0,s=1,a.forEach(function(n){e+=n*s,s*=60}),r*e):r*parseFloat(e,10)}var Bqe=/^[-+]?[0-9]+e/;function vqe(t,e){var r;if(isNaN(t))switch(e){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===t)switch(e){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===t)switch(e){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(Kee.isNegativeZero(t))return"-0.0";return r=t.toString(10),Bqe.test(r)?r.replace("e",".e"):r}function Sqe(t){return Object.prototype.toString.call(t)==="[object Number]"&&(t%1!==0||Kee.isNegativeZero(t))}zee.exports=new Eqe("tag:yaml.org,2002:float",{kind:"scalar",resolve:Cqe,construct:wqe,predicate:Sqe,represent:vqe,defaultStyle:"lowercase"})});var JU=_((gQt,Zee)=>{"use strict";var Dqe=Pd();Zee.exports=new Dqe({include:[dx()],implicit:[qee(),Yee(),Jee(),Xee()]})});var KU=_((dQt,$ee)=>{"use strict";var bqe=Pd();$ee.exports=new bqe({include:[JU()]})});var nte=_((mQt,rte)=>{"use strict";var Pqe=Ss(),ete=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),tte=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");function xqe(t){return t===null?!1:ete.exec(t)!==null||tte.exec(t)!==null}function kqe(t){var e,r,s,a,n,c,f,p=0,h=null,E,C,S;if(e=ete.exec(t),e===null&&(e=tte.exec(t)),e===null)throw new Error("Date resolve error");if(r=+e[1],s=+e[2]-1,a=+e[3],!e[4])return new Date(Date.UTC(r,s,a));if(n=+e[4],c=+e[5],f=+e[6],e[7]){for(p=e[7].slice(0,3);p.length<3;)p+="0";p=+p}return e[9]&&(E=+e[10],C=+(e[11]||0),h=(E*60+C)*6e4,e[9]==="-"&&(h=-h)),S=new Date(Date.UTC(r,s,a,n,c,f,p)),h&&S.setTime(S.getTime()-h),S}function Qqe(t){return t.toISOString()}rte.exports=new Pqe("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:xqe,construct:kqe,instanceOf:Date,represent:Qqe})});var ste=_((yQt,ite)=>{"use strict";var Tqe=Ss();function Rqe(t){return t==="<<"||t===null}ite.exports=new Tqe("tag:yaml.org,2002:merge",{kind:"scalar",resolve:Rqe})});var lte=_((EQt,ate)=>{"use strict";var xd;try{ote=Ie,xd=ote("buffer").Buffer}catch{}var ote,Fqe=Ss(),zU=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= \r`;function Nqe(t){if(t===null)return!1;var e,r,s=0,a=t.length,n=zU;for(r=0;r64)){if(e<0)return!1;s+=6}return s%8===0}function Oqe(t){var e,r,s=t.replace(/[\r\n=]/g,""),a=s.length,n=zU,c=0,f=[];for(e=0;e>16&255),f.push(c>>8&255),f.push(c&255)),c=c<<6|n.indexOf(s.charAt(e));return r=a%4*6,r===0?(f.push(c>>16&255),f.push(c>>8&255),f.push(c&255)):r===18?(f.push(c>>10&255),f.push(c>>2&255)):r===12&&f.push(c>>4&255),xd?xd.from?xd.from(f):new xd(f):f}function Lqe(t){var e="",r=0,s,a,n=t.length,c=zU;for(s=0;s>18&63],e+=c[r>>12&63],e+=c[r>>6&63],e+=c[r&63]),r=(r<<8)+t[s];return a=n%3,a===0?(e+=c[r>>18&63],e+=c[r>>12&63],e+=c[r>>6&63],e+=c[r&63]):a===2?(e+=c[r>>10&63],e+=c[r>>4&63],e+=c[r<<2&63],e+=c[64]):a===1&&(e+=c[r>>2&63],e+=c[r<<4&63],e+=c[64],e+=c[64]),e}function Mqe(t){return xd&&xd.isBuffer(t)}ate.exports=new Fqe("tag:yaml.org,2002:binary",{kind:"scalar",resolve:Nqe,construct:Oqe,predicate:Mqe,represent:Lqe})});var ute=_((CQt,cte)=>{"use strict";var Uqe=Ss(),_qe=Object.prototype.hasOwnProperty,Hqe=Object.prototype.toString;function jqe(t){if(t===null)return!0;var e=[],r,s,a,n,c,f=t;for(r=0,s=f.length;r{"use strict";var qqe=Ss(),Wqe=Object.prototype.toString;function Yqe(t){if(t===null)return!0;var e,r,s,a,n,c=t;for(n=new Array(c.length),e=0,r=c.length;e{"use strict";var Jqe=Ss(),Kqe=Object.prototype.hasOwnProperty;function zqe(t){if(t===null)return!0;var e,r=t;for(e in r)if(Kqe.call(r,e)&&r[e]!==null)return!1;return!0}function Xqe(t){return t!==null?t:{}}pte.exports=new Jqe("tag:yaml.org,2002:set",{kind:"mapping",resolve:zqe,construct:Xqe})});var gE=_((vQt,gte)=>{"use strict";var Zqe=Pd();gte.exports=new Zqe({include:[KU()],implicit:[nte(),ste()],explicit:[lte(),ute(),Ate(),hte()]})});var mte=_((SQt,dte)=>{"use strict";var $qe=Ss();function e9e(){return!0}function t9e(){}function r9e(){return""}function n9e(t){return typeof t>"u"}dte.exports=new $qe("tag:yaml.org,2002:js/undefined",{kind:"scalar",resolve:e9e,construct:t9e,predicate:n9e,represent:r9e})});var Ete=_((DQt,yte)=>{"use strict";var i9e=Ss();function s9e(t){if(t===null||t.length===0)return!1;var e=t,r=/\/([gim]*)$/.exec(t),s="";return!(e[0]==="/"&&(r&&(s=r[1]),s.length>3||e[e.length-s.length-1]!=="/"))}function o9e(t){var e=t,r=/\/([gim]*)$/.exec(t),s="";return e[0]==="/"&&(r&&(s=r[1]),e=e.slice(1,e.length-s.length-1)),new RegExp(e,s)}function a9e(t){var e="/"+t.source+"/";return t.global&&(e+="g"),t.multiline&&(e+="m"),t.ignoreCase&&(e+="i"),e}function l9e(t){return Object.prototype.toString.call(t)==="[object RegExp]"}yte.exports=new i9e("tag:yaml.org,2002:js/regexp",{kind:"scalar",resolve:s9e,construct:o9e,predicate:l9e,represent:a9e})});var wte=_((bQt,Cte)=>{"use strict";var mx;try{Ite=Ie,mx=Ite("esprima")}catch{typeof window<"u"&&(mx=window.esprima)}var Ite,c9e=Ss();function u9e(t){if(t===null)return!1;try{var e="("+t+")",r=mx.parse(e,{range:!0});return!(r.type!=="Program"||r.body.length!==1||r.body[0].type!=="ExpressionStatement"||r.body[0].expression.type!=="ArrowFunctionExpression"&&r.body[0].expression.type!=="FunctionExpression")}catch{return!1}}function f9e(t){var e="("+t+")",r=mx.parse(e,{range:!0}),s=[],a;if(r.type!=="Program"||r.body.length!==1||r.body[0].type!=="ExpressionStatement"||r.body[0].expression.type!=="ArrowFunctionExpression"&&r.body[0].expression.type!=="FunctionExpression")throw new Error("Failed to resolve function");return r.body[0].expression.params.forEach(function(n){s.push(n.name)}),a=r.body[0].expression.body.range,r.body[0].expression.body.type==="BlockStatement"?new Function(s,e.slice(a[0]+1,a[1]-1)):new Function(s,"return "+e.slice(a[0],a[1]))}function A9e(t){return t.toString()}function p9e(t){return Object.prototype.toString.call(t)==="[object Function]"}Cte.exports=new c9e("tag:yaml.org,2002:js/function",{kind:"scalar",resolve:u9e,construct:f9e,predicate:p9e,represent:A9e})});var G2=_((xQt,vte)=>{"use strict";var Bte=Pd();vte.exports=Bte.DEFAULT=new Bte({include:[gE()],explicit:[mte(),Ete(),wte()]})});var Gte=_((kQt,q2)=>{"use strict";var Ip=bd(),Qte=pE(),h9e=Qee(),Tte=gE(),g9e=G2(),i0=Object.prototype.hasOwnProperty,yx=1,Rte=2,Fte=3,Ex=4,XU=1,d9e=2,Ste=3,m9e=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,y9e=/[\x85\u2028\u2029]/,E9e=/[,\[\]\{\}]/,Nte=/^(?:!|!!|![a-z\-]+!)$/i,Ote=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function Dte(t){return Object.prototype.toString.call(t)}function jf(t){return t===10||t===13}function Qd(t){return t===9||t===32}function rl(t){return t===9||t===32||t===10||t===13}function dE(t){return t===44||t===91||t===93||t===123||t===125}function I9e(t){var e;return 48<=t&&t<=57?t-48:(e=t|32,97<=e&&e<=102?e-97+10:-1)}function C9e(t){return t===120?2:t===117?4:t===85?8:0}function w9e(t){return 48<=t&&t<=57?t-48:-1}function bte(t){return t===48?"\0":t===97?"\x07":t===98?"\b":t===116||t===9?" ":t===110?` `:t===118?"\v":t===102?"\f":t===114?"\r":t===101?"\x1B":t===32?" ":t===34?'"':t===47?"/":t===92?"\\":t===78?"\x85":t===95?"\xA0":t===76?"\u2028":t===80?"\u2029":""}function B9e(t){return t<=65535?String.fromCharCode(t):String.fromCharCode((t-65536>>10)+55296,(t-65536&1023)+56320)}var Lte=new Array(256),Mte=new Array(256);for(kd=0;kd<256;kd++)Lte[kd]=bte(kd)?1:0,Mte[kd]=bte(kd);var kd;function v9e(t,e){this.input=t,this.filename=e.filename||null,this.schema=e.schema||g9e,this.onWarning=e.onWarning||null,this.legacy=e.legacy||!1,this.json=e.json||!1,this.listener=e.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=t.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function Ute(t,e){return new Qte(e,new h9e(t.filename,t.input,t.position,t.line,t.position-t.lineStart))}function Rr(t,e){throw Ute(t,e)}function Ix(t,e){t.onWarning&&t.onWarning.call(null,Ute(t,e))}var Pte={YAML:function(e,r,s){var a,n,c;e.version!==null&&Rr(e,"duplication of %YAML directive"),s.length!==1&&Rr(e,"YAML directive accepts exactly one argument"),a=/^([0-9]+)\.([0-9]+)$/.exec(s[0]),a===null&&Rr(e,"ill-formed argument of the YAML directive"),n=parseInt(a[1],10),c=parseInt(a[2],10),n!==1&&Rr(e,"unacceptable YAML version of the document"),e.version=s[0],e.checkLineBreaks=c<2,c!==1&&c!==2&&Ix(e,"unsupported YAML version of the document")},TAG:function(e,r,s){var a,n;s.length!==2&&Rr(e,"TAG directive accepts exactly two arguments"),a=s[0],n=s[1],Nte.test(a)||Rr(e,"ill-formed tag handle (first argument) of the TAG directive"),i0.call(e.tagMap,a)&&Rr(e,'there is a previously declared suffix for "'+a+'" tag handle'),Ote.test(n)||Rr(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[a]=n}};function n0(t,e,r,s){var a,n,c,f;if(e1&&(t.result+=Ip.repeat(` `,e-1))}function S9e(t,e,r){var s,a,n,c,f,p,h,E,C=t.kind,S=t.result,P;if(P=t.input.charCodeAt(t.position),rl(P)||dE(P)||P===35||P===38||P===42||P===33||P===124||P===62||P===39||P===34||P===37||P===64||P===96||(P===63||P===45)&&(a=t.input.charCodeAt(t.position+1),rl(a)||r&&dE(a)))return!1;for(t.kind="scalar",t.result="",n=c=t.position,f=!1;P!==0;){if(P===58){if(a=t.input.charCodeAt(t.position+1),rl(a)||r&&dE(a))break}else if(P===35){if(s=t.input.charCodeAt(t.position-1),rl(s))break}else{if(t.position===t.lineStart&&Cx(t)||r&&dE(P))break;if(jf(P))if(p=t.line,h=t.lineStart,E=t.lineIndent,as(t,!1,-1),t.lineIndent>=e){f=!0,P=t.input.charCodeAt(t.position);continue}else{t.position=c,t.line=p,t.lineStart=h,t.lineIndent=E;break}}f&&(n0(t,n,c,!1),$U(t,t.line-p),n=c=t.position,f=!1),Qd(P)||(c=t.position+1),P=t.input.charCodeAt(++t.position)}return n0(t,n,c,!1),t.result?!0:(t.kind=C,t.result=S,!1)}function D9e(t,e){var r,s,a;if(r=t.input.charCodeAt(t.position),r!==39)return!1;for(t.kind="scalar",t.result="",t.position++,s=a=t.position;(r=t.input.charCodeAt(t.position))!==0;)if(r===39)if(n0(t,s,t.position,!0),r=t.input.charCodeAt(++t.position),r===39)s=t.position,t.position++,a=t.position;else return!0;else jf(r)?(n0(t,s,a,!0),$U(t,as(t,!1,e)),s=a=t.position):t.position===t.lineStart&&Cx(t)?Rr(t,"unexpected end of the document within a single quoted scalar"):(t.position++,a=t.position);Rr(t,"unexpected end of the stream within a single quoted scalar")}function b9e(t,e){var r,s,a,n,c,f;if(f=t.input.charCodeAt(t.position),f!==34)return!1;for(t.kind="scalar",t.result="",t.position++,r=s=t.position;(f=t.input.charCodeAt(t.position))!==0;){if(f===34)return n0(t,r,t.position,!0),t.position++,!0;if(f===92){if(n0(t,r,t.position,!0),f=t.input.charCodeAt(++t.position),jf(f))as(t,!1,e);else if(f<256&&Lte[f])t.result+=Mte[f],t.position++;else if((c=C9e(f))>0){for(a=c,n=0;a>0;a--)f=t.input.charCodeAt(++t.position),(c=I9e(f))>=0?n=(n<<4)+c:Rr(t,"expected hexadecimal character");t.result+=B9e(n),t.position++}else Rr(t,"unknown escape sequence");r=s=t.position}else jf(f)?(n0(t,r,s,!0),$U(t,as(t,!1,e)),r=s=t.position):t.position===t.lineStart&&Cx(t)?Rr(t,"unexpected end of the document within a double quoted scalar"):(t.position++,s=t.position)}Rr(t,"unexpected end of the stream within a double quoted scalar")}function P9e(t,e){var r=!0,s,a=t.tag,n,c=t.anchor,f,p,h,E,C,S={},P,I,R,N;if(N=t.input.charCodeAt(t.position),N===91)p=93,C=!1,n=[];else if(N===123)p=125,C=!0,n={};else return!1;for(t.anchor!==null&&(t.anchorMap[t.anchor]=n),N=t.input.charCodeAt(++t.position);N!==0;){if(as(t,!0,e),N=t.input.charCodeAt(t.position),N===p)return t.position++,t.tag=a,t.anchor=c,t.kind=C?"mapping":"sequence",t.result=n,!0;r||Rr(t,"missed comma between flow collection entries"),I=P=R=null,h=E=!1,N===63&&(f=t.input.charCodeAt(t.position+1),rl(f)&&(h=E=!0,t.position++,as(t,!0,e))),s=t.line,yE(t,e,yx,!1,!0),I=t.tag,P=t.result,as(t,!0,e),N=t.input.charCodeAt(t.position),(E||t.line===s)&&N===58&&(h=!0,N=t.input.charCodeAt(++t.position),as(t,!0,e),yE(t,e,yx,!1,!0),R=t.result),C?mE(t,n,S,I,P,R):h?n.push(mE(t,null,S,I,P,R)):n.push(P),as(t,!0,e),N=t.input.charCodeAt(t.position),N===44?(r=!0,N=t.input.charCodeAt(++t.position)):r=!1}Rr(t,"unexpected end of the stream within a flow collection")}function x9e(t,e){var r,s,a=XU,n=!1,c=!1,f=e,p=0,h=!1,E,C;if(C=t.input.charCodeAt(t.position),C===124)s=!1;else if(C===62)s=!0;else return!1;for(t.kind="scalar",t.result="";C!==0;)if(C=t.input.charCodeAt(++t.position),C===43||C===45)XU===a?a=C===43?Ste:d9e:Rr(t,"repeat of a chomping mode identifier");else if((E=w9e(C))>=0)E===0?Rr(t,"bad explicit indentation width of a block scalar; it cannot be less than one"):c?Rr(t,"repeat of an indentation width identifier"):(f=e+E-1,c=!0);else break;if(Qd(C)){do C=t.input.charCodeAt(++t.position);while(Qd(C));if(C===35)do C=t.input.charCodeAt(++t.position);while(!jf(C)&&C!==0)}for(;C!==0;){for(ZU(t),t.lineIndent=0,C=t.input.charCodeAt(t.position);(!c||t.lineIndentf&&(f=t.lineIndent),jf(C)){p++;continue}if(t.lineIndente)&&p!==0)Rr(t,"bad indentation of a sequence entry");else if(t.lineIndente)&&(yE(t,e,Ex,!0,a)&&(I?S=t.result:P=t.result),I||(mE(t,h,E,C,S,P,n,c),C=S=P=null),as(t,!0,-1),N=t.input.charCodeAt(t.position)),t.lineIndent>e&&N!==0)Rr(t,"bad indentation of a mapping entry");else if(t.lineIndente?p=1:t.lineIndent===e?p=0:t.lineIndente?p=1:t.lineIndent===e?p=0:t.lineIndent tag; it should be "scalar", not "'+t.kind+'"'),C=0,S=t.implicitTypes.length;C tag; it should be "'+P.kind+'", not "'+t.kind+'"'),P.resolve(t.result)?(t.result=P.construct(t.result),t.anchor!==null&&(t.anchorMap[t.anchor]=t.result)):Rr(t,"cannot resolve a node with !<"+t.tag+"> explicit tag")):Rr(t,"unknown tag !<"+t.tag+">");return t.listener!==null&&t.listener("close",t),t.tag!==null||t.anchor!==null||E}function F9e(t){var e=t.position,r,s,a,n=!1,c;for(t.version=null,t.checkLineBreaks=t.legacy,t.tagMap={},t.anchorMap={};(c=t.input.charCodeAt(t.position))!==0&&(as(t,!0,-1),c=t.input.charCodeAt(t.position),!(t.lineIndent>0||c!==37));){for(n=!0,c=t.input.charCodeAt(++t.position),r=t.position;c!==0&&!rl(c);)c=t.input.charCodeAt(++t.position);for(s=t.input.slice(r,t.position),a=[],s.length<1&&Rr(t,"directive name must not be less than one character in length");c!==0;){for(;Qd(c);)c=t.input.charCodeAt(++t.position);if(c===35){do c=t.input.charCodeAt(++t.position);while(c!==0&&!jf(c));break}if(jf(c))break;for(r=t.position;c!==0&&!rl(c);)c=t.input.charCodeAt(++t.position);a.push(t.input.slice(r,t.position))}c!==0&&ZU(t),i0.call(Pte,s)?Pte[s](t,s,a):Ix(t,'unknown document directive "'+s+'"')}if(as(t,!0,-1),t.lineIndent===0&&t.input.charCodeAt(t.position)===45&&t.input.charCodeAt(t.position+1)===45&&t.input.charCodeAt(t.position+2)===45?(t.position+=3,as(t,!0,-1)):n&&Rr(t,"directives end mark is expected"),yE(t,t.lineIndent-1,Ex,!1,!0),as(t,!0,-1),t.checkLineBreaks&&y9e.test(t.input.slice(e,t.position))&&Ix(t,"non-ASCII line breaks are interpreted as content"),t.documents.push(t.result),t.position===t.lineStart&&Cx(t)){t.input.charCodeAt(t.position)===46&&(t.position+=3,as(t,!0,-1));return}if(t.position"u"&&(r=e,e=null);var s=_te(t,r);if(typeof e!="function")return s;for(var a=0,n=s.length;a"u"&&(r=e,e=null),Hte(t,e,Ip.extend({schema:Tte},r))}function O9e(t,e){return jte(t,Ip.extend({schema:Tte},e))}q2.exports.loadAll=Hte;q2.exports.load=jte;q2.exports.safeLoadAll=N9e;q2.exports.safeLoad=O9e});var Are=_((QQt,n_)=>{"use strict";var Y2=bd(),V2=pE(),L9e=G2(),M9e=gE(),Xte=Object.prototype.toString,Zte=Object.prototype.hasOwnProperty,U9e=9,W2=10,_9e=13,H9e=32,j9e=33,G9e=34,$te=35,q9e=37,W9e=38,Y9e=39,V9e=42,ere=44,J9e=45,tre=58,K9e=61,z9e=62,X9e=63,Z9e=64,rre=91,nre=93,$9e=96,ire=123,eWe=124,sre=125,_o={};_o[0]="\\0";_o[7]="\\a";_o[8]="\\b";_o[9]="\\t";_o[10]="\\n";_o[11]="\\v";_o[12]="\\f";_o[13]="\\r";_o[27]="\\e";_o[34]='\\"';_o[92]="\\\\";_o[133]="\\N";_o[160]="\\_";_o[8232]="\\L";_o[8233]="\\P";var tWe=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"];function rWe(t,e){var r,s,a,n,c,f,p;if(e===null)return{};for(r={},s=Object.keys(e),a=0,n=s.length;a0?t.charCodeAt(n-1):null,S=S&&Yte(c,f)}else{for(n=0;ns&&t[C+1]!==" ",C=n);else if(!EE(c))return wx;f=n>0?t.charCodeAt(n-1):null,S=S&&Yte(c,f)}h=h||E&&n-C-1>s&&t[C+1]!==" "}return!p&&!h?S&&!a(t)?are:lre:r>9&&ore(t)?wx:h?ure:cre}function lWe(t,e,r,s){t.dump=function(){if(e.length===0)return"''";if(!t.noCompatMode&&tWe.indexOf(e)!==-1)return"'"+e+"'";var a=t.indent*Math.max(1,r),n=t.lineWidth===-1?-1:Math.max(Math.min(t.lineWidth,40),t.lineWidth-a),c=s||t.flowLevel>-1&&r>=t.flowLevel;function f(p){return iWe(t,p)}switch(aWe(e,c,t.indent,n,f)){case are:return e;case lre:return"'"+e.replace(/'/g,"''")+"'";case cre:return"|"+Vte(e,t.indent)+Jte(Wte(e,a));case ure:return">"+Vte(e,t.indent)+Jte(Wte(cWe(e,n),a));case wx:return'"'+uWe(e,n)+'"';default:throw new V2("impossible error: invalid scalar style")}}()}function Vte(t,e){var r=ore(t)?String(e):"",s=t[t.length-1]===` `,a=s&&(t[t.length-2]===` `||t===` `),n=a?"+":s?"":"-";return r+n+` `}function Jte(t){return t[t.length-1]===` `?t.slice(0,-1):t}function cWe(t,e){for(var r=/(\n+)([^\n]*)/g,s=function(){var h=t.indexOf(` `);return h=h!==-1?h:t.length,r.lastIndex=h,Kte(t.slice(0,h),e)}(),a=t[0]===` `||t[0]===" ",n,c;c=r.exec(t);){var f=c[1],p=c[2];n=p[0]===" ",s+=f+(!a&&!n&&p!==""?` `:"")+Kte(p,e),a=n}return s}function Kte(t,e){if(t===""||t[0]===" ")return t;for(var r=/ [^ ]/g,s,a=0,n,c=0,f=0,p="";s=r.exec(t);)f=s.index,f-a>e&&(n=c>a?c:f,p+=` `+t.slice(a,n),a=n+1),c=f;return p+=` `,t.length-a>e&&c>a?p+=t.slice(a,c)+` `+t.slice(c+1):p+=t.slice(a),p.slice(1)}function uWe(t){for(var e="",r,s,a,n=0;n=55296&&r<=56319&&(s=t.charCodeAt(n+1),s>=56320&&s<=57343)){e+=qte((r-55296)*1024+s-56320+65536),n++;continue}a=_o[r],e+=!a&&EE(r)?t[n]:a||qte(r)}return e}function fWe(t,e,r){var s="",a=t.tag,n,c;for(n=0,c=r.length;n1024&&(E+="? "),E+=t.dump+(t.condenseFlow?'"':"")+":"+(t.condenseFlow?"":" "),Td(t,e,h,!1,!1)&&(E+=t.dump,s+=E));t.tag=a,t.dump="{"+s+"}"}function hWe(t,e,r,s){var a="",n=t.tag,c=Object.keys(r),f,p,h,E,C,S;if(t.sortKeys===!0)c.sort();else if(typeof t.sortKeys=="function")c.sort(t.sortKeys);else if(t.sortKeys)throw new V2("sortKeys must be a boolean or a function");for(f=0,p=c.length;f1024,C&&(t.dump&&W2===t.dump.charCodeAt(0)?S+="?":S+="? "),S+=t.dump,C&&(S+=e_(t,e)),Td(t,e+1,E,!0,C)&&(t.dump&&W2===t.dump.charCodeAt(0)?S+=":":S+=": ",S+=t.dump,a+=S));t.tag=n,t.dump=a||"{}"}function zte(t,e,r){var s,a,n,c,f,p;for(a=r?t.explicitTypes:t.implicitTypes,n=0,c=a.length;n tag resolver accepts not "'+p+'" style');t.dump=s}return!0}return!1}function Td(t,e,r,s,a,n){t.tag=null,t.dump=r,zte(t,r,!1)||zte(t,r,!0);var c=Xte.call(t.dump);s&&(s=t.flowLevel<0||t.flowLevel>e);var f=c==="[object Object]"||c==="[object Array]",p,h;if(f&&(p=t.duplicates.indexOf(r),h=p!==-1),(t.tag!==null&&t.tag!=="?"||h||t.indent!==2&&e>0)&&(a=!1),h&&t.usedDuplicates[p])t.dump="*ref_"+p;else{if(f&&h&&!t.usedDuplicates[p]&&(t.usedDuplicates[p]=!0),c==="[object Object]")s&&Object.keys(t.dump).length!==0?(hWe(t,e,t.dump,a),h&&(t.dump="&ref_"+p+t.dump)):(pWe(t,e,t.dump),h&&(t.dump="&ref_"+p+" "+t.dump));else if(c==="[object Array]"){var E=t.noArrayIndent&&e>0?e-1:e;s&&t.dump.length!==0?(AWe(t,E,t.dump,a),h&&(t.dump="&ref_"+p+t.dump)):(fWe(t,E,t.dump),h&&(t.dump="&ref_"+p+" "+t.dump))}else if(c==="[object String]")t.tag!=="?"&&lWe(t,t.dump,e,n);else{if(t.skipInvalid)return!1;throw new V2("unacceptable kind of an object to dump "+c)}t.tag!==null&&t.tag!=="?"&&(t.dump="!<"+t.tag+"> "+t.dump)}return!0}function gWe(t,e){var r=[],s=[],a,n;for(t_(t,r,s),a=0,n=s.length;a{"use strict";var Bx=Gte(),pre=Are();function vx(t){return function(){throw new Error("Function "+t+" is deprecated and cannot be used.")}}Wi.exports.Type=Ss();Wi.exports.Schema=Pd();Wi.exports.FAILSAFE_SCHEMA=dx();Wi.exports.JSON_SCHEMA=JU();Wi.exports.CORE_SCHEMA=KU();Wi.exports.DEFAULT_SAFE_SCHEMA=gE();Wi.exports.DEFAULT_FULL_SCHEMA=G2();Wi.exports.load=Bx.load;Wi.exports.loadAll=Bx.loadAll;Wi.exports.safeLoad=Bx.safeLoad;Wi.exports.safeLoadAll=Bx.safeLoadAll;Wi.exports.dump=pre.dump;Wi.exports.safeDump=pre.safeDump;Wi.exports.YAMLException=pE();Wi.exports.MINIMAL_SCHEMA=dx();Wi.exports.SAFE_SCHEMA=gE();Wi.exports.DEFAULT_SCHEMA=G2();Wi.exports.scan=vx("scan");Wi.exports.parse=vx("parse");Wi.exports.compose=vx("compose");Wi.exports.addConstructor=vx("addConstructor")});var dre=_((RQt,gre)=>{"use strict";var mWe=hre();gre.exports=mWe});var yre=_((FQt,mre)=>{"use strict";function yWe(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function Rd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Rd)}yWe(Rd,Error);Rd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;C({[dt]:Oe})))},ue=function(te){return te},le=function(te){return te},me=Oa("correct indentation"),pe=" ",Be=dn(" ",!1),Ce=function(te){return te.length===lr*St},g=function(te){return te.length===(lr+1)*St},we=function(){return lr++,!0},ye=function(){return lr--,!0},Ae=function(){return la()},se=Oa("pseudostring"),Z=/^[^\r\n\t ?:,\][{}#&*!|>'"%@`\-]/,De=Kn(["\r",` `," "," ","?",":",",","]","[","{","}","#","&","*","!","|",">","'",'"',"%","@","`","-"],!0,!1),Re=/^[^\r\n\t ,\][{}:#"']/,mt=Kn(["\r",` `," "," ",",","]","[","{","}",":","#",'"',"'"],!0,!1),j=function(){return la().replace(/^ *| *$/g,"")},rt="--",Fe=dn("--",!1),Ne=/^[a-zA-Z\/0-9]/,Pe=Kn([["a","z"],["A","Z"],"/",["0","9"]],!1,!1),Ve=/^[^\r\n\t :,]/,ke=Kn(["\r",` `," "," ",":",","],!0,!1),it="null",Ue=dn("null",!1),x=function(){return null},w="true",b=dn("true",!1),y=function(){return!0},F="false",z=dn("false",!1),X=function(){return!1},$=Oa("string"),oe='"',xe=dn('"',!1),Te=function(){return""},lt=function(te){return te},Ct=function(te){return te.join("")},qt=/^[^"\\\0-\x1F\x7F]/,ir=Kn(['"',"\\",["\0",""],"\x7F"],!0,!1),Pt='\\"',gn=dn('\\"',!1),Pr=function(){return'"'},Ir="\\\\",Or=dn("\\\\",!1),on=function(){return"\\"},ai="\\/",Io=dn("\\/",!1),rs=function(){return"/"},$s="\\b",Co=dn("\\b",!1),ji=function(){return"\b"},eo="\\f",wo=dn("\\f",!1),QA=function(){return"\f"},Af="\\n",dh=dn("\\n",!1),mh=function(){return` `},to="\\r",jn=dn("\\r",!1),Ts=function(){return"\r"},ro="\\t",ou=dn("\\t",!1),au=function(){return" "},lu="\\u",TA=dn("\\u",!1),RA=function(te,Ee,Oe,dt){return String.fromCharCode(parseInt(`0x${te}${Ee}${Oe}${dt}`))},oa=/^[0-9a-fA-F]/,aa=Kn([["0","9"],["a","f"],["A","F"]],!1,!1),FA=Oa("blank space"),gr=/^[ \t]/,Bo=Kn([" "," "],!1,!1),Me=Oa("white space"),cu=/^[ \t\n\r]/,Cr=Kn([" "," ",` `,"\r"],!1,!1),pf=`\r `,NA=dn(`\r `,!1),OA=` `,uu=dn(` `,!1),fu="\r",oc=dn("\r",!1),ve=0,Nt=0,ac=[{line:1,column:1}],Oi=0,no=[],Rt=0,xn;if("startRule"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');a=s[e.startRule]}function la(){return t.substring(Nt,ve)}function Gi(){return Ma(Nt,ve)}function Li(te,Ee){throw Ee=Ee!==void 0?Ee:Ma(Nt,ve),hf([Oa(te)],t.substring(Nt,ve),Ee)}function Na(te,Ee){throw Ee=Ee!==void 0?Ee:Ma(Nt,ve),Ua(te,Ee)}function dn(te,Ee){return{type:"literal",text:te,ignoreCase:Ee}}function Kn(te,Ee,Oe){return{type:"class",parts:te,inverted:Ee,ignoreCase:Oe}}function Au(){return{type:"any"}}function yh(){return{type:"end"}}function Oa(te){return{type:"other",description:te}}function La(te){var Ee=ac[te],Oe;if(Ee)return Ee;for(Oe=te-1;!ac[Oe];)Oe--;for(Ee=ac[Oe],Ee={line:Ee.line,column:Ee.column};OeOi&&(Oi=ve,no=[]),no.push(te))}function Ua(te,Ee){return new Rd(te,null,null,Ee)}function hf(te,Ee,Oe){return new Rd(Rd.buildMessage(te,Ee),te,Ee,Oe)}function lc(){var te;return te=LA(),te}function wn(){var te,Ee,Oe;for(te=ve,Ee=[],Oe=ca();Oe!==r;)Ee.push(Oe),Oe=ca();return Ee!==r&&(Nt=te,Ee=n(Ee)),te=Ee,te}function ca(){var te,Ee,Oe,dt,Et;return te=ve,Ee=Bl(),Ee!==r?(t.charCodeAt(ve)===45?(Oe=c,ve++):(Oe=r,Rt===0&&$e(f)),Oe!==r?(dt=Qn(),dt!==r?(Et=ua(),Et!==r?(Nt=te,Ee=p(Et),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r),te}function LA(){var te,Ee,Oe;for(te=ve,Ee=[],Oe=MA();Oe!==r;)Ee.push(Oe),Oe=MA();return Ee!==r&&(Nt=te,Ee=h(Ee)),te=Ee,te}function MA(){var te,Ee,Oe,dt,Et,bt,tr,An,li;if(te=ve,Ee=Qn(),Ee===r&&(Ee=null),Ee!==r){if(Oe=ve,t.charCodeAt(ve)===35?(dt=E,ve++):(dt=r,Rt===0&&$e(C)),dt!==r){if(Et=[],bt=ve,tr=ve,Rt++,An=st(),Rt--,An===r?tr=void 0:(ve=tr,tr=r),tr!==r?(t.length>ve?(An=t.charAt(ve),ve++):(An=r,Rt===0&&$e(S)),An!==r?(tr=[tr,An],bt=tr):(ve=bt,bt=r)):(ve=bt,bt=r),bt!==r)for(;bt!==r;)Et.push(bt),bt=ve,tr=ve,Rt++,An=st(),Rt--,An===r?tr=void 0:(ve=tr,tr=r),tr!==r?(t.length>ve?(An=t.charAt(ve),ve++):(An=r,Rt===0&&$e(S)),An!==r?(tr=[tr,An],bt=tr):(ve=bt,bt=r)):(ve=bt,bt=r);else Et=r;Et!==r?(dt=[dt,Et],Oe=dt):(ve=Oe,Oe=r)}else ve=Oe,Oe=r;if(Oe===r&&(Oe=null),Oe!==r){if(dt=[],Et=Ke(),Et!==r)for(;Et!==r;)dt.push(Et),Et=Ke();else dt=r;dt!==r?(Nt=te,Ee=P(),te=Ee):(ve=te,te=r)}else ve=te,te=r}else ve=te,te=r;if(te===r&&(te=ve,Ee=Bl(),Ee!==r?(Oe=Ha(),Oe!==r?(dt=Qn(),dt===r&&(dt=null),dt!==r?(t.charCodeAt(ve)===58?(Et=I,ve++):(Et=r,Rt===0&&$e(R)),Et!==r?(bt=Qn(),bt===r&&(bt=null),bt!==r?(tr=ua(),tr!==r?(Nt=te,Ee=N(Oe,tr),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r),te===r&&(te=ve,Ee=Bl(),Ee!==r?(Oe=ns(),Oe!==r?(dt=Qn(),dt===r&&(dt=null),dt!==r?(t.charCodeAt(ve)===58?(Et=I,ve++):(Et=r,Rt===0&&$e(R)),Et!==r?(bt=Qn(),bt===r&&(bt=null),bt!==r?(tr=ua(),tr!==r?(Nt=te,Ee=N(Oe,tr),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r),te===r))){if(te=ve,Ee=Bl(),Ee!==r)if(Oe=ns(),Oe!==r)if(dt=Qn(),dt!==r)if(Et=pu(),Et!==r){if(bt=[],tr=Ke(),tr!==r)for(;tr!==r;)bt.push(tr),tr=Ke();else bt=r;bt!==r?(Nt=te,Ee=N(Oe,Et),te=Ee):(ve=te,te=r)}else ve=te,te=r;else ve=te,te=r;else ve=te,te=r;else ve=te,te=r;if(te===r)if(te=ve,Ee=Bl(),Ee!==r)if(Oe=ns(),Oe!==r){if(dt=[],Et=ve,bt=Qn(),bt===r&&(bt=null),bt!==r?(t.charCodeAt(ve)===44?(tr=U,ve++):(tr=r,Rt===0&&$e(W)),tr!==r?(An=Qn(),An===r&&(An=null),An!==r?(li=ns(),li!==r?(Nt=Et,bt=ee(Oe,li),Et=bt):(ve=Et,Et=r)):(ve=Et,Et=r)):(ve=Et,Et=r)):(ve=Et,Et=r),Et!==r)for(;Et!==r;)dt.push(Et),Et=ve,bt=Qn(),bt===r&&(bt=null),bt!==r?(t.charCodeAt(ve)===44?(tr=U,ve++):(tr=r,Rt===0&&$e(W)),tr!==r?(An=Qn(),An===r&&(An=null),An!==r?(li=ns(),li!==r?(Nt=Et,bt=ee(Oe,li),Et=bt):(ve=Et,Et=r)):(ve=Et,Et=r)):(ve=Et,Et=r)):(ve=Et,Et=r);else dt=r;dt!==r?(Et=Qn(),Et===r&&(Et=null),Et!==r?(t.charCodeAt(ve)===58?(bt=I,ve++):(bt=r,Rt===0&&$e(R)),bt!==r?(tr=Qn(),tr===r&&(tr=null),tr!==r?(An=ua(),An!==r?(Nt=te,Ee=ie(Oe,dt,An),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)}else ve=te,te=r;else ve=te,te=r}return te}function ua(){var te,Ee,Oe,dt,Et,bt,tr;if(te=ve,Ee=ve,Rt++,Oe=ve,dt=st(),dt!==r?(Et=Mt(),Et!==r?(t.charCodeAt(ve)===45?(bt=c,ve++):(bt=r,Rt===0&&$e(f)),bt!==r?(tr=Qn(),tr!==r?(dt=[dt,Et,bt,tr],Oe=dt):(ve=Oe,Oe=r)):(ve=Oe,Oe=r)):(ve=Oe,Oe=r)):(ve=Oe,Oe=r),Rt--,Oe!==r?(ve=Ee,Ee=void 0):Ee=r,Ee!==r?(Oe=Ke(),Oe!==r?(dt=kn(),dt!==r?(Et=wn(),Et!==r?(bt=fa(),bt!==r?(Nt=te,Ee=ue(Et),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r),te===r&&(te=ve,Ee=st(),Ee!==r?(Oe=kn(),Oe!==r?(dt=LA(),dt!==r?(Et=fa(),Et!==r?(Nt=te,Ee=ue(dt),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r),te===r))if(te=ve,Ee=cc(),Ee!==r){if(Oe=[],dt=Ke(),dt!==r)for(;dt!==r;)Oe.push(dt),dt=Ke();else Oe=r;Oe!==r?(Nt=te,Ee=le(Ee),te=Ee):(ve=te,te=r)}else ve=te,te=r;return te}function Bl(){var te,Ee,Oe;for(Rt++,te=ve,Ee=[],t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&$e(Be));Oe!==r;)Ee.push(Oe),t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&$e(Be));return Ee!==r?(Nt=ve,Oe=Ce(Ee),Oe?Oe=void 0:Oe=r,Oe!==r?(Ee=[Ee,Oe],te=Ee):(ve=te,te=r)):(ve=te,te=r),Rt--,te===r&&(Ee=r,Rt===0&&$e(me)),te}function Mt(){var te,Ee,Oe;for(te=ve,Ee=[],t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&$e(Be));Oe!==r;)Ee.push(Oe),t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&$e(Be));return Ee!==r?(Nt=ve,Oe=g(Ee),Oe?Oe=void 0:Oe=r,Oe!==r?(Ee=[Ee,Oe],te=Ee):(ve=te,te=r)):(ve=te,te=r),te}function kn(){var te;return Nt=ve,te=we(),te?te=void 0:te=r,te}function fa(){var te;return Nt=ve,te=ye(),te?te=void 0:te=r,te}function Ha(){var te;return te=vl(),te===r&&(te=uc()),te}function ns(){var te,Ee,Oe;if(te=vl(),te===r){if(te=ve,Ee=[],Oe=ja(),Oe!==r)for(;Oe!==r;)Ee.push(Oe),Oe=ja();else Ee=r;Ee!==r&&(Nt=te,Ee=Ae()),te=Ee}return te}function cc(){var te;return te=Mi(),te===r&&(te=Is(),te===r&&(te=vl(),te===r&&(te=uc()))),te}function pu(){var te;return te=Mi(),te===r&&(te=vl(),te===r&&(te=ja())),te}function uc(){var te,Ee,Oe,dt,Et,bt;if(Rt++,te=ve,Z.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&$e(De)),Ee!==r){for(Oe=[],dt=ve,Et=Qn(),Et===r&&(Et=null),Et!==r?(Re.test(t.charAt(ve))?(bt=t.charAt(ve),ve++):(bt=r,Rt===0&&$e(mt)),bt!==r?(Et=[Et,bt],dt=Et):(ve=dt,dt=r)):(ve=dt,dt=r);dt!==r;)Oe.push(dt),dt=ve,Et=Qn(),Et===r&&(Et=null),Et!==r?(Re.test(t.charAt(ve))?(bt=t.charAt(ve),ve++):(bt=r,Rt===0&&$e(mt)),bt!==r?(Et=[Et,bt],dt=Et):(ve=dt,dt=r)):(ve=dt,dt=r);Oe!==r?(Nt=te,Ee=j(),te=Ee):(ve=te,te=r)}else ve=te,te=r;return Rt--,te===r&&(Ee=r,Rt===0&&$e(se)),te}function ja(){var te,Ee,Oe,dt,Et;if(te=ve,t.substr(ve,2)===rt?(Ee=rt,ve+=2):(Ee=r,Rt===0&&$e(Fe)),Ee===r&&(Ee=null),Ee!==r)if(Ne.test(t.charAt(ve))?(Oe=t.charAt(ve),ve++):(Oe=r,Rt===0&&$e(Pe)),Oe!==r){for(dt=[],Ve.test(t.charAt(ve))?(Et=t.charAt(ve),ve++):(Et=r,Rt===0&&$e(ke));Et!==r;)dt.push(Et),Ve.test(t.charAt(ve))?(Et=t.charAt(ve),ve++):(Et=r,Rt===0&&$e(ke));dt!==r?(Nt=te,Ee=j(),te=Ee):(ve=te,te=r)}else ve=te,te=r;else ve=te,te=r;return te}function Mi(){var te,Ee;return te=ve,t.substr(ve,4)===it?(Ee=it,ve+=4):(Ee=r,Rt===0&&$e(Ue)),Ee!==r&&(Nt=te,Ee=x()),te=Ee,te}function Is(){var te,Ee;return te=ve,t.substr(ve,4)===w?(Ee=w,ve+=4):(Ee=r,Rt===0&&$e(b)),Ee!==r&&(Nt=te,Ee=y()),te=Ee,te===r&&(te=ve,t.substr(ve,5)===F?(Ee=F,ve+=5):(Ee=r,Rt===0&&$e(z)),Ee!==r&&(Nt=te,Ee=X()),te=Ee),te}function vl(){var te,Ee,Oe,dt;return Rt++,te=ve,t.charCodeAt(ve)===34?(Ee=oe,ve++):(Ee=r,Rt===0&&$e(xe)),Ee!==r?(t.charCodeAt(ve)===34?(Oe=oe,ve++):(Oe=r,Rt===0&&$e(xe)),Oe!==r?(Nt=te,Ee=Te(),te=Ee):(ve=te,te=r)):(ve=te,te=r),te===r&&(te=ve,t.charCodeAt(ve)===34?(Ee=oe,ve++):(Ee=r,Rt===0&&$e(xe)),Ee!==r?(Oe=gf(),Oe!==r?(t.charCodeAt(ve)===34?(dt=oe,ve++):(dt=r,Rt===0&&$e(xe)),dt!==r?(Nt=te,Ee=lt(Oe),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)),Rt--,te===r&&(Ee=r,Rt===0&&$e($)),te}function gf(){var te,Ee,Oe;if(te=ve,Ee=[],Oe=fc(),Oe!==r)for(;Oe!==r;)Ee.push(Oe),Oe=fc();else Ee=r;return Ee!==r&&(Nt=te,Ee=Ct(Ee)),te=Ee,te}function fc(){var te,Ee,Oe,dt,Et,bt;return qt.test(t.charAt(ve))?(te=t.charAt(ve),ve++):(te=r,Rt===0&&$e(ir)),te===r&&(te=ve,t.substr(ve,2)===Pt?(Ee=Pt,ve+=2):(Ee=r,Rt===0&&$e(gn)),Ee!==r&&(Nt=te,Ee=Pr()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===Ir?(Ee=Ir,ve+=2):(Ee=r,Rt===0&&$e(Or)),Ee!==r&&(Nt=te,Ee=on()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===ai?(Ee=ai,ve+=2):(Ee=r,Rt===0&&$e(Io)),Ee!==r&&(Nt=te,Ee=rs()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===$s?(Ee=$s,ve+=2):(Ee=r,Rt===0&&$e(Co)),Ee!==r&&(Nt=te,Ee=ji()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===eo?(Ee=eo,ve+=2):(Ee=r,Rt===0&&$e(wo)),Ee!==r&&(Nt=te,Ee=QA()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===Af?(Ee=Af,ve+=2):(Ee=r,Rt===0&&$e(dh)),Ee!==r&&(Nt=te,Ee=mh()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===to?(Ee=to,ve+=2):(Ee=r,Rt===0&&$e(jn)),Ee!==r&&(Nt=te,Ee=Ts()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===ro?(Ee=ro,ve+=2):(Ee=r,Rt===0&&$e(ou)),Ee!==r&&(Nt=te,Ee=au()),te=Ee,te===r&&(te=ve,t.substr(ve,2)===lu?(Ee=lu,ve+=2):(Ee=r,Rt===0&&$e(TA)),Ee!==r?(Oe=wi(),Oe!==r?(dt=wi(),dt!==r?(Et=wi(),Et!==r?(bt=wi(),bt!==r?(Nt=te,Ee=RA(Oe,dt,Et,bt),te=Ee):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)):(ve=te,te=r)))))))))),te}function wi(){var te;return oa.test(t.charAt(ve))?(te=t.charAt(ve),ve++):(te=r,Rt===0&&$e(aa)),te}function Qn(){var te,Ee;if(Rt++,te=[],gr.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&$e(Bo)),Ee!==r)for(;Ee!==r;)te.push(Ee),gr.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&$e(Bo));else te=r;return Rt--,te===r&&(Ee=r,Rt===0&&$e(FA)),te}function Ac(){var te,Ee;if(Rt++,te=[],cu.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&$e(Cr)),Ee!==r)for(;Ee!==r;)te.push(Ee),cu.test(t.charAt(ve))?(Ee=t.charAt(ve),ve++):(Ee=r,Rt===0&&$e(Cr));else te=r;return Rt--,te===r&&(Ee=r,Rt===0&&$e(Me)),te}function Ke(){var te,Ee,Oe,dt,Et,bt;if(te=ve,Ee=st(),Ee!==r){for(Oe=[],dt=ve,Et=Qn(),Et===r&&(Et=null),Et!==r?(bt=st(),bt!==r?(Et=[Et,bt],dt=Et):(ve=dt,dt=r)):(ve=dt,dt=r);dt!==r;)Oe.push(dt),dt=ve,Et=Qn(),Et===r&&(Et=null),Et!==r?(bt=st(),bt!==r?(Et=[Et,bt],dt=Et):(ve=dt,dt=r)):(ve=dt,dt=r);Oe!==r?(Ee=[Ee,Oe],te=Ee):(ve=te,te=r)}else ve=te,te=r;return te}function st(){var te;return t.substr(ve,2)===pf?(te=pf,ve+=2):(te=r,Rt===0&&$e(NA)),te===r&&(t.charCodeAt(ve)===10?(te=OA,ve++):(te=r,Rt===0&&$e(uu)),te===r&&(t.charCodeAt(ve)===13?(te=fu,ve++):(te=r,Rt===0&&$e(oc)))),te}let St=2,lr=0;if(xn=a(),xn!==r&&ve===t.length)return xn;throw xn!==r&&ve"u"?!0:typeof t=="object"&&t!==null&&!Array.isArray(t)?Object.keys(t).every(e=>wre(t[e])):!1}function i_(t,e,r){if(t===null)return`null `;if(typeof t=="number"||typeof t=="boolean")return`${t.toString()} `;if(typeof t=="string")return`${Ire(t)} `;if(Array.isArray(t)){if(t.length===0)return`[] `;let s=" ".repeat(e);return` ${t.map(n=>`${s}- ${i_(n,e+1,!1)}`).join("")}`}if(typeof t=="object"&&t){let[s,a]=t instanceof Sx?[t.data,!1]:[t,!0],n=" ".repeat(e),c=Object.keys(s);a&&c.sort((p,h)=>{let E=Ere.indexOf(p),C=Ere.indexOf(h);return E===-1&&C===-1?ph?1:0:E!==-1&&C===-1?-1:E===-1&&C!==-1?1:E-C});let f=c.filter(p=>!wre(s[p])).map((p,h)=>{let E=s[p],C=Ire(p),S=i_(E,e+1,!0),P=h>0||r?n:"",I=C.length>1024?`? ${C} ${P}:`:`${C}:`,R=S.startsWith(` `)?S:` ${S}`;return`${P}${I}${R}`}).join(e===0?` `:"")||` `;return r?` ${f}`:`${f}`}throw new Error(`Unsupported value type (${t})`)}function nl(t){try{let e=i_(t,0,!1);return e!==` `?e:""}catch(e){throw e.location&&(e.message=e.message.replace(/(\.)?$/,` (line ${e.location.start.line}, column ${e.location.start.column})$1`)),e}}function CWe(t){return t.endsWith(` `)||(t+=` `),(0,Cre.parse)(t)}function BWe(t){if(wWe.test(t))return CWe(t);let e=(0,Dx.safeLoad)(t,{schema:Dx.FAILSAFE_SCHEMA,json:!0});if(e==null)return{};if(typeof e!="object")throw new Error(`Expected an indexed object, got a ${typeof e} instead. Does your file follow Yaml's rules?`);if(Array.isArray(e))throw new Error("Expected an indexed object, got an array instead. Does your file follow Yaml's rules?");return e}function ls(t){return BWe(t)}var Dx,Cre,IWe,Ere,Sx,wWe,Bre=Xe(()=>{Dx=ut(dre()),Cre=ut(yre()),IWe=/^(?![-?:,\][{}#&*!|>'"%@` \t\r\n]).([ \t]*(?![,\][{}:# \t\r\n]).)*$/,Ere=["__metadata","version","resolution","dependencies","peerDependencies","dependenciesMeta","peerDependenciesMeta","binaries"],Sx=class{constructor(e){this.data=e}};nl.PreserveOrdering=Sx;wWe=/^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i});var J2={};Vt(J2,{parseResolution:()=>px,parseShell:()=>ux,parseSyml:()=>ls,stringifyArgument:()=>qU,stringifyArgumentSegment:()=>WU,stringifyArithmeticExpression:()=>Ax,stringifyCommand:()=>GU,stringifyCommandChain:()=>AE,stringifyCommandChainThen:()=>jU,stringifyCommandLine:()=>fx,stringifyCommandLineThen:()=>HU,stringifyEnvSegment:()=>cx,stringifyRedirectArgument:()=>H2,stringifyResolution:()=>hx,stringifyShell:()=>fE,stringifyShellLine:()=>fE,stringifySyml:()=>nl,stringifyValueArgument:()=>vd});var wc=Xe(()=>{wee();Dee();Bre()});var Sre=_((UQt,s_)=>{"use strict";var vWe=t=>{let e=!1,r=!1,s=!1;for(let a=0;a{if(!(typeof t=="string"||Array.isArray(t)))throw new TypeError("Expected the input to be `string | string[]`");e=Object.assign({pascalCase:!1},e);let r=a=>e.pascalCase?a.charAt(0).toUpperCase()+a.slice(1):a;return Array.isArray(t)?t=t.map(a=>a.trim()).filter(a=>a.length).join("-"):t=t.trim(),t.length===0?"":t.length===1?e.pascalCase?t.toUpperCase():t.toLowerCase():(t!==t.toLowerCase()&&(t=vWe(t)),t=t.replace(/^[_.\- ]+/,"").toLowerCase().replace(/[_.\- ]+(\w|$)/g,(a,n)=>n.toUpperCase()).replace(/\d+(\w|$)/g,a=>a.toUpperCase()),r(t))};s_.exports=vre;s_.exports.default=vre});var Dre=_((_Qt,SWe)=>{SWe.exports=[{name:"Agola CI",constant:"AGOLA",env:"AGOLA_GIT_REF",pr:"AGOLA_PULL_REQUEST_ID"},{name:"Appcircle",constant:"APPCIRCLE",env:"AC_APPCIRCLE"},{name:"AppVeyor",constant:"APPVEYOR",env:"APPVEYOR",pr:"APPVEYOR_PULL_REQUEST_NUMBER"},{name:"AWS CodeBuild",constant:"CODEBUILD",env:"CODEBUILD_BUILD_ARN"},{name:"Azure Pipelines",constant:"AZURE_PIPELINES",env:"TF_BUILD",pr:{BUILD_REASON:"PullRequest"}},{name:"Bamboo",constant:"BAMBOO",env:"bamboo_planKey"},{name:"Bitbucket Pipelines",constant:"BITBUCKET",env:"BITBUCKET_COMMIT",pr:"BITBUCKET_PR_ID"},{name:"Bitrise",constant:"BITRISE",env:"BITRISE_IO",pr:"BITRISE_PULL_REQUEST"},{name:"Buddy",constant:"BUDDY",env:"BUDDY_WORKSPACE_ID",pr:"BUDDY_EXECUTION_PULL_REQUEST_ID"},{name:"Buildkite",constant:"BUILDKITE",env:"BUILDKITE",pr:{env:"BUILDKITE_PULL_REQUEST",ne:"false"}},{name:"CircleCI",constant:"CIRCLE",env:"CIRCLECI",pr:"CIRCLE_PULL_REQUEST"},{name:"Cirrus CI",constant:"CIRRUS",env:"CIRRUS_CI",pr:"CIRRUS_PR"},{name:"Codefresh",constant:"CODEFRESH",env:"CF_BUILD_ID",pr:{any:["CF_PULL_REQUEST_NUMBER","CF_PULL_REQUEST_ID"]}},{name:"Codemagic",constant:"CODEMAGIC",env:"CM_BUILD_ID",pr:"CM_PULL_REQUEST"},{name:"Codeship",constant:"CODESHIP",env:{CI_NAME:"codeship"}},{name:"Drone",constant:"DRONE",env:"DRONE",pr:{DRONE_BUILD_EVENT:"pull_request"}},{name:"dsari",constant:"DSARI",env:"DSARI"},{name:"Earthly",constant:"EARTHLY",env:"EARTHLY_CI"},{name:"Expo Application Services",constant:"EAS",env:"EAS_BUILD"},{name:"Gerrit",constant:"GERRIT",env:"GERRIT_PROJECT"},{name:"Gitea Actions",constant:"GITEA_ACTIONS",env:"GITEA_ACTIONS"},{name:"GitHub Actions",constant:"GITHUB_ACTIONS",env:"GITHUB_ACTIONS",pr:{GITHUB_EVENT_NAME:"pull_request"}},{name:"GitLab CI",constant:"GITLAB",env:"GITLAB_CI",pr:"CI_MERGE_REQUEST_ID"},{name:"GoCD",constant:"GOCD",env:"GO_PIPELINE_LABEL"},{name:"Google Cloud Build",constant:"GOOGLE_CLOUD_BUILD",env:"BUILDER_OUTPUT"},{name:"Harness CI",constant:"HARNESS",env:"HARNESS_BUILD_ID"},{name:"Heroku",constant:"HEROKU",env:{env:"NODE",includes:"/app/.heroku/node/bin/node"}},{name:"Hudson",constant:"HUDSON",env:"HUDSON_URL"},{name:"Jenkins",constant:"JENKINS",env:["JENKINS_URL","BUILD_ID"],pr:{any:["ghprbPullId","CHANGE_ID"]}},{name:"LayerCI",constant:"LAYERCI",env:"LAYERCI",pr:"LAYERCI_PULL_REQUEST"},{name:"Magnum CI",constant:"MAGNUM",env:"MAGNUM"},{name:"Netlify CI",constant:"NETLIFY",env:"NETLIFY",pr:{env:"PULL_REQUEST",ne:"false"}},{name:"Nevercode",constant:"NEVERCODE",env:"NEVERCODE",pr:{env:"NEVERCODE_PULL_REQUEST",ne:"false"}},{name:"Prow",constant:"PROW",env:"PROW_JOB_ID"},{name:"ReleaseHub",constant:"RELEASEHUB",env:"RELEASE_BUILD_ID"},{name:"Render",constant:"RENDER",env:"RENDER",pr:{IS_PULL_REQUEST:"true"}},{name:"Sail CI",constant:"SAIL",env:"SAILCI",pr:"SAIL_PULL_REQUEST_NUMBER"},{name:"Screwdriver",constant:"SCREWDRIVER",env:"SCREWDRIVER",pr:{env:"SD_PULL_REQUEST",ne:"false"}},{name:"Semaphore",constant:"SEMAPHORE",env:"SEMAPHORE",pr:"PULL_REQUEST_NUMBER"},{name:"Sourcehut",constant:"SOURCEHUT",env:{CI_NAME:"sourcehut"}},{name:"Strider CD",constant:"STRIDER",env:"STRIDER"},{name:"TaskCluster",constant:"TASKCLUSTER",env:["TASK_ID","RUN_ID"]},{name:"TeamCity",constant:"TEAMCITY",env:"TEAMCITY_VERSION"},{name:"Travis CI",constant:"TRAVIS",env:"TRAVIS",pr:{env:"TRAVIS_PULL_REQUEST",ne:"false"}},{name:"Vela",constant:"VELA",env:"VELA",pr:{VELA_PULL_REQUEST:"1"}},{name:"Vercel",constant:"VERCEL",env:{any:["NOW_BUILDER","VERCEL"]},pr:"VERCEL_GIT_PULL_REQUEST_ID"},{name:"Visual Studio App Center",constant:"APPCENTER",env:"APPCENTER_BUILD_ID"},{name:"Woodpecker",constant:"WOODPECKER",env:{CI:"woodpecker"},pr:{CI_BUILD_EVENT:"pull_request"}},{name:"Xcode Cloud",constant:"XCODE_CLOUD",env:"CI_XCODE_PROJECT",pr:"CI_PULL_REQUEST_NUMBER"},{name:"Xcode Server",constant:"XCODE_SERVER",env:"XCS"}]});var Fd=_(Ml=>{"use strict";var Pre=Dre(),Ds=process.env;Object.defineProperty(Ml,"_vendors",{value:Pre.map(function(t){return t.constant})});Ml.name=null;Ml.isPR=null;Pre.forEach(function(t){let r=(Array.isArray(t.env)?t.env:[t.env]).every(function(s){return bre(s)});if(Ml[t.constant]=r,!!r)switch(Ml.name=t.name,typeof t.pr){case"string":Ml.isPR=!!Ds[t.pr];break;case"object":"env"in t.pr?Ml.isPR=t.pr.env in Ds&&Ds[t.pr.env]!==t.pr.ne:"any"in t.pr?Ml.isPR=t.pr.any.some(function(s){return!!Ds[s]}):Ml.isPR=bre(t.pr);break;default:Ml.isPR=null}});Ml.isCI=!!(Ds.CI!=="false"&&(Ds.BUILD_ID||Ds.BUILD_NUMBER||Ds.CI||Ds.CI_APP_ID||Ds.CI_BUILD_ID||Ds.CI_BUILD_NUMBER||Ds.CI_NAME||Ds.CONTINUOUS_INTEGRATION||Ds.RUN_ID||Ml.name));function bre(t){return typeof t=="string"?!!Ds[t]:"env"in t?Ds[t.env]&&Ds[t.env].includes(t.includes):"any"in t?t.any.some(function(e){return!!Ds[e]}):Object.keys(t).every(function(e){return Ds[e]===t[e]})}});var ei,En,Nd,o_,bx,xre,a_,l_,Px=Xe(()=>{(function(t){t.StartOfInput="\0",t.EndOfInput="",t.EndOfPartialInput=""})(ei||(ei={}));(function(t){t[t.InitialNode=0]="InitialNode",t[t.SuccessNode=1]="SuccessNode",t[t.ErrorNode=2]="ErrorNode",t[t.CustomNode=3]="CustomNode"})(En||(En={}));Nd=-1,o_=/^(-h|--help)(?:=([0-9]+))?$/,bx=/^(--[a-z]+(?:-[a-z]+)*|-[a-zA-Z]+)$/,xre=/^-[a-zA-Z]{2,}$/,a_=/^([^=]+)=([\s\S]*)$/,l_=process.env.DEBUG_CLI==="1"});var nt,IE,xx,c_,kx=Xe(()=>{Px();nt=class extends Error{constructor(e){super(e),this.clipanion={type:"usage"},this.name="UsageError"}},IE=class extends Error{constructor(e,r){if(super(),this.input=e,this.candidates=r,this.clipanion={type:"none"},this.name="UnknownSyntaxError",this.candidates.length===0)this.message="Command not found, but we're not sure what's the alternative.";else if(this.candidates.every(s=>s.reason!==null&&s.reason===r[0].reason)){let[{reason:s}]=this.candidates;this.message=`${s} ${this.candidates.map(({usage:a})=>`$ ${a}`).join(` `)}`}else if(this.candidates.length===1){let[{usage:s}]=this.candidates;this.message=`Command not found; did you mean: $ ${s} ${c_(e)}`}else this.message=`Command not found; did you mean one of: ${this.candidates.map(({usage:s},a)=>`${`${a}.`.padStart(4)} ${s}`).join(` `)} ${c_(e)}`}},xx=class extends Error{constructor(e,r){super(),this.input=e,this.usages=r,this.clipanion={type:"none"},this.name="AmbiguousSyntaxError",this.message=`Cannot find which to pick amongst the following alternatives: ${this.usages.map((s,a)=>`${`${a}.`.padStart(4)} ${s}`).join(` `)} ${c_(e)}`}},c_=t=>`While running ${t.filter(e=>e!==ei.EndOfInput&&e!==ei.EndOfPartialInput).map(e=>{let r=JSON.stringify(e);return e.match(/\s/)||e.length===0||r!==`"${e}"`?r:e}).join(" ")}`});function DWe(t){let e=t.split(` `),r=e.filter(a=>a.match(/\S/)),s=r.length>0?r.reduce((a,n)=>Math.min(a,n.length-n.trimStart().length),Number.MAX_VALUE):0;return e.map(a=>a.slice(s).trimRight()).join(` `)}function Ho(t,{format:e,paragraphs:r}){return t=t.replace(/\r\n?/g,` `),t=DWe(t),t=t.replace(/^\n+|\n+$/g,""),t=t.replace(/^(\s*)-([^\n]*?)\n+/gm,`$1-$2 `),t=t.replace(/\n(\n)?\n*/g,(s,a)=>a||" "),r&&(t=t.split(/\n/).map(s=>{let a=s.match(/^\s*[*-][\t ]+(.*)/);if(!a)return s.match(/(.{1,80})(?: |$)/g).join(` `);let n=s.length-s.trimStart().length;return a[1].match(new RegExp(`(.{1,${78-n}})(?: |$)`,"g")).map((c,f)=>" ".repeat(n)+(f===0?"- ":" ")+c).join(` `)}).join(` `)),t=t.replace(/(`+)((?:.|[\n])*?)\1/g,(s,a,n)=>e.code(a+n+a)),t=t.replace(/(\*\*)((?:.|[\n])*?)\1/g,(s,a,n)=>e.bold(a+n+a)),t?`${t} `:""}var u_,kre,Qre,f_=Xe(()=>{u_=Array(80).fill("\u2501");for(let t=0;t<=24;++t)u_[u_.length-t]=`\x1B[38;5;${232+t}m\u2501`;kre={header:t=>`\x1B[1m\u2501\u2501\u2501 ${t}${t.length<75?` ${u_.slice(t.length+5).join("")}`:":"}\x1B[0m`,bold:t=>`\x1B[1m${t}\x1B[22m`,error:t=>`\x1B[31m\x1B[1m${t}\x1B[22m\x1B[39m`,code:t=>`\x1B[36m${t}\x1B[39m`},Qre={header:t=>t,bold:t=>t,error:t=>t,code:t=>t}});function ya(t){return{...t,[K2]:!0}}function Gf(t,e){return typeof t>"u"?[t,e]:typeof t=="object"&&t!==null&&!Array.isArray(t)?[void 0,t]:[t,e]}function Qx(t,{mergeName:e=!1}={}){let r=t.match(/^([^:]+): (.*)$/m);if(!r)return"validation failed";let[,s,a]=r;return e&&(a=a[0].toLowerCase()+a.slice(1)),a=s!=="."||!e?`${s.replace(/^\.(\[|$)/,"$1")}: ${a}`:`: ${a}`,a}function z2(t,e){return e.length===1?new nt(`${t}${Qx(e[0],{mergeName:!0})}`):new nt(`${t}: ${e.map(r=>` - ${Qx(r)}`).join("")}`)}function Od(t,e,r){if(typeof r>"u")return e;let s=[],a=[],n=f=>{let p=e;return e=f,n.bind(null,p)};if(!r(e,{errors:s,coercions:a,coercion:n}))throw z2(`Invalid value for ${t}`,s);for(let[,f]of a)f();return e}var K2,Cp=Xe(()=>{kx();K2=Symbol("clipanion/isOption")});var Ea={};Vt(Ea,{KeyRelationship:()=>qf,TypeAssertionError:()=>o0,applyCascade:()=>$2,as:()=>WWe,assert:()=>jWe,assertWithErrors:()=>GWe,cascade:()=>Nx,fn:()=>YWe,hasAtLeastOneKey:()=>y_,hasExactLength:()=>Ore,hasForbiddenKeys:()=>fYe,hasKeyRelationship:()=>tB,hasMaxLength:()=>JWe,hasMinLength:()=>VWe,hasMutuallyExclusiveKeys:()=>AYe,hasRequiredKeys:()=>uYe,hasUniqueItems:()=>KWe,isArray:()=>Tx,isAtLeast:()=>d_,isAtMost:()=>ZWe,isBase64:()=>oYe,isBoolean:()=>FWe,isDate:()=>OWe,isDict:()=>UWe,isEnum:()=>fo,isHexColor:()=>sYe,isISO8601:()=>iYe,isInExclusiveRange:()=>eYe,isInInclusiveRange:()=>$We,isInstanceOf:()=>HWe,isInteger:()=>m_,isJSON:()=>aYe,isLiteral:()=>Rre,isLowerCase:()=>tYe,isMap:()=>MWe,isNegative:()=>zWe,isNullable:()=>cYe,isNumber:()=>h_,isObject:()=>Fre,isOneOf:()=>g_,isOptional:()=>lYe,isPartial:()=>_We,isPayload:()=>NWe,isPositive:()=>XWe,isRecord:()=>Fx,isSet:()=>LWe,isString:()=>wE,isTuple:()=>Rx,isUUID4:()=>nYe,isUnknown:()=>p_,isUpperCase:()=>rYe,makeTrait:()=>Nre,makeValidator:()=>Wr,matchesRegExp:()=>Z2,softAssert:()=>qWe});function ti(t){return t===null?"null":t===void 0?"undefined":t===""?"an empty string":typeof t=="symbol"?`<${t.toString()}>`:Array.isArray(t)?"an array":JSON.stringify(t)}function CE(t,e){if(t.length===0)return"nothing";if(t.length===1)return ti(t[0]);let r=t.slice(0,-1),s=t[t.length-1],a=t.length>2?`, ${e} `:` ${e} `;return`${r.map(n=>ti(n)).join(", ")}${a}${ti(s)}`}function s0(t,e){var r,s,a;return typeof e=="number"?`${(r=t?.p)!==null&&r!==void 0?r:"."}[${e}]`:bWe.test(e)?`${(s=t?.p)!==null&&s!==void 0?s:""}.${e}`:`${(a=t?.p)!==null&&a!==void 0?a:"."}[${JSON.stringify(e)}]`}function A_(t,e,r){return t===1?e:r}function mr({errors:t,p:e}={},r){return t?.push(`${e??"."}: ${r}`),!1}function TWe(t,e){return r=>{t[e]=r}}function Wf(t,e){return r=>{let s=t[e];return t[e]=r,Wf(t,e).bind(null,s)}}function X2(t,e,r){let s=()=>(t(r()),a),a=()=>(t(e),s);return s}function p_(){return Wr({test:(t,e)=>!0})}function Rre(t){return Wr({test:(e,r)=>e!==t?mr(r,`Expected ${ti(t)} (got ${ti(e)})`):!0})}function wE(){return Wr({test:(t,e)=>typeof t!="string"?mr(e,`Expected a string (got ${ti(t)})`):!0})}function fo(t){let e=Array.isArray(t)?t:Object.values(t),r=e.every(a=>typeof a=="string"||typeof a=="number"),s=new Set(e);return s.size===1?Rre([...s][0]):Wr({test:(a,n)=>s.has(a)?!0:r?mr(n,`Expected one of ${CE(e,"or")} (got ${ti(a)})`):mr(n,`Expected a valid enumeration value (got ${ti(a)})`)})}function FWe(){return Wr({test:(t,e)=>{var r;if(typeof t!="boolean"){if(typeof e?.coercions<"u"){if(typeof e?.coercion>"u")return mr(e,"Unbound coercion result");let s=RWe.get(t);if(typeof s<"u")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:".",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a boolean (got ${ti(t)})`)}return!0}})}function h_(){return Wr({test:(t,e)=>{var r;if(typeof t!="number"){if(typeof e?.coercions<"u"){if(typeof e?.coercion>"u")return mr(e,"Unbound coercion result");let s;if(typeof t=="string"){let a;try{a=JSON.parse(t)}catch{}if(typeof a=="number")if(JSON.stringify(a)===t)s=a;else return mr(e,`Received a number that can't be safely represented by the runtime (${t})`)}if(typeof s<"u")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:".",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a number (got ${ti(t)})`)}return!0}})}function NWe(t){return Wr({test:(e,r)=>{var s;if(typeof r?.coercions>"u")return mr(r,"The isPayload predicate can only be used with coercion enabled");if(typeof r.coercion>"u")return mr(r,"Unbound coercion result");if(typeof e!="string")return mr(r,`Expected a string (got ${ti(e)})`);let a;try{a=JSON.parse(e)}catch{return mr(r,`Expected a JSON string (got ${ti(e)})`)}let n={value:a};return t(a,Object.assign(Object.assign({},r),{coercion:Wf(n,"value")}))?(r.coercions.push([(s=r.p)!==null&&s!==void 0?s:".",r.coercion.bind(null,n.value)]),!0):!1}})}function OWe(){return Wr({test:(t,e)=>{var r;if(!(t instanceof Date)){if(typeof e?.coercions<"u"){if(typeof e?.coercion>"u")return mr(e,"Unbound coercion result");let s;if(typeof t=="string"&&Tre.test(t))s=new Date(t);else{let a;if(typeof t=="string"){let n;try{n=JSON.parse(t)}catch{}typeof n=="number"&&(a=n)}else typeof t=="number"&&(a=t);if(typeof a<"u")if(Number.isSafeInteger(a)||!Number.isSafeInteger(a*1e3))s=new Date(a*1e3);else return mr(e,`Received a timestamp that can't be safely represented by the runtime (${t})`)}if(typeof s<"u")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:".",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a date (got ${ti(t)})`)}return!0}})}function Tx(t,{delimiter:e}={}){return Wr({test:(r,s)=>{var a;let n=r;if(typeof r=="string"&&typeof e<"u"&&typeof s?.coercions<"u"){if(typeof s?.coercion>"u")return mr(s,"Unbound coercion result");r=r.split(e)}if(!Array.isArray(r))return mr(s,`Expected an array (got ${ti(r)})`);let c=!0;for(let f=0,p=r.length;f{var n,c;if(Object.getPrototypeOf(s).toString()==="[object Set]")if(typeof a?.coercions<"u"){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");let f=[...s],p=[...s];if(!r(p,Object.assign(Object.assign({},a),{coercion:void 0})))return!1;let h=()=>p.some((E,C)=>E!==f[C])?new Set(p):s;return a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",X2(a.coercion,s,h)]),!0}else{let f=!0;for(let p of s)if(f=t(p,Object.assign({},a))&&f,!f&&a?.errors==null)break;return f}if(typeof a?.coercions<"u"){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");let f={value:s};return r(s,Object.assign(Object.assign({},a),{coercion:Wf(f,"value")}))?(a.coercions.push([(c=a.p)!==null&&c!==void 0?c:".",X2(a.coercion,s,()=>new Set(f.value))]),!0):!1}return mr(a,`Expected a set (got ${ti(s)})`)}})}function MWe(t,e){let r=Tx(Rx([t,e])),s=Fx(e,{keys:t});return Wr({test:(a,n)=>{var c,f,p;if(Object.getPrototypeOf(a).toString()==="[object Map]")if(typeof n?.coercions<"u"){if(typeof n?.coercion>"u")return mr(n,"Unbound coercion result");let h=[...a],E=[...a];if(!r(E,Object.assign(Object.assign({},n),{coercion:void 0})))return!1;let C=()=>E.some((S,P)=>S[0]!==h[P][0]||S[1]!==h[P][1])?new Map(E):a;return n.coercions.push([(c=n.p)!==null&&c!==void 0?c:".",X2(n.coercion,a,C)]),!0}else{let h=!0;for(let[E,C]of a)if(h=t(E,Object.assign({},n))&&h,!h&&n?.errors==null||(h=e(C,Object.assign(Object.assign({},n),{p:s0(n,E)}))&&h,!h&&n?.errors==null))break;return h}if(typeof n?.coercions<"u"){if(typeof n?.coercion>"u")return mr(n,"Unbound coercion result");let h={value:a};return Array.isArray(a)?r(a,Object.assign(Object.assign({},n),{coercion:void 0}))?(n.coercions.push([(f=n.p)!==null&&f!==void 0?f:".",X2(n.coercion,a,()=>new Map(h.value))]),!0):!1:s(a,Object.assign(Object.assign({},n),{coercion:Wf(h,"value")}))?(n.coercions.push([(p=n.p)!==null&&p!==void 0?p:".",X2(n.coercion,a,()=>new Map(Object.entries(h.value)))]),!0):!1}return mr(n,`Expected a map (got ${ti(a)})`)}})}function Rx(t,{delimiter:e}={}){let r=Ore(t.length);return Wr({test:(s,a)=>{var n;if(typeof s=="string"&&typeof e<"u"&&typeof a?.coercions<"u"){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");s=s.split(e),a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",a.coercion.bind(null,s)])}if(!Array.isArray(s))return mr(a,`Expected a tuple (got ${ti(s)})`);let c=r(s,Object.assign({},a));for(let f=0,p=s.length;f{var n;if(Array.isArray(s)&&typeof a?.coercions<"u")return typeof a?.coercion>"u"?mr(a,"Unbound coercion result"):r(s,Object.assign(Object.assign({},a),{coercion:void 0}))?(s=Object.fromEntries(s),a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",a.coercion.bind(null,s)]),!0):!1;if(typeof s!="object"||s===null)return mr(a,`Expected an object (got ${ti(s)})`);let c=Object.keys(s),f=!0;for(let p=0,h=c.length;p{if(typeof a!="object"||a===null)return mr(n,`Expected an object (got ${ti(a)})`);let c=new Set([...r,...Object.keys(a)]),f={},p=!0;for(let h of c){if(h==="constructor"||h==="__proto__")p=mr(Object.assign(Object.assign({},n),{p:s0(n,h)}),"Unsafe property name");else{let E=Object.prototype.hasOwnProperty.call(t,h)?t[h]:void 0,C=Object.prototype.hasOwnProperty.call(a,h)?a[h]:void 0;typeof E<"u"?p=E(C,Object.assign(Object.assign({},n),{p:s0(n,h),coercion:Wf(a,h)}))&&p:e===null?p=mr(Object.assign(Object.assign({},n),{p:s0(n,h)}),`Extraneous property (got ${ti(C)})`):Object.defineProperty(f,h,{enumerable:!0,get:()=>C,set:TWe(a,h)})}if(!p&&n?.errors==null)break}return e!==null&&(p||n?.errors!=null)&&(p=e(f,n)&&p),p}});return Object.assign(s,{properties:t})}function _We(t){return Fre(t,{extra:Fx(p_())})}function Nre(t){return()=>t}function Wr({test:t}){return Nre(t)()}function jWe(t,e){if(!e(t))throw new o0}function GWe(t,e){let r=[];if(!e(t,{errors:r}))throw new o0({errors:r})}function qWe(t,e){}function WWe(t,e,{coerce:r=!1,errors:s,throw:a}={}){let n=s?[]:void 0;if(!r){if(e(t,{errors:n}))return a?t:{value:t,errors:void 0};if(a)throw new o0({errors:n});return{value:void 0,errors:n??!0}}let c={value:t},f=Wf(c,"value"),p=[];if(!e(t,{errors:n,coercion:f,coercions:p})){if(a)throw new o0({errors:n});return{value:void 0,errors:n??!0}}for(let[,h]of p)h();return a?c.value:{value:c.value,errors:void 0}}function YWe(t,e){let r=Rx(t);return(...s)=>{if(!r(s))throw new o0;return e(...s)}}function VWe(t){return Wr({test:(e,r)=>e.length>=t?!0:mr(r,`Expected to have a length of at least ${t} elements (got ${e.length})`)})}function JWe(t){return Wr({test:(e,r)=>e.length<=t?!0:mr(r,`Expected to have a length of at most ${t} elements (got ${e.length})`)})}function Ore(t){return Wr({test:(e,r)=>e.length!==t?mr(r,`Expected to have a length of exactly ${t} elements (got ${e.length})`):!0})}function KWe({map:t}={}){return Wr({test:(e,r)=>{let s=new Set,a=new Set;for(let n=0,c=e.length;nt<=0?!0:mr(e,`Expected to be negative (got ${t})`)})}function XWe(){return Wr({test:(t,e)=>t>=0?!0:mr(e,`Expected to be positive (got ${t})`)})}function d_(t){return Wr({test:(e,r)=>e>=t?!0:mr(r,`Expected to be at least ${t} (got ${e})`)})}function ZWe(t){return Wr({test:(e,r)=>e<=t?!0:mr(r,`Expected to be at most ${t} (got ${e})`)})}function $We(t,e){return Wr({test:(r,s)=>r>=t&&r<=e?!0:mr(s,`Expected to be in the [${t}; ${e}] range (got ${r})`)})}function eYe(t,e){return Wr({test:(r,s)=>r>=t&&re!==Math.round(e)?mr(r,`Expected to be an integer (got ${e})`):!t&&!Number.isSafeInteger(e)?mr(r,`Expected to be a safe integer (got ${e})`):!0})}function Z2(t){return Wr({test:(e,r)=>t.test(e)?!0:mr(r,`Expected to match the pattern ${t.toString()} (got ${ti(e)})`)})}function tYe(){return Wr({test:(t,e)=>t!==t.toLowerCase()?mr(e,`Expected to be all-lowercase (got ${t})`):!0})}function rYe(){return Wr({test:(t,e)=>t!==t.toUpperCase()?mr(e,`Expected to be all-uppercase (got ${t})`):!0})}function nYe(){return Wr({test:(t,e)=>QWe.test(t)?!0:mr(e,`Expected to be a valid UUID v4 (got ${ti(t)})`)})}function iYe(){return Wr({test:(t,e)=>Tre.test(t)?!0:mr(e,`Expected to be a valid ISO 8601 date string (got ${ti(t)})`)})}function sYe({alpha:t=!1}){return Wr({test:(e,r)=>(t?PWe.test(e):xWe.test(e))?!0:mr(r,`Expected to be a valid hexadecimal color string (got ${ti(e)})`)})}function oYe(){return Wr({test:(t,e)=>kWe.test(t)?!0:mr(e,`Expected to be a valid base 64 string (got ${ti(t)})`)})}function aYe(t=p_()){return Wr({test:(e,r)=>{let s;try{s=JSON.parse(e)}catch{return mr(r,`Expected to be a valid JSON string (got ${ti(e)})`)}return t(s,r)}})}function Nx(t,...e){let r=Array.isArray(e[0])?e[0]:e;return Wr({test:(s,a)=>{var n,c;let f={value:s},p=typeof a?.coercions<"u"?Wf(f,"value"):void 0,h=typeof a?.coercions<"u"?[]:void 0;if(!t(s,Object.assign(Object.assign({},a),{coercion:p,coercions:h})))return!1;let E=[];if(typeof h<"u")for(let[,C]of h)E.push(C());try{if(typeof a?.coercions<"u"){if(f.value!==s){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",a.coercion.bind(null,f.value)])}(c=a?.coercions)===null||c===void 0||c.push(...h)}return r.every(C=>C(f.value,a))}finally{for(let C of E)C()}}})}function $2(t,...e){let r=Array.isArray(e[0])?e[0]:e;return Nx(t,r)}function lYe(t){return Wr({test:(e,r)=>typeof e>"u"?!0:t(e,r)})}function cYe(t){return Wr({test:(e,r)=>e===null?!0:t(e,r)})}function uYe(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)||p.push(h);return p.length>0?mr(c,`Missing required ${A_(p.length,"property","properties")} ${CE(p,"and")}`):!0}})}function y_(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>Object.keys(n).some(h=>a(s,h,n))?!0:mr(c,`Missing at least one property from ${CE(Array.from(s),"or")}`)})}function fYe(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)&&p.push(h);return p.length>0?mr(c,`Forbidden ${A_(p.length,"property","properties")} ${CE(p,"and")}`):!0}})}function AYe(t,e){var r;let s=new Set(t),a=eB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)&&p.push(h);return p.length>1?mr(c,`Mutually exclusive properties ${CE(p,"and")}`):!0}})}function tB(t,e,r,s){var a,n;let c=new Set((a=s?.ignore)!==null&&a!==void 0?a:[]),f=eB[(n=s?.missingIf)!==null&&n!==void 0?n:"missing"],p=new Set(r),h=pYe[e],E=e===qf.Forbids?"or":"and";return Wr({test:(C,S)=>{let P=new Set(Object.keys(C));if(!f(P,t,C)||c.has(C[t]))return!0;let I=[];for(let R of p)(f(P,R,C)&&!c.has(C[R]))!==h.expect&&I.push(R);return I.length>=1?mr(S,`Property "${t}" ${h.message} ${A_(I.length,"property","properties")} ${CE(I,E)}`):!0}})}var bWe,PWe,xWe,kWe,QWe,Tre,RWe,HWe,g_,o0,eB,qf,pYe,Ul=Xe(()=>{bWe=/^[a-zA-Z_][a-zA-Z0-9_]*$/;PWe=/^#[0-9a-f]{6}$/i,xWe=/^#[0-9a-f]{6}([0-9a-f]{2})?$/i,kWe=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,QWe=/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$/i,Tre=/^(?:[1-9]\d{3}(-?)(?:(?:0[1-9]|1[0-2])\1(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])\1(?:29|30)|(?:0[13578]|1[02])(?:\1)31|00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[0-5]))|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)(?:(-?)02(?:\2)29|-?366))T(?:[01]\d|2[0-3])(:?)[0-5]\d(?:\3[0-5]\d)?(?:Z|[+-][01]\d(?:\3[0-5]\d)?)$/;RWe=new Map([["true",!0],["True",!0],["1",!0],[1,!0],["false",!1],["False",!1],["0",!1],[0,!1]]);HWe=t=>Wr({test:(e,r)=>e instanceof t?!0:mr(r,`Expected an instance of ${t.name} (got ${ti(e)})`)}),g_=(t,{exclusive:e=!1}={})=>Wr({test:(r,s)=>{var a,n,c;let f=[],p=typeof s?.errors<"u"?[]:void 0;for(let h=0,E=t.length;h1?mr(s,`Expected to match exactly a single predicate (matched ${f.join(", ")})`):(c=s?.errors)===null||c===void 0||c.push(...p),!1}});o0=class extends Error{constructor({errors:e}={}){let r="Type mismatch";if(e&&e.length>0){r+=` `;for(let s of e)r+=` - ${s}`}super(r)}};eB={missing:(t,e)=>t.has(e),undefined:(t,e,r)=>t.has(e)&&typeof r[e]<"u",nil:(t,e,r)=>t.has(e)&&r[e]!=null,falsy:(t,e,r)=>t.has(e)&&!!r[e]};(function(t){t.Forbids="Forbids",t.Requires="Requires"})(qf||(qf={}));pYe={[qf.Forbids]:{expect:!1,message:"forbids using"},[qf.Requires]:{expect:!0,message:"requires using"}}});var ot,a0=Xe(()=>{Cp();ot=class{constructor(){this.help=!1}static Usage(e){return e}async catch(e){throw e}async validateAndExecute(){let r=this.constructor.schema;if(Array.isArray(r)){let{isDict:a,isUnknown:n,applyCascade:c}=await Promise.resolve().then(()=>(Ul(),Ea)),f=c(a(n()),r),p=[],h=[];if(!f(this,{errors:p,coercions:h}))throw z2("Invalid option schema",p);for(let[,C]of h)C()}else if(r!=null)throw new Error("Invalid command schema");let s=await this.execute();return typeof s<"u"?s:0}};ot.isOption=K2;ot.Default=[]});function il(t){l_&&console.log(t)}function Mre(){let t={nodes:[]};for(let e=0;e{if(e.has(s))return;e.add(s);let a=t.nodes[s];for(let c of Object.values(a.statics))for(let{to:f}of c)r(f);for(let[,{to:c}]of a.dynamics)r(c);for(let{to:c}of a.shortcuts)r(c);let n=new Set(a.shortcuts.map(({to:c})=>c));for(;a.shortcuts.length>0;){let{to:c}=a.shortcuts.shift(),f=t.nodes[c];for(let[p,h]of Object.entries(f.statics)){let E=Object.prototype.hasOwnProperty.call(a.statics,p)?a.statics[p]:a.statics[p]=[];for(let C of h)E.some(({to:S})=>C.to===S)||E.push(C)}for(let[p,h]of f.dynamics)a.dynamics.some(([E,{to:C}])=>p===E&&h.to===C)||a.dynamics.push([p,h]);for(let p of f.shortcuts)n.has(p.to)||(a.shortcuts.push(p),n.add(p.to))}};r(En.InitialNode)}function dYe(t,{prefix:e=""}={}){if(l_){il(`${e}Nodes are:`);for(let r=0;rE!==En.ErrorNode).map(({state:E})=>({usage:E.candidateUsage,reason:null})));if(h.every(({node:E})=>E===En.ErrorNode))throw new IE(e,h.map(({state:E})=>({usage:E.candidateUsage,reason:E.errorMessage})));s=EYe(h)}if(s.length>0){il(" Results:");for(let n of s)il(` - ${n.node} -> ${JSON.stringify(n.state)}`)}else il(" No results");return s}function yYe(t,e,{endToken:r=ei.EndOfInput}={}){let s=mYe(t,[...e,r]);return IYe(e,s.map(({state:a})=>a))}function EYe(t){let e=0;for(let{state:r}of t)r.path.length>e&&(e=r.path.length);return t.filter(({state:r})=>r.path.length===e)}function IYe(t,e){let r=e.filter(S=>S.selectedIndex!==null),s=r.filter(S=>!S.partial);if(s.length>0&&(r=s),r.length===0)throw new Error;let a=r.filter(S=>S.selectedIndex===Nd||S.requiredOptions.every(P=>P.some(I=>S.options.find(R=>R.name===I))));if(a.length===0)throw new IE(t,r.map(S=>({usage:S.candidateUsage,reason:null})));let n=0;for(let S of a)S.path.length>n&&(n=S.path.length);let c=a.filter(S=>S.path.length===n),f=S=>S.positionals.filter(({extra:P})=>!P).length+S.options.length,p=c.map(S=>({state:S,positionalCount:f(S)})),h=0;for(let{positionalCount:S}of p)S>h&&(h=S);let E=p.filter(({positionalCount:S})=>S===h).map(({state:S})=>S),C=CYe(E);if(C.length>1)throw new xx(t,C.map(S=>S.candidateUsage));return C[0]}function CYe(t){let e=[],r=[];for(let s of t)s.selectedIndex===Nd?r.push(s):e.push(s);return r.length>0&&e.push({...Lre,path:Ure(...r.map(s=>s.path)),options:r.reduce((s,a)=>s.concat(a.options),[])}),e}function Ure(t,e,...r){return e===void 0?Array.from(t):Ure(t.filter((s,a)=>s===e[a]),...r)}function _l(){return{dynamics:[],shortcuts:[],statics:{}}}function _re(t){return t===En.SuccessNode||t===En.ErrorNode}function E_(t,e=0){return{to:_re(t.to)?t.to:t.to>=En.CustomNode?t.to+e-En.CustomNode+1:t.to+e,reducer:t.reducer}}function wYe(t,e=0){let r=_l();for(let[s,a]of t.dynamics)r.dynamics.push([s,E_(a,e)]);for(let s of t.shortcuts)r.shortcuts.push(E_(s,e));for(let[s,a]of Object.entries(t.statics))r.statics[s]=a.map(n=>E_(n,e));return r}function Hs(t,e,r,s,a){t.nodes[e].dynamics.push([r,{to:s,reducer:a}])}function BE(t,e,r,s){t.nodes[e].shortcuts.push({to:r,reducer:s})}function Ia(t,e,r,s,a){(Object.prototype.hasOwnProperty.call(t.nodes[e].statics,r)?t.nodes[e].statics[r]:t.nodes[e].statics[r]=[]).push({to:s,reducer:a})}function Ox(t,e,r,s,a){if(Array.isArray(e)){let[n,...c]=e;return t[n](r,s,a,...c)}else return t[e](r,s,a)}var Lre,BYe,I_,Hl,C_,Lx,Mx=Xe(()=>{Px();kx();Lre={candidateUsage:null,requiredOptions:[],errorMessage:null,ignoreOptions:!1,path:[],positionals:[],options:[],remainder:null,selectedIndex:Nd,partial:!1,tokens:[]};BYe={always:()=>!0,isOptionLike:(t,e)=>!t.ignoreOptions&&e!=="-"&&e.startsWith("-"),isNotOptionLike:(t,e)=>t.ignoreOptions||e==="-"||!e.startsWith("-"),isOption:(t,e,r,s)=>!t.ignoreOptions&&e===s,isBatchOption:(t,e,r,s)=>!t.ignoreOptions&&xre.test(e)&&[...e.slice(1)].every(a=>s.has(`-${a}`)),isBoundOption:(t,e,r,s,a)=>{let n=e.match(a_);return!t.ignoreOptions&&!!n&&bx.test(n[1])&&s.has(n[1])&&a.filter(c=>c.nameSet.includes(n[1])).every(c=>c.allowBinding)},isNegatedOption:(t,e,r,s)=>!t.ignoreOptions&&e===`--no-${s.slice(2)}`,isHelp:(t,e)=>!t.ignoreOptions&&o_.test(e),isUnsupportedOption:(t,e,r,s)=>!t.ignoreOptions&&e.startsWith("-")&&bx.test(e)&&!s.has(e),isInvalidOption:(t,e)=>!t.ignoreOptions&&e.startsWith("-")&&!bx.test(e)},I_={setCandidateState:(t,e,r,s)=>({...t,...s}),setSelectedIndex:(t,e,r,s)=>({...t,selectedIndex:s}),setPartialIndex:(t,e,r,s)=>({...t,selectedIndex:s,partial:!0}),pushBatch:(t,e,r,s)=>{let a=t.options.slice(),n=t.tokens.slice();for(let c=1;c{let[,s,a]=e.match(a_),n=t.options.concat({name:s,value:a}),c=t.tokens.concat([{segmentIndex:r,type:"option",slice:[0,s.length],option:s},{segmentIndex:r,type:"assign",slice:[s.length,s.length+1]},{segmentIndex:r,type:"value",slice:[s.length+1,s.length+a.length+1]}]);return{...t,options:n,tokens:c}},pushPath:(t,e,r)=>{let s=t.path.concat(e),a=t.tokens.concat({segmentIndex:r,type:"path"});return{...t,path:s,tokens:a}},pushPositional:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:!1}),a=t.tokens.concat({segmentIndex:r,type:"positional"});return{...t,positionals:s,tokens:a}},pushExtra:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:!0}),a=t.tokens.concat({segmentIndex:r,type:"positional"});return{...t,positionals:s,tokens:a}},pushExtraNoLimits:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:Hl}),a=t.tokens.concat({segmentIndex:r,type:"positional"});return{...t,positionals:s,tokens:a}},pushTrue:(t,e,r,s)=>{let a=t.options.concat({name:s,value:!0}),n=t.tokens.concat({segmentIndex:r,type:"option",option:s});return{...t,options:a,tokens:n}},pushFalse:(t,e,r,s)=>{let a=t.options.concat({name:s,value:!1}),n=t.tokens.concat({segmentIndex:r,type:"option",option:s});return{...t,options:a,tokens:n}},pushUndefined:(t,e,r,s)=>{let a=t.options.concat({name:e,value:void 0}),n=t.tokens.concat({segmentIndex:r,type:"option",option:e});return{...t,options:a,tokens:n}},pushStringValue:(t,e,r)=>{var s;let a=t.options[t.options.length-1],n=t.options.slice(),c=t.tokens.concat({segmentIndex:r,type:"value"});return a.value=((s=a.value)!==null&&s!==void 0?s:[]).concat([e]),{...t,options:n,tokens:c}},setStringValue:(t,e,r)=>{let s=t.options[t.options.length-1],a=t.options.slice(),n=t.tokens.concat({segmentIndex:r,type:"value"});return s.value=e,{...t,options:a,tokens:n}},inhibateOptions:t=>({...t,ignoreOptions:!0}),useHelp:(t,e,r,s)=>{let[,,a]=e.match(o_);return typeof a<"u"?{...t,options:[{name:"-c",value:String(s)},{name:"-i",value:a}]}:{...t,options:[{name:"-c",value:String(s)}]}},setError:(t,e,r,s)=>e===ei.EndOfInput||e===ei.EndOfPartialInput?{...t,errorMessage:`${s}.`}:{...t,errorMessage:`${s} ("${e}").`},setOptionArityError:(t,e)=>{let r=t.options[t.options.length-1];return{...t,errorMessage:`Not enough arguments to option ${r.name}.`}}},Hl=Symbol(),C_=class{constructor(e,r){this.allOptionNames=new Map,this.arity={leading:[],trailing:[],extra:[],proxy:!1},this.options=[],this.paths=[],this.cliIndex=e,this.cliOpts=r}addPath(e){this.paths.push(e)}setArity({leading:e=this.arity.leading,trailing:r=this.arity.trailing,extra:s=this.arity.extra,proxy:a=this.arity.proxy}){Object.assign(this.arity,{leading:e,trailing:r,extra:s,proxy:a})}addPositional({name:e="arg",required:r=!0}={}){if(!r&&this.arity.extra===Hl)throw new Error("Optional parameters cannot be declared when using .rest() or .proxy()");if(!r&&this.arity.trailing.length>0)throw new Error("Optional parameters cannot be declared after the required trailing positional arguments");!r&&this.arity.extra!==Hl?this.arity.extra.push(e):this.arity.extra!==Hl&&this.arity.extra.length===0?this.arity.leading.push(e):this.arity.trailing.push(e)}addRest({name:e="arg",required:r=0}={}){if(this.arity.extra===Hl)throw new Error("Infinite lists cannot be declared multiple times in the same command");if(this.arity.trailing.length>0)throw new Error("Infinite lists cannot be declared after the required trailing positional arguments");for(let s=0;s1)throw new Error("The arity cannot be higher than 1 when the option only supports the --arg=value syntax");if(!Number.isInteger(s))throw new Error(`The arity must be an integer, got ${s}`);if(s<0)throw new Error(`The arity must be positive, got ${s}`);let f=e.reduce((p,h)=>h.length>p.length?h:p,"");for(let p of e)this.allOptionNames.set(p,f);this.options.push({preferredName:f,nameSet:e,description:r,arity:s,hidden:a,required:n,allowBinding:c})}setContext(e){this.context=e}usage({detailed:e=!0,inlineOptions:r=!0}={}){let s=[this.cliOpts.binaryName],a=[];if(this.paths.length>0&&s.push(...this.paths[0]),e){for(let{preferredName:c,nameSet:f,arity:p,hidden:h,description:E,required:C}of this.options){if(h)continue;let S=[];for(let I=0;I`:`[${P}]`)}s.push(...this.arity.leading.map(c=>`<${c}>`)),this.arity.extra===Hl?s.push("..."):s.push(...this.arity.extra.map(c=>`[${c}]`)),s.push(...this.arity.trailing.map(c=>`<${c}>`))}return{usage:s.join(" "),options:a}}compile(){if(typeof this.context>"u")throw new Error("Assertion failed: No context attached");let e=Mre(),r=En.InitialNode,s=this.usage().usage,a=this.options.filter(f=>f.required).map(f=>f.nameSet);r=Ou(e,_l()),Ia(e,En.InitialNode,ei.StartOfInput,r,["setCandidateState",{candidateUsage:s,requiredOptions:a}]);let n=this.arity.proxy?"always":"isNotOptionLike",c=this.paths.length>0?this.paths:[[]];for(let f of c){let p=r;if(f.length>0){let S=Ou(e,_l());BE(e,p,S),this.registerOptions(e,S),p=S}for(let S=0;S0||!this.arity.proxy){let S=Ou(e,_l());Hs(e,p,"isHelp",S,["useHelp",this.cliIndex]),Hs(e,S,"always",S,"pushExtra"),Ia(e,S,ei.EndOfInput,En.SuccessNode,["setSelectedIndex",Nd]),this.registerOptions(e,p)}this.arity.leading.length>0&&(Ia(e,p,ei.EndOfInput,En.ErrorNode,["setError","Not enough positional arguments"]),Ia(e,p,ei.EndOfPartialInput,En.SuccessNode,["setPartialIndex",this.cliIndex]));let h=p;for(let S=0;S0||S+1!==this.arity.leading.length)&&(Ia(e,P,ei.EndOfInput,En.ErrorNode,["setError","Not enough positional arguments"]),Ia(e,P,ei.EndOfPartialInput,En.SuccessNode,["setPartialIndex",this.cliIndex])),Hs(e,h,"isNotOptionLike",P,"pushPositional"),h=P}let E=h;if(this.arity.extra===Hl||this.arity.extra.length>0){let S=Ou(e,_l());if(BE(e,h,S),this.arity.extra===Hl){let P=Ou(e,_l());this.arity.proxy||this.registerOptions(e,P),Hs(e,h,n,P,"pushExtraNoLimits"),Hs(e,P,n,P,"pushExtraNoLimits"),BE(e,P,S)}else for(let P=0;P0)&&this.registerOptions(e,I),Hs(e,E,n,I,"pushExtra"),BE(e,I,S),E=I}E=S}this.arity.trailing.length>0&&(Ia(e,E,ei.EndOfInput,En.ErrorNode,["setError","Not enough positional arguments"]),Ia(e,E,ei.EndOfPartialInput,En.SuccessNode,["setPartialIndex",this.cliIndex]));let C=E;for(let S=0;S=0&&e{let c=n?ei.EndOfPartialInput:ei.EndOfInput;return yYe(s,a,{endToken:c})}}}}});function jre(){return Ux.default&&"getColorDepth"in Ux.default.WriteStream.prototype?Ux.default.WriteStream.prototype.getColorDepth():process.env.FORCE_COLOR==="0"?1:process.env.FORCE_COLOR==="1"||typeof process.stdout<"u"&&process.stdout.isTTY?8:1}function Gre(t){let e=Hre;if(typeof e>"u"){if(t.stdout===process.stdout&&t.stderr===process.stderr)return null;let{AsyncLocalStorage:r}=Ie("async_hooks");e=Hre=new r;let s=process.stdout._write;process.stdout._write=function(n,c,f){let p=e.getStore();return typeof p>"u"?s.call(this,n,c,f):p.stdout.write(n,c,f)};let a=process.stderr._write;process.stderr._write=function(n,c,f){let p=e.getStore();return typeof p>"u"?a.call(this,n,c,f):p.stderr.write(n,c,f)}}return r=>e.run(t,r)}var Ux,Hre,qre=Xe(()=>{Ux=ut(Ie("tty"),1)});var _x,Wre=Xe(()=>{a0();_x=class t extends ot{constructor(e){super(),this.contexts=e,this.commands=[]}static from(e,r){let s=new t(r);s.path=e.path;for(let a of e.options)switch(a.name){case"-c":s.commands.push(Number(a.value));break;case"-i":s.index=Number(a.value);break}return s}async execute(){let e=this.commands;if(typeof this.index<"u"&&this.index>=0&&this.index1){this.context.stdout.write(`Multiple commands match your selection: `),this.context.stdout.write(` `);let r=0;for(let s of this.commands)this.context.stdout.write(this.cli.usage(this.contexts[s].commandClass,{prefix:`${r++}. `.padStart(5)}));this.context.stdout.write(` `),this.context.stdout.write(`Run again with -h= to see the longer details of any of those commands. `)}}}});async function Jre(...t){let{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}=zre(t);return Ca.from(r,e).runExit(s,a)}async function Kre(...t){let{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}=zre(t);return Ca.from(r,e).run(s,a)}function zre(t){let e,r,s,a;switch(typeof process<"u"&&typeof process.argv<"u"&&(s=process.argv.slice(2)),t.length){case 1:r=t[0];break;case 2:t[0]&&t[0].prototype instanceof ot||Array.isArray(t[0])?(r=t[0],Array.isArray(t[1])?s=t[1]:a=t[1]):(e=t[0],r=t[1]);break;case 3:Array.isArray(t[2])?(e=t[0],r=t[1],s=t[2]):t[0]&&t[0].prototype instanceof ot||Array.isArray(t[0])?(r=t[0],s=t[1],a=t[2]):(e=t[0],r=t[1],a=t[2]);break;default:e=t[0],r=t[1],s=t[2],a=t[3];break}if(typeof s>"u")throw new Error("The argv parameter must be provided when running Clipanion outside of a Node context");return{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}}function Vre(t){return t()}var Yre,Ca,Xre=Xe(()=>{Px();Mx();f_();qre();a0();Wre();Yre=Symbol("clipanion/errorCommand");Ca=class t{constructor({binaryLabel:e,binaryName:r="...",binaryVersion:s,enableCapture:a=!1,enableColors:n}={}){this.registrations=new Map,this.builder=new Lx({binaryName:r}),this.binaryLabel=e,this.binaryName=r,this.binaryVersion=s,this.enableCapture=a,this.enableColors=n}static from(e,r={}){let s=new t(r),a=Array.isArray(e)?e:[e];for(let n of a)s.register(n);return s}register(e){var r;let s=new Map,a=new e;for(let p in a){let h=a[p];typeof h=="object"&&h!==null&&h[ot.isOption]&&s.set(p,h)}let n=this.builder.command(),c=n.cliIndex,f=(r=e.paths)!==null&&r!==void 0?r:a.paths;if(typeof f<"u")for(let p of f)n.addPath(p);this.registrations.set(e,{specs:s,builder:n,index:c});for(let[p,{definition:h}]of s.entries())h(n,p);n.setContext({commandClass:e})}process(e,r){let{input:s,context:a,partial:n}=typeof e=="object"&&Array.isArray(e)?{input:e,context:r}:e,{contexts:c,process:f}=this.builder.compile(),p=f(s,{partial:n}),h={...t.defaultContext,...a};switch(p.selectedIndex){case Nd:{let E=_x.from(p,c);return E.context=h,E.tokens=p.tokens,E}default:{let{commandClass:E}=c[p.selectedIndex],C=this.registrations.get(E);if(typeof C>"u")throw new Error("Assertion failed: Expected the command class to have been registered.");let S=new E;S.context=h,S.tokens=p.tokens,S.path=p.path;try{for(let[P,{transformer:I}]of C.specs.entries())S[P]=I(C.builder,P,p,h);return S}catch(P){throw P[Yre]=S,P}}break}}async run(e,r){var s,a;let n,c={...t.defaultContext,...r},f=(s=this.enableColors)!==null&&s!==void 0?s:c.colorDepth>1;if(!Array.isArray(e))n=e;else try{n=this.process(e,c)}catch(E){return c.stdout.write(this.error(E,{colored:f})),1}if(n.help)return c.stdout.write(this.usage(n,{colored:f,detailed:!0})),0;n.context=c,n.cli={binaryLabel:this.binaryLabel,binaryName:this.binaryName,binaryVersion:this.binaryVersion,enableCapture:this.enableCapture,enableColors:this.enableColors,definitions:()=>this.definitions(),definition:E=>this.definition(E),error:(E,C)=>this.error(E,C),format:E=>this.format(E),process:(E,C)=>this.process(E,{...c,...C}),run:(E,C)=>this.run(E,{...c,...C}),usage:(E,C)=>this.usage(E,C)};let p=this.enableCapture&&(a=Gre(c))!==null&&a!==void 0?a:Vre,h;try{h=await p(()=>n.validateAndExecute().catch(E=>n.catch(E).then(()=>0)))}catch(E){return c.stdout.write(this.error(E,{colored:f,command:n})),1}return h}async runExit(e,r){process.exitCode=await this.run(e,r)}definition(e,{colored:r=!1}={}){if(!e.usage)return null;let{usage:s}=this.getUsageByRegistration(e,{detailed:!1}),{usage:a,options:n}=this.getUsageByRegistration(e,{detailed:!0,inlineOptions:!1}),c=typeof e.usage.category<"u"?Ho(e.usage.category,{format:this.format(r),paragraphs:!1}):void 0,f=typeof e.usage.description<"u"?Ho(e.usage.description,{format:this.format(r),paragraphs:!1}):void 0,p=typeof e.usage.details<"u"?Ho(e.usage.details,{format:this.format(r),paragraphs:!0}):void 0,h=typeof e.usage.examples<"u"?e.usage.examples.map(([E,C])=>[Ho(E,{format:this.format(r),paragraphs:!1}),C.replace(/\$0/g,this.binaryName)]):void 0;return{path:s,usage:a,category:c,description:f,details:p,examples:h,options:n}}definitions({colored:e=!1}={}){let r=[];for(let s of this.registrations.keys()){let a=this.definition(s,{colored:e});a&&r.push(a)}return r}usage(e=null,{colored:r,detailed:s=!1,prefix:a="$ "}={}){var n;if(e===null){for(let p of this.registrations.keys()){let h=p.paths,E=typeof p.usage<"u";if(!h||h.length===0||h.length===1&&h[0].length===0||((n=h?.some(P=>P.length===0))!==null&&n!==void 0?n:!1))if(e){e=null;break}else e=p;else if(E){e=null;continue}}e&&(s=!0)}let c=e!==null&&e instanceof ot?e.constructor:e,f="";if(c)if(s){let{description:p="",details:h="",examples:E=[]}=c.usage||{};p!==""&&(f+=Ho(p,{format:this.format(r),paragraphs:!1}).replace(/^./,P=>P.toUpperCase()),f+=` `),(h!==""||E.length>0)&&(f+=`${this.format(r).header("Usage")} `,f+=` `);let{usage:C,options:S}=this.getUsageByRegistration(c,{inlineOptions:!1});if(f+=`${this.format(r).bold(a)}${C} `,S.length>0){f+=` `,f+=`${this.format(r).header("Options")} `;let P=S.reduce((I,R)=>Math.max(I,R.definition.length),0);f+=` `;for(let{definition:I,description:R}of S)f+=` ${this.format(r).bold(I.padEnd(P))} ${Ho(R,{format:this.format(r),paragraphs:!1})}`}if(h!==""&&(f+=` `,f+=`${this.format(r).header("Details")} `,f+=` `,f+=Ho(h,{format:this.format(r),paragraphs:!0})),E.length>0){f+=` `,f+=`${this.format(r).header("Examples")} `;for(let[P,I]of E)f+=` `,f+=Ho(P,{format:this.format(r),paragraphs:!1}),f+=`${I.replace(/^/m,` ${this.format(r).bold(a)}`).replace(/\$0/g,this.binaryName)} `}}else{let{usage:p}=this.getUsageByRegistration(c);f+=`${this.format(r).bold(a)}${p} `}else{let p=new Map;for(let[S,{index:P}]of this.registrations.entries()){if(typeof S.usage>"u")continue;let I=typeof S.usage.category<"u"?Ho(S.usage.category,{format:this.format(r),paragraphs:!1}):null,R=p.get(I);typeof R>"u"&&p.set(I,R=[]);let{usage:N}=this.getUsageByIndex(P);R.push({commandClass:S,usage:N})}let h=Array.from(p.keys()).sort((S,P)=>S===null?-1:P===null?1:S.localeCompare(P,"en",{usage:"sort",caseFirst:"upper"})),E=typeof this.binaryLabel<"u",C=typeof this.binaryVersion<"u";E||C?(E&&C?f+=`${this.format(r).header(`${this.binaryLabel} - ${this.binaryVersion}`)} `:E?f+=`${this.format(r).header(`${this.binaryLabel}`)} `:f+=`${this.format(r).header(`${this.binaryVersion}`)} `,f+=` ${this.format(r).bold(a)}${this.binaryName} `):f+=`${this.format(r).bold(a)}${this.binaryName} `;for(let S of h){let P=p.get(S).slice().sort((R,N)=>R.usage.localeCompare(N.usage,"en",{usage:"sort",caseFirst:"upper"})),I=S!==null?S.trim():"General commands";f+=` `,f+=`${this.format(r).header(`${I}`)} `;for(let{commandClass:R,usage:N}of P){let U=R.usage.description||"undocumented";f+=` `,f+=` ${this.format(r).bold(N)} `,f+=` ${Ho(U,{format:this.format(r),paragraphs:!1})}`}}f+=` `,f+=Ho("You can also print more details about any of these commands by calling them with the `-h,--help` flag right after the command name.",{format:this.format(r),paragraphs:!0})}return f}error(e,r){var s,{colored:a,command:n=(s=e[Yre])!==null&&s!==void 0?s:null}=r===void 0?{}:r;(!e||typeof e!="object"||!("stack"in e))&&(e=new Error(`Execution failed with a non-error rejection (rejected value: ${JSON.stringify(e)})`));let c="",f=e.name.replace(/([a-z])([A-Z])/g,"$1 $2");f==="Error"&&(f="Internal Error"),c+=`${this.format(a).error(f)}: ${e.message} `;let p=e.clipanion;return typeof p<"u"?p.type==="usage"&&(c+=` `,c+=this.usage(n)):e.stack&&(c+=`${e.stack.replace(/^.*\n/,"")} `),c}format(e){var r;return((r=e??this.enableColors)!==null&&r!==void 0?r:t.defaultContext.colorDepth>1)?kre:Qre}getUsageByRegistration(e,r){let s=this.registrations.get(e);if(typeof s>"u")throw new Error("Assertion failed: Unregistered command");return this.getUsageByIndex(s.index,r)}getUsageByIndex(e,r){return this.builder.getBuilderByIndex(e).usage(r)}};Ca.defaultContext={env:process.env,stdin:process.stdin,stdout:process.stdout,stderr:process.stderr,colorDepth:jre()}});var rB,Zre=Xe(()=>{a0();rB=class extends ot{async execute(){this.context.stdout.write(`${JSON.stringify(this.cli.definitions(),null,2)} `)}};rB.paths=[["--clipanion=definitions"]]});var nB,$re=Xe(()=>{a0();nB=class extends ot{async execute(){this.context.stdout.write(this.cli.usage())}};nB.paths=[["-h"],["--help"]]});function Hx(t={}){return ya({definition(e,r){var s;e.addProxy({name:(s=t.name)!==null&&s!==void 0?s:r,required:t.required})},transformer(e,r,s){return s.positionals.map(({value:a})=>a)}})}var w_=Xe(()=>{Cp()});var iB,ene=Xe(()=>{a0();w_();iB=class extends ot{constructor(){super(...arguments),this.args=Hx()}async execute(){this.context.stdout.write(`${JSON.stringify(this.cli.process(this.args).tokens,null,2)} `)}};iB.paths=[["--clipanion=tokens"]]});var sB,tne=Xe(()=>{a0();sB=class extends ot{async execute(){var e;this.context.stdout.write(`${(e=this.cli.binaryVersion)!==null&&e!==void 0?e:""} `)}};sB.paths=[["-v"],["--version"]]});var B_={};Vt(B_,{DefinitionsCommand:()=>rB,HelpCommand:()=>nB,TokensCommand:()=>iB,VersionCommand:()=>sB});var rne=Xe(()=>{Zre();$re();ene();tne()});function nne(t,e,r){let[s,a]=Gf(e,r??{}),{arity:n=1}=a,c=t.split(","),f=new Set(c);return ya({definition(p){p.addOption({names:c,arity:n,hidden:a?.hidden,description:a?.description,required:a.required})},transformer(p,h,E){let C,S=typeof s<"u"?[...s]:void 0;for(let{name:P,value:I}of E.options)f.has(P)&&(C=P,S=S??[],S.push(I));return typeof S<"u"?Od(C??h,S,a.validator):S}})}var ine=Xe(()=>{Cp()});function sne(t,e,r){let[s,a]=Gf(e,r??{}),n=t.split(","),c=new Set(n);return ya({definition(f){f.addOption({names:n,allowBinding:!1,arity:0,hidden:a.hidden,description:a.description,required:a.required})},transformer(f,p,h){let E=s;for(let{name:C,value:S}of h.options)c.has(C)&&(E=S);return E}})}var one=Xe(()=>{Cp()});function ane(t,e,r){let[s,a]=Gf(e,r??{}),n=t.split(","),c=new Set(n);return ya({definition(f){f.addOption({names:n,allowBinding:!1,arity:0,hidden:a.hidden,description:a.description,required:a.required})},transformer(f,p,h){let E=s;for(let{name:C,value:S}of h.options)c.has(C)&&(E??(E=0),S?E+=1:E=0);return E}})}var lne=Xe(()=>{Cp()});function cne(t={}){return ya({definition(e,r){var s;e.addRest({name:(s=t.name)!==null&&s!==void 0?s:r,required:t.required})},transformer(e,r,s){let a=c=>{let f=s.positionals[c];return f.extra===Hl||f.extra===!1&&cc)}})}var une=Xe(()=>{Mx();Cp()});function vYe(t,e,r){let[s,a]=Gf(e,r??{}),{arity:n=1}=a,c=t.split(","),f=new Set(c);return ya({definition(p){p.addOption({names:c,arity:a.tolerateBoolean?0:n,hidden:a.hidden,description:a.description,required:a.required})},transformer(p,h,E,C){let S,P=s;typeof a.env<"u"&&C.env[a.env]&&(S=a.env,P=C.env[a.env]);for(let{name:I,value:R}of E.options)f.has(I)&&(S=I,P=R);return typeof P=="string"?Od(S??h,P,a.validator):P}})}function SYe(t={}){let{required:e=!0}=t;return ya({definition(r,s){var a;r.addPositional({name:(a=t.name)!==null&&a!==void 0?a:s,required:t.required})},transformer(r,s,a){var n;for(let c=0;c{Mx();Cp()});var ge={};Vt(ge,{Array:()=>nne,Boolean:()=>sne,Counter:()=>ane,Proxy:()=>Hx,Rest:()=>cne,String:()=>fne,applyValidator:()=>Od,cleanValidationError:()=>Qx,formatError:()=>z2,isOptionSymbol:()=>K2,makeCommandOption:()=>ya,rerouteArguments:()=>Gf});var pne=Xe(()=>{Cp();w_();ine();one();lne();une();Ane()});var oB={};Vt(oB,{Builtins:()=>B_,Cli:()=>Ca,Command:()=>ot,Option:()=>ge,UsageError:()=>nt,formatMarkdownish:()=>Ho,run:()=>Kre,runExit:()=>Jre});var Yt=Xe(()=>{kx();f_();a0();Xre();rne();pne()});var hne=_((VTt,DYe)=>{DYe.exports={name:"dotenv",version:"16.3.1",description:"Loads environment variables from .env file",main:"lib/main.js",types:"lib/main.d.ts",exports:{".":{types:"./lib/main.d.ts",require:"./lib/main.js",default:"./lib/main.js"},"./config":"./config.js","./config.js":"./config.js","./lib/env-options":"./lib/env-options.js","./lib/env-options.js":"./lib/env-options.js","./lib/cli-options":"./lib/cli-options.js","./lib/cli-options.js":"./lib/cli-options.js","./package.json":"./package.json"},scripts:{"dts-check":"tsc --project tests/types/tsconfig.json",lint:"standard","lint-readme":"standard-markdown",pretest:"npm run lint && npm run dts-check",test:"tap tests/*.js --100 -Rspec",prerelease:"npm test",release:"standard-version"},repository:{type:"git",url:"git://github.com/motdotla/dotenv.git"},funding:"https://github.com/motdotla/dotenv?sponsor=1",keywords:["dotenv","env",".env","environment","variables","config","settings"],readmeFilename:"README.md",license:"BSD-2-Clause",devDependencies:{"@definitelytyped/dtslint":"^0.0.133","@types/node":"^18.11.3",decache:"^4.6.1",sinon:"^14.0.1",standard:"^17.0.0","standard-markdown":"^7.1.0","standard-version":"^9.5.0",tap:"^16.3.0",tar:"^6.1.11",typescript:"^4.8.4"},engines:{node:">=12"},browser:{fs:!1}}});var yne=_((JTt,wp)=>{var gne=Ie("fs"),S_=Ie("path"),bYe=Ie("os"),PYe=Ie("crypto"),xYe=hne(),D_=xYe.version,kYe=/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;function QYe(t){let e={},r=t.toString();r=r.replace(/\r\n?/mg,` `);let s;for(;(s=kYe.exec(r))!=null;){let a=s[1],n=s[2]||"";n=n.trim();let c=n[0];n=n.replace(/^(['"`])([\s\S]*)\1$/mg,"$2"),c==='"'&&(n=n.replace(/\\n/g,` `),n=n.replace(/\\r/g,"\r")),e[a]=n}return e}function TYe(t){let e=mne(t),r=js.configDotenv({path:e});if(!r.parsed)throw new Error(`MISSING_DATA: Cannot parse ${e} for an unknown reason`);let s=dne(t).split(","),a=s.length,n;for(let c=0;c=a)throw f}return js.parse(n)}function RYe(t){console.log(`[dotenv@${D_}][INFO] ${t}`)}function FYe(t){console.log(`[dotenv@${D_}][WARN] ${t}`)}function v_(t){console.log(`[dotenv@${D_}][DEBUG] ${t}`)}function dne(t){return t&&t.DOTENV_KEY&&t.DOTENV_KEY.length>0?t.DOTENV_KEY:process.env.DOTENV_KEY&&process.env.DOTENV_KEY.length>0?process.env.DOTENV_KEY:""}function NYe(t,e){let r;try{r=new URL(e)}catch(f){throw f.code==="ERR_INVALID_URL"?new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=development"):f}let s=r.password;if(!s)throw new Error("INVALID_DOTENV_KEY: Missing key part");let a=r.searchParams.get("environment");if(!a)throw new Error("INVALID_DOTENV_KEY: Missing environment part");let n=`DOTENV_VAULT_${a.toUpperCase()}`,c=t.parsed[n];if(!c)throw new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${n} in your .env.vault file.`);return{ciphertext:c,key:s}}function mne(t){let e=S_.resolve(process.cwd(),".env");return t&&t.path&&t.path.length>0&&(e=t.path),e.endsWith(".vault")?e:`${e}.vault`}function OYe(t){return t[0]==="~"?S_.join(bYe.homedir(),t.slice(1)):t}function LYe(t){RYe("Loading env from encrypted .env.vault");let e=js._parseVault(t),r=process.env;return t&&t.processEnv!=null&&(r=t.processEnv),js.populate(r,e,t),{parsed:e}}function MYe(t){let e=S_.resolve(process.cwd(),".env"),r="utf8",s=!!(t&&t.debug);t&&(t.path!=null&&(e=OYe(t.path)),t.encoding!=null&&(r=t.encoding));try{let a=js.parse(gne.readFileSync(e,{encoding:r})),n=process.env;return t&&t.processEnv!=null&&(n=t.processEnv),js.populate(n,a,t),{parsed:a}}catch(a){return s&&v_(`Failed to load ${e} ${a.message}`),{error:a}}}function UYe(t){let e=mne(t);return dne(t).length===0?js.configDotenv(t):gne.existsSync(e)?js._configVault(t):(FYe(`You set DOTENV_KEY but you are missing a .env.vault file at ${e}. Did you forget to build it?`),js.configDotenv(t))}function _Ye(t,e){let r=Buffer.from(e.slice(-64),"hex"),s=Buffer.from(t,"base64"),a=s.slice(0,12),n=s.slice(-16);s=s.slice(12,-16);try{let c=PYe.createDecipheriv("aes-256-gcm",r,a);return c.setAuthTag(n),`${c.update(s)}${c.final()}`}catch(c){let f=c instanceof RangeError,p=c.message==="Invalid key length",h=c.message==="Unsupported state or unable to authenticate data";if(f||p){let E="INVALID_DOTENV_KEY: It must be 64 characters long (or more)";throw new Error(E)}else if(h){let E="DECRYPTION_FAILED: Please check your DOTENV_KEY";throw new Error(E)}else throw console.error("Error: ",c.code),console.error("Error: ",c.message),c}}function HYe(t,e,r={}){let s=!!(r&&r.debug),a=!!(r&&r.override);if(typeof e!="object")throw new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");for(let n of Object.keys(e))Object.prototype.hasOwnProperty.call(t,n)?(a===!0&&(t[n]=e[n]),s&&v_(a===!0?`"${n}" is already defined and WAS overwritten`:`"${n}" is already defined and was NOT overwritten`)):t[n]=e[n]}var js={configDotenv:MYe,_configVault:LYe,_parseVault:TYe,config:UYe,decrypt:_Ye,parse:QYe,populate:HYe};wp.exports.configDotenv=js.configDotenv;wp.exports._configVault=js._configVault;wp.exports._parseVault=js._parseVault;wp.exports.config=js.config;wp.exports.decrypt=js.decrypt;wp.exports.parse=js.parse;wp.exports.populate=js.populate;wp.exports=js});var Ine=_((KTt,Ene)=>{"use strict";Ene.exports=(t,...e)=>new Promise(r=>{r(t(...e))})});var Ld=_((zTt,b_)=>{"use strict";var jYe=Ine(),Cne=t=>{if(t<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let e=[],r=0,s=()=>{r--,e.length>0&&e.shift()()},a=(f,p,...h)=>{r++;let E=jYe(f,...h);p(E),E.then(s,s)},n=(f,p,...h)=>{rnew Promise(h=>n(f,h,...p));return Object.defineProperties(c,{activeCount:{get:()=>r},pendingCount:{get:()=>e.length}}),c};b_.exports=Cne;b_.exports.default=Cne});function Yf(t){return`YN${t.toString(10).padStart(4,"0")}`}function jx(t){let e=Number(t.slice(2));if(typeof Br[e]>"u")throw new Error(`Unknown message name: "${t}"`);return e}var Br,Gx=Xe(()=>{Br=(Me=>(Me[Me.UNNAMED=0]="UNNAMED",Me[Me.EXCEPTION=1]="EXCEPTION",Me[Me.MISSING_PEER_DEPENDENCY=2]="MISSING_PEER_DEPENDENCY",Me[Me.CYCLIC_DEPENDENCIES=3]="CYCLIC_DEPENDENCIES",Me[Me.DISABLED_BUILD_SCRIPTS=4]="DISABLED_BUILD_SCRIPTS",Me[Me.BUILD_DISABLED=5]="BUILD_DISABLED",Me[Me.SOFT_LINK_BUILD=6]="SOFT_LINK_BUILD",Me[Me.MUST_BUILD=7]="MUST_BUILD",Me[Me.MUST_REBUILD=8]="MUST_REBUILD",Me[Me.BUILD_FAILED=9]="BUILD_FAILED",Me[Me.RESOLVER_NOT_FOUND=10]="RESOLVER_NOT_FOUND",Me[Me.FETCHER_NOT_FOUND=11]="FETCHER_NOT_FOUND",Me[Me.LINKER_NOT_FOUND=12]="LINKER_NOT_FOUND",Me[Me.FETCH_NOT_CACHED=13]="FETCH_NOT_CACHED",Me[Me.YARN_IMPORT_FAILED=14]="YARN_IMPORT_FAILED",Me[Me.REMOTE_INVALID=15]="REMOTE_INVALID",Me[Me.REMOTE_NOT_FOUND=16]="REMOTE_NOT_FOUND",Me[Me.RESOLUTION_PACK=17]="RESOLUTION_PACK",Me[Me.CACHE_CHECKSUM_MISMATCH=18]="CACHE_CHECKSUM_MISMATCH",Me[Me.UNUSED_CACHE_ENTRY=19]="UNUSED_CACHE_ENTRY",Me[Me.MISSING_LOCKFILE_ENTRY=20]="MISSING_LOCKFILE_ENTRY",Me[Me.WORKSPACE_NOT_FOUND=21]="WORKSPACE_NOT_FOUND",Me[Me.TOO_MANY_MATCHING_WORKSPACES=22]="TOO_MANY_MATCHING_WORKSPACES",Me[Me.CONSTRAINTS_MISSING_DEPENDENCY=23]="CONSTRAINTS_MISSING_DEPENDENCY",Me[Me.CONSTRAINTS_INCOMPATIBLE_DEPENDENCY=24]="CONSTRAINTS_INCOMPATIBLE_DEPENDENCY",Me[Me.CONSTRAINTS_EXTRANEOUS_DEPENDENCY=25]="CONSTRAINTS_EXTRANEOUS_DEPENDENCY",Me[Me.CONSTRAINTS_INVALID_DEPENDENCY=26]="CONSTRAINTS_INVALID_DEPENDENCY",Me[Me.CANT_SUGGEST_RESOLUTIONS=27]="CANT_SUGGEST_RESOLUTIONS",Me[Me.FROZEN_LOCKFILE_EXCEPTION=28]="FROZEN_LOCKFILE_EXCEPTION",Me[Me.CROSS_DRIVE_VIRTUAL_LOCAL=29]="CROSS_DRIVE_VIRTUAL_LOCAL",Me[Me.FETCH_FAILED=30]="FETCH_FAILED",Me[Me.DANGEROUS_NODE_MODULES=31]="DANGEROUS_NODE_MODULES",Me[Me.NODE_GYP_INJECTED=32]="NODE_GYP_INJECTED",Me[Me.AUTHENTICATION_NOT_FOUND=33]="AUTHENTICATION_NOT_FOUND",Me[Me.INVALID_CONFIGURATION_KEY=34]="INVALID_CONFIGURATION_KEY",Me[Me.NETWORK_ERROR=35]="NETWORK_ERROR",Me[Me.LIFECYCLE_SCRIPT=36]="LIFECYCLE_SCRIPT",Me[Me.CONSTRAINTS_MISSING_FIELD=37]="CONSTRAINTS_MISSING_FIELD",Me[Me.CONSTRAINTS_INCOMPATIBLE_FIELD=38]="CONSTRAINTS_INCOMPATIBLE_FIELD",Me[Me.CONSTRAINTS_EXTRANEOUS_FIELD=39]="CONSTRAINTS_EXTRANEOUS_FIELD",Me[Me.CONSTRAINTS_INVALID_FIELD=40]="CONSTRAINTS_INVALID_FIELD",Me[Me.AUTHENTICATION_INVALID=41]="AUTHENTICATION_INVALID",Me[Me.PROLOG_UNKNOWN_ERROR=42]="PROLOG_UNKNOWN_ERROR",Me[Me.PROLOG_SYNTAX_ERROR=43]="PROLOG_SYNTAX_ERROR",Me[Me.PROLOG_EXISTENCE_ERROR=44]="PROLOG_EXISTENCE_ERROR",Me[Me.STACK_OVERFLOW_RESOLUTION=45]="STACK_OVERFLOW_RESOLUTION",Me[Me.AUTOMERGE_FAILED_TO_PARSE=46]="AUTOMERGE_FAILED_TO_PARSE",Me[Me.AUTOMERGE_IMMUTABLE=47]="AUTOMERGE_IMMUTABLE",Me[Me.AUTOMERGE_SUCCESS=48]="AUTOMERGE_SUCCESS",Me[Me.AUTOMERGE_REQUIRED=49]="AUTOMERGE_REQUIRED",Me[Me.DEPRECATED_CLI_SETTINGS=50]="DEPRECATED_CLI_SETTINGS",Me[Me.PLUGIN_NAME_NOT_FOUND=51]="PLUGIN_NAME_NOT_FOUND",Me[Me.INVALID_PLUGIN_REFERENCE=52]="INVALID_PLUGIN_REFERENCE",Me[Me.CONSTRAINTS_AMBIGUITY=53]="CONSTRAINTS_AMBIGUITY",Me[Me.CACHE_OUTSIDE_PROJECT=54]="CACHE_OUTSIDE_PROJECT",Me[Me.IMMUTABLE_INSTALL=55]="IMMUTABLE_INSTALL",Me[Me.IMMUTABLE_CACHE=56]="IMMUTABLE_CACHE",Me[Me.INVALID_MANIFEST=57]="INVALID_MANIFEST",Me[Me.PACKAGE_PREPARATION_FAILED=58]="PACKAGE_PREPARATION_FAILED",Me[Me.INVALID_RANGE_PEER_DEPENDENCY=59]="INVALID_RANGE_PEER_DEPENDENCY",Me[Me.INCOMPATIBLE_PEER_DEPENDENCY=60]="INCOMPATIBLE_PEER_DEPENDENCY",Me[Me.DEPRECATED_PACKAGE=61]="DEPRECATED_PACKAGE",Me[Me.INCOMPATIBLE_OS=62]="INCOMPATIBLE_OS",Me[Me.INCOMPATIBLE_CPU=63]="INCOMPATIBLE_CPU",Me[Me.FROZEN_ARTIFACT_EXCEPTION=64]="FROZEN_ARTIFACT_EXCEPTION",Me[Me.TELEMETRY_NOTICE=65]="TELEMETRY_NOTICE",Me[Me.PATCH_HUNK_FAILED=66]="PATCH_HUNK_FAILED",Me[Me.INVALID_CONFIGURATION_VALUE=67]="INVALID_CONFIGURATION_VALUE",Me[Me.UNUSED_PACKAGE_EXTENSION=68]="UNUSED_PACKAGE_EXTENSION",Me[Me.REDUNDANT_PACKAGE_EXTENSION=69]="REDUNDANT_PACKAGE_EXTENSION",Me[Me.AUTO_NM_SUCCESS=70]="AUTO_NM_SUCCESS",Me[Me.NM_CANT_INSTALL_EXTERNAL_SOFT_LINK=71]="NM_CANT_INSTALL_EXTERNAL_SOFT_LINK",Me[Me.NM_PRESERVE_SYMLINKS_REQUIRED=72]="NM_PRESERVE_SYMLINKS_REQUIRED",Me[Me.UPDATE_LOCKFILE_ONLY_SKIP_LINK=73]="UPDATE_LOCKFILE_ONLY_SKIP_LINK",Me[Me.NM_HARDLINKS_MODE_DOWNGRADED=74]="NM_HARDLINKS_MODE_DOWNGRADED",Me[Me.PROLOG_INSTANTIATION_ERROR=75]="PROLOG_INSTANTIATION_ERROR",Me[Me.INCOMPATIBLE_ARCHITECTURE=76]="INCOMPATIBLE_ARCHITECTURE",Me[Me.GHOST_ARCHITECTURE=77]="GHOST_ARCHITECTURE",Me[Me.RESOLUTION_MISMATCH=78]="RESOLUTION_MISMATCH",Me[Me.PROLOG_LIMIT_EXCEEDED=79]="PROLOG_LIMIT_EXCEEDED",Me[Me.NETWORK_DISABLED=80]="NETWORK_DISABLED",Me[Me.NETWORK_UNSAFE_HTTP=81]="NETWORK_UNSAFE_HTTP",Me[Me.RESOLUTION_FAILED=82]="RESOLUTION_FAILED",Me[Me.AUTOMERGE_GIT_ERROR=83]="AUTOMERGE_GIT_ERROR",Me[Me.CONSTRAINTS_CHECK_FAILED=84]="CONSTRAINTS_CHECK_FAILED",Me[Me.UPDATED_RESOLUTION_RECORD=85]="UPDATED_RESOLUTION_RECORD",Me[Me.EXPLAIN_PEER_DEPENDENCIES_CTA=86]="EXPLAIN_PEER_DEPENDENCIES_CTA",Me[Me.MIGRATION_SUCCESS=87]="MIGRATION_SUCCESS",Me[Me.VERSION_NOTICE=88]="VERSION_NOTICE",Me[Me.TIPS_NOTICE=89]="TIPS_NOTICE",Me[Me.OFFLINE_MODE_ENABLED=90]="OFFLINE_MODE_ENABLED",Me[Me.INVALID_PROVENANCE_ENVIRONMENT=91]="INVALID_PROVENANCE_ENVIRONMENT",Me))(Br||{})});var aB=_((ZTt,wne)=>{var GYe="2.0.0",qYe=Number.MAX_SAFE_INTEGER||9007199254740991,WYe=16,YYe=250,VYe=["major","premajor","minor","preminor","patch","prepatch","prerelease"];wne.exports={MAX_LENGTH:256,MAX_SAFE_COMPONENT_LENGTH:WYe,MAX_SAFE_BUILD_LENGTH:YYe,MAX_SAFE_INTEGER:qYe,RELEASE_TYPES:VYe,SEMVER_SPEC_VERSION:GYe,FLAG_INCLUDE_PRERELEASE:1,FLAG_LOOSE:2}});var lB=_(($Tt,Bne)=>{var JYe=typeof process=="object"&&process.env&&process.env.NODE_DEBUG&&/\bsemver\b/i.test(process.env.NODE_DEBUG)?(...t)=>console.error("SEMVER",...t):()=>{};Bne.exports=JYe});var vE=_((Bp,vne)=>{var{MAX_SAFE_COMPONENT_LENGTH:P_,MAX_SAFE_BUILD_LENGTH:KYe,MAX_LENGTH:zYe}=aB(),XYe=lB();Bp=vne.exports={};var ZYe=Bp.re=[],$Ye=Bp.safeRe=[],rr=Bp.src=[],nr=Bp.t={},eVe=0,x_="[a-zA-Z0-9-]",tVe=[["\\s",1],["\\d",zYe],[x_,KYe]],rVe=t=>{for(let[e,r]of tVe)t=t.split(`${e}*`).join(`${e}{0,${r}}`).split(`${e}+`).join(`${e}{1,${r}}`);return t},Jr=(t,e,r)=>{let s=rVe(e),a=eVe++;XYe(t,a,e),nr[t]=a,rr[a]=e,ZYe[a]=new RegExp(e,r?"g":void 0),$Ye[a]=new RegExp(s,r?"g":void 0)};Jr("NUMERICIDENTIFIER","0|[1-9]\\d*");Jr("NUMERICIDENTIFIERLOOSE","\\d+");Jr("NONNUMERICIDENTIFIER",`\\d*[a-zA-Z-]${x_}*`);Jr("MAINVERSION",`(${rr[nr.NUMERICIDENTIFIER]})\\.(${rr[nr.NUMERICIDENTIFIER]})\\.(${rr[nr.NUMERICIDENTIFIER]})`);Jr("MAINVERSIONLOOSE",`(${rr[nr.NUMERICIDENTIFIERLOOSE]})\\.(${rr[nr.NUMERICIDENTIFIERLOOSE]})\\.(${rr[nr.NUMERICIDENTIFIERLOOSE]})`);Jr("PRERELEASEIDENTIFIER",`(?:${rr[nr.NUMERICIDENTIFIER]}|${rr[nr.NONNUMERICIDENTIFIER]})`);Jr("PRERELEASEIDENTIFIERLOOSE",`(?:${rr[nr.NUMERICIDENTIFIERLOOSE]}|${rr[nr.NONNUMERICIDENTIFIER]})`);Jr("PRERELEASE",`(?:-(${rr[nr.PRERELEASEIDENTIFIER]}(?:\\.${rr[nr.PRERELEASEIDENTIFIER]})*))`);Jr("PRERELEASELOOSE",`(?:-?(${rr[nr.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${rr[nr.PRERELEASEIDENTIFIERLOOSE]})*))`);Jr("BUILDIDENTIFIER",`${x_}+`);Jr("BUILD",`(?:\\+(${rr[nr.BUILDIDENTIFIER]}(?:\\.${rr[nr.BUILDIDENTIFIER]})*))`);Jr("FULLPLAIN",`v?${rr[nr.MAINVERSION]}${rr[nr.PRERELEASE]}?${rr[nr.BUILD]}?`);Jr("FULL",`^${rr[nr.FULLPLAIN]}$`);Jr("LOOSEPLAIN",`[v=\\s]*${rr[nr.MAINVERSIONLOOSE]}${rr[nr.PRERELEASELOOSE]}?${rr[nr.BUILD]}?`);Jr("LOOSE",`^${rr[nr.LOOSEPLAIN]}$`);Jr("GTLT","((?:<|>)?=?)");Jr("XRANGEIDENTIFIERLOOSE",`${rr[nr.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`);Jr("XRANGEIDENTIFIER",`${rr[nr.NUMERICIDENTIFIER]}|x|X|\\*`);Jr("XRANGEPLAIN",`[v=\\s]*(${rr[nr.XRANGEIDENTIFIER]})(?:\\.(${rr[nr.XRANGEIDENTIFIER]})(?:\\.(${rr[nr.XRANGEIDENTIFIER]})(?:${rr[nr.PRERELEASE]})?${rr[nr.BUILD]}?)?)?`);Jr("XRANGEPLAINLOOSE",`[v=\\s]*(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:\\.(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:\\.(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:${rr[nr.PRERELEASELOOSE]})?${rr[nr.BUILD]}?)?)?`);Jr("XRANGE",`^${rr[nr.GTLT]}\\s*${rr[nr.XRANGEPLAIN]}$`);Jr("XRANGELOOSE",`^${rr[nr.GTLT]}\\s*${rr[nr.XRANGEPLAINLOOSE]}$`);Jr("COERCEPLAIN",`(^|[^\\d])(\\d{1,${P_}})(?:\\.(\\d{1,${P_}}))?(?:\\.(\\d{1,${P_}}))?`);Jr("COERCE",`${rr[nr.COERCEPLAIN]}(?:$|[^\\d])`);Jr("COERCEFULL",rr[nr.COERCEPLAIN]+`(?:${rr[nr.PRERELEASE]})?(?:${rr[nr.BUILD]})?(?:$|[^\\d])`);Jr("COERCERTL",rr[nr.COERCE],!0);Jr("COERCERTLFULL",rr[nr.COERCEFULL],!0);Jr("LONETILDE","(?:~>?)");Jr("TILDETRIM",`(\\s*)${rr[nr.LONETILDE]}\\s+`,!0);Bp.tildeTrimReplace="$1~";Jr("TILDE",`^${rr[nr.LONETILDE]}${rr[nr.XRANGEPLAIN]}$`);Jr("TILDELOOSE",`^${rr[nr.LONETILDE]}${rr[nr.XRANGEPLAINLOOSE]}$`);Jr("LONECARET","(?:\\^)");Jr("CARETTRIM",`(\\s*)${rr[nr.LONECARET]}\\s+`,!0);Bp.caretTrimReplace="$1^";Jr("CARET",`^${rr[nr.LONECARET]}${rr[nr.XRANGEPLAIN]}$`);Jr("CARETLOOSE",`^${rr[nr.LONECARET]}${rr[nr.XRANGEPLAINLOOSE]}$`);Jr("COMPARATORLOOSE",`^${rr[nr.GTLT]}\\s*(${rr[nr.LOOSEPLAIN]})$|^$`);Jr("COMPARATOR",`^${rr[nr.GTLT]}\\s*(${rr[nr.FULLPLAIN]})$|^$`);Jr("COMPARATORTRIM",`(\\s*)${rr[nr.GTLT]}\\s*(${rr[nr.LOOSEPLAIN]}|${rr[nr.XRANGEPLAIN]})`,!0);Bp.comparatorTrimReplace="$1$2$3";Jr("HYPHENRANGE",`^\\s*(${rr[nr.XRANGEPLAIN]})\\s+-\\s+(${rr[nr.XRANGEPLAIN]})\\s*$`);Jr("HYPHENRANGELOOSE",`^\\s*(${rr[nr.XRANGEPLAINLOOSE]})\\s+-\\s+(${rr[nr.XRANGEPLAINLOOSE]})\\s*$`);Jr("STAR","(<|>)?=?\\s*\\*");Jr("GTE0","^\\s*>=\\s*0\\.0\\.0\\s*$");Jr("GTE0PRE","^\\s*>=\\s*0\\.0\\.0-0\\s*$")});var qx=_((eRt,Sne)=>{var nVe=Object.freeze({loose:!0}),iVe=Object.freeze({}),sVe=t=>t?typeof t!="object"?nVe:t:iVe;Sne.exports=sVe});var k_=_((tRt,Pne)=>{var Dne=/^[0-9]+$/,bne=(t,e)=>{let r=Dne.test(t),s=Dne.test(e);return r&&s&&(t=+t,e=+e),t===e?0:r&&!s?-1:s&&!r?1:tbne(e,t);Pne.exports={compareIdentifiers:bne,rcompareIdentifiers:oVe}});var jo=_((rRt,Tne)=>{var Wx=lB(),{MAX_LENGTH:xne,MAX_SAFE_INTEGER:Yx}=aB(),{safeRe:kne,t:Qne}=vE(),aVe=qx(),{compareIdentifiers:SE}=k_(),Q_=class t{constructor(e,r){if(r=aVe(r),e instanceof t){if(e.loose===!!r.loose&&e.includePrerelease===!!r.includePrerelease)return e;e=e.version}else if(typeof e!="string")throw new TypeError(`Invalid version. Must be a string. Got type "${typeof e}".`);if(e.length>xne)throw new TypeError(`version is longer than ${xne} characters`);Wx("SemVer",e,r),this.options=r,this.loose=!!r.loose,this.includePrerelease=!!r.includePrerelease;let s=e.trim().match(r.loose?kne[Qne.LOOSE]:kne[Qne.FULL]);if(!s)throw new TypeError(`Invalid Version: ${e}`);if(this.raw=e,this.major=+s[1],this.minor=+s[2],this.patch=+s[3],this.major>Yx||this.major<0)throw new TypeError("Invalid major version");if(this.minor>Yx||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>Yx||this.patch<0)throw new TypeError("Invalid patch version");s[4]?this.prerelease=s[4].split(".").map(a=>{if(/^[0-9]+$/.test(a)){let n=+a;if(n>=0&&n=0;)typeof this.prerelease[n]=="number"&&(this.prerelease[n]++,n=-2);if(n===-1){if(r===this.prerelease.join(".")&&s===!1)throw new Error("invalid increment argument: identifier already exists");this.prerelease.push(a)}}if(r){let n=[r,a];s===!1&&(n=[r]),SE(this.prerelease[0],r)===0?isNaN(this.prerelease[1])&&(this.prerelease=n):this.prerelease=n}break}default:throw new Error(`invalid increment argument: ${e}`)}return this.raw=this.format(),this.build.length&&(this.raw+=`+${this.build.join(".")}`),this}};Tne.exports=Q_});var Md=_((nRt,Fne)=>{var Rne=jo(),lVe=(t,e,r=!1)=>{if(t instanceof Rne)return t;try{return new Rne(t,e)}catch(s){if(!r)return null;throw s}};Fne.exports=lVe});var One=_((iRt,Nne)=>{var cVe=Md(),uVe=(t,e)=>{let r=cVe(t,e);return r?r.version:null};Nne.exports=uVe});var Mne=_((sRt,Lne)=>{var fVe=Md(),AVe=(t,e)=>{let r=fVe(t.trim().replace(/^[=v]+/,""),e);return r?r.version:null};Lne.exports=AVe});var Hne=_((oRt,_ne)=>{var Une=jo(),pVe=(t,e,r,s,a)=>{typeof r=="string"&&(a=s,s=r,r=void 0);try{return new Une(t instanceof Une?t.version:t,r).inc(e,s,a).version}catch{return null}};_ne.exports=pVe});var qne=_((aRt,Gne)=>{var jne=Md(),hVe=(t,e)=>{let r=jne(t,null,!0),s=jne(e,null,!0),a=r.compare(s);if(a===0)return null;let n=a>0,c=n?r:s,f=n?s:r,p=!!c.prerelease.length;if(!!f.prerelease.length&&!p)return!f.patch&&!f.minor?"major":c.patch?"patch":c.minor?"minor":"major";let E=p?"pre":"";return r.major!==s.major?E+"major":r.minor!==s.minor?E+"minor":r.patch!==s.patch?E+"patch":"prerelease"};Gne.exports=hVe});var Yne=_((lRt,Wne)=>{var gVe=jo(),dVe=(t,e)=>new gVe(t,e).major;Wne.exports=dVe});var Jne=_((cRt,Vne)=>{var mVe=jo(),yVe=(t,e)=>new mVe(t,e).minor;Vne.exports=yVe});var zne=_((uRt,Kne)=>{var EVe=jo(),IVe=(t,e)=>new EVe(t,e).patch;Kne.exports=IVe});var Zne=_((fRt,Xne)=>{var CVe=Md(),wVe=(t,e)=>{let r=CVe(t,e);return r&&r.prerelease.length?r.prerelease:null};Xne.exports=wVe});var Bc=_((ARt,eie)=>{var $ne=jo(),BVe=(t,e,r)=>new $ne(t,r).compare(new $ne(e,r));eie.exports=BVe});var rie=_((pRt,tie)=>{var vVe=Bc(),SVe=(t,e,r)=>vVe(e,t,r);tie.exports=SVe});var iie=_((hRt,nie)=>{var DVe=Bc(),bVe=(t,e)=>DVe(t,e,!0);nie.exports=bVe});var Vx=_((gRt,oie)=>{var sie=jo(),PVe=(t,e,r)=>{let s=new sie(t,r),a=new sie(e,r);return s.compare(a)||s.compareBuild(a)};oie.exports=PVe});var lie=_((dRt,aie)=>{var xVe=Vx(),kVe=(t,e)=>t.sort((r,s)=>xVe(r,s,e));aie.exports=kVe});var uie=_((mRt,cie)=>{var QVe=Vx(),TVe=(t,e)=>t.sort((r,s)=>QVe(s,r,e));cie.exports=TVe});var cB=_((yRt,fie)=>{var RVe=Bc(),FVe=(t,e,r)=>RVe(t,e,r)>0;fie.exports=FVe});var Jx=_((ERt,Aie)=>{var NVe=Bc(),OVe=(t,e,r)=>NVe(t,e,r)<0;Aie.exports=OVe});var T_=_((IRt,pie)=>{var LVe=Bc(),MVe=(t,e,r)=>LVe(t,e,r)===0;pie.exports=MVe});var R_=_((CRt,hie)=>{var UVe=Bc(),_Ve=(t,e,r)=>UVe(t,e,r)!==0;hie.exports=_Ve});var Kx=_((wRt,gie)=>{var HVe=Bc(),jVe=(t,e,r)=>HVe(t,e,r)>=0;gie.exports=jVe});var zx=_((BRt,die)=>{var GVe=Bc(),qVe=(t,e,r)=>GVe(t,e,r)<=0;die.exports=qVe});var F_=_((vRt,mie)=>{var WVe=T_(),YVe=R_(),VVe=cB(),JVe=Kx(),KVe=Jx(),zVe=zx(),XVe=(t,e,r,s)=>{switch(e){case"===":return typeof t=="object"&&(t=t.version),typeof r=="object"&&(r=r.version),t===r;case"!==":return typeof t=="object"&&(t=t.version),typeof r=="object"&&(r=r.version),t!==r;case"":case"=":case"==":return WVe(t,r,s);case"!=":return YVe(t,r,s);case">":return VVe(t,r,s);case">=":return JVe(t,r,s);case"<":return KVe(t,r,s);case"<=":return zVe(t,r,s);default:throw new TypeError(`Invalid operator: ${e}`)}};mie.exports=XVe});var Eie=_((SRt,yie)=>{var ZVe=jo(),$Ve=Md(),{safeRe:Xx,t:Zx}=vE(),e7e=(t,e)=>{if(t instanceof ZVe)return t;if(typeof t=="number"&&(t=String(t)),typeof t!="string")return null;e=e||{};let r=null;if(!e.rtl)r=t.match(e.includePrerelease?Xx[Zx.COERCEFULL]:Xx[Zx.COERCE]);else{let p=e.includePrerelease?Xx[Zx.COERCERTLFULL]:Xx[Zx.COERCERTL],h;for(;(h=p.exec(t))&&(!r||r.index+r[0].length!==t.length);)(!r||h.index+h[0].length!==r.index+r[0].length)&&(r=h),p.lastIndex=h.index+h[1].length+h[2].length;p.lastIndex=-1}if(r===null)return null;let s=r[2],a=r[3]||"0",n=r[4]||"0",c=e.includePrerelease&&r[5]?`-${r[5]}`:"",f=e.includePrerelease&&r[6]?`+${r[6]}`:"";return $Ve(`${s}.${a}.${n}${c}${f}`,e)};yie.exports=e7e});var Cie=_((DRt,Iie)=>{"use strict";Iie.exports=function(t){t.prototype[Symbol.iterator]=function*(){for(let e=this.head;e;e=e.next)yield e.value}}});var $x=_((bRt,wie)=>{"use strict";wie.exports=Fn;Fn.Node=Ud;Fn.create=Fn;function Fn(t){var e=this;if(e instanceof Fn||(e=new Fn),e.tail=null,e.head=null,e.length=0,t&&typeof t.forEach=="function")t.forEach(function(a){e.push(a)});else if(arguments.length>0)for(var r=0,s=arguments.length;r1)r=e;else if(this.head)s=this.head.next,r=this.head.value;else throw new TypeError("Reduce of empty list with no initial value");for(var a=0;s!==null;a++)r=t(r,s.value,a),s=s.next;return r};Fn.prototype.reduceReverse=function(t,e){var r,s=this.tail;if(arguments.length>1)r=e;else if(this.tail)s=this.tail.prev,r=this.tail.value;else throw new TypeError("Reduce of empty list with no initial value");for(var a=this.length-1;s!==null;a--)r=t(r,s.value,a),s=s.prev;return r};Fn.prototype.toArray=function(){for(var t=new Array(this.length),e=0,r=this.head;r!==null;e++)t[e]=r.value,r=r.next;return t};Fn.prototype.toArrayReverse=function(){for(var t=new Array(this.length),e=0,r=this.tail;r!==null;e++)t[e]=r.value,r=r.prev;return t};Fn.prototype.slice=function(t,e){e=e||this.length,e<0&&(e+=this.length),t=t||0,t<0&&(t+=this.length);var r=new Fn;if(ethis.length&&(e=this.length);for(var s=0,a=this.head;a!==null&&sthis.length&&(e=this.length);for(var s=this.length,a=this.tail;a!==null&&s>e;s--)a=a.prev;for(;a!==null&&s>t;s--,a=a.prev)r.push(a.value);return r};Fn.prototype.splice=function(t,e,...r){t>this.length&&(t=this.length-1),t<0&&(t=this.length+t);for(var s=0,a=this.head;a!==null&&s{"use strict";var i7e=$x(),_d=Symbol("max"),Sp=Symbol("length"),DE=Symbol("lengthCalculator"),fB=Symbol("allowStale"),Hd=Symbol("maxAge"),vp=Symbol("dispose"),Bie=Symbol("noDisposeOnSet"),Gs=Symbol("lruList"),Lu=Symbol("cache"),Sie=Symbol("updateAgeOnGet"),N_=()=>1,L_=class{constructor(e){if(typeof e=="number"&&(e={max:e}),e||(e={}),e.max&&(typeof e.max!="number"||e.max<0))throw new TypeError("max must be a non-negative number");let r=this[_d]=e.max||1/0,s=e.length||N_;if(this[DE]=typeof s!="function"?N_:s,this[fB]=e.stale||!1,e.maxAge&&typeof e.maxAge!="number")throw new TypeError("maxAge must be a number");this[Hd]=e.maxAge||0,this[vp]=e.dispose,this[Bie]=e.noDisposeOnSet||!1,this[Sie]=e.updateAgeOnGet||!1,this.reset()}set max(e){if(typeof e!="number"||e<0)throw new TypeError("max must be a non-negative number");this[_d]=e||1/0,uB(this)}get max(){return this[_d]}set allowStale(e){this[fB]=!!e}get allowStale(){return this[fB]}set maxAge(e){if(typeof e!="number")throw new TypeError("maxAge must be a non-negative number");this[Hd]=e,uB(this)}get maxAge(){return this[Hd]}set lengthCalculator(e){typeof e!="function"&&(e=N_),e!==this[DE]&&(this[DE]=e,this[Sp]=0,this[Gs].forEach(r=>{r.length=this[DE](r.value,r.key),this[Sp]+=r.length})),uB(this)}get lengthCalculator(){return this[DE]}get length(){return this[Sp]}get itemCount(){return this[Gs].length}rforEach(e,r){r=r||this;for(let s=this[Gs].tail;s!==null;){let a=s.prev;vie(this,e,s,r),s=a}}forEach(e,r){r=r||this;for(let s=this[Gs].head;s!==null;){let a=s.next;vie(this,e,s,r),s=a}}keys(){return this[Gs].toArray().map(e=>e.key)}values(){return this[Gs].toArray().map(e=>e.value)}reset(){this[vp]&&this[Gs]&&this[Gs].length&&this[Gs].forEach(e=>this[vp](e.key,e.value)),this[Lu]=new Map,this[Gs]=new i7e,this[Sp]=0}dump(){return this[Gs].map(e=>ek(this,e)?!1:{k:e.key,v:e.value,e:e.now+(e.maxAge||0)}).toArray().filter(e=>e)}dumpLru(){return this[Gs]}set(e,r,s){if(s=s||this[Hd],s&&typeof s!="number")throw new TypeError("maxAge must be a number");let a=s?Date.now():0,n=this[DE](r,e);if(this[Lu].has(e)){if(n>this[_d])return bE(this,this[Lu].get(e)),!1;let p=this[Lu].get(e).value;return this[vp]&&(this[Bie]||this[vp](e,p.value)),p.now=a,p.maxAge=s,p.value=r,this[Sp]+=n-p.length,p.length=n,this.get(e),uB(this),!0}let c=new M_(e,r,n,a,s);return c.length>this[_d]?(this[vp]&&this[vp](e,r),!1):(this[Sp]+=c.length,this[Gs].unshift(c),this[Lu].set(e,this[Gs].head),uB(this),!0)}has(e){if(!this[Lu].has(e))return!1;let r=this[Lu].get(e).value;return!ek(this,r)}get(e){return O_(this,e,!0)}peek(e){return O_(this,e,!1)}pop(){let e=this[Gs].tail;return e?(bE(this,e),e.value):null}del(e){bE(this,this[Lu].get(e))}load(e){this.reset();let r=Date.now();for(let s=e.length-1;s>=0;s--){let a=e[s],n=a.e||0;if(n===0)this.set(a.k,a.v);else{let c=n-r;c>0&&this.set(a.k,a.v,c)}}}prune(){this[Lu].forEach((e,r)=>O_(this,r,!1))}},O_=(t,e,r)=>{let s=t[Lu].get(e);if(s){let a=s.value;if(ek(t,a)){if(bE(t,s),!t[fB])return}else r&&(t[Sie]&&(s.value.now=Date.now()),t[Gs].unshiftNode(s));return a.value}},ek=(t,e)=>{if(!e||!e.maxAge&&!t[Hd])return!1;let r=Date.now()-e.now;return e.maxAge?r>e.maxAge:t[Hd]&&r>t[Hd]},uB=t=>{if(t[Sp]>t[_d])for(let e=t[Gs].tail;t[Sp]>t[_d]&&e!==null;){let r=e.prev;bE(t,e),e=r}},bE=(t,e)=>{if(e){let r=e.value;t[vp]&&t[vp](r.key,r.value),t[Sp]-=r.length,t[Lu].delete(r.key),t[Gs].removeNode(e)}},M_=class{constructor(e,r,s,a,n){this.key=e,this.value=r,this.length=s,this.now=a,this.maxAge=n||0}},vie=(t,e,r,s)=>{let a=r.value;ek(t,a)&&(bE(t,r),t[fB]||(a=void 0)),a&&e.call(s,a.value,a.key,t)};Die.exports=L_});var vc=_((xRt,Qie)=>{var U_=class t{constructor(e,r){if(r=o7e(r),e instanceof t)return e.loose===!!r.loose&&e.includePrerelease===!!r.includePrerelease?e:new t(e.raw,r);if(e instanceof __)return this.raw=e.value,this.set=[[e]],this.format(),this;if(this.options=r,this.loose=!!r.loose,this.includePrerelease=!!r.includePrerelease,this.raw=e.trim().split(/\s+/).join(" "),this.set=this.raw.split("||").map(s=>this.parseRange(s.trim())).filter(s=>s.length),!this.set.length)throw new TypeError(`Invalid SemVer Range: ${this.raw}`);if(this.set.length>1){let s=this.set[0];if(this.set=this.set.filter(a=>!xie(a[0])),this.set.length===0)this.set=[s];else if(this.set.length>1){for(let a of this.set)if(a.length===1&&p7e(a[0])){this.set=[a];break}}}this.format()}format(){return this.range=this.set.map(e=>e.join(" ").trim()).join("||").trim(),this.range}toString(){return this.range}parseRange(e){let s=((this.options.includePrerelease&&f7e)|(this.options.loose&&A7e))+":"+e,a=Pie.get(s);if(a)return a;let n=this.options.loose,c=n?sl[wa.HYPHENRANGELOOSE]:sl[wa.HYPHENRANGE];e=e.replace(c,B7e(this.options.includePrerelease)),vi("hyphen replace",e),e=e.replace(sl[wa.COMPARATORTRIM],l7e),vi("comparator trim",e),e=e.replace(sl[wa.TILDETRIM],c7e),vi("tilde trim",e),e=e.replace(sl[wa.CARETTRIM],u7e),vi("caret trim",e);let f=e.split(" ").map(C=>h7e(C,this.options)).join(" ").split(/\s+/).map(C=>w7e(C,this.options));n&&(f=f.filter(C=>(vi("loose invalid filter",C,this.options),!!C.match(sl[wa.COMPARATORLOOSE])))),vi("range list",f);let p=new Map,h=f.map(C=>new __(C,this.options));for(let C of h){if(xie(C))return[C];p.set(C.value,C)}p.size>1&&p.has("")&&p.delete("");let E=[...p.values()];return Pie.set(s,E),E}intersects(e,r){if(!(e instanceof t))throw new TypeError("a Range is required");return this.set.some(s=>kie(s,r)&&e.set.some(a=>kie(a,r)&&s.every(n=>a.every(c=>n.intersects(c,r)))))}test(e){if(!e)return!1;if(typeof e=="string")try{e=new a7e(e,this.options)}catch{return!1}for(let r=0;rt.value==="<0.0.0-0",p7e=t=>t.value==="",kie=(t,e)=>{let r=!0,s=t.slice(),a=s.pop();for(;r&&s.length;)r=s.every(n=>a.intersects(n,e)),a=s.pop();return r},h7e=(t,e)=>(vi("comp",t,e),t=m7e(t,e),vi("caret",t),t=g7e(t,e),vi("tildes",t),t=E7e(t,e),vi("xrange",t),t=C7e(t,e),vi("stars",t),t),Ba=t=>!t||t.toLowerCase()==="x"||t==="*",g7e=(t,e)=>t.trim().split(/\s+/).map(r=>d7e(r,e)).join(" "),d7e=(t,e)=>{let r=e.loose?sl[wa.TILDELOOSE]:sl[wa.TILDE];return t.replace(r,(s,a,n,c,f)=>{vi("tilde",t,s,a,n,c,f);let p;return Ba(a)?p="":Ba(n)?p=`>=${a}.0.0 <${+a+1}.0.0-0`:Ba(c)?p=`>=${a}.${n}.0 <${a}.${+n+1}.0-0`:f?(vi("replaceTilde pr",f),p=`>=${a}.${n}.${c}-${f} <${a}.${+n+1}.0-0`):p=`>=${a}.${n}.${c} <${a}.${+n+1}.0-0`,vi("tilde return",p),p})},m7e=(t,e)=>t.trim().split(/\s+/).map(r=>y7e(r,e)).join(" "),y7e=(t,e)=>{vi("caret",t,e);let r=e.loose?sl[wa.CARETLOOSE]:sl[wa.CARET],s=e.includePrerelease?"-0":"";return t.replace(r,(a,n,c,f,p)=>{vi("caret",t,a,n,c,f,p);let h;return Ba(n)?h="":Ba(c)?h=`>=${n}.0.0${s} <${+n+1}.0.0-0`:Ba(f)?n==="0"?h=`>=${n}.${c}.0${s} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.0${s} <${+n+1}.0.0-0`:p?(vi("replaceCaret pr",p),n==="0"?c==="0"?h=`>=${n}.${c}.${f}-${p} <${n}.${c}.${+f+1}-0`:h=`>=${n}.${c}.${f}-${p} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.${f}-${p} <${+n+1}.0.0-0`):(vi("no pr"),n==="0"?c==="0"?h=`>=${n}.${c}.${f}${s} <${n}.${c}.${+f+1}-0`:h=`>=${n}.${c}.${f}${s} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.${f} <${+n+1}.0.0-0`),vi("caret return",h),h})},E7e=(t,e)=>(vi("replaceXRanges",t,e),t.split(/\s+/).map(r=>I7e(r,e)).join(" ")),I7e=(t,e)=>{t=t.trim();let r=e.loose?sl[wa.XRANGELOOSE]:sl[wa.XRANGE];return t.replace(r,(s,a,n,c,f,p)=>{vi("xRange",t,s,a,n,c,f,p);let h=Ba(n),E=h||Ba(c),C=E||Ba(f),S=C;return a==="="&&S&&(a=""),p=e.includePrerelease?"-0":"",h?a===">"||a==="<"?s="<0.0.0-0":s="*":a&&S?(E&&(c=0),f=0,a===">"?(a=">=",E?(n=+n+1,c=0,f=0):(c=+c+1,f=0)):a==="<="&&(a="<",E?n=+n+1:c=+c+1),a==="<"&&(p="-0"),s=`${a+n}.${c}.${f}${p}`):E?s=`>=${n}.0.0${p} <${+n+1}.0.0-0`:C&&(s=`>=${n}.${c}.0${p} <${n}.${+c+1}.0-0`),vi("xRange return",s),s})},C7e=(t,e)=>(vi("replaceStars",t,e),t.trim().replace(sl[wa.STAR],"")),w7e=(t,e)=>(vi("replaceGTE0",t,e),t.trim().replace(sl[e.includePrerelease?wa.GTE0PRE:wa.GTE0],"")),B7e=t=>(e,r,s,a,n,c,f,p,h,E,C,S,P)=>(Ba(s)?r="":Ba(a)?r=`>=${s}.0.0${t?"-0":""}`:Ba(n)?r=`>=${s}.${a}.0${t?"-0":""}`:c?r=`>=${r}`:r=`>=${r}${t?"-0":""}`,Ba(h)?p="":Ba(E)?p=`<${+h+1}.0.0-0`:Ba(C)?p=`<${h}.${+E+1}.0-0`:S?p=`<=${h}.${E}.${C}-${S}`:t?p=`<${h}.${E}.${+C+1}-0`:p=`<=${p}`,`${r} ${p}`.trim()),v7e=(t,e,r)=>{for(let s=0;s0){let a=t[s].semver;if(a.major===e.major&&a.minor===e.minor&&a.patch===e.patch)return!0}return!1}return!0}});var AB=_((kRt,Lie)=>{var pB=Symbol("SemVer ANY"),G_=class t{static get ANY(){return pB}constructor(e,r){if(r=Tie(r),e instanceof t){if(e.loose===!!r.loose)return e;e=e.value}e=e.trim().split(/\s+/).join(" "),j_("comparator",e,r),this.options=r,this.loose=!!r.loose,this.parse(e),this.semver===pB?this.value="":this.value=this.operator+this.semver.version,j_("comp",this)}parse(e){let r=this.options.loose?Rie[Fie.COMPARATORLOOSE]:Rie[Fie.COMPARATOR],s=e.match(r);if(!s)throw new TypeError(`Invalid comparator: ${e}`);this.operator=s[1]!==void 0?s[1]:"",this.operator==="="&&(this.operator=""),s[2]?this.semver=new Nie(s[2],this.options.loose):this.semver=pB}toString(){return this.value}test(e){if(j_("Comparator.test",e,this.options.loose),this.semver===pB||e===pB)return!0;if(typeof e=="string")try{e=new Nie(e,this.options)}catch{return!1}return H_(e,this.operator,this.semver,this.options)}intersects(e,r){if(!(e instanceof t))throw new TypeError("a Comparator is required");return this.operator===""?this.value===""?!0:new Oie(e.value,r).test(this.value):e.operator===""?e.value===""?!0:new Oie(this.value,r).test(e.semver):(r=Tie(r),r.includePrerelease&&(this.value==="<0.0.0-0"||e.value==="<0.0.0-0")||!r.includePrerelease&&(this.value.startsWith("<0.0.0")||e.value.startsWith("<0.0.0"))?!1:!!(this.operator.startsWith(">")&&e.operator.startsWith(">")||this.operator.startsWith("<")&&e.operator.startsWith("<")||this.semver.version===e.semver.version&&this.operator.includes("=")&&e.operator.includes("=")||H_(this.semver,"<",e.semver,r)&&this.operator.startsWith(">")&&e.operator.startsWith("<")||H_(this.semver,">",e.semver,r)&&this.operator.startsWith("<")&&e.operator.startsWith(">")))}};Lie.exports=G_;var Tie=qx(),{safeRe:Rie,t:Fie}=vE(),H_=F_(),j_=lB(),Nie=jo(),Oie=vc()});var hB=_((QRt,Mie)=>{var S7e=vc(),D7e=(t,e,r)=>{try{e=new S7e(e,r)}catch{return!1}return e.test(t)};Mie.exports=D7e});var _ie=_((TRt,Uie)=>{var b7e=vc(),P7e=(t,e)=>new b7e(t,e).set.map(r=>r.map(s=>s.value).join(" ").trim().split(" "));Uie.exports=P7e});var jie=_((RRt,Hie)=>{var x7e=jo(),k7e=vc(),Q7e=(t,e,r)=>{let s=null,a=null,n=null;try{n=new k7e(e,r)}catch{return null}return t.forEach(c=>{n.test(c)&&(!s||a.compare(c)===-1)&&(s=c,a=new x7e(s,r))}),s};Hie.exports=Q7e});var qie=_((FRt,Gie)=>{var T7e=jo(),R7e=vc(),F7e=(t,e,r)=>{let s=null,a=null,n=null;try{n=new R7e(e,r)}catch{return null}return t.forEach(c=>{n.test(c)&&(!s||a.compare(c)===1)&&(s=c,a=new T7e(s,r))}),s};Gie.exports=F7e});var Vie=_((NRt,Yie)=>{var q_=jo(),N7e=vc(),Wie=cB(),O7e=(t,e)=>{t=new N7e(t,e);let r=new q_("0.0.0");if(t.test(r)||(r=new q_("0.0.0-0"),t.test(r)))return r;r=null;for(let s=0;s{let f=new q_(c.semver.version);switch(c.operator){case">":f.prerelease.length===0?f.patch++:f.prerelease.push(0),f.raw=f.format();case"":case">=":(!n||Wie(f,n))&&(n=f);break;case"<":case"<=":break;default:throw new Error(`Unexpected operation: ${c.operator}`)}}),n&&(!r||Wie(r,n))&&(r=n)}return r&&t.test(r)?r:null};Yie.exports=O7e});var Kie=_((ORt,Jie)=>{var L7e=vc(),M7e=(t,e)=>{try{return new L7e(t,e).range||"*"}catch{return null}};Jie.exports=M7e});var tk=_((LRt,$ie)=>{var U7e=jo(),Zie=AB(),{ANY:_7e}=Zie,H7e=vc(),j7e=hB(),zie=cB(),Xie=Jx(),G7e=zx(),q7e=Kx(),W7e=(t,e,r,s)=>{t=new U7e(t,s),e=new H7e(e,s);let a,n,c,f,p;switch(r){case">":a=zie,n=G7e,c=Xie,f=">",p=">=";break;case"<":a=Xie,n=q7e,c=zie,f="<",p="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(j7e(t,e,s))return!1;for(let h=0;h{P.semver===_7e&&(P=new Zie(">=0.0.0")),C=C||P,S=S||P,a(P.semver,C.semver,s)?C=P:c(P.semver,S.semver,s)&&(S=P)}),C.operator===f||C.operator===p||(!S.operator||S.operator===f)&&n(t,S.semver))return!1;if(S.operator===p&&c(t,S.semver))return!1}return!0};$ie.exports=W7e});var tse=_((MRt,ese)=>{var Y7e=tk(),V7e=(t,e,r)=>Y7e(t,e,">",r);ese.exports=V7e});var nse=_((URt,rse)=>{var J7e=tk(),K7e=(t,e,r)=>J7e(t,e,"<",r);rse.exports=K7e});var ose=_((_Rt,sse)=>{var ise=vc(),z7e=(t,e,r)=>(t=new ise(t,r),e=new ise(e,r),t.intersects(e,r));sse.exports=z7e});var lse=_((HRt,ase)=>{var X7e=hB(),Z7e=Bc();ase.exports=(t,e,r)=>{let s=[],a=null,n=null,c=t.sort((E,C)=>Z7e(E,C,r));for(let E of c)X7e(E,e,r)?(n=E,a||(a=E)):(n&&s.push([a,n]),n=null,a=null);a&&s.push([a,null]);let f=[];for(let[E,C]of s)E===C?f.push(E):!C&&E===c[0]?f.push("*"):C?E===c[0]?f.push(`<=${C}`):f.push(`${E} - ${C}`):f.push(`>=${E}`);let p=f.join(" || "),h=typeof e.raw=="string"?e.raw:String(e);return p.length{var cse=vc(),Y_=AB(),{ANY:W_}=Y_,gB=hB(),V_=Bc(),$7e=(t,e,r={})=>{if(t===e)return!0;t=new cse(t,r),e=new cse(e,r);let s=!1;e:for(let a of t.set){for(let n of e.set){let c=tJe(a,n,r);if(s=s||c!==null,c)continue e}if(s)return!1}return!0},eJe=[new Y_(">=0.0.0-0")],use=[new Y_(">=0.0.0")],tJe=(t,e,r)=>{if(t===e)return!0;if(t.length===1&&t[0].semver===W_){if(e.length===1&&e[0].semver===W_)return!0;r.includePrerelease?t=eJe:t=use}if(e.length===1&&e[0].semver===W_){if(r.includePrerelease)return!0;e=use}let s=new Set,a,n;for(let P of t)P.operator===">"||P.operator===">="?a=fse(a,P,r):P.operator==="<"||P.operator==="<="?n=Ase(n,P,r):s.add(P.semver);if(s.size>1)return null;let c;if(a&&n){if(c=V_(a.semver,n.semver,r),c>0)return null;if(c===0&&(a.operator!==">="||n.operator!=="<="))return null}for(let P of s){if(a&&!gB(P,String(a),r)||n&&!gB(P,String(n),r))return null;for(let I of e)if(!gB(P,String(I),r))return!1;return!0}let f,p,h,E,C=n&&!r.includePrerelease&&n.semver.prerelease.length?n.semver:!1,S=a&&!r.includePrerelease&&a.semver.prerelease.length?a.semver:!1;C&&C.prerelease.length===1&&n.operator==="<"&&C.prerelease[0]===0&&(C=!1);for(let P of e){if(E=E||P.operator===">"||P.operator===">=",h=h||P.operator==="<"||P.operator==="<=",a){if(S&&P.semver.prerelease&&P.semver.prerelease.length&&P.semver.major===S.major&&P.semver.minor===S.minor&&P.semver.patch===S.patch&&(S=!1),P.operator===">"||P.operator===">="){if(f=fse(a,P,r),f===P&&f!==a)return!1}else if(a.operator===">="&&!gB(a.semver,String(P),r))return!1}if(n){if(C&&P.semver.prerelease&&P.semver.prerelease.length&&P.semver.major===C.major&&P.semver.minor===C.minor&&P.semver.patch===C.patch&&(C=!1),P.operator==="<"||P.operator==="<="){if(p=Ase(n,P,r),p===P&&p!==n)return!1}else if(n.operator==="<="&&!gB(n.semver,String(P),r))return!1}if(!P.operator&&(n||a)&&c!==0)return!1}return!(a&&h&&!n&&c!==0||n&&E&&!a&&c!==0||S||C)},fse=(t,e,r)=>{if(!t)return e;let s=V_(t.semver,e.semver,r);return s>0?t:s<0||e.operator===">"&&t.operator===">="?e:t},Ase=(t,e,r)=>{if(!t)return e;let s=V_(t.semver,e.semver,r);return s<0?t:s>0||e.operator==="<"&&t.operator==="<="?e:t};pse.exports=$7e});var Ai=_((GRt,mse)=>{var J_=vE(),gse=aB(),rJe=jo(),dse=k_(),nJe=Md(),iJe=One(),sJe=Mne(),oJe=Hne(),aJe=qne(),lJe=Yne(),cJe=Jne(),uJe=zne(),fJe=Zne(),AJe=Bc(),pJe=rie(),hJe=iie(),gJe=Vx(),dJe=lie(),mJe=uie(),yJe=cB(),EJe=Jx(),IJe=T_(),CJe=R_(),wJe=Kx(),BJe=zx(),vJe=F_(),SJe=Eie(),DJe=AB(),bJe=vc(),PJe=hB(),xJe=_ie(),kJe=jie(),QJe=qie(),TJe=Vie(),RJe=Kie(),FJe=tk(),NJe=tse(),OJe=nse(),LJe=ose(),MJe=lse(),UJe=hse();mse.exports={parse:nJe,valid:iJe,clean:sJe,inc:oJe,diff:aJe,major:lJe,minor:cJe,patch:uJe,prerelease:fJe,compare:AJe,rcompare:pJe,compareLoose:hJe,compareBuild:gJe,sort:dJe,rsort:mJe,gt:yJe,lt:EJe,eq:IJe,neq:CJe,gte:wJe,lte:BJe,cmp:vJe,coerce:SJe,Comparator:DJe,Range:bJe,satisfies:PJe,toComparators:xJe,maxSatisfying:kJe,minSatisfying:QJe,minVersion:TJe,validRange:RJe,outside:FJe,gtr:NJe,ltr:OJe,intersects:LJe,simplifyRange:MJe,subset:UJe,SemVer:rJe,re:J_.re,src:J_.src,tokens:J_.t,SEMVER_SPEC_VERSION:gse.SEMVER_SPEC_VERSION,RELEASE_TYPES:gse.RELEASE_TYPES,compareIdentifiers:dse.compareIdentifiers,rcompareIdentifiers:dse.rcompareIdentifiers}});var Ese=_((qRt,yse)=>{"use strict";function _Je(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function jd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,jd)}_Je(jd,Error);jd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;C{switch(Te[1]){case"|":return xe|Te[3];case"&":return xe&Te[3];case"^":return xe^Te[3]}},$)},S="!",P=Fe("!",!1),I=function($){return!$},R="(",N=Fe("(",!1),U=")",W=Fe(")",!1),ee=function($){return $},ie=/^[^ \t\n\r()!|&\^]/,ue=Ne([" "," ",` `,"\r","(",")","!","|","&","^"],!0,!1),le=function($){return e.queryPattern.test($)},me=function($){return e.checkFn($)},pe=ke("whitespace"),Be=/^[ \t\n\r]/,Ce=Ne([" "," ",` `,"\r"],!1,!1),g=0,we=0,ye=[{line:1,column:1}],Ae=0,se=[],Z=0,De;if("startRule"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');a=s[e.startRule]}function Re(){return t.substring(we,g)}function mt(){return Ue(we,g)}function j($,oe){throw oe=oe!==void 0?oe:Ue(we,g),b([ke($)],t.substring(we,g),oe)}function rt($,oe){throw oe=oe!==void 0?oe:Ue(we,g),w($,oe)}function Fe($,oe){return{type:"literal",text:$,ignoreCase:oe}}function Ne($,oe,xe){return{type:"class",parts:$,inverted:oe,ignoreCase:xe}}function Pe(){return{type:"any"}}function Ve(){return{type:"end"}}function ke($){return{type:"other",description:$}}function it($){var oe=ye[$],xe;if(oe)return oe;for(xe=$-1;!ye[xe];)xe--;for(oe=ye[xe],oe={line:oe.line,column:oe.column};xe<$;)t.charCodeAt(xe)===10?(oe.line++,oe.column=1):oe.column++,xe++;return ye[$]=oe,oe}function Ue($,oe){var xe=it($),Te=it(oe);return{start:{offset:$,line:xe.line,column:xe.column},end:{offset:oe,line:Te.line,column:Te.column}}}function x($){gAe&&(Ae=g,se=[]),se.push($))}function w($,oe){return new jd($,null,null,oe)}function b($,oe,xe){return new jd(jd.buildMessage($,oe),$,oe,xe)}function y(){var $,oe,xe,Te,lt,Ct,qt,ir;if($=g,oe=F(),oe!==r){for(xe=[],Te=g,lt=X(),lt!==r?(t.charCodeAt(g)===124?(Ct=n,g++):(Ct=r,Z===0&&x(c)),Ct===r&&(t.charCodeAt(g)===38?(Ct=f,g++):(Ct=r,Z===0&&x(p)),Ct===r&&(t.charCodeAt(g)===94?(Ct=h,g++):(Ct=r,Z===0&&x(E)))),Ct!==r?(qt=X(),qt!==r?(ir=F(),ir!==r?(lt=[lt,Ct,qt,ir],Te=lt):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r);Te!==r;)xe.push(Te),Te=g,lt=X(),lt!==r?(t.charCodeAt(g)===124?(Ct=n,g++):(Ct=r,Z===0&&x(c)),Ct===r&&(t.charCodeAt(g)===38?(Ct=f,g++):(Ct=r,Z===0&&x(p)),Ct===r&&(t.charCodeAt(g)===94?(Ct=h,g++):(Ct=r,Z===0&&x(E)))),Ct!==r?(qt=X(),qt!==r?(ir=F(),ir!==r?(lt=[lt,Ct,qt,ir],Te=lt):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r);xe!==r?(we=$,oe=C(oe,xe),$=oe):(g=$,$=r)}else g=$,$=r;return $}function F(){var $,oe,xe,Te,lt,Ct;return $=g,t.charCodeAt(g)===33?(oe=S,g++):(oe=r,Z===0&&x(P)),oe!==r?(xe=F(),xe!==r?(we=$,oe=I(xe),$=oe):(g=$,$=r)):(g=$,$=r),$===r&&($=g,t.charCodeAt(g)===40?(oe=R,g++):(oe=r,Z===0&&x(N)),oe!==r?(xe=X(),xe!==r?(Te=y(),Te!==r?(lt=X(),lt!==r?(t.charCodeAt(g)===41?(Ct=U,g++):(Ct=r,Z===0&&x(W)),Ct!==r?(we=$,oe=ee(Te),$=oe):(g=$,$=r)):(g=$,$=r)):(g=$,$=r)):(g=$,$=r)):(g=$,$=r),$===r&&($=z())),$}function z(){var $,oe,xe,Te,lt;if($=g,oe=X(),oe!==r){if(xe=g,Te=[],ie.test(t.charAt(g))?(lt=t.charAt(g),g++):(lt=r,Z===0&&x(ue)),lt!==r)for(;lt!==r;)Te.push(lt),ie.test(t.charAt(g))?(lt=t.charAt(g),g++):(lt=r,Z===0&&x(ue));else Te=r;Te!==r?xe=t.substring(xe,g):xe=Te,xe!==r?(we=g,Te=le(xe),Te?Te=void 0:Te=r,Te!==r?(we=$,oe=me(xe),$=oe):(g=$,$=r)):(g=$,$=r)}else g=$,$=r;return $}function X(){var $,oe;for(Z++,$=[],Be.test(t.charAt(g))?(oe=t.charAt(g),g++):(oe=r,Z===0&&x(Ce));oe!==r;)$.push(oe),Be.test(t.charAt(g))?(oe=t.charAt(g),g++):(oe=r,Z===0&&x(Ce));return Z--,$===r&&(oe=r,Z===0&&x(pe)),$}if(De=a(),De!==r&&g===t.length)return De;throw De!==r&&g{var{parse:jJe}=Ese();rk.makeParser=(t=/[a-z]+/)=>(e,r)=>jJe(e,{queryPattern:t,checkFn:r});rk.parse=rk.makeParser()});var wse=_((YRt,Cse)=>{"use strict";Cse.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}});var K_=_((VRt,vse)=>{var dB=wse(),Bse={};for(let t of Object.keys(dB))Bse[dB[t]]=t;var hr={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};vse.exports=hr;for(let t of Object.keys(hr)){if(!("channels"in hr[t]))throw new Error("missing channels property: "+t);if(!("labels"in hr[t]))throw new Error("missing channel labels property: "+t);if(hr[t].labels.length!==hr[t].channels)throw new Error("channel and label counts mismatch: "+t);let{channels:e,labels:r}=hr[t];delete hr[t].channels,delete hr[t].labels,Object.defineProperty(hr[t],"channels",{value:e}),Object.defineProperty(hr[t],"labels",{value:r})}hr.rgb.hsl=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.min(e,r,s),n=Math.max(e,r,s),c=n-a,f,p;n===a?f=0:e===n?f=(r-s)/c:r===n?f=2+(s-e)/c:s===n&&(f=4+(e-r)/c),f=Math.min(f*60,360),f<0&&(f+=360);let h=(a+n)/2;return n===a?p=0:h<=.5?p=c/(n+a):p=c/(2-n-a),[f,p*100,h*100]};hr.rgb.hsv=function(t){let e,r,s,a,n,c=t[0]/255,f=t[1]/255,p=t[2]/255,h=Math.max(c,f,p),E=h-Math.min(c,f,p),C=function(S){return(h-S)/6/E+1/2};return E===0?(a=0,n=0):(n=E/h,e=C(c),r=C(f),s=C(p),c===h?a=s-r:f===h?a=1/3+e-s:p===h&&(a=2/3+r-e),a<0?a+=1:a>1&&(a-=1)),[a*360,n*100,h*100]};hr.rgb.hwb=function(t){let e=t[0],r=t[1],s=t[2],a=hr.rgb.hsl(t)[0],n=1/255*Math.min(e,Math.min(r,s));return s=1-1/255*Math.max(e,Math.max(r,s)),[a,n*100,s*100]};hr.rgb.cmyk=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.min(1-e,1-r,1-s),n=(1-e-a)/(1-a)||0,c=(1-r-a)/(1-a)||0,f=(1-s-a)/(1-a)||0;return[n*100,c*100,f*100,a*100]};function GJe(t,e){return(t[0]-e[0])**2+(t[1]-e[1])**2+(t[2]-e[2])**2}hr.rgb.keyword=function(t){let e=Bse[t];if(e)return e;let r=1/0,s;for(let a of Object.keys(dB)){let n=dB[a],c=GJe(t,n);c.04045?((e+.055)/1.055)**2.4:e/12.92,r=r>.04045?((r+.055)/1.055)**2.4:r/12.92,s=s>.04045?((s+.055)/1.055)**2.4:s/12.92;let a=e*.4124+r*.3576+s*.1805,n=e*.2126+r*.7152+s*.0722,c=e*.0193+r*.1192+s*.9505;return[a*100,n*100,c*100]};hr.rgb.lab=function(t){let e=hr.rgb.xyz(t),r=e[0],s=e[1],a=e[2];r/=95.047,s/=100,a/=108.883,r=r>.008856?r**(1/3):7.787*r+16/116,s=s>.008856?s**(1/3):7.787*s+16/116,a=a>.008856?a**(1/3):7.787*a+16/116;let n=116*s-16,c=500*(r-s),f=200*(s-a);return[n,c,f]};hr.hsl.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100,a,n,c;if(r===0)return c=s*255,[c,c,c];s<.5?a=s*(1+r):a=s+r-s*r;let f=2*s-a,p=[0,0,0];for(let h=0;h<3;h++)n=e+1/3*-(h-1),n<0&&n++,n>1&&n--,6*n<1?c=f+(a-f)*6*n:2*n<1?c=a:3*n<2?c=f+(a-f)*(2/3-n)*6:c=f,p[h]=c*255;return p};hr.hsl.hsv=function(t){let e=t[0],r=t[1]/100,s=t[2]/100,a=r,n=Math.max(s,.01);s*=2,r*=s<=1?s:2-s,a*=n<=1?n:2-n;let c=(s+r)/2,f=s===0?2*a/(n+a):2*r/(s+r);return[e,f*100,c*100]};hr.hsv.rgb=function(t){let e=t[0]/60,r=t[1]/100,s=t[2]/100,a=Math.floor(e)%6,n=e-Math.floor(e),c=255*s*(1-r),f=255*s*(1-r*n),p=255*s*(1-r*(1-n));switch(s*=255,a){case 0:return[s,p,c];case 1:return[f,s,c];case 2:return[c,s,p];case 3:return[c,f,s];case 4:return[p,c,s];case 5:return[s,c,f]}};hr.hsv.hsl=function(t){let e=t[0],r=t[1]/100,s=t[2]/100,a=Math.max(s,.01),n,c;c=(2-r)*s;let f=(2-r)*a;return n=r*a,n/=f<=1?f:2-f,n=n||0,c/=2,[e,n*100,c*100]};hr.hwb.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100,a=r+s,n;a>1&&(r/=a,s/=a);let c=Math.floor(6*e),f=1-s;n=6*e-c,c&1&&(n=1-n);let p=r+n*(f-r),h,E,C;switch(c){default:case 6:case 0:h=f,E=p,C=r;break;case 1:h=p,E=f,C=r;break;case 2:h=r,E=f,C=p;break;case 3:h=r,E=p,C=f;break;case 4:h=p,E=r,C=f;break;case 5:h=f,E=r,C=p;break}return[h*255,E*255,C*255]};hr.cmyk.rgb=function(t){let e=t[0]/100,r=t[1]/100,s=t[2]/100,a=t[3]/100,n=1-Math.min(1,e*(1-a)+a),c=1-Math.min(1,r*(1-a)+a),f=1-Math.min(1,s*(1-a)+a);return[n*255,c*255,f*255]};hr.xyz.rgb=function(t){let e=t[0]/100,r=t[1]/100,s=t[2]/100,a,n,c;return a=e*3.2406+r*-1.5372+s*-.4986,n=e*-.9689+r*1.8758+s*.0415,c=e*.0557+r*-.204+s*1.057,a=a>.0031308?1.055*a**(1/2.4)-.055:a*12.92,n=n>.0031308?1.055*n**(1/2.4)-.055:n*12.92,c=c>.0031308?1.055*c**(1/2.4)-.055:c*12.92,a=Math.min(Math.max(0,a),1),n=Math.min(Math.max(0,n),1),c=Math.min(Math.max(0,c),1),[a*255,n*255,c*255]};hr.xyz.lab=function(t){let e=t[0],r=t[1],s=t[2];e/=95.047,r/=100,s/=108.883,e=e>.008856?e**(1/3):7.787*e+16/116,r=r>.008856?r**(1/3):7.787*r+16/116,s=s>.008856?s**(1/3):7.787*s+16/116;let a=116*r-16,n=500*(e-r),c=200*(r-s);return[a,n,c]};hr.lab.xyz=function(t){let e=t[0],r=t[1],s=t[2],a,n,c;n=(e+16)/116,a=r/500+n,c=n-s/200;let f=n**3,p=a**3,h=c**3;return n=f>.008856?f:(n-16/116)/7.787,a=p>.008856?p:(a-16/116)/7.787,c=h>.008856?h:(c-16/116)/7.787,a*=95.047,n*=100,c*=108.883,[a,n,c]};hr.lab.lch=function(t){let e=t[0],r=t[1],s=t[2],a;a=Math.atan2(s,r)*360/2/Math.PI,a<0&&(a+=360);let c=Math.sqrt(r*r+s*s);return[e,c,a]};hr.lch.lab=function(t){let e=t[0],r=t[1],a=t[2]/360*2*Math.PI,n=r*Math.cos(a),c=r*Math.sin(a);return[e,n,c]};hr.rgb.ansi16=function(t,e=null){let[r,s,a]=t,n=e===null?hr.rgb.hsv(t)[2]:e;if(n=Math.round(n/50),n===0)return 30;let c=30+(Math.round(a/255)<<2|Math.round(s/255)<<1|Math.round(r/255));return n===2&&(c+=60),c};hr.hsv.ansi16=function(t){return hr.rgb.ansi16(hr.hsv.rgb(t),t[2])};hr.rgb.ansi256=function(t){let e=t[0],r=t[1],s=t[2];return e===r&&r===s?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(r/255*5)+Math.round(s/255*5)};hr.ansi16.rgb=function(t){let e=t%10;if(e===0||e===7)return t>50&&(e+=3.5),e=e/10.5*255,[e,e,e];let r=(~~(t>50)+1)*.5,s=(e&1)*r*255,a=(e>>1&1)*r*255,n=(e>>2&1)*r*255;return[s,a,n]};hr.ansi256.rgb=function(t){if(t>=232){let n=(t-232)*10+8;return[n,n,n]}t-=16;let e,r=Math.floor(t/36)/5*255,s=Math.floor((e=t%36)/6)/5*255,a=e%6/5*255;return[r,s,a]};hr.rgb.hex=function(t){let r=(((Math.round(t[0])&255)<<16)+((Math.round(t[1])&255)<<8)+(Math.round(t[2])&255)).toString(16).toUpperCase();return"000000".substring(r.length)+r};hr.hex.rgb=function(t){let e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];let r=e[0];e[0].length===3&&(r=r.split("").map(f=>f+f).join(""));let s=parseInt(r,16),a=s>>16&255,n=s>>8&255,c=s&255;return[a,n,c]};hr.rgb.hcg=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.max(Math.max(e,r),s),n=Math.min(Math.min(e,r),s),c=a-n,f,p;return c<1?f=n/(1-c):f=0,c<=0?p=0:a===e?p=(r-s)/c%6:a===r?p=2+(s-e)/c:p=4+(e-r)/c,p/=6,p%=1,[p*360,c*100,f*100]};hr.hsl.hcg=function(t){let e=t[1]/100,r=t[2]/100,s=r<.5?2*e*r:2*e*(1-r),a=0;return s<1&&(a=(r-.5*s)/(1-s)),[t[0],s*100,a*100]};hr.hsv.hcg=function(t){let e=t[1]/100,r=t[2]/100,s=e*r,a=0;return s<1&&(a=(r-s)/(1-s)),[t[0],s*100,a*100]};hr.hcg.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100;if(r===0)return[s*255,s*255,s*255];let a=[0,0,0],n=e%1*6,c=n%1,f=1-c,p=0;switch(Math.floor(n)){case 0:a[0]=1,a[1]=c,a[2]=0;break;case 1:a[0]=f,a[1]=1,a[2]=0;break;case 2:a[0]=0,a[1]=1,a[2]=c;break;case 3:a[0]=0,a[1]=f,a[2]=1;break;case 4:a[0]=c,a[1]=0,a[2]=1;break;default:a[0]=1,a[1]=0,a[2]=f}return p=(1-r)*s,[(r*a[0]+p)*255,(r*a[1]+p)*255,(r*a[2]+p)*255]};hr.hcg.hsv=function(t){let e=t[1]/100,r=t[2]/100,s=e+r*(1-e),a=0;return s>0&&(a=e/s),[t[0],a*100,s*100]};hr.hcg.hsl=function(t){let e=t[1]/100,s=t[2]/100*(1-e)+.5*e,a=0;return s>0&&s<.5?a=e/(2*s):s>=.5&&s<1&&(a=e/(2*(1-s))),[t[0],a*100,s*100]};hr.hcg.hwb=function(t){let e=t[1]/100,r=t[2]/100,s=e+r*(1-e);return[t[0],(s-e)*100,(1-s)*100]};hr.hwb.hcg=function(t){let e=t[1]/100,s=1-t[2]/100,a=s-e,n=0;return a<1&&(n=(s-a)/(1-a)),[t[0],a*100,n*100]};hr.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]};hr.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]};hr.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]};hr.gray.hsl=function(t){return[0,0,t[0]]};hr.gray.hsv=hr.gray.hsl;hr.gray.hwb=function(t){return[0,100,t[0]]};hr.gray.cmyk=function(t){return[0,0,0,t[0]]};hr.gray.lab=function(t){return[t[0],0,0]};hr.gray.hex=function(t){let e=Math.round(t[0]/100*255)&255,s=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(s.length)+s};hr.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}});var Dse=_((JRt,Sse)=>{var nk=K_();function qJe(){let t={},e=Object.keys(nk);for(let r=e.length,s=0;s{var z_=K_(),JJe=Dse(),PE={},KJe=Object.keys(z_);function zJe(t){let e=function(...r){let s=r[0];return s==null?s:(s.length>1&&(r=s),t(r))};return"conversion"in t&&(e.conversion=t.conversion),e}function XJe(t){let e=function(...r){let s=r[0];if(s==null)return s;s.length>1&&(r=s);let a=t(r);if(typeof a=="object")for(let n=a.length,c=0;c{PE[t]={},Object.defineProperty(PE[t],"channels",{value:z_[t].channels}),Object.defineProperty(PE[t],"labels",{value:z_[t].labels});let e=JJe(t);Object.keys(e).forEach(s=>{let a=e[s];PE[t][s]=XJe(a),PE[t][s].raw=zJe(a)})});bse.exports=PE});var sk=_((zRt,Rse)=>{"use strict";var xse=(t,e)=>(...r)=>`\x1B[${t(...r)+e}m`,kse=(t,e)=>(...r)=>{let s=t(...r);return`\x1B[${38+e};5;${s}m`},Qse=(t,e)=>(...r)=>{let s=t(...r);return`\x1B[${38+e};2;${s[0]};${s[1]};${s[2]}m`},ik=t=>t,Tse=(t,e,r)=>[t,e,r],xE=(t,e,r)=>{Object.defineProperty(t,e,{get:()=>{let s=r();return Object.defineProperty(t,e,{value:s,enumerable:!0,configurable:!0}),s},enumerable:!0,configurable:!0})},X_,kE=(t,e,r,s)=>{X_===void 0&&(X_=Pse());let a=s?10:0,n={};for(let[c,f]of Object.entries(X_)){let p=c==="ansi16"?"ansi":c;c===e?n[p]=t(r,a):typeof f=="object"&&(n[p]=t(f[e],a))}return n};function ZJe(){let t=new Map,e={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};e.color.gray=e.color.blackBright,e.bgColor.bgGray=e.bgColor.bgBlackBright,e.color.grey=e.color.blackBright,e.bgColor.bgGrey=e.bgColor.bgBlackBright;for(let[r,s]of Object.entries(e)){for(let[a,n]of Object.entries(s))e[a]={open:`\x1B[${n[0]}m`,close:`\x1B[${n[1]}m`},s[a]=e[a],t.set(n[0],n[1]);Object.defineProperty(e,r,{value:s,enumerable:!1})}return Object.defineProperty(e,"codes",{value:t,enumerable:!1}),e.color.close="\x1B[39m",e.bgColor.close="\x1B[49m",xE(e.color,"ansi",()=>kE(xse,"ansi16",ik,!1)),xE(e.color,"ansi256",()=>kE(kse,"ansi256",ik,!1)),xE(e.color,"ansi16m",()=>kE(Qse,"rgb",Tse,!1)),xE(e.bgColor,"ansi",()=>kE(xse,"ansi16",ik,!0)),xE(e.bgColor,"ansi256",()=>kE(kse,"ansi256",ik,!0)),xE(e.bgColor,"ansi16m",()=>kE(Qse,"rgb",Tse,!0)),e}Object.defineProperty(Rse,"exports",{enumerable:!0,get:ZJe})});var Nse=_((XRt,Fse)=>{"use strict";Fse.exports=(t,e=process.argv)=>{let r=t.startsWith("-")?"":t.length===1?"-":"--",s=e.indexOf(r+t),a=e.indexOf("--");return s!==-1&&(a===-1||s{"use strict";var $Je=Ie("os"),Ose=Ie("tty"),Sc=Nse(),{env:bs}=process,l0;Sc("no-color")||Sc("no-colors")||Sc("color=false")||Sc("color=never")?l0=0:(Sc("color")||Sc("colors")||Sc("color=true")||Sc("color=always"))&&(l0=1);"FORCE_COLOR"in bs&&(bs.FORCE_COLOR==="true"?l0=1:bs.FORCE_COLOR==="false"?l0=0:l0=bs.FORCE_COLOR.length===0?1:Math.min(parseInt(bs.FORCE_COLOR,10),3));function Z_(t){return t===0?!1:{level:t,hasBasic:!0,has256:t>=2,has16m:t>=3}}function $_(t,e){if(l0===0)return 0;if(Sc("color=16m")||Sc("color=full")||Sc("color=truecolor"))return 3;if(Sc("color=256"))return 2;if(t&&!e&&l0===void 0)return 0;let r=l0||0;if(bs.TERM==="dumb")return r;if(process.platform==="win32"){let s=$Je.release().split(".");return Number(s[0])>=10&&Number(s[2])>=10586?Number(s[2])>=14931?3:2:1}if("CI"in bs)return["TRAVIS","CIRCLECI","APPVEYOR","GITLAB_CI"].some(s=>s in bs)||bs.CI_NAME==="codeship"?1:r;if("TEAMCITY_VERSION"in bs)return/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(bs.TEAMCITY_VERSION)?1:0;if("GITHUB_ACTIONS"in bs)return 1;if(bs.COLORTERM==="truecolor")return 3;if("TERM_PROGRAM"in bs){let s=parseInt((bs.TERM_PROGRAM_VERSION||"").split(".")[0],10);switch(bs.TERM_PROGRAM){case"iTerm.app":return s>=3?3:2;case"Apple_Terminal":return 2}}return/-256(color)?$/i.test(bs.TERM)?2:/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(bs.TERM)||"COLORTERM"in bs?1:r}function eKe(t){let e=$_(t,t&&t.isTTY);return Z_(e)}Lse.exports={supportsColor:eKe,stdout:Z_($_(!0,Ose.isatty(1))),stderr:Z_($_(!0,Ose.isatty(2)))}});var _se=_(($Rt,Use)=>{"use strict";var tKe=(t,e,r)=>{let s=t.indexOf(e);if(s===-1)return t;let a=e.length,n=0,c="";do c+=t.substr(n,s-n)+e+r,n=s+a,s=t.indexOf(e,n);while(s!==-1);return c+=t.substr(n),c},rKe=(t,e,r,s)=>{let a=0,n="";do{let c=t[s-1]==="\r";n+=t.substr(a,(c?s-1:s)-a)+e+(c?`\r `:` `)+r,a=s+1,s=t.indexOf(` `,a)}while(s!==-1);return n+=t.substr(a),n};Use.exports={stringReplaceAll:tKe,stringEncaseCRLFWithFirstIndex:rKe}});var Wse=_((eFt,qse)=>{"use strict";var nKe=/(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi,Hse=/(?:^|\.)(\w+)(?:\(([^)]*)\))?/g,iKe=/^(['"])((?:\\.|(?!\1)[^\\])*)\1$/,sKe=/\\(u(?:[a-f\d]{4}|{[a-f\d]{1,6}})|x[a-f\d]{2}|.)|([^\\])/gi,oKe=new Map([["n",` `],["r","\r"],["t"," "],["b","\b"],["f","\f"],["v","\v"],["0","\0"],["\\","\\"],["e","\x1B"],["a","\x07"]]);function Gse(t){let e=t[0]==="u",r=t[1]==="{";return e&&!r&&t.length===5||t[0]==="x"&&t.length===3?String.fromCharCode(parseInt(t.slice(1),16)):e&&r?String.fromCodePoint(parseInt(t.slice(2,-1),16)):oKe.get(t)||t}function aKe(t,e){let r=[],s=e.trim().split(/\s*,\s*/g),a;for(let n of s){let c=Number(n);if(!Number.isNaN(c))r.push(c);else if(a=n.match(iKe))r.push(a[2].replace(sKe,(f,p,h)=>p?Gse(p):h));else throw new Error(`Invalid Chalk template style argument: ${n} (in style '${t}')`)}return r}function lKe(t){Hse.lastIndex=0;let e=[],r;for(;(r=Hse.exec(t))!==null;){let s=r[1];if(r[2]){let a=aKe(s,r[2]);e.push([s].concat(a))}else e.push([s])}return e}function jse(t,e){let r={};for(let a of e)for(let n of a.styles)r[n[0]]=a.inverse?null:n.slice(1);let s=t;for(let[a,n]of Object.entries(r))if(Array.isArray(n)){if(!(a in s))throw new Error(`Unknown Chalk style: ${a}`);s=n.length>0?s[a](...n):s[a]}return s}qse.exports=(t,e)=>{let r=[],s=[],a=[];if(e.replace(nKe,(n,c,f,p,h,E)=>{if(c)a.push(Gse(c));else if(p){let C=a.join("");a=[],s.push(r.length===0?C:jse(t,r)(C)),r.push({inverse:f,styles:lKe(p)})}else if(h){if(r.length===0)throw new Error("Found extraneous } in Chalk template literal");s.push(jse(t,r)(a.join(""))),a=[],r.pop()}else a.push(E)}),s.push(a.join("")),r.length>0){let n=`Chalk template literal is missing ${r.length} closing bracket${r.length===1?"":"s"} (\`}\`)`;throw new Error(n)}return s.join("")}});var TE=_((tFt,Xse)=>{"use strict";var mB=sk(),{stdout:t4,stderr:r4}=Mse(),{stringReplaceAll:cKe,stringEncaseCRLFWithFirstIndex:uKe}=_se(),{isArray:ok}=Array,Vse=["ansi","ansi","ansi256","ansi16m"],QE=Object.create(null),fKe=(t,e={})=>{if(e.level&&!(Number.isInteger(e.level)&&e.level>=0&&e.level<=3))throw new Error("The `level` option should be an integer from 0 to 3");let r=t4?t4.level:0;t.level=e.level===void 0?r:e.level},n4=class{constructor(e){return Jse(e)}},Jse=t=>{let e={};return fKe(e,t),e.template=(...r)=>zse(e.template,...r),Object.setPrototypeOf(e,ak.prototype),Object.setPrototypeOf(e.template,e),e.template.constructor=()=>{throw new Error("`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.")},e.template.Instance=n4,e.template};function ak(t){return Jse(t)}for(let[t,e]of Object.entries(mB))QE[t]={get(){let r=lk(this,i4(e.open,e.close,this._styler),this._isEmpty);return Object.defineProperty(this,t,{value:r}),r}};QE.visible={get(){let t=lk(this,this._styler,!0);return Object.defineProperty(this,"visible",{value:t}),t}};var Kse=["rgb","hex","keyword","hsl","hsv","hwb","ansi","ansi256"];for(let t of Kse)QE[t]={get(){let{level:e}=this;return function(...r){let s=i4(mB.color[Vse[e]][t](...r),mB.color.close,this._styler);return lk(this,s,this._isEmpty)}}};for(let t of Kse){let e="bg"+t[0].toUpperCase()+t.slice(1);QE[e]={get(){let{level:r}=this;return function(...s){let a=i4(mB.bgColor[Vse[r]][t](...s),mB.bgColor.close,this._styler);return lk(this,a,this._isEmpty)}}}}var AKe=Object.defineProperties(()=>{},{...QE,level:{enumerable:!0,get(){return this._generator.level},set(t){this._generator.level=t}}}),i4=(t,e,r)=>{let s,a;return r===void 0?(s=t,a=e):(s=r.openAll+t,a=e+r.closeAll),{open:t,close:e,openAll:s,closeAll:a,parent:r}},lk=(t,e,r)=>{let s=(...a)=>ok(a[0])&&ok(a[0].raw)?Yse(s,zse(s,...a)):Yse(s,a.length===1?""+a[0]:a.join(" "));return Object.setPrototypeOf(s,AKe),s._generator=t,s._styler=e,s._isEmpty=r,s},Yse=(t,e)=>{if(t.level<=0||!e)return t._isEmpty?"":e;let r=t._styler;if(r===void 0)return e;let{openAll:s,closeAll:a}=r;if(e.indexOf("\x1B")!==-1)for(;r!==void 0;)e=cKe(e,r.close,r.open),r=r.parent;let n=e.indexOf(` `);return n!==-1&&(e=uKe(e,a,s,n)),s+e+a},e4,zse=(t,...e)=>{let[r]=e;if(!ok(r)||!ok(r.raw))return e.join(" ");let s=e.slice(1),a=[r.raw[0]];for(let n=1;n{"use strict";Dc.isInteger=t=>typeof t=="number"?Number.isInteger(t):typeof t=="string"&&t.trim()!==""?Number.isInteger(Number(t)):!1;Dc.find=(t,e)=>t.nodes.find(r=>r.type===e);Dc.exceedsLimit=(t,e,r=1,s)=>s===!1||!Dc.isInteger(t)||!Dc.isInteger(e)?!1:(Number(e)-Number(t))/Number(r)>=s;Dc.escapeNode=(t,e=0,r)=>{let s=t.nodes[e];s&&(r&&s.type===r||s.type==="open"||s.type==="close")&&s.escaped!==!0&&(s.value="\\"+s.value,s.escaped=!0)};Dc.encloseBrace=t=>t.type!=="brace"||t.commas>>0+t.ranges>>0?!1:(t.invalid=!0,!0);Dc.isInvalidBrace=t=>t.type!=="brace"?!1:t.invalid===!0||t.dollar?!0:!(t.commas>>0+t.ranges>>0)||t.open!==!0||t.close!==!0?(t.invalid=!0,!0):!1;Dc.isOpenOrClose=t=>t.type==="open"||t.type==="close"?!0:t.open===!0||t.close===!0;Dc.reduce=t=>t.reduce((e,r)=>(r.type==="text"&&e.push(r.value),r.type==="range"&&(r.type="text"),e),[]);Dc.flatten=(...t)=>{let e=[],r=s=>{for(let a=0;a{"use strict";var Zse=uk();$se.exports=(t,e={})=>{let r=(s,a={})=>{let n=e.escapeInvalid&&Zse.isInvalidBrace(a),c=s.invalid===!0&&e.escapeInvalid===!0,f="";if(s.value)return(n||c)&&Zse.isOpenOrClose(s)?"\\"+s.value:s.value;if(s.value)return s.value;if(s.nodes)for(let p of s.nodes)f+=r(p);return f};return r(t)}});var toe=_((iFt,eoe)=>{"use strict";eoe.exports=function(t){return typeof t=="number"?t-t===0:typeof t=="string"&&t.trim()!==""?Number.isFinite?Number.isFinite(+t):isFinite(+t):!1}});var uoe=_((sFt,coe)=>{"use strict";var roe=toe(),Gd=(t,e,r)=>{if(roe(t)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(e===void 0||t===e)return String(t);if(roe(e)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let s={relaxZeros:!0,...r};typeof s.strictZeros=="boolean"&&(s.relaxZeros=s.strictZeros===!1);let a=String(s.relaxZeros),n=String(s.shorthand),c=String(s.capture),f=String(s.wrap),p=t+":"+e+"="+a+n+c+f;if(Gd.cache.hasOwnProperty(p))return Gd.cache[p].result;let h=Math.min(t,e),E=Math.max(t,e);if(Math.abs(h-E)===1){let R=t+"|"+e;return s.capture?`(${R})`:s.wrap===!1?R:`(?:${R})`}let C=loe(t)||loe(e),S={min:t,max:e,a:h,b:E},P=[],I=[];if(C&&(S.isPadded=C,S.maxLen=String(S.max).length),h<0){let R=E<0?Math.abs(E):1;I=noe(R,Math.abs(h),S,s),h=S.a=0}return E>=0&&(P=noe(h,E,S,s)),S.negatives=I,S.positives=P,S.result=pKe(I,P,s),s.capture===!0?S.result=`(${S.result})`:s.wrap!==!1&&P.length+I.length>1&&(S.result=`(?:${S.result})`),Gd.cache[p]=S,S.result};function pKe(t,e,r){let s=s4(t,e,"-",!1,r)||[],a=s4(e,t,"",!1,r)||[],n=s4(t,e,"-?",!0,r)||[];return s.concat(n).concat(a).join("|")}function hKe(t,e){let r=1,s=1,a=soe(t,r),n=new Set([e]);for(;t<=a&&a<=e;)n.add(a),r+=1,a=soe(t,r);for(a=ooe(e+1,s)-1;t1&&f.count.pop(),f.count.push(E.count[0]),f.string=f.pattern+aoe(f.count),c=h+1;continue}r.isPadded&&(C=EKe(h,r,s)),E.string=C+E.pattern+aoe(E.count),n.push(E),c=h+1,f=E}return n}function s4(t,e,r,s,a){let n=[];for(let c of t){let{string:f}=c;!s&&!ioe(e,"string",f)&&n.push(r+f),s&&ioe(e,"string",f)&&n.push(r+f)}return n}function dKe(t,e){let r=[];for(let s=0;se?1:e>t?-1:0}function ioe(t,e,r){return t.some(s=>s[e]===r)}function soe(t,e){return Number(String(t).slice(0,-e)+"9".repeat(e))}function ooe(t,e){return t-t%Math.pow(10,e)}function aoe(t){let[e=0,r=""]=t;return r||e>1?`{${e+(r?","+r:"")}}`:""}function yKe(t,e,r){return`[${t}${e-t===1?"":"-"}${e}]`}function loe(t){return/^-?(0+)\d/.test(t)}function EKe(t,e,r){if(!e.isPadded)return t;let s=Math.abs(e.maxLen-String(t).length),a=r.relaxZeros!==!1;switch(s){case 0:return"";case 1:return a?"0?":"0";case 2:return a?"0{0,2}":"00";default:return a?`0{0,${s}}`:`0{${s}}`}}Gd.cache={};Gd.clearCache=()=>Gd.cache={};coe.exports=Gd});var l4=_((oFt,yoe)=>{"use strict";var IKe=Ie("util"),poe=uoe(),foe=t=>t!==null&&typeof t=="object"&&!Array.isArray(t),CKe=t=>e=>t===!0?Number(e):String(e),o4=t=>typeof t=="number"||typeof t=="string"&&t!=="",yB=t=>Number.isInteger(+t),a4=t=>{let e=`${t}`,r=-1;if(e[0]==="-"&&(e=e.slice(1)),e==="0")return!1;for(;e[++r]==="0";);return r>0},wKe=(t,e,r)=>typeof t=="string"||typeof e=="string"?!0:r.stringify===!0,BKe=(t,e,r)=>{if(e>0){let s=t[0]==="-"?"-":"";s&&(t=t.slice(1)),t=s+t.padStart(s?e-1:e,"0")}return r===!1?String(t):t},Aoe=(t,e)=>{let r=t[0]==="-"?"-":"";for(r&&(t=t.slice(1),e--);t.length{t.negatives.sort((c,f)=>cf?1:0),t.positives.sort((c,f)=>cf?1:0);let r=e.capture?"":"?:",s="",a="",n;return t.positives.length&&(s=t.positives.join("|")),t.negatives.length&&(a=`-(${r}${t.negatives.join("|")})`),s&&a?n=`${s}|${a}`:n=s||a,e.wrap?`(${r}${n})`:n},hoe=(t,e,r,s)=>{if(r)return poe(t,e,{wrap:!1,...s});let a=String.fromCharCode(t);if(t===e)return a;let n=String.fromCharCode(e);return`[${a}-${n}]`},goe=(t,e,r)=>{if(Array.isArray(t)){let s=r.wrap===!0,a=r.capture?"":"?:";return s?`(${a}${t.join("|")})`:t.join("|")}return poe(t,e,r)},doe=(...t)=>new RangeError("Invalid range arguments: "+IKe.inspect(...t)),moe=(t,e,r)=>{if(r.strictRanges===!0)throw doe([t,e]);return[]},SKe=(t,e)=>{if(e.strictRanges===!0)throw new TypeError(`Expected step "${t}" to be a number`);return[]},DKe=(t,e,r=1,s={})=>{let a=Number(t),n=Number(e);if(!Number.isInteger(a)||!Number.isInteger(n)){if(s.strictRanges===!0)throw doe([t,e]);return[]}a===0&&(a=0),n===0&&(n=0);let c=a>n,f=String(t),p=String(e),h=String(r);r=Math.max(Math.abs(r),1);let E=a4(f)||a4(p)||a4(h),C=E?Math.max(f.length,p.length,h.length):0,S=E===!1&&wKe(t,e,s)===!1,P=s.transform||CKe(S);if(s.toRegex&&r===1)return hoe(Aoe(t,C),Aoe(e,C),!0,s);let I={negatives:[],positives:[]},R=W=>I[W<0?"negatives":"positives"].push(Math.abs(W)),N=[],U=0;for(;c?a>=n:a<=n;)s.toRegex===!0&&r>1?R(a):N.push(BKe(P(a,U),C,S)),a=c?a-r:a+r,U++;return s.toRegex===!0?r>1?vKe(I,s):goe(N,null,{wrap:!1,...s}):N},bKe=(t,e,r=1,s={})=>{if(!yB(t)&&t.length>1||!yB(e)&&e.length>1)return moe(t,e,s);let a=s.transform||(S=>String.fromCharCode(S)),n=`${t}`.charCodeAt(0),c=`${e}`.charCodeAt(0),f=n>c,p=Math.min(n,c),h=Math.max(n,c);if(s.toRegex&&r===1)return hoe(p,h,!1,s);let E=[],C=0;for(;f?n>=c:n<=c;)E.push(a(n,C)),n=f?n-r:n+r,C++;return s.toRegex===!0?goe(E,null,{wrap:!1,options:s}):E},Ak=(t,e,r,s={})=>{if(e==null&&o4(t))return[t];if(!o4(t)||!o4(e))return moe(t,e,s);if(typeof r=="function")return Ak(t,e,1,{transform:r});if(foe(r))return Ak(t,e,0,r);let a={...s};return a.capture===!0&&(a.wrap=!0),r=r||a.step||1,yB(r)?yB(t)&&yB(e)?DKe(t,e,r,a):bKe(t,e,Math.max(Math.abs(r),1),a):r!=null&&!foe(r)?SKe(r,a):Ak(t,e,1,r)};yoe.exports=Ak});var Coe=_((aFt,Ioe)=>{"use strict";var PKe=l4(),Eoe=uk(),xKe=(t,e={})=>{let r=(s,a={})=>{let n=Eoe.isInvalidBrace(a),c=s.invalid===!0&&e.escapeInvalid===!0,f=n===!0||c===!0,p=e.escapeInvalid===!0?"\\":"",h="";if(s.isOpen===!0||s.isClose===!0)return p+s.value;if(s.type==="open")return f?p+s.value:"(";if(s.type==="close")return f?p+s.value:")";if(s.type==="comma")return s.prev.type==="comma"?"":f?s.value:"|";if(s.value)return s.value;if(s.nodes&&s.ranges>0){let E=Eoe.reduce(s.nodes),C=PKe(...E,{...e,wrap:!1,toRegex:!0});if(C.length!==0)return E.length>1&&C.length>1?`(${C})`:C}if(s.nodes)for(let E of s.nodes)h+=r(E,s);return h};return r(t)};Ioe.exports=xKe});var voe=_((lFt,Boe)=>{"use strict";var kKe=l4(),woe=fk(),RE=uk(),qd=(t="",e="",r=!1)=>{let s=[];if(t=[].concat(t),e=[].concat(e),!e.length)return t;if(!t.length)return r?RE.flatten(e).map(a=>`{${a}}`):e;for(let a of t)if(Array.isArray(a))for(let n of a)s.push(qd(n,e,r));else for(let n of e)r===!0&&typeof n=="string"&&(n=`{${n}}`),s.push(Array.isArray(n)?qd(a,n,r):a+n);return RE.flatten(s)},QKe=(t,e={})=>{let r=e.rangeLimit===void 0?1e3:e.rangeLimit,s=(a,n={})=>{a.queue=[];let c=n,f=n.queue;for(;c.type!=="brace"&&c.type!=="root"&&c.parent;)c=c.parent,f=c.queue;if(a.invalid||a.dollar){f.push(qd(f.pop(),woe(a,e)));return}if(a.type==="brace"&&a.invalid!==!0&&a.nodes.length===2){f.push(qd(f.pop(),["{}"]));return}if(a.nodes&&a.ranges>0){let C=RE.reduce(a.nodes);if(RE.exceedsLimit(...C,e.step,r))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let S=kKe(...C,e);S.length===0&&(S=woe(a,e)),f.push(qd(f.pop(),S)),a.nodes=[];return}let p=RE.encloseBrace(a),h=a.queue,E=a;for(;E.type!=="brace"&&E.type!=="root"&&E.parent;)E=E.parent,h=E.queue;for(let C=0;C{"use strict";Soe.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` `,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Qoe=_((uFt,koe)=>{"use strict";var TKe=fk(),{MAX_LENGTH:boe,CHAR_BACKSLASH:c4,CHAR_BACKTICK:RKe,CHAR_COMMA:FKe,CHAR_DOT:NKe,CHAR_LEFT_PARENTHESES:OKe,CHAR_RIGHT_PARENTHESES:LKe,CHAR_LEFT_CURLY_BRACE:MKe,CHAR_RIGHT_CURLY_BRACE:UKe,CHAR_LEFT_SQUARE_BRACKET:Poe,CHAR_RIGHT_SQUARE_BRACKET:xoe,CHAR_DOUBLE_QUOTE:_Ke,CHAR_SINGLE_QUOTE:HKe,CHAR_NO_BREAK_SPACE:jKe,CHAR_ZERO_WIDTH_NOBREAK_SPACE:GKe}=Doe(),qKe=(t,e={})=>{if(typeof t!="string")throw new TypeError("Expected a string");let r=e||{},s=typeof r.maxLength=="number"?Math.min(boe,r.maxLength):boe;if(t.length>s)throw new SyntaxError(`Input length (${t.length}), exceeds max characters (${s})`);let a={type:"root",input:t,nodes:[]},n=[a],c=a,f=a,p=0,h=t.length,E=0,C=0,S,P={},I=()=>t[E++],R=N=>{if(N.type==="text"&&f.type==="dot"&&(f.type="text"),f&&f.type==="text"&&N.type==="text"){f.value+=N.value;return}return c.nodes.push(N),N.parent=c,N.prev=f,f=N,N};for(R({type:"bos"});E0){if(c.ranges>0){c.ranges=0;let N=c.nodes.shift();c.nodes=[N,{type:"text",value:TKe(c)}]}R({type:"comma",value:S}),c.commas++;continue}if(S===NKe&&C>0&&c.commas===0){let N=c.nodes;if(C===0||N.length===0){R({type:"text",value:S});continue}if(f.type==="dot"){if(c.range=[],f.value+=S,f.type="range",c.nodes.length!==3&&c.nodes.length!==5){c.invalid=!0,c.ranges=0,f.type="text";continue}c.ranges++,c.args=[];continue}if(f.type==="range"){N.pop();let U=N[N.length-1];U.value+=f.value+S,f=U,c.ranges--;continue}R({type:"dot",value:S});continue}R({type:"text",value:S})}do if(c=n.pop(),c.type!=="root"){c.nodes.forEach(W=>{W.nodes||(W.type==="open"&&(W.isOpen=!0),W.type==="close"&&(W.isClose=!0),W.nodes||(W.type="text"),W.invalid=!0)});let N=n[n.length-1],U=N.nodes.indexOf(c);N.nodes.splice(U,1,...c.nodes)}while(n.length>0);return R({type:"eos"}),a};koe.exports=qKe});var Foe=_((fFt,Roe)=>{"use strict";var Toe=fk(),WKe=Coe(),YKe=voe(),VKe=Qoe(),jl=(t,e={})=>{let r=[];if(Array.isArray(t))for(let s of t){let a=jl.create(s,e);Array.isArray(a)?r.push(...a):r.push(a)}else r=[].concat(jl.create(t,e));return e&&e.expand===!0&&e.nodupes===!0&&(r=[...new Set(r)]),r};jl.parse=(t,e={})=>VKe(t,e);jl.stringify=(t,e={})=>Toe(typeof t=="string"?jl.parse(t,e):t,e);jl.compile=(t,e={})=>(typeof t=="string"&&(t=jl.parse(t,e)),WKe(t,e));jl.expand=(t,e={})=>{typeof t=="string"&&(t=jl.parse(t,e));let r=YKe(t,e);return e.noempty===!0&&(r=r.filter(Boolean)),e.nodupes===!0&&(r=[...new Set(r)]),r};jl.create=(t,e={})=>t===""||t.length<3?[t]:e.expand!==!0?jl.compile(t,e):jl.expand(t,e);Roe.exports=jl});var EB=_((AFt,Uoe)=>{"use strict";var JKe=Ie("path"),Vf="\\\\/",Noe=`[^${Vf}]`,Dp="\\.",KKe="\\+",zKe="\\?",pk="\\/",XKe="(?=.)",Ooe="[^/]",u4=`(?:${pk}|$)`,Loe=`(?:^|${pk})`,f4=`${Dp}{1,2}${u4}`,ZKe=`(?!${Dp})`,$Ke=`(?!${Loe}${f4})`,eze=`(?!${Dp}{0,1}${u4})`,tze=`(?!${f4})`,rze=`[^.${pk}]`,nze=`${Ooe}*?`,Moe={DOT_LITERAL:Dp,PLUS_LITERAL:KKe,QMARK_LITERAL:zKe,SLASH_LITERAL:pk,ONE_CHAR:XKe,QMARK:Ooe,END_ANCHOR:u4,DOTS_SLASH:f4,NO_DOT:ZKe,NO_DOTS:$Ke,NO_DOT_SLASH:eze,NO_DOTS_SLASH:tze,QMARK_NO_DOT:rze,STAR:nze,START_ANCHOR:Loe},ize={...Moe,SLASH_LITERAL:`[${Vf}]`,QMARK:Noe,STAR:`${Noe}*?`,DOTS_SLASH:`${Dp}{1,2}(?:[${Vf}]|$)`,NO_DOT:`(?!${Dp})`,NO_DOTS:`(?!(?:^|[${Vf}])${Dp}{1,2}(?:[${Vf}]|$))`,NO_DOT_SLASH:`(?!${Dp}{0,1}(?:[${Vf}]|$))`,NO_DOTS_SLASH:`(?!${Dp}{1,2}(?:[${Vf}]|$))`,QMARK_NO_DOT:`[^.${Vf}]`,START_ANCHOR:`(?:^|[${Vf}])`,END_ANCHOR:`(?:[${Vf}]|$)`},sze={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};Uoe.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:sze,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:JKe.sep,extglobChars(t){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${t.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(t){return t===!0?ize:Moe}}});var IB=_(ol=>{"use strict";var oze=Ie("path"),aze=process.platform==="win32",{REGEX_BACKSLASH:lze,REGEX_REMOVE_BACKSLASH:cze,REGEX_SPECIAL_CHARS:uze,REGEX_SPECIAL_CHARS_GLOBAL:fze}=EB();ol.isObject=t=>t!==null&&typeof t=="object"&&!Array.isArray(t);ol.hasRegexChars=t=>uze.test(t);ol.isRegexChar=t=>t.length===1&&ol.hasRegexChars(t);ol.escapeRegex=t=>t.replace(fze,"\\$1");ol.toPosixSlashes=t=>t.replace(lze,"/");ol.removeBackslashes=t=>t.replace(cze,e=>e==="\\"?"":e);ol.supportsLookbehinds=()=>{let t=process.version.slice(1).split(".").map(Number);return t.length===3&&t[0]>=9||t[0]===8&&t[1]>=10};ol.isWindows=t=>t&&typeof t.windows=="boolean"?t.windows:aze===!0||oze.sep==="\\";ol.escapeLast=(t,e,r)=>{let s=t.lastIndexOf(e,r);return s===-1?t:t[s-1]==="\\"?ol.escapeLast(t,e,s-1):`${t.slice(0,s)}\\${t.slice(s)}`};ol.removePrefix=(t,e={})=>{let r=t;return r.startsWith("./")&&(r=r.slice(2),e.prefix="./"),r};ol.wrapOutput=(t,e={},r={})=>{let s=r.contains?"":"^",a=r.contains?"":"$",n=`${s}(?:${t})${a}`;return e.negated===!0&&(n=`(?:^(?!${n}).*$)`),n}});var Voe=_((hFt,Yoe)=>{"use strict";var _oe=IB(),{CHAR_ASTERISK:A4,CHAR_AT:Aze,CHAR_BACKWARD_SLASH:CB,CHAR_COMMA:pze,CHAR_DOT:p4,CHAR_EXCLAMATION_MARK:h4,CHAR_FORWARD_SLASH:Woe,CHAR_LEFT_CURLY_BRACE:g4,CHAR_LEFT_PARENTHESES:d4,CHAR_LEFT_SQUARE_BRACKET:hze,CHAR_PLUS:gze,CHAR_QUESTION_MARK:Hoe,CHAR_RIGHT_CURLY_BRACE:dze,CHAR_RIGHT_PARENTHESES:joe,CHAR_RIGHT_SQUARE_BRACKET:mze}=EB(),Goe=t=>t===Woe||t===CB,qoe=t=>{t.isPrefix!==!0&&(t.depth=t.isGlobstar?1/0:1)},yze=(t,e)=>{let r=e||{},s=t.length-1,a=r.parts===!0||r.scanToEnd===!0,n=[],c=[],f=[],p=t,h=-1,E=0,C=0,S=!1,P=!1,I=!1,R=!1,N=!1,U=!1,W=!1,ee=!1,ie=!1,ue=!1,le=0,me,pe,Be={value:"",depth:0,isGlob:!1},Ce=()=>h>=s,g=()=>p.charCodeAt(h+1),we=()=>(me=pe,p.charCodeAt(++h));for(;h0&&(Ae=p.slice(0,E),p=p.slice(E),C-=E),ye&&I===!0&&C>0?(ye=p.slice(0,C),se=p.slice(C)):I===!0?(ye="",se=p):ye=p,ye&&ye!==""&&ye!=="/"&&ye!==p&&Goe(ye.charCodeAt(ye.length-1))&&(ye=ye.slice(0,-1)),r.unescape===!0&&(se&&(se=_oe.removeBackslashes(se)),ye&&W===!0&&(ye=_oe.removeBackslashes(ye)));let Z={prefix:Ae,input:t,start:E,base:ye,glob:se,isBrace:S,isBracket:P,isGlob:I,isExtglob:R,isGlobstar:N,negated:ee,negatedExtglob:ie};if(r.tokens===!0&&(Z.maxDepth=0,Goe(pe)||c.push(Be),Z.tokens=c),r.parts===!0||r.tokens===!0){let De;for(let Re=0;Re{"use strict";var hk=EB(),Gl=IB(),{MAX_LENGTH:gk,POSIX_REGEX_SOURCE:Eze,REGEX_NON_SPECIAL_CHARS:Ize,REGEX_SPECIAL_CHARS_BACKREF:Cze,REPLACEMENTS:Joe}=hk,wze=(t,e)=>{if(typeof e.expandRange=="function")return e.expandRange(...t,e);t.sort();let r=`[${t.join("-")}]`;try{new RegExp(r)}catch{return t.map(a=>Gl.escapeRegex(a)).join("..")}return r},FE=(t,e)=>`Missing ${t}: "${e}" - use "\\\\${e}" to match literal characters`,m4=(t,e)=>{if(typeof t!="string")throw new TypeError("Expected a string");t=Joe[t]||t;let r={...e},s=typeof r.maxLength=="number"?Math.min(gk,r.maxLength):gk,a=t.length;if(a>s)throw new SyntaxError(`Input length: ${a}, exceeds maximum allowed length: ${s}`);let n={type:"bos",value:"",output:r.prepend||""},c=[n],f=r.capture?"":"?:",p=Gl.isWindows(e),h=hk.globChars(p),E=hk.extglobChars(h),{DOT_LITERAL:C,PLUS_LITERAL:S,SLASH_LITERAL:P,ONE_CHAR:I,DOTS_SLASH:R,NO_DOT:N,NO_DOT_SLASH:U,NO_DOTS_SLASH:W,QMARK:ee,QMARK_NO_DOT:ie,STAR:ue,START_ANCHOR:le}=h,me=x=>`(${f}(?:(?!${le}${x.dot?R:C}).)*?)`,pe=r.dot?"":N,Be=r.dot?ee:ie,Ce=r.bash===!0?me(r):ue;r.capture&&(Ce=`(${Ce})`),typeof r.noext=="boolean"&&(r.noextglob=r.noext);let g={input:t,index:-1,start:0,dot:r.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:c};t=Gl.removePrefix(t,g),a=t.length;let we=[],ye=[],Ae=[],se=n,Z,De=()=>g.index===a-1,Re=g.peek=(x=1)=>t[g.index+x],mt=g.advance=()=>t[++g.index]||"",j=()=>t.slice(g.index+1),rt=(x="",w=0)=>{g.consumed+=x,g.index+=w},Fe=x=>{g.output+=x.output!=null?x.output:x.value,rt(x.value)},Ne=()=>{let x=1;for(;Re()==="!"&&(Re(2)!=="("||Re(3)==="?");)mt(),g.start++,x++;return x%2===0?!1:(g.negated=!0,g.start++,!0)},Pe=x=>{g[x]++,Ae.push(x)},Ve=x=>{g[x]--,Ae.pop()},ke=x=>{if(se.type==="globstar"){let w=g.braces>0&&(x.type==="comma"||x.type==="brace"),b=x.extglob===!0||we.length&&(x.type==="pipe"||x.type==="paren");x.type!=="slash"&&x.type!=="paren"&&!w&&!b&&(g.output=g.output.slice(0,-se.output.length),se.type="star",se.value="*",se.output=Ce,g.output+=se.output)}if(we.length&&x.type!=="paren"&&(we[we.length-1].inner+=x.value),(x.value||x.output)&&Fe(x),se&&se.type==="text"&&x.type==="text"){se.value+=x.value,se.output=(se.output||"")+x.value;return}x.prev=se,c.push(x),se=x},it=(x,w)=>{let b={...E[w],conditions:1,inner:""};b.prev=se,b.parens=g.parens,b.output=g.output;let y=(r.capture?"(":"")+b.open;Pe("parens"),ke({type:x,value:w,output:g.output?"":I}),ke({type:"paren",extglob:!0,value:mt(),output:y}),we.push(b)},Ue=x=>{let w=x.close+(r.capture?")":""),b;if(x.type==="negate"){let y=Ce;if(x.inner&&x.inner.length>1&&x.inner.includes("/")&&(y=me(r)),(y!==Ce||De()||/^\)+$/.test(j()))&&(w=x.close=`)$))${y}`),x.inner.includes("*")&&(b=j())&&/^\.[^\\/.]+$/.test(b)){let F=m4(b,{...e,fastpaths:!1}).output;w=x.close=`)${F})${y})`}x.prev.type==="bos"&&(g.negatedExtglob=!0)}ke({type:"paren",extglob:!0,value:Z,output:w}),Ve("parens")};if(r.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(t)){let x=!1,w=t.replace(Cze,(b,y,F,z,X,$)=>z==="\\"?(x=!0,b):z==="?"?y?y+z+(X?ee.repeat(X.length):""):$===0?Be+(X?ee.repeat(X.length):""):ee.repeat(F.length):z==="."?C.repeat(F.length):z==="*"?y?y+z+(X?Ce:""):Ce:y?b:`\\${b}`);return x===!0&&(r.unescape===!0?w=w.replace(/\\/g,""):w=w.replace(/\\+/g,b=>b.length%2===0?"\\\\":b?"\\":"")),w===t&&r.contains===!0?(g.output=t,g):(g.output=Gl.wrapOutput(w,g,e),g)}for(;!De();){if(Z=mt(),Z==="\0")continue;if(Z==="\\"){let b=Re();if(b==="/"&&r.bash!==!0||b==="."||b===";")continue;if(!b){Z+="\\",ke({type:"text",value:Z});continue}let y=/^\\+/.exec(j()),F=0;if(y&&y[0].length>2&&(F=y[0].length,g.index+=F,F%2!==0&&(Z+="\\")),r.unescape===!0?Z=mt():Z+=mt(),g.brackets===0){ke({type:"text",value:Z});continue}}if(g.brackets>0&&(Z!=="]"||se.value==="["||se.value==="[^")){if(r.posix!==!1&&Z===":"){let b=se.value.slice(1);if(b.includes("[")&&(se.posix=!0,b.includes(":"))){let y=se.value.lastIndexOf("["),F=se.value.slice(0,y),z=se.value.slice(y+2),X=Eze[z];if(X){se.value=F+X,g.backtrack=!0,mt(),!n.output&&c.indexOf(se)===1&&(n.output=I);continue}}}(Z==="["&&Re()!==":"||Z==="-"&&Re()==="]")&&(Z=`\\${Z}`),Z==="]"&&(se.value==="["||se.value==="[^")&&(Z=`\\${Z}`),r.posix===!0&&Z==="!"&&se.value==="["&&(Z="^"),se.value+=Z,Fe({value:Z});continue}if(g.quotes===1&&Z!=='"'){Z=Gl.escapeRegex(Z),se.value+=Z,Fe({value:Z});continue}if(Z==='"'){g.quotes=g.quotes===1?0:1,r.keepQuotes===!0&&ke({type:"text",value:Z});continue}if(Z==="("){Pe("parens"),ke({type:"paren",value:Z});continue}if(Z===")"){if(g.parens===0&&r.strictBrackets===!0)throw new SyntaxError(FE("opening","("));let b=we[we.length-1];if(b&&g.parens===b.parens+1){Ue(we.pop());continue}ke({type:"paren",value:Z,output:g.parens?")":"\\)"}),Ve("parens");continue}if(Z==="["){if(r.nobracket===!0||!j().includes("]")){if(r.nobracket!==!0&&r.strictBrackets===!0)throw new SyntaxError(FE("closing","]"));Z=`\\${Z}`}else Pe("brackets");ke({type:"bracket",value:Z});continue}if(Z==="]"){if(r.nobracket===!0||se&&se.type==="bracket"&&se.value.length===1){ke({type:"text",value:Z,output:`\\${Z}`});continue}if(g.brackets===0){if(r.strictBrackets===!0)throw new SyntaxError(FE("opening","["));ke({type:"text",value:Z,output:`\\${Z}`});continue}Ve("brackets");let b=se.value.slice(1);if(se.posix!==!0&&b[0]==="^"&&!b.includes("/")&&(Z=`/${Z}`),se.value+=Z,Fe({value:Z}),r.literalBrackets===!1||Gl.hasRegexChars(b))continue;let y=Gl.escapeRegex(se.value);if(g.output=g.output.slice(0,-se.value.length),r.literalBrackets===!0){g.output+=y,se.value=y;continue}se.value=`(${f}${y}|${se.value})`,g.output+=se.value;continue}if(Z==="{"&&r.nobrace!==!0){Pe("braces");let b={type:"brace",value:Z,output:"(",outputIndex:g.output.length,tokensIndex:g.tokens.length};ye.push(b),ke(b);continue}if(Z==="}"){let b=ye[ye.length-1];if(r.nobrace===!0||!b){ke({type:"text",value:Z,output:Z});continue}let y=")";if(b.dots===!0){let F=c.slice(),z=[];for(let X=F.length-1;X>=0&&(c.pop(),F[X].type!=="brace");X--)F[X].type!=="dots"&&z.unshift(F[X].value);y=wze(z,r),g.backtrack=!0}if(b.comma!==!0&&b.dots!==!0){let F=g.output.slice(0,b.outputIndex),z=g.tokens.slice(b.tokensIndex);b.value=b.output="\\{",Z=y="\\}",g.output=F;for(let X of z)g.output+=X.output||X.value}ke({type:"brace",value:Z,output:y}),Ve("braces"),ye.pop();continue}if(Z==="|"){we.length>0&&we[we.length-1].conditions++,ke({type:"text",value:Z});continue}if(Z===","){let b=Z,y=ye[ye.length-1];y&&Ae[Ae.length-1]==="braces"&&(y.comma=!0,b="|"),ke({type:"comma",value:Z,output:b});continue}if(Z==="/"){if(se.type==="dot"&&g.index===g.start+1){g.start=g.index+1,g.consumed="",g.output="",c.pop(),se=n;continue}ke({type:"slash",value:Z,output:P});continue}if(Z==="."){if(g.braces>0&&se.type==="dot"){se.value==="."&&(se.output=C);let b=ye[ye.length-1];se.type="dots",se.output+=Z,se.value+=Z,b.dots=!0;continue}if(g.braces+g.parens===0&&se.type!=="bos"&&se.type!=="slash"){ke({type:"text",value:Z,output:C});continue}ke({type:"dot",value:Z,output:C});continue}if(Z==="?"){if(!(se&&se.value==="(")&&r.noextglob!==!0&&Re()==="("&&Re(2)!=="?"){it("qmark",Z);continue}if(se&&se.type==="paren"){let y=Re(),F=Z;if(y==="<"&&!Gl.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(se.value==="("&&!/[!=<:]/.test(y)||y==="<"&&!/<([!=]|\w+>)/.test(j()))&&(F=`\\${Z}`),ke({type:"text",value:Z,output:F});continue}if(r.dot!==!0&&(se.type==="slash"||se.type==="bos")){ke({type:"qmark",value:Z,output:ie});continue}ke({type:"qmark",value:Z,output:ee});continue}if(Z==="!"){if(r.noextglob!==!0&&Re()==="("&&(Re(2)!=="?"||!/[!=<:]/.test(Re(3)))){it("negate",Z);continue}if(r.nonegate!==!0&&g.index===0){Ne();continue}}if(Z==="+"){if(r.noextglob!==!0&&Re()==="("&&Re(2)!=="?"){it("plus",Z);continue}if(se&&se.value==="("||r.regex===!1){ke({type:"plus",value:Z,output:S});continue}if(se&&(se.type==="bracket"||se.type==="paren"||se.type==="brace")||g.parens>0){ke({type:"plus",value:Z});continue}ke({type:"plus",value:S});continue}if(Z==="@"){if(r.noextglob!==!0&&Re()==="("&&Re(2)!=="?"){ke({type:"at",extglob:!0,value:Z,output:""});continue}ke({type:"text",value:Z});continue}if(Z!=="*"){(Z==="$"||Z==="^")&&(Z=`\\${Z}`);let b=Ize.exec(j());b&&(Z+=b[0],g.index+=b[0].length),ke({type:"text",value:Z});continue}if(se&&(se.type==="globstar"||se.star===!0)){se.type="star",se.star=!0,se.value+=Z,se.output=Ce,g.backtrack=!0,g.globstar=!0,rt(Z);continue}let x=j();if(r.noextglob!==!0&&/^\([^?]/.test(x)){it("star",Z);continue}if(se.type==="star"){if(r.noglobstar===!0){rt(Z);continue}let b=se.prev,y=b.prev,F=b.type==="slash"||b.type==="bos",z=y&&(y.type==="star"||y.type==="globstar");if(r.bash===!0&&(!F||x[0]&&x[0]!=="/")){ke({type:"star",value:Z,output:""});continue}let X=g.braces>0&&(b.type==="comma"||b.type==="brace"),$=we.length&&(b.type==="pipe"||b.type==="paren");if(!F&&b.type!=="paren"&&!X&&!$){ke({type:"star",value:Z,output:""});continue}for(;x.slice(0,3)==="/**";){let oe=t[g.index+4];if(oe&&oe!=="/")break;x=x.slice(3),rt("/**",3)}if(b.type==="bos"&&De()){se.type="globstar",se.value+=Z,se.output=me(r),g.output=se.output,g.globstar=!0,rt(Z);continue}if(b.type==="slash"&&b.prev.type!=="bos"&&!z&&De()){g.output=g.output.slice(0,-(b.output+se.output).length),b.output=`(?:${b.output}`,se.type="globstar",se.output=me(r)+(r.strictSlashes?")":"|$)"),se.value+=Z,g.globstar=!0,g.output+=b.output+se.output,rt(Z);continue}if(b.type==="slash"&&b.prev.type!=="bos"&&x[0]==="/"){let oe=x[1]!==void 0?"|$":"";g.output=g.output.slice(0,-(b.output+se.output).length),b.output=`(?:${b.output}`,se.type="globstar",se.output=`${me(r)}${P}|${P}${oe})`,se.value+=Z,g.output+=b.output+se.output,g.globstar=!0,rt(Z+mt()),ke({type:"slash",value:"/",output:""});continue}if(b.type==="bos"&&x[0]==="/"){se.type="globstar",se.value+=Z,se.output=`(?:^|${P}|${me(r)}${P})`,g.output=se.output,g.globstar=!0,rt(Z+mt()),ke({type:"slash",value:"/",output:""});continue}g.output=g.output.slice(0,-se.output.length),se.type="globstar",se.output=me(r),se.value+=Z,g.output+=se.output,g.globstar=!0,rt(Z);continue}let w={type:"star",value:Z,output:Ce};if(r.bash===!0){w.output=".*?",(se.type==="bos"||se.type==="slash")&&(w.output=pe+w.output),ke(w);continue}if(se&&(se.type==="bracket"||se.type==="paren")&&r.regex===!0){w.output=Z,ke(w);continue}(g.index===g.start||se.type==="slash"||se.type==="dot")&&(se.type==="dot"?(g.output+=U,se.output+=U):r.dot===!0?(g.output+=W,se.output+=W):(g.output+=pe,se.output+=pe),Re()!=="*"&&(g.output+=I,se.output+=I)),ke(w)}for(;g.brackets>0;){if(r.strictBrackets===!0)throw new SyntaxError(FE("closing","]"));g.output=Gl.escapeLast(g.output,"["),Ve("brackets")}for(;g.parens>0;){if(r.strictBrackets===!0)throw new SyntaxError(FE("closing",")"));g.output=Gl.escapeLast(g.output,"("),Ve("parens")}for(;g.braces>0;){if(r.strictBrackets===!0)throw new SyntaxError(FE("closing","}"));g.output=Gl.escapeLast(g.output,"{"),Ve("braces")}if(r.strictSlashes!==!0&&(se.type==="star"||se.type==="bracket")&&ke({type:"maybe_slash",value:"",output:`${P}?`}),g.backtrack===!0){g.output="";for(let x of g.tokens)g.output+=x.output!=null?x.output:x.value,x.suffix&&(g.output+=x.suffix)}return g};m4.fastpaths=(t,e)=>{let r={...e},s=typeof r.maxLength=="number"?Math.min(gk,r.maxLength):gk,a=t.length;if(a>s)throw new SyntaxError(`Input length: ${a}, exceeds maximum allowed length: ${s}`);t=Joe[t]||t;let n=Gl.isWindows(e),{DOT_LITERAL:c,SLASH_LITERAL:f,ONE_CHAR:p,DOTS_SLASH:h,NO_DOT:E,NO_DOTS:C,NO_DOTS_SLASH:S,STAR:P,START_ANCHOR:I}=hk.globChars(n),R=r.dot?C:E,N=r.dot?S:E,U=r.capture?"":"?:",W={negated:!1,prefix:""},ee=r.bash===!0?".*?":P;r.capture&&(ee=`(${ee})`);let ie=pe=>pe.noglobstar===!0?ee:`(${U}(?:(?!${I}${pe.dot?h:c}).)*?)`,ue=pe=>{switch(pe){case"*":return`${R}${p}${ee}`;case".*":return`${c}${p}${ee}`;case"*.*":return`${R}${ee}${c}${p}${ee}`;case"*/*":return`${R}${ee}${f}${p}${N}${ee}`;case"**":return R+ie(r);case"**/*":return`(?:${R}${ie(r)}${f})?${N}${p}${ee}`;case"**/*.*":return`(?:${R}${ie(r)}${f})?${N}${ee}${c}${p}${ee}`;case"**/.*":return`(?:${R}${ie(r)}${f})?${c}${p}${ee}`;default:{let Be=/^(.*?)\.(\w+)$/.exec(pe);if(!Be)return;let Ce=ue(Be[1]);return Ce?Ce+c+Be[2]:void 0}}},le=Gl.removePrefix(t,W),me=ue(le);return me&&r.strictSlashes!==!0&&(me+=`${f}?`),me};Koe.exports=m4});var Zoe=_((dFt,Xoe)=>{"use strict";var Bze=Ie("path"),vze=Voe(),y4=zoe(),E4=IB(),Sze=EB(),Dze=t=>t&&typeof t=="object"&&!Array.isArray(t),Zi=(t,e,r=!1)=>{if(Array.isArray(t)){let E=t.map(S=>Zi(S,e,r));return S=>{for(let P of E){let I=P(S);if(I)return I}return!1}}let s=Dze(t)&&t.tokens&&t.input;if(t===""||typeof t!="string"&&!s)throw new TypeError("Expected pattern to be a non-empty string");let a=e||{},n=E4.isWindows(e),c=s?Zi.compileRe(t,e):Zi.makeRe(t,e,!1,!0),f=c.state;delete c.state;let p=()=>!1;if(a.ignore){let E={...e,ignore:null,onMatch:null,onResult:null};p=Zi(a.ignore,E,r)}let h=(E,C=!1)=>{let{isMatch:S,match:P,output:I}=Zi.test(E,c,e,{glob:t,posix:n}),R={glob:t,state:f,regex:c,posix:n,input:E,output:I,match:P,isMatch:S};return typeof a.onResult=="function"&&a.onResult(R),S===!1?(R.isMatch=!1,C?R:!1):p(E)?(typeof a.onIgnore=="function"&&a.onIgnore(R),R.isMatch=!1,C?R:!1):(typeof a.onMatch=="function"&&a.onMatch(R),C?R:!0)};return r&&(h.state=f),h};Zi.test=(t,e,r,{glob:s,posix:a}={})=>{if(typeof t!="string")throw new TypeError("Expected input to be a string");if(t==="")return{isMatch:!1,output:""};let n=r||{},c=n.format||(a?E4.toPosixSlashes:null),f=t===s,p=f&&c?c(t):t;return f===!1&&(p=c?c(t):t,f=p===s),(f===!1||n.capture===!0)&&(n.matchBase===!0||n.basename===!0?f=Zi.matchBase(t,e,r,a):f=e.exec(p)),{isMatch:!!f,match:f,output:p}};Zi.matchBase=(t,e,r,s=E4.isWindows(r))=>(e instanceof RegExp?e:Zi.makeRe(e,r)).test(Bze.basename(t));Zi.isMatch=(t,e,r)=>Zi(e,r)(t);Zi.parse=(t,e)=>Array.isArray(t)?t.map(r=>Zi.parse(r,e)):y4(t,{...e,fastpaths:!1});Zi.scan=(t,e)=>vze(t,e);Zi.compileRe=(t,e,r=!1,s=!1)=>{if(r===!0)return t.output;let a=e||{},n=a.contains?"":"^",c=a.contains?"":"$",f=`${n}(?:${t.output})${c}`;t&&t.negated===!0&&(f=`^(?!${f}).*$`);let p=Zi.toRegex(f,e);return s===!0&&(p.state=t),p};Zi.makeRe=(t,e={},r=!1,s=!1)=>{if(!t||typeof t!="string")throw new TypeError("Expected a non-empty string");let a={negated:!1,fastpaths:!0};return e.fastpaths!==!1&&(t[0]==="."||t[0]==="*")&&(a.output=y4.fastpaths(t,e)),a.output||(a=y4(t,e)),Zi.compileRe(a,e,r,s)};Zi.toRegex=(t,e)=>{try{let r=e||{};return new RegExp(t,r.flags||(r.nocase?"i":""))}catch(r){if(e&&e.debug===!0)throw r;return/$^/}};Zi.constants=Sze;Xoe.exports=Zi});var eae=_((mFt,$oe)=>{"use strict";$oe.exports=Zoe()});var Go=_((yFt,iae)=>{"use strict";var rae=Ie("util"),nae=Foe(),Jf=eae(),I4=IB(),tae=t=>t===""||t==="./",xi=(t,e,r)=>{e=[].concat(e),t=[].concat(t);let s=new Set,a=new Set,n=new Set,c=0,f=E=>{n.add(E.output),r&&r.onResult&&r.onResult(E)};for(let E=0;E!s.has(E));if(r&&h.length===0){if(r.failglob===!0)throw new Error(`No matches found for "${e.join(", ")}"`);if(r.nonull===!0||r.nullglob===!0)return r.unescape?e.map(E=>E.replace(/\\/g,"")):e}return h};xi.match=xi;xi.matcher=(t,e)=>Jf(t,e);xi.isMatch=(t,e,r)=>Jf(e,r)(t);xi.any=xi.isMatch;xi.not=(t,e,r={})=>{e=[].concat(e).map(String);let s=new Set,a=[],n=f=>{r.onResult&&r.onResult(f),a.push(f.output)},c=new Set(xi(t,e,{...r,onResult:n}));for(let f of a)c.has(f)||s.add(f);return[...s]};xi.contains=(t,e,r)=>{if(typeof t!="string")throw new TypeError(`Expected a string: "${rae.inspect(t)}"`);if(Array.isArray(e))return e.some(s=>xi.contains(t,s,r));if(typeof e=="string"){if(tae(t)||tae(e))return!1;if(t.includes(e)||t.startsWith("./")&&t.slice(2).includes(e))return!0}return xi.isMatch(t,e,{...r,contains:!0})};xi.matchKeys=(t,e,r)=>{if(!I4.isObject(t))throw new TypeError("Expected the first argument to be an object");let s=xi(Object.keys(t),e,r),a={};for(let n of s)a[n]=t[n];return a};xi.some=(t,e,r)=>{let s=[].concat(t);for(let a of[].concat(e)){let n=Jf(String(a),r);if(s.some(c=>n(c)))return!0}return!1};xi.every=(t,e,r)=>{let s=[].concat(t);for(let a of[].concat(e)){let n=Jf(String(a),r);if(!s.every(c=>n(c)))return!1}return!0};xi.all=(t,e,r)=>{if(typeof t!="string")throw new TypeError(`Expected a string: "${rae.inspect(t)}"`);return[].concat(e).every(s=>Jf(s,r)(t))};xi.capture=(t,e,r)=>{let s=I4.isWindows(r),n=Jf.makeRe(String(t),{...r,capture:!0}).exec(s?I4.toPosixSlashes(e):e);if(n)return n.slice(1).map(c=>c===void 0?"":c)};xi.makeRe=(...t)=>Jf.makeRe(...t);xi.scan=(...t)=>Jf.scan(...t);xi.parse=(t,e)=>{let r=[];for(let s of[].concat(t||[]))for(let a of nae(String(s),e))r.push(Jf.parse(a,e));return r};xi.braces=(t,e)=>{if(typeof t!="string")throw new TypeError("Expected a string");return e&&e.nobrace===!0||!/\{.*\}/.test(t)?[t]:nae(t,e)};xi.braceExpand=(t,e)=>{if(typeof t!="string")throw new TypeError("Expected a string");return xi.braces(t,{...e,expand:!0})};iae.exports=xi});var oae=_((EFt,sae)=>{"use strict";sae.exports=({onlyFirst:t=!1}={})=>{let e=["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)","(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"].join("|");return new RegExp(e,t?void 0:"g")}});var dk=_((IFt,aae)=>{"use strict";var bze=oae();aae.exports=t=>typeof t=="string"?t.replace(bze(),""):t});function lae(t){return Number.isSafeInteger(t)&&t>=0}var cae=Xe(()=>{});function uae(t){return t!=null&&typeof t!="function"&&lae(t.length)}var fae=Xe(()=>{cae()});function bc(t){return t==="__proto__"}var wB=Xe(()=>{});function NE(t){switch(typeof t){case"number":case"symbol":return!1;case"string":return t.includes(".")||t.includes("[")||t.includes("]")}}var mk=Xe(()=>{});function OE(t){return typeof t=="string"||typeof t=="symbol"?t:Object.is(t?.valueOf?.(),-0)?"-0":String(t)}var yk=Xe(()=>{});function Mu(t){let e=[],r=t.length;if(r===0)return e;let s=0,a="",n="",c=!1;for(t.charCodeAt(0)===46&&(e.push(""),s++);s{});function va(t,e,r){if(t==null)return r;switch(typeof e){case"string":{if(bc(e))return r;let s=t[e];return s===void 0?NE(e)?va(t,Mu(e),r):r:s}case"number":case"symbol":{typeof e=="number"&&(e=OE(e));let s=t[e];return s===void 0?r:s}default:{if(Array.isArray(e))return Pze(t,e,r);if(Object.is(e?.valueOf(),-0)?e="-0":e=String(e),bc(e))return r;let s=t[e];return s===void 0?r:s}}}function Pze(t,e,r){if(e.length===0)return r;let s=t;for(let a=0;a{wB();mk();yk();LE()});function C4(t){return t!==null&&(typeof t=="object"||typeof t=="function")}var Aae=Xe(()=>{});function ME(t){return t==null||typeof t!="object"&&typeof t!="function"}var Ik=Xe(()=>{});function Ck(t,e){return t===e||Number.isNaN(t)&&Number.isNaN(e)}var w4=Xe(()=>{});function Wd(t){return Object.getOwnPropertySymbols(t).filter(e=>Object.prototype.propertyIsEnumerable.call(t,e))}var wk=Xe(()=>{});function Yd(t){return t==null?t===void 0?"[object Undefined]":"[object Null]":Object.prototype.toString.call(t)}var Bk=Xe(()=>{});var vk,UE,_E,HE,Vd,Sk,Dk,bk,Pk,xk,pae,kk,jE,hae,Qk,Tk,Rk,Fk,Nk,gae,Ok,Lk,Mk,dae,Uk,_k,Hk=Xe(()=>{vk="[object RegExp]",UE="[object String]",_E="[object Number]",HE="[object Boolean]",Vd="[object Arguments]",Sk="[object Symbol]",Dk="[object Date]",bk="[object Map]",Pk="[object Set]",xk="[object Array]",pae="[object Function]",kk="[object ArrayBuffer]",jE="[object Object]",hae="[object Error]",Qk="[object DataView]",Tk="[object Uint8Array]",Rk="[object Uint8ClampedArray]",Fk="[object Uint16Array]",Nk="[object Uint32Array]",gae="[object BigUint64Array]",Ok="[object Int8Array]",Lk="[object Int16Array]",Mk="[object Int32Array]",dae="[object BigInt64Array]",Uk="[object Float32Array]",_k="[object Float64Array]"});function GE(t){return ArrayBuffer.isView(t)&&!(t instanceof DataView)}var jk=Xe(()=>{});function mae(t,e){return u0(t,void 0,t,new Map,e)}function u0(t,e,r,s=new Map,a=void 0){let n=a?.(t,e,r,s);if(n!=null)return n;if(ME(t))return t;if(s.has(t))return s.get(t);if(Array.isArray(t)){let c=new Array(t.length);s.set(t,c);for(let f=0;f{wk();Bk();Hk();Ik();jk()});function yae(t){return u0(t,void 0,t,new Map,void 0)}var Eae=Xe(()=>{B4()});function Iae(t,e){return mae(t,(r,s,a,n)=>{let c=e?.(r,s,a,n);if(c!=null)return c;if(typeof t=="object")switch(Object.prototype.toString.call(t)){case _E:case UE:case HE:{let f=new t.constructor(t?.valueOf());return c0(f,t),f}case Vd:{let f={};return c0(f,t),f.length=t.length,f[Symbol.iterator]=t[Symbol.iterator],f}default:return}})}var Cae=Xe(()=>{B4();Hk()});function f0(t){return Iae(t)}var v4=Xe(()=>{Cae()});function Gk(t,e=Number.MAX_SAFE_INTEGER){switch(typeof t){case"number":return Number.isInteger(t)&&t>=0&&t{kze=/^(?:0|[1-9]\d*)$/});function BB(t){return t!==null&&typeof t=="object"&&Yd(t)==="[object Arguments]"}var D4=Xe(()=>{Bk()});function vB(t,e){let r;if(Array.isArray(e)?r=e:typeof e=="string"&&NE(e)&&t?.[e]==null?r=Mu(e):r=[e],r.length===0)return!1;let s=t;for(let a=0;a{mk();S4();D4();LE()});function P4(t){return typeof t=="object"&&t!==null}var wae=Xe(()=>{});function Bae(t){return typeof t=="symbol"||t instanceof Symbol}var vae=Xe(()=>{});function Sae(t,e){return Array.isArray(t)?!1:typeof t=="number"||typeof t=="boolean"||t==null||Bae(t)?!0:typeof t=="string"&&(Tze.test(t)||!Qze.test(t))||e!=null&&Object.hasOwn(e,t)}var Qze,Tze,Dae=Xe(()=>{vae();Qze=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Tze=/^\w*$/});function A0(t,e){if(t==null)return!0;switch(typeof e){case"symbol":case"number":case"object":{if(Array.isArray(e))return bae(t,e);if(typeof e=="number"?e=OE(e):typeof e=="object"&&(Object.is(e?.valueOf(),-0)?e="-0":e=String(e)),bc(e))return!1;if(t?.[e]===void 0)return!0;try{return delete t[e],!0}catch{return!1}}case"string":{if(t?.[e]===void 0&&NE(e))return bae(t,Mu(e));if(bc(e))return!1;try{return delete t[e],!0}catch{return!1}}}}function bae(t,e){let r=va(t,e.slice(0,-1),t),s=e[e.length-1];if(r?.[s]===void 0)return!0;if(bc(s))return!1;try{return delete r[s],!0}catch{return!1}}var x4=Xe(()=>{Ek();wB();mk();yk();LE()});function Pae(t){return t==null}var xae=Xe(()=>{});var kae,Qae=Xe(()=>{w4();kae=(t,e,r)=>{let s=t[e];(!(Object.hasOwn(t,e)&&Ck(s,r))||r===void 0&&!(e in t))&&(t[e]=r)}});function Tae(t,e,r,s){if(t==null&&!C4(t))return t;let a=Sae(e,t)?[e]:Array.isArray(e)?e:typeof e=="string"?Mu(e):[e],n=t;for(let c=0;c{wB();Qae();S4();Dae();yk();Aae();LE()});function Jd(t,e,r){return Tae(t,e,()=>r,()=>{})}var k4=Xe(()=>{Rae()});function Fae(t,e=0,r={}){typeof r!="object"&&(r={});let s=null,a=null,n=null,c=0,f=null,p,{leading:h=!1,trailing:E=!0,maxWait:C}=r,S="maxWait"in r,P=S?Math.max(Number(C)||0,e):0,I=ue=>(s!==null&&(p=t.apply(a,s)),s=a=null,c=ue,p),R=ue=>(c=ue,f=setTimeout(ee,e),h&&s!==null?I(ue):p),N=ue=>(f=null,E&&s!==null?I(ue):p),U=ue=>{if(n===null)return!0;let le=ue-n,me=le>=e||le<0,pe=S&&ue-c>=P;return me||pe},W=ue=>{let le=n===null?0:ue-n,me=e-le,pe=P-(ue-c);return S?Math.min(me,pe):me},ee=()=>{let ue=Date.now();if(U(ue))return N(ue);f=setTimeout(ee,W(ue))},ie=function(...ue){let le=Date.now(),me=U(le);if(s=ue,a=this,n=le,me){if(f===null)return R(le);if(S)return clearTimeout(f),f=setTimeout(ee,e),I(le)}return f===null&&(f=setTimeout(ee,e)),p};return ie.cancel=()=>{f!==null&&clearTimeout(f),c=0,n=s=a=f=null},ie.flush=()=>f===null?p:N(Date.now()),ie}var Nae=Xe(()=>{});function Q4(t,e=0,r={}){let{leading:s=!0,trailing:a=!0}=r;return Fae(t,e,{leading:s,maxWait:e,trailing:a})}var Oae=Xe(()=>{Nae()});function T4(t){if(t==null)return"";if(typeof t=="string")return t;if(Array.isArray(t))return t.map(T4).join(",");let e=String(t);return e==="0"&&Object.is(Number(t),-0)?"-0":e}var Lae=Xe(()=>{});function R4(t){if(!t||typeof t!="object")return!1;let e=Object.getPrototypeOf(t);return e===null||e===Object.prototype||Object.getPrototypeOf(e)===null?Object.prototype.toString.call(t)==="[object Object]":!1}var Mae=Xe(()=>{});function Uae(t,e,r){return SB(t,e,void 0,void 0,void 0,void 0,r)}function SB(t,e,r,s,a,n,c){let f=c(t,e,r,s,a,n);if(f!==void 0)return f;if(typeof t==typeof e)switch(typeof t){case"bigint":case"string":case"boolean":case"symbol":case"undefined":return t===e;case"number":return t===e||Object.is(t,e);case"function":return t===e;case"object":return DB(t,e,n,c)}return DB(t,e,n,c)}function DB(t,e,r,s){if(Object.is(t,e))return!0;let a=Yd(t),n=Yd(e);if(a===Vd&&(a=jE),n===Vd&&(n=jE),a!==n)return!1;switch(a){case UE:return t.toString()===e.toString();case _E:{let p=t.valueOf(),h=e.valueOf();return Ck(p,h)}case HE:case Dk:case Sk:return Object.is(t.valueOf(),e.valueOf());case vk:return t.source===e.source&&t.flags===e.flags;case pae:return t===e}r=r??new Map;let c=r.get(t),f=r.get(e);if(c!=null&&f!=null)return c===e;r.set(t,e),r.set(e,t);try{switch(a){case bk:{if(t.size!==e.size)return!1;for(let[p,h]of t.entries())if(!e.has(p)||!SB(h,e.get(p),p,t,e,r,s))return!1;return!0}case Pk:{if(t.size!==e.size)return!1;let p=Array.from(t.values()),h=Array.from(e.values());for(let E=0;ESB(C,P,void 0,t,e,r,s));if(S===-1)return!1;h.splice(S,1)}return!0}case xk:case Tk:case Rk:case Fk:case Nk:case gae:case Ok:case Lk:case Mk:case dae:case Uk:case _k:{if(typeof Buffer<"u"&&Buffer.isBuffer(t)!==Buffer.isBuffer(e)||t.length!==e.length)return!1;for(let p=0;p{Mae();wk();Bk();Hk();w4()});function Hae(){}var jae=Xe(()=>{});function F4(t,e){return Uae(t,e,Hae)}var Gae=Xe(()=>{_ae();jae()});function qae(t){return GE(t)}var Wae=Xe(()=>{jk()});function Yae(t){if(typeof t!="object"||t==null)return!1;if(Object.getPrototypeOf(t)===null)return!0;if(Object.prototype.toString.call(t)!=="[object Object]"){let r=t[Symbol.toStringTag];return r==null||!Object.getOwnPropertyDescriptor(t,Symbol.toStringTag)?.writable?!1:t.toString()===`[object ${r}]`}let e=t;for(;Object.getPrototypeOf(e)!==null;)e=Object.getPrototypeOf(e);return Object.getPrototypeOf(t)===e}var Vae=Xe(()=>{});function Jae(t){if(ME(t))return t;if(Array.isArray(t)||GE(t)||t instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&t instanceof SharedArrayBuffer)return t.slice(0);let e=Object.getPrototypeOf(t),r=e.constructor;if(t instanceof Date||t instanceof Map||t instanceof Set)return new r(t);if(t instanceof RegExp){let s=new r(t);return s.lastIndex=t.lastIndex,s}if(t instanceof DataView)return new r(t.buffer.slice(0));if(t instanceof Error){let s=new r(t.message);return s.stack=t.stack,s.name=t.name,s.cause=t.cause,s}if(typeof File<"u"&&t instanceof File)return new r([t],t.name,{type:t.type,lastModified:t.lastModified});if(typeof t=="object"){let s=Object.create(e);return Object.assign(s,t)}return t}var Kae=Xe(()=>{Ik();jk()});function N4(t,...e){let r=e.slice(0,-1),s=e[e.length-1],a=t;for(let n=0;n{v4();wB();Kae();Ik();wk();D4();wae();Vae();Wae()});function O4(t,...e){if(t==null)return{};let r=yae(t);for(let s=0;s{x4();Eae()});function Kd(t,...e){if(Pae(t))return{};let r={};for(let s=0;s{Ek();b4();k4();fae();xae()});function $ae(t){return t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()}var ele=Xe(()=>{});function bB(t){return $ae(T4(t))}var tle=Xe(()=>{ele();Lae()});var ql=Xe(()=>{Oae();Gae();v4();Ek();b4();zae();Xae();Zae();k4();x4();tle();LE()});var je={};Vt(je,{AsyncActions:()=>U4,BufferStream:()=>M4,CachingStrategy:()=>fle,DefaultStream:()=>_4,allSettledSafe:()=>Uu,assertNever:()=>G4,bufferStream:()=>WE,buildIgnorePattern:()=>Uze,convertMapsToIndexableObjects:()=>Yk,dynamicRequire:()=>Pp,escapeRegExp:()=>Fze,getArrayWithDefault:()=>xB,getFactoryWithDefault:()=>Yl,getMapWithDefault:()=>q4,getSetWithDefault:()=>bp,groupBy:()=>jze,isIndexableObject:()=>L4,isPathLike:()=>_ze,isTaggedYarnVersion:()=>Rze,makeDeferred:()=>lle,mapAndFilter:()=>Wl,mapAndFind:()=>p0,mergeIntoTarget:()=>ple,overrideType:()=>Nze,parseBoolean:()=>kB,parseDuration:()=>Jk,parseInt:()=>YE,parseOptionalBoolean:()=>Ale,plural:()=>Wk,prettifyAsyncErrors:()=>qE,prettifySyncErrors:()=>W4,releaseAfterUseAsync:()=>Lze,replaceEnvVariables:()=>Vk,sortMap:()=>qs,toMerged:()=>Hze,tryParseOptionalBoolean:()=>Y4,validateEnum:()=>Oze});function Rze(t){return!!(sle.default.valid(t)&&t.match(/^[^-]+(-rc\.[0-9]+)?$/))}function Wk(t,{one:e,more:r,zero:s=r}){return t===0?s:t===1?e:r}function Fze(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Nze(t){}function G4(t){throw new Error(`Assertion failed: Unexpected object '${t}'`)}function Oze(t,e){let r=Object.values(t);if(!r.includes(e))throw new nt(`Invalid value for enumeration: ${JSON.stringify(e)} (expected one of ${r.map(s=>JSON.stringify(s)).join(", ")})`);return e}function Wl(t,e){let r=[];for(let s of t){let a=e(s);a!==ole&&r.push(a)}return r}function p0(t,e){for(let r of t){let s=e(r);if(s!==ale)return s}}function L4(t){return typeof t=="object"&&t!==null}async function Uu(t){let e=await Promise.allSettled(t),r=[];for(let s of e){if(s.status==="rejected")throw s.reason;r.push(s.value)}return r}function Yk(t){if(t instanceof Map&&(t=Object.fromEntries(t)),L4(t))for(let e of Object.keys(t)){let r=t[e];L4(r)&&(t[e]=Yk(r))}return t}function Yl(t,e,r){let s=t.get(e);return typeof s>"u"&&t.set(e,s=r()),s}function xB(t,e){let r=t.get(e);return typeof r>"u"&&t.set(e,r=[]),r}function bp(t,e){let r=t.get(e);return typeof r>"u"&&t.set(e,r=new Set),r}function q4(t,e){let r=t.get(e);return typeof r>"u"&&t.set(e,r=new Map),r}async function Lze(t,e){if(e==null)return await t();try{return await t()}finally{await e()}}async function qE(t,e){try{return await t()}catch(r){throw r.message=e(r.message),r}}function W4(t,e){try{return t()}catch(r){throw r.message=e(r.message),r}}async function WE(t){return await new Promise((e,r)=>{let s=[];t.on("error",a=>{r(a)}),t.on("data",a=>{s.push(a)}),t.on("end",()=>{e(Buffer.concat(s))})})}function lle(){let t,e;return{promise:new Promise((s,a)=>{t=s,e=a}),resolve:t,reject:e}}function cle(t){return PB(fe.fromPortablePath(t))}function ule(path){let physicalPath=fe.fromPortablePath(path),currentCacheEntry=PB.cache[physicalPath];delete PB.cache[physicalPath];let result;try{result=cle(physicalPath);let freshCacheEntry=PB.cache[physicalPath],dynamicModule=eval("module"),freshCacheIndex=dynamicModule.children.indexOf(freshCacheEntry);freshCacheIndex!==-1&&dynamicModule.children.splice(freshCacheIndex,1)}finally{PB.cache[physicalPath]=currentCacheEntry}return result}function Mze(t){let e=rle.get(t),r=ce.statSync(t);if(e?.mtime===r.mtimeMs)return e.instance;let s=ule(t);return rle.set(t,{mtime:r.mtimeMs,instance:s}),s}function Pp(t,{cachingStrategy:e=2}={}){switch(e){case 0:return ule(t);case 1:return Mze(t);case 2:return cle(t);default:throw new Error("Unsupported caching strategy")}}function qs(t,e){let r=Array.from(t);Array.isArray(e)||(e=[e]);let s=[];for(let n of e)s.push(r.map(c=>n(c)));let a=r.map((n,c)=>c);return a.sort((n,c)=>{for(let f of s){let p=f[n]f[c]?1:0;if(p!==0)return p}return 0}),a.map(n=>r[n])}function Uze(t){return t.length===0?null:t.map(e=>`(${nle.default.makeRe(e,{windows:!1,dot:!0}).source})`).join("|")}function Vk(t,{env:e}){let r=/\\?\${(?[\d\w_]+)(?:)?(?:-(?[^}]*))?}/g;return t.replace(r,(s,...a)=>{if(s.startsWith("\\"))return s.slice(1);let{variableName:n,colon:c,fallback:f}=a[a.length-1],p=Object.hasOwn(e,n),h=e[n];if(h||p&&!c)return h;if(f!=null)return f;throw new nt(`Environment variable not found (${n})`)})}function kB(t){switch(t){case"true":case"1":case 1:case!0:return!0;case"false":case"0":case 0:case!1:return!1;default:throw new Error(`Couldn't parse "${t}" as a boolean`)}}function Ale(t){return typeof t>"u"?t:kB(t)}function Y4(t){try{return Ale(t)}catch{return null}}function _ze(t){return!!(fe.isAbsolute(t)||t.match(/^(\.{1,2}|~)\//))}function ple(t,...e){let r=c=>({value:c}),s=r(t),a=e.map(c=>r(c)),{value:n}=N4(s,...a,(c,f)=>{if(Array.isArray(c)&&Array.isArray(f)){for(let p of f)c.find(h=>F4(h,p))||c.push(p);return c}});return n}function Hze(...t){return ple({},...t)}function jze(t,e){let r=Object.create(null);for(let s of t){let a=s[e];r[a]??=[],r[a].push(s)}return r}function YE(t){return typeof t=="string"?Number.parseInt(t,10):t}function Jk(t,e){let r=Gze.exec(t)?.groups;if(!r)throw new Error(`Couldn't parse "${t}" as a duration`);if(r.unit===void 0)return parseFloat(r.num);let s=H4[r.unit];if(!s)throw new Error(`Invalid duration unit "${r.unit}"`);return parseFloat(r.num)*s/H4[e]}var nle,ile,sle,j4,ole,ale,M4,U4,_4,PB,rle,fle,H4,Gze,Pc=Xe(()=>{Dt();Yt();ql();nle=ut(Go()),ile=ut(Ld()),sle=ut(Ai()),j4=Ie("stream");ole=Symbol();Wl.skip=ole;ale=Symbol();p0.skip=ale;M4=class extends j4.Transform{constructor(){super(...arguments);this.chunks=[]}_transform(r,s,a){if(s!=="buffer"||!Buffer.isBuffer(r))throw new Error("Assertion failed: BufferStream only accept buffers");this.chunks.push(r),a(null,null)}_flush(r){r(null,Buffer.concat(this.chunks))}};U4=class{constructor(e){this.deferred=new Map;this.promises=new Map;this.limit=(0,ile.default)(e)}set(e,r){let s=this.deferred.get(e);typeof s>"u"&&this.deferred.set(e,s=lle());let a=this.limit(()=>r());return this.promises.set(e,a),a.then(()=>{this.promises.get(e)===a&&s.resolve()},n=>{this.promises.get(e)===a&&s.reject(n)}),s.promise}reduce(e,r){let s=this.promises.get(e)??Promise.resolve();this.set(e,()=>r(s))}async wait(){await Promise.all(this.promises.values())}},_4=class extends j4.Transform{constructor(r=Buffer.alloc(0)){super();this.active=!0;this.ifEmpty=r}_transform(r,s,a){if(s!=="buffer"||!Buffer.isBuffer(r))throw new Error("Assertion failed: DefaultStream only accept buffers");this.active=!1,a(null,r)}_flush(r){this.active&&this.ifEmpty.length>0?r(null,this.ifEmpty):r(null)}},PB=eval("require");rle=new Map;fle=(s=>(s[s.NoCache=0]="NoCache",s[s.FsTime=1]="FsTime",s[s.Node=2]="Node",s))(fle||{});H4={ms:1,s:1e3,m:60*1e3,h:60*60*1e3,d:24*60*60*1e3,w:7*24*60*60*1e3},Gze=new RegExp(`^(?\\d*\\.?\\d+)(?${Object.keys(H4).join("|")})?$`)});var VE,V4,J4,hle=Xe(()=>{VE=(r=>(r.HARD="HARD",r.SOFT="SOFT",r))(VE||{}),V4=(s=>(s.Dependency="Dependency",s.PeerDependency="PeerDependency",s.PeerDependencyMeta="PeerDependencyMeta",s))(V4||{}),J4=(s=>(s.Inactive="inactive",s.Redundant="redundant",s.Active="active",s))(J4||{})});var he={};Vt(he,{LogLevel:()=>eQ,Style:()=>Xk,Type:()=>ht,addLogFilterSupport:()=>RB,applyColor:()=>ri,applyHyperlink:()=>KE,applyStyle:()=>zd,json:()=>Xd,jsonOrPretty:()=>Yze,mark:()=>$4,pretty:()=>Ht,prettyField:()=>Kf,prettyList:()=>Z4,prettyTruncatedLocatorList:()=>$k,stripAnsi:()=>JE.default,supportsColor:()=>Zk,supportsHyperlinks:()=>X4,tuple:()=>_u});function gle(t){let e=["KiB","MiB","GiB","TiB"],r=e.length;for(;r>1&&t<1024**r;)r-=1;let s=1024**r;return`${Math.floor(t*100/s)/100} ${e[r-1]}`}function Kk(t,e){if(Array.isArray(e))return e.length===0?ri(t,"[]",ht.CODE):ri(t,"[ ",ht.CODE)+e.map(r=>Kk(t,r)).join(", ")+ri(t," ]",ht.CODE);if(typeof e=="string")return ri(t,JSON.stringify(e),ht.STRING);if(typeof e=="number")return ri(t,JSON.stringify(e),ht.NUMBER);if(typeof e=="boolean")return ri(t,JSON.stringify(e),ht.BOOLEAN);if(e===null)return ri(t,"null",ht.NULL);if(typeof e=="object"&&Object.getPrototypeOf(e)===Object.prototype){let r=Object.entries(e);return r.length===0?ri(t,"{}",ht.CODE):ri(t,"{ ",ht.CODE)+r.map(([s,a])=>`${Kk(t,s)}: ${Kk(t,a)}`).join(", ")+ri(t," }",ht.CODE)}if(typeof e>"u")return ri(t,"undefined",ht.NULL);throw new Error("Assertion failed: The value doesn't seem to be a valid JSON object")}function _u(t,e){return[e,t]}function zd(t,e,r){return t.get("enableColors")&&r&2&&(e=TB.default.bold(e)),e}function ri(t,e,r){if(!t.get("enableColors"))return e;let s=qze.get(r);if(s===null)return e;let a=typeof s>"u"?r:z4.level>=3?s[0]:s[1],n=typeof a=="number"?K4.ansi256(a):a.startsWith("#")?K4.hex(a):K4[a];if(typeof n!="function")throw new Error(`Invalid format type ${a}`);return n(e)}function KE(t,e,r){return t.get("enableHyperlinks")?Wze?`\x1B]8;;${r}\x1B\\${e}\x1B]8;;\x1B\\`:`\x1B]8;;${r}\x07${e}\x1B]8;;\x07`:e}function Ht(t,e,r){if(e===null)return ri(t,"null",ht.NULL);if(Object.hasOwn(zk,r))return zk[r].pretty(t,e);if(typeof e!="string")throw new Error(`Assertion failed: Expected the value to be a string, got ${typeof e}`);return ri(t,e,r)}function Z4(t,e,r,{separator:s=", "}={}){return[...e].map(a=>Ht(t,a,r)).join(s)}function Xd(t,e){if(t===null)return null;if(Object.hasOwn(zk,e))return zk[e].json(t);if(typeof t!="string")throw new Error(`Assertion failed: Expected the value to be a string, got ${typeof t}`);return t}function Yze(t,e,[r,s]){return t?Xd(r,s):Ht(e,r,s)}function $4(t){return{Check:ri(t,"\u2713","green"),Cross:ri(t,"\u2718","red"),Question:ri(t,"?","cyan")}}function Kf(t,{label:e,value:[r,s]}){return`${Ht(t,e,ht.CODE)}: ${Ht(t,r,s)}`}function $k(t,e,r){let s=[],a=[...e],n=r;for(;a.length>0;){let h=a[0],E=`${Yr(t,h)}, `,C=e3(h).length+2;if(s.length>0&&nh).join("").slice(0,-2);let c="X".repeat(a.length.toString().length),f=`and ${c} more.`,p=a.length;for(;s.length>1&&nh).join(""),f.replace(c,Ht(t,p,ht.NUMBER))].join("")}function RB(t,{configuration:e}){let r=e.get("logFilters"),s=new Map,a=new Map,n=[];for(let C of r){let S=C.get("level");if(typeof S>"u")continue;let P=C.get("code");typeof P<"u"&&s.set(P,S);let I=C.get("text");typeof I<"u"&&a.set(I,S);let R=C.get("pattern");typeof R<"u"&&n.push([dle.default.matcher(R,{contains:!0}),S])}n.reverse();let c=(C,S,P)=>{if(C===null||C===0)return P;let I=a.size>0||n.length>0?(0,JE.default)(S):S;if(a.size>0){let R=a.get(I);if(typeof R<"u")return R??P}if(n.length>0){for(let[R,N]of n)if(R(I))return N??P}if(s.size>0){let R=s.get(Yf(C));if(typeof R<"u")return R??P}return P},f=t.reportInfo,p=t.reportWarning,h=t.reportError,E=function(C,S,P,I){switch(c(S,P,I)){case"info":f.call(C,S,P);break;case"warning":p.call(C,S??0,P);break;case"error":h.call(C,S??0,P);break}};t.reportInfo=function(...C){return E(this,...C,"info")},t.reportWarning=function(...C){return E(this,...C,"warning")},t.reportError=function(...C){return E(this,...C,"error")}}var TB,QB,dle,JE,ht,Xk,z4,Zk,X4,K4,qze,qo,zk,Wze,eQ,xc=Xe(()=>{Dt();TB=ut(TE()),QB=ut(Fd());Yt();dle=ut(Go()),JE=ut(dk());Gx();Wo();ht={NO_HINT:"NO_HINT",ID:"ID",NULL:"NULL",SCOPE:"SCOPE",NAME:"NAME",RANGE:"RANGE",REFERENCE:"REFERENCE",NUMBER:"NUMBER",STRING:"STRING",BOOLEAN:"BOOLEAN",PATH:"PATH",URL:"URL",ADDED:"ADDED",REMOVED:"REMOVED",CODE:"CODE",INSPECT:"INSPECT",DURATION:"DURATION",SIZE:"SIZE",SIZE_DIFF:"SIZE_DIFF",IDENT:"IDENT",DESCRIPTOR:"DESCRIPTOR",LOCATOR:"LOCATOR",RESOLUTION:"RESOLUTION",DEPENDENT:"DEPENDENT",PACKAGE_EXTENSION:"PACKAGE_EXTENSION",SETTING:"SETTING",MARKDOWN:"MARKDOWN",MARKDOWN_INLINE:"MARKDOWN_INLINE"},Xk=(e=>(e[e.BOLD=2]="BOLD",e))(Xk||{}),z4=QB.default.GITHUB_ACTIONS?{level:2}:TB.default.supportsColor?{level:TB.default.supportsColor.level}:{level:0},Zk=z4.level!==0,X4=Zk&&!QB.default.GITHUB_ACTIONS&&!QB.default.CIRCLE&&!QB.default.GITLAB,K4=new TB.default.Instance(z4),qze=new Map([[ht.NO_HINT,null],[ht.NULL,["#a853b5",129]],[ht.SCOPE,["#d75f00",166]],[ht.NAME,["#d7875f",173]],[ht.RANGE,["#00afaf",37]],[ht.REFERENCE,["#87afff",111]],[ht.NUMBER,["#ffd700",220]],[ht.STRING,["#b4bd68",32]],[ht.BOOLEAN,["#faa023",209]],[ht.PATH,["#d75fd7",170]],[ht.URL,["#d75fd7",170]],[ht.ADDED,["#5faf00",70]],[ht.REMOVED,["#ff3131",160]],[ht.CODE,["#87afff",111]],[ht.SIZE,["#ffd700",220]]]),qo=t=>t;zk={[ht.ID]:qo({pretty:(t,e)=>typeof e=="number"?ri(t,`${e}`,ht.NUMBER):ri(t,e,ht.CODE),json:t=>t}),[ht.INSPECT]:qo({pretty:(t,e)=>Kk(t,e),json:t=>t}),[ht.NUMBER]:qo({pretty:(t,e)=>ri(t,`${e}`,ht.NUMBER),json:t=>t}),[ht.IDENT]:qo({pretty:(t,e)=>$i(t,e),json:t=>un(t)}),[ht.LOCATOR]:qo({pretty:(t,e)=>Yr(t,e),json:t=>ll(t)}),[ht.DESCRIPTOR]:qo({pretty:(t,e)=>ni(t,e),json:t=>al(t)}),[ht.RESOLUTION]:qo({pretty:(t,{descriptor:e,locator:r})=>FB(t,e,r),json:({descriptor:t,locator:e})=>({descriptor:al(t),locator:e!==null?ll(e):null})}),[ht.DEPENDENT]:qo({pretty:(t,{locator:e,descriptor:r})=>t3(t,e,r),json:({locator:t,descriptor:e})=>({locator:ll(t),descriptor:al(e)})}),[ht.PACKAGE_EXTENSION]:qo({pretty:(t,e)=>{switch(e.type){case"Dependency":return`${$i(t,e.parentDescriptor)} \u27A4 ${ri(t,"dependencies",ht.CODE)} \u27A4 ${$i(t,e.descriptor)}`;case"PeerDependency":return`${$i(t,e.parentDescriptor)} \u27A4 ${ri(t,"peerDependencies",ht.CODE)} \u27A4 ${$i(t,e.descriptor)}`;case"PeerDependencyMeta":return`${$i(t,e.parentDescriptor)} \u27A4 ${ri(t,"peerDependenciesMeta",ht.CODE)} \u27A4 ${$i(t,Sa(e.selector))} \u27A4 ${ri(t,e.key,ht.CODE)}`;default:throw new Error(`Assertion failed: Unsupported package extension type: ${e.type}`)}},json:t=>{switch(t.type){case"Dependency":return`${un(t.parentDescriptor)} > ${un(t.descriptor)}`;case"PeerDependency":return`${un(t.parentDescriptor)} >> ${un(t.descriptor)}`;case"PeerDependencyMeta":return`${un(t.parentDescriptor)} >> ${t.selector} / ${t.key}`;default:throw new Error(`Assertion failed: Unsupported package extension type: ${t.type}`)}}}),[ht.SETTING]:qo({pretty:(t,e)=>(t.get(e),KE(t,ri(t,e,ht.CODE),`https://yarnpkg.com/configuration/yarnrc#${e}`)),json:t=>t}),[ht.DURATION]:qo({pretty:(t,e)=>{if(e>1e3*60){let r=Math.floor(e/1e3/60),s=Math.ceil((e-r*60*1e3)/1e3);return s===0?`${r}m`:`${r}m ${s}s`}else{let r=Math.floor(e/1e3),s=e-r*1e3;return s===0?`${r}s`:`${r}s ${s}ms`}},json:t=>t}),[ht.SIZE]:qo({pretty:(t,e)=>ri(t,gle(e),ht.NUMBER),json:t=>t}),[ht.SIZE_DIFF]:qo({pretty:(t,e)=>{let r=e>=0?"+":"-",s=r==="+"?ht.REMOVED:ht.ADDED;return ri(t,`${r} ${gle(Math.max(Math.abs(e),1))}`,s)},json:t=>t}),[ht.PATH]:qo({pretty:(t,e)=>ri(t,fe.fromPortablePath(e),ht.PATH),json:t=>fe.fromPortablePath(t)}),[ht.MARKDOWN]:qo({pretty:(t,{text:e,format:r,paragraphs:s})=>Ho(e,{format:r,paragraphs:s}),json:({text:t})=>t}),[ht.MARKDOWN_INLINE]:qo({pretty:(t,e)=>(e=e.replace(/(`+)((?:.|[\n])*?)\1/g,(r,s,a)=>Ht(t,s+a+s,ht.CODE)),e=e.replace(/(\*\*)((?:.|[\n])*?)\1/g,(r,s,a)=>zd(t,a,2)),e),json:t=>t})};Wze=!!process.env.KONSOLE_VERSION;eQ=(a=>(a.Error="error",a.Warning="warning",a.Info="info",a.Discard="discard",a))(eQ||{})});var mle=_(zE=>{"use strict";Object.defineProperty(zE,"__esModule",{value:!0});zE.splitWhen=zE.flatten=void 0;function Vze(t){return t.reduce((e,r)=>[].concat(e,r),[])}zE.flatten=Vze;function Jze(t,e){let r=[[]],s=0;for(let a of t)e(a)?(s++,r[s]=[]):r[s].push(a);return r}zE.splitWhen=Jze});var yle=_(tQ=>{"use strict";Object.defineProperty(tQ,"__esModule",{value:!0});tQ.isEnoentCodeError=void 0;function Kze(t){return t.code==="ENOENT"}tQ.isEnoentCodeError=Kze});var Ele=_(rQ=>{"use strict";Object.defineProperty(rQ,"__esModule",{value:!0});rQ.createDirentFromStats=void 0;var r3=class{constructor(e,r){this.name=e,this.isBlockDevice=r.isBlockDevice.bind(r),this.isCharacterDevice=r.isCharacterDevice.bind(r),this.isDirectory=r.isDirectory.bind(r),this.isFIFO=r.isFIFO.bind(r),this.isFile=r.isFile.bind(r),this.isSocket=r.isSocket.bind(r),this.isSymbolicLink=r.isSymbolicLink.bind(r)}};function zze(t,e){return new r3(t,e)}rQ.createDirentFromStats=zze});var Ble=_(cs=>{"use strict";Object.defineProperty(cs,"__esModule",{value:!0});cs.convertPosixPathToPattern=cs.convertWindowsPathToPattern=cs.convertPathToPattern=cs.escapePosixPath=cs.escapeWindowsPath=cs.escape=cs.removeLeadingDotSegment=cs.makeAbsolute=cs.unixify=void 0;var Xze=Ie("os"),Zze=Ie("path"),Ile=Xze.platform()==="win32",$ze=2,eXe=/(\\?)([()*?[\]{|}]|^!|[!+@](?=\()|\\(?![!()*+?@[\]{|}]))/g,tXe=/(\\?)([()[\]{}]|^!|[!+@](?=\())/g,rXe=/^\\\\([.?])/,nXe=/\\(?![!()+@[\]{}])/g;function iXe(t){return t.replace(/\\/g,"/")}cs.unixify=iXe;function sXe(t,e){return Zze.resolve(t,e)}cs.makeAbsolute=sXe;function oXe(t){if(t.charAt(0)==="."){let e=t.charAt(1);if(e==="/"||e==="\\")return t.slice($ze)}return t}cs.removeLeadingDotSegment=oXe;cs.escape=Ile?n3:i3;function n3(t){return t.replace(tXe,"\\$2")}cs.escapeWindowsPath=n3;function i3(t){return t.replace(eXe,"\\$2")}cs.escapePosixPath=i3;cs.convertPathToPattern=Ile?Cle:wle;function Cle(t){return n3(t).replace(rXe,"//$1").replace(nXe,"/")}cs.convertWindowsPathToPattern=Cle;function wle(t){return i3(t)}cs.convertPosixPathToPattern=wle});var Sle=_((JOt,vle)=>{vle.exports=function(e){if(typeof e!="string"||e==="")return!1;for(var r;r=/(\\).|([@?!+*]\(.*\))/g.exec(e);){if(r[2])return!0;e=e.slice(r.index+r[0].length)}return!1}});var Ple=_((KOt,ble)=>{var aXe=Sle(),Dle={"{":"}","(":")","[":"]"},lXe=function(t){if(t[0]==="!")return!0;for(var e=0,r=-2,s=-2,a=-2,n=-2,c=-2;ee&&(c===-1||c>s||(c=t.indexOf("\\",e),c===-1||c>s)))||a!==-1&&t[e]==="{"&&t[e+1]!=="}"&&(a=t.indexOf("}",e),a>e&&(c=t.indexOf("\\",e),c===-1||c>a))||n!==-1&&t[e]==="("&&t[e+1]==="?"&&/[:!=]/.test(t[e+2])&&t[e+3]!==")"&&(n=t.indexOf(")",e),n>e&&(c=t.indexOf("\\",e),c===-1||c>n))||r!==-1&&t[e]==="("&&t[e+1]!=="|"&&(rr&&(c=t.indexOf("\\",r),c===-1||c>n))))return!0;if(t[e]==="\\"){var f=t[e+1];e+=2;var p=Dle[f];if(p){var h=t.indexOf(p,e);h!==-1&&(e=h+1)}if(t[e]==="!")return!0}else e++}return!1},cXe=function(t){if(t[0]==="!")return!0;for(var e=0;e{"use strict";var uXe=Ple(),fXe=Ie("path").posix.dirname,AXe=Ie("os").platform()==="win32",s3="/",pXe=/\\/g,hXe=/[\{\[].*[\}\]]$/,gXe=/(^|[^\\])([\{\[]|\([^\)]+$)/,dXe=/\\([\!\*\?\|\[\]\(\)\{\}])/g;xle.exports=function(e,r){var s=Object.assign({flipBackslashes:!0},r);s.flipBackslashes&&AXe&&e.indexOf(s3)<0&&(e=e.replace(pXe,s3)),hXe.test(e)&&(e+=s3),e+="a";do e=fXe(e);while(uXe(e)||gXe.test(e));return e.replace(dXe,"$1")}});var Mle=_(jr=>{"use strict";Object.defineProperty(jr,"__esModule",{value:!0});jr.removeDuplicateSlashes=jr.matchAny=jr.convertPatternsToRe=jr.makeRe=jr.getPatternParts=jr.expandBraceExpansion=jr.expandPatternsWithBraceExpansion=jr.isAffectDepthOfReadingPattern=jr.endsWithSlashGlobStar=jr.hasGlobStar=jr.getBaseDirectory=jr.isPatternRelatedToParentDirectory=jr.getPatternsOutsideCurrentDirectory=jr.getPatternsInsideCurrentDirectory=jr.getPositivePatterns=jr.getNegativePatterns=jr.isPositivePattern=jr.isNegativePattern=jr.convertToNegativePattern=jr.convertToPositivePattern=jr.isDynamicPattern=jr.isStaticPattern=void 0;var mXe=Ie("path"),yXe=kle(),o3=Go(),Qle="**",EXe="\\",IXe=/[*?]|^!/,CXe=/\[[^[]*]/,wXe=/(?:^|[^!*+?@])\([^(]*\|[^|]*\)/,BXe=/[!*+?@]\([^(]*\)/,vXe=/,|\.\./,SXe=/(?!^)\/{2,}/g;function Tle(t,e={}){return!Rle(t,e)}jr.isStaticPattern=Tle;function Rle(t,e={}){return t===""?!1:!!(e.caseSensitiveMatch===!1||t.includes(EXe)||IXe.test(t)||CXe.test(t)||wXe.test(t)||e.extglob!==!1&&BXe.test(t)||e.braceExpansion!==!1&&DXe(t))}jr.isDynamicPattern=Rle;function DXe(t){let e=t.indexOf("{");if(e===-1)return!1;let r=t.indexOf("}",e+1);if(r===-1)return!1;let s=t.slice(e,r);return vXe.test(s)}function bXe(t){return nQ(t)?t.slice(1):t}jr.convertToPositivePattern=bXe;function PXe(t){return"!"+t}jr.convertToNegativePattern=PXe;function nQ(t){return t.startsWith("!")&&t[1]!=="("}jr.isNegativePattern=nQ;function Fle(t){return!nQ(t)}jr.isPositivePattern=Fle;function xXe(t){return t.filter(nQ)}jr.getNegativePatterns=xXe;function kXe(t){return t.filter(Fle)}jr.getPositivePatterns=kXe;function QXe(t){return t.filter(e=>!a3(e))}jr.getPatternsInsideCurrentDirectory=QXe;function TXe(t){return t.filter(a3)}jr.getPatternsOutsideCurrentDirectory=TXe;function a3(t){return t.startsWith("..")||t.startsWith("./..")}jr.isPatternRelatedToParentDirectory=a3;function RXe(t){return yXe(t,{flipBackslashes:!1})}jr.getBaseDirectory=RXe;function FXe(t){return t.includes(Qle)}jr.hasGlobStar=FXe;function Nle(t){return t.endsWith("/"+Qle)}jr.endsWithSlashGlobStar=Nle;function NXe(t){let e=mXe.basename(t);return Nle(t)||Tle(e)}jr.isAffectDepthOfReadingPattern=NXe;function OXe(t){return t.reduce((e,r)=>e.concat(Ole(r)),[])}jr.expandPatternsWithBraceExpansion=OXe;function Ole(t){let e=o3.braces(t,{expand:!0,nodupes:!0,keepEscaping:!0});return e.sort((r,s)=>r.length-s.length),e.filter(r=>r!=="")}jr.expandBraceExpansion=Ole;function LXe(t,e){let{parts:r}=o3.scan(t,Object.assign(Object.assign({},e),{parts:!0}));return r.length===0&&(r=[t]),r[0].startsWith("/")&&(r[0]=r[0].slice(1),r.unshift("")),r}jr.getPatternParts=LXe;function Lle(t,e){return o3.makeRe(t,e)}jr.makeRe=Lle;function MXe(t,e){return t.map(r=>Lle(r,e))}jr.convertPatternsToRe=MXe;function UXe(t,e){return e.some(r=>r.test(t))}jr.matchAny=UXe;function _Xe(t){return t.replace(SXe,"/")}jr.removeDuplicateSlashes=_Xe});var jle=_((ZOt,Hle)=>{"use strict";var HXe=Ie("stream"),Ule=HXe.PassThrough,jXe=Array.prototype.slice;Hle.exports=GXe;function GXe(){let t=[],e=jXe.call(arguments),r=!1,s=e[e.length-1];s&&!Array.isArray(s)&&s.pipe==null?e.pop():s={};let a=s.end!==!1,n=s.pipeError===!0;s.objectMode==null&&(s.objectMode=!0),s.highWaterMark==null&&(s.highWaterMark=64*1024);let c=Ule(s);function f(){for(let E=0,C=arguments.length;E0||(r=!1,p())}function P(I){function R(){I.removeListener("merge2UnpipeEnd",R),I.removeListener("end",R),n&&I.removeListener("error",N),S()}function N(U){c.emit("error",U)}if(I._readableState.endEmitted)return S();I.on("merge2UnpipeEnd",R),I.on("end",R),n&&I.on("error",N),I.pipe(c,{end:!1}),I.resume()}for(let I=0;I{"use strict";Object.defineProperty(iQ,"__esModule",{value:!0});iQ.merge=void 0;var qXe=jle();function WXe(t){let e=qXe(t);return t.forEach(r=>{r.once("error",s=>e.emit("error",s))}),e.once("close",()=>Gle(t)),e.once("end",()=>Gle(t)),e}iQ.merge=WXe;function Gle(t){t.forEach(e=>e.emit("close"))}});var Wle=_(XE=>{"use strict";Object.defineProperty(XE,"__esModule",{value:!0});XE.isEmpty=XE.isString=void 0;function YXe(t){return typeof t=="string"}XE.isString=YXe;function VXe(t){return t===""}XE.isEmpty=VXe});var xp=_(Yo=>{"use strict";Object.defineProperty(Yo,"__esModule",{value:!0});Yo.string=Yo.stream=Yo.pattern=Yo.path=Yo.fs=Yo.errno=Yo.array=void 0;var JXe=mle();Yo.array=JXe;var KXe=yle();Yo.errno=KXe;var zXe=Ele();Yo.fs=zXe;var XXe=Ble();Yo.path=XXe;var ZXe=Mle();Yo.pattern=ZXe;var $Xe=qle();Yo.stream=$Xe;var eZe=Wle();Yo.string=eZe});var Kle=_(Vo=>{"use strict";Object.defineProperty(Vo,"__esModule",{value:!0});Vo.convertPatternGroupToTask=Vo.convertPatternGroupsToTasks=Vo.groupPatternsByBaseDirectory=Vo.getNegativePatternsAsPositive=Vo.getPositivePatterns=Vo.convertPatternsToTasks=Vo.generate=void 0;var Hu=xp();function tZe(t,e){let r=Yle(t,e),s=Yle(e.ignore,e),a=Vle(r),n=Jle(r,s),c=a.filter(E=>Hu.pattern.isStaticPattern(E,e)),f=a.filter(E=>Hu.pattern.isDynamicPattern(E,e)),p=l3(c,n,!1),h=l3(f,n,!0);return p.concat(h)}Vo.generate=tZe;function Yle(t,e){let r=t;return e.braceExpansion&&(r=Hu.pattern.expandPatternsWithBraceExpansion(r)),e.baseNameMatch&&(r=r.map(s=>s.includes("/")?s:`**/${s}`)),r.map(s=>Hu.pattern.removeDuplicateSlashes(s))}function l3(t,e,r){let s=[],a=Hu.pattern.getPatternsOutsideCurrentDirectory(t),n=Hu.pattern.getPatternsInsideCurrentDirectory(t),c=c3(a),f=c3(n);return s.push(...u3(c,e,r)),"."in f?s.push(f3(".",n,e,r)):s.push(...u3(f,e,r)),s}Vo.convertPatternsToTasks=l3;function Vle(t){return Hu.pattern.getPositivePatterns(t)}Vo.getPositivePatterns=Vle;function Jle(t,e){return Hu.pattern.getNegativePatterns(t).concat(e).map(Hu.pattern.convertToPositivePattern)}Vo.getNegativePatternsAsPositive=Jle;function c3(t){let e={};return t.reduce((r,s)=>{let a=Hu.pattern.getBaseDirectory(s);return a in r?r[a].push(s):r[a]=[s],r},e)}Vo.groupPatternsByBaseDirectory=c3;function u3(t,e,r){return Object.keys(t).map(s=>f3(s,t[s],e,r))}Vo.convertPatternGroupsToTasks=u3;function f3(t,e,r,s){return{dynamic:s,positive:e,negative:r,base:t,patterns:[].concat(e,r.map(Hu.pattern.convertToNegativePattern))}}Vo.convertPatternGroupToTask=f3});var Xle=_(sQ=>{"use strict";Object.defineProperty(sQ,"__esModule",{value:!0});sQ.read=void 0;function rZe(t,e,r){e.fs.lstat(t,(s,a)=>{if(s!==null){zle(r,s);return}if(!a.isSymbolicLink()||!e.followSymbolicLink){A3(r,a);return}e.fs.stat(t,(n,c)=>{if(n!==null){if(e.throwErrorOnBrokenSymbolicLink){zle(r,n);return}A3(r,a);return}e.markSymbolicLink&&(c.isSymbolicLink=()=>!0),A3(r,c)})})}sQ.read=rZe;function zle(t,e){t(e)}function A3(t,e){t(null,e)}});var Zle=_(oQ=>{"use strict";Object.defineProperty(oQ,"__esModule",{value:!0});oQ.read=void 0;function nZe(t,e){let r=e.fs.lstatSync(t);if(!r.isSymbolicLink()||!e.followSymbolicLink)return r;try{let s=e.fs.statSync(t);return e.markSymbolicLink&&(s.isSymbolicLink=()=>!0),s}catch(s){if(!e.throwErrorOnBrokenSymbolicLink)return r;throw s}}oQ.read=nZe});var $le=_(h0=>{"use strict";Object.defineProperty(h0,"__esModule",{value:!0});h0.createFileSystemAdapter=h0.FILE_SYSTEM_ADAPTER=void 0;var aQ=Ie("fs");h0.FILE_SYSTEM_ADAPTER={lstat:aQ.lstat,stat:aQ.stat,lstatSync:aQ.lstatSync,statSync:aQ.statSync};function iZe(t){return t===void 0?h0.FILE_SYSTEM_ADAPTER:Object.assign(Object.assign({},h0.FILE_SYSTEM_ADAPTER),t)}h0.createFileSystemAdapter=iZe});var ece=_(h3=>{"use strict";Object.defineProperty(h3,"__esModule",{value:!0});var sZe=$le(),p3=class{constructor(e={}){this._options=e,this.followSymbolicLink=this._getValue(this._options.followSymbolicLink,!0),this.fs=sZe.createFileSystemAdapter(this._options.fs),this.markSymbolicLink=this._getValue(this._options.markSymbolicLink,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!0)}_getValue(e,r){return e??r}};h3.default=p3});var Zd=_(g0=>{"use strict";Object.defineProperty(g0,"__esModule",{value:!0});g0.statSync=g0.stat=g0.Settings=void 0;var tce=Xle(),oZe=Zle(),g3=ece();g0.Settings=g3.default;function aZe(t,e,r){if(typeof e=="function"){tce.read(t,d3(),e);return}tce.read(t,d3(e),r)}g0.stat=aZe;function lZe(t,e){let r=d3(e);return oZe.read(t,r)}g0.statSync=lZe;function d3(t={}){return t instanceof g3.default?t:new g3.default(t)}});var ice=_((lLt,nce)=>{var rce;nce.exports=typeof queueMicrotask=="function"?queueMicrotask.bind(typeof window<"u"?window:global):t=>(rce||(rce=Promise.resolve())).then(t).catch(e=>setTimeout(()=>{throw e},0))});var oce=_((cLt,sce)=>{sce.exports=uZe;var cZe=ice();function uZe(t,e){let r,s,a,n=!0;Array.isArray(t)?(r=[],s=t.length):(a=Object.keys(t),r={},s=a.length);function c(p){function h(){e&&e(p,r),e=null}n?cZe(h):h()}function f(p,h,E){r[p]=E,(--s===0||h)&&c(h)}s?a?a.forEach(function(p){t[p](function(h,E){f(p,h,E)})}):t.forEach(function(p,h){p(function(E,C){f(h,E,C)})}):c(null),n=!1}});var m3=_(cQ=>{"use strict";Object.defineProperty(cQ,"__esModule",{value:!0});cQ.IS_SUPPORT_READDIR_WITH_FILE_TYPES=void 0;var lQ=process.versions.node.split(".");if(lQ[0]===void 0||lQ[1]===void 0)throw new Error(`Unexpected behavior. The 'process.versions.node' variable has invalid value: ${process.versions.node}`);var ace=Number.parseInt(lQ[0],10),fZe=Number.parseInt(lQ[1],10),lce=10,AZe=10,pZe=ace>lce,hZe=ace===lce&&fZe>=AZe;cQ.IS_SUPPORT_READDIR_WITH_FILE_TYPES=pZe||hZe});var cce=_(uQ=>{"use strict";Object.defineProperty(uQ,"__esModule",{value:!0});uQ.createDirentFromStats=void 0;var y3=class{constructor(e,r){this.name=e,this.isBlockDevice=r.isBlockDevice.bind(r),this.isCharacterDevice=r.isCharacterDevice.bind(r),this.isDirectory=r.isDirectory.bind(r),this.isFIFO=r.isFIFO.bind(r),this.isFile=r.isFile.bind(r),this.isSocket=r.isSocket.bind(r),this.isSymbolicLink=r.isSymbolicLink.bind(r)}};function gZe(t,e){return new y3(t,e)}uQ.createDirentFromStats=gZe});var E3=_(fQ=>{"use strict";Object.defineProperty(fQ,"__esModule",{value:!0});fQ.fs=void 0;var dZe=cce();fQ.fs=dZe});var I3=_(AQ=>{"use strict";Object.defineProperty(AQ,"__esModule",{value:!0});AQ.joinPathSegments=void 0;function mZe(t,e,r){return t.endsWith(r)?t+e:t+r+e}AQ.joinPathSegments=mZe});var gce=_(d0=>{"use strict";Object.defineProperty(d0,"__esModule",{value:!0});d0.readdir=d0.readdirWithFileTypes=d0.read=void 0;var yZe=Zd(),uce=oce(),EZe=m3(),fce=E3(),Ace=I3();function IZe(t,e,r){if(!e.stats&&EZe.IS_SUPPORT_READDIR_WITH_FILE_TYPES){pce(t,e,r);return}hce(t,e,r)}d0.read=IZe;function pce(t,e,r){e.fs.readdir(t,{withFileTypes:!0},(s,a)=>{if(s!==null){pQ(r,s);return}let n=a.map(f=>({dirent:f,name:f.name,path:Ace.joinPathSegments(t,f.name,e.pathSegmentSeparator)}));if(!e.followSymbolicLinks){C3(r,n);return}let c=n.map(f=>CZe(f,e));uce(c,(f,p)=>{if(f!==null){pQ(r,f);return}C3(r,p)})})}d0.readdirWithFileTypes=pce;function CZe(t,e){return r=>{if(!t.dirent.isSymbolicLink()){r(null,t);return}e.fs.stat(t.path,(s,a)=>{if(s!==null){if(e.throwErrorOnBrokenSymbolicLink){r(s);return}r(null,t);return}t.dirent=fce.fs.createDirentFromStats(t.name,a),r(null,t)})}}function hce(t,e,r){e.fs.readdir(t,(s,a)=>{if(s!==null){pQ(r,s);return}let n=a.map(c=>{let f=Ace.joinPathSegments(t,c,e.pathSegmentSeparator);return p=>{yZe.stat(f,e.fsStatSettings,(h,E)=>{if(h!==null){p(h);return}let C={name:c,path:f,dirent:fce.fs.createDirentFromStats(c,E)};e.stats&&(C.stats=E),p(null,C)})}});uce(n,(c,f)=>{if(c!==null){pQ(r,c);return}C3(r,f)})})}d0.readdir=hce;function pQ(t,e){t(e)}function C3(t,e){t(null,e)}});var Ice=_(m0=>{"use strict";Object.defineProperty(m0,"__esModule",{value:!0});m0.readdir=m0.readdirWithFileTypes=m0.read=void 0;var wZe=Zd(),BZe=m3(),dce=E3(),mce=I3();function vZe(t,e){return!e.stats&&BZe.IS_SUPPORT_READDIR_WITH_FILE_TYPES?yce(t,e):Ece(t,e)}m0.read=vZe;function yce(t,e){return e.fs.readdirSync(t,{withFileTypes:!0}).map(s=>{let a={dirent:s,name:s.name,path:mce.joinPathSegments(t,s.name,e.pathSegmentSeparator)};if(a.dirent.isSymbolicLink()&&e.followSymbolicLinks)try{let n=e.fs.statSync(a.path);a.dirent=dce.fs.createDirentFromStats(a.name,n)}catch(n){if(e.throwErrorOnBrokenSymbolicLink)throw n}return a})}m0.readdirWithFileTypes=yce;function Ece(t,e){return e.fs.readdirSync(t).map(s=>{let a=mce.joinPathSegments(t,s,e.pathSegmentSeparator),n=wZe.statSync(a,e.fsStatSettings),c={name:s,path:a,dirent:dce.fs.createDirentFromStats(s,n)};return e.stats&&(c.stats=n),c})}m0.readdir=Ece});var Cce=_(y0=>{"use strict";Object.defineProperty(y0,"__esModule",{value:!0});y0.createFileSystemAdapter=y0.FILE_SYSTEM_ADAPTER=void 0;var ZE=Ie("fs");y0.FILE_SYSTEM_ADAPTER={lstat:ZE.lstat,stat:ZE.stat,lstatSync:ZE.lstatSync,statSync:ZE.statSync,readdir:ZE.readdir,readdirSync:ZE.readdirSync};function SZe(t){return t===void 0?y0.FILE_SYSTEM_ADAPTER:Object.assign(Object.assign({},y0.FILE_SYSTEM_ADAPTER),t)}y0.createFileSystemAdapter=SZe});var wce=_(B3=>{"use strict";Object.defineProperty(B3,"__esModule",{value:!0});var DZe=Ie("path"),bZe=Zd(),PZe=Cce(),w3=class{constructor(e={}){this._options=e,this.followSymbolicLinks=this._getValue(this._options.followSymbolicLinks,!1),this.fs=PZe.createFileSystemAdapter(this._options.fs),this.pathSegmentSeparator=this._getValue(this._options.pathSegmentSeparator,DZe.sep),this.stats=this._getValue(this._options.stats,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!0),this.fsStatSettings=new bZe.Settings({followSymbolicLink:this.followSymbolicLinks,fs:this.fs,throwErrorOnBrokenSymbolicLink:this.throwErrorOnBrokenSymbolicLink})}_getValue(e,r){return e??r}};B3.default=w3});var hQ=_(E0=>{"use strict";Object.defineProperty(E0,"__esModule",{value:!0});E0.Settings=E0.scandirSync=E0.scandir=void 0;var Bce=gce(),xZe=Ice(),v3=wce();E0.Settings=v3.default;function kZe(t,e,r){if(typeof e=="function"){Bce.read(t,S3(),e);return}Bce.read(t,S3(e),r)}E0.scandir=kZe;function QZe(t,e){let r=S3(e);return xZe.read(t,r)}E0.scandirSync=QZe;function S3(t={}){return t instanceof v3.default?t:new v3.default(t)}});var Sce=_((ELt,vce)=>{"use strict";function TZe(t){var e=new t,r=e;function s(){var n=e;return n.next?e=n.next:(e=new t,r=e),n.next=null,n}function a(n){r.next=n,r=n}return{get:s,release:a}}vce.exports=TZe});var bce=_((ILt,D3)=>{"use strict";var RZe=Sce();function Dce(t,e,r){if(typeof t=="function"&&(r=e,e=t,t=null),!(r>=1))throw new Error("fastqueue concurrency must be equal to or greater than 1");var s=RZe(FZe),a=null,n=null,c=0,f=null,p={push:R,drain:kc,saturated:kc,pause:E,paused:!1,get concurrency(){return r},set concurrency(ue){if(!(ue>=1))throw new Error("fastqueue concurrency must be equal to or greater than 1");if(r=ue,!p.paused)for(;a&&c=r||p.paused?n?(n.next=me,n=me):(a=me,n=me,p.saturated()):(c++,e.call(t,me.value,me.worked))}function N(ue,le){var me=s.get();me.context=t,me.release=U,me.value=ue,me.callback=le||kc,me.errorHandler=f,c>=r||p.paused?a?(me.next=a,a=me):(a=me,n=me,p.saturated()):(c++,e.call(t,me.value,me.worked))}function U(ue){ue&&s.release(ue);var le=a;le&&c<=r?p.paused?c--:(n===a&&(n=null),a=le.next,le.next=null,e.call(t,le.value,le.worked),n===null&&p.empty()):--c===0&&p.drain()}function W(){a=null,n=null,p.drain=kc}function ee(){a=null,n=null,p.drain(),p.drain=kc}function ie(ue){f=ue}}function kc(){}function FZe(){this.value=null,this.callback=kc,this.next=null,this.release=kc,this.context=null,this.errorHandler=null;var t=this;this.worked=function(r,s){var a=t.callback,n=t.errorHandler,c=t.value;t.value=null,t.callback=kc,t.errorHandler&&n(r,c),a.call(t.context,r,s),t.release(t)}}function NZe(t,e,r){typeof t=="function"&&(r=e,e=t,t=null);function s(E,C){e.call(this,E).then(function(S){C(null,S)},C)}var a=Dce(t,s,r),n=a.push,c=a.unshift;return a.push=f,a.unshift=p,a.drained=h,a;function f(E){var C=new Promise(function(S,P){n(E,function(I,R){if(I){P(I);return}S(R)})});return C.catch(kc),C}function p(E){var C=new Promise(function(S,P){c(E,function(I,R){if(I){P(I);return}S(R)})});return C.catch(kc),C}function h(){if(a.idle())return new Promise(function(S){S()});var E=a.drain,C=new Promise(function(S){a.drain=function(){E(),S()}});return C}}D3.exports=Dce;D3.exports.promise=NZe});var gQ=_(zf=>{"use strict";Object.defineProperty(zf,"__esModule",{value:!0});zf.joinPathSegments=zf.replacePathSegmentSeparator=zf.isAppliedFilter=zf.isFatalError=void 0;function OZe(t,e){return t.errorFilter===null?!0:!t.errorFilter(e)}zf.isFatalError=OZe;function LZe(t,e){return t===null||t(e)}zf.isAppliedFilter=LZe;function MZe(t,e){return t.split(/[/\\]/).join(e)}zf.replacePathSegmentSeparator=MZe;function UZe(t,e,r){return t===""?e:t.endsWith(r)?t+e:t+r+e}zf.joinPathSegments=UZe});var x3=_(P3=>{"use strict";Object.defineProperty(P3,"__esModule",{value:!0});var _Ze=gQ(),b3=class{constructor(e,r){this._root=e,this._settings=r,this._root=_Ze.replacePathSegmentSeparator(e,r.pathSegmentSeparator)}};P3.default=b3});var T3=_(Q3=>{"use strict";Object.defineProperty(Q3,"__esModule",{value:!0});var HZe=Ie("events"),jZe=hQ(),GZe=bce(),dQ=gQ(),qZe=x3(),k3=class extends qZe.default{constructor(e,r){super(e,r),this._settings=r,this._scandir=jZe.scandir,this._emitter=new HZe.EventEmitter,this._queue=GZe(this._worker.bind(this),this._settings.concurrency),this._isFatalError=!1,this._isDestroyed=!1,this._queue.drain=()=>{this._isFatalError||this._emitter.emit("end")}}read(){return this._isFatalError=!1,this._isDestroyed=!1,setImmediate(()=>{this._pushToQueue(this._root,this._settings.basePath)}),this._emitter}get isDestroyed(){return this._isDestroyed}destroy(){if(this._isDestroyed)throw new Error("The reader is already destroyed");this._isDestroyed=!0,this._queue.killAndDrain()}onEntry(e){this._emitter.on("entry",e)}onError(e){this._emitter.once("error",e)}onEnd(e){this._emitter.once("end",e)}_pushToQueue(e,r){let s={directory:e,base:r};this._queue.push(s,a=>{a!==null&&this._handleError(a)})}_worker(e,r){this._scandir(e.directory,this._settings.fsScandirSettings,(s,a)=>{if(s!==null){r(s,void 0);return}for(let n of a)this._handleEntry(n,e.base);r(null,void 0)})}_handleError(e){this._isDestroyed||!dQ.isFatalError(this._settings,e)||(this._isFatalError=!0,this._isDestroyed=!0,this._emitter.emit("error",e))}_handleEntry(e,r){if(this._isDestroyed||this._isFatalError)return;let s=e.path;r!==void 0&&(e.path=dQ.joinPathSegments(r,e.name,this._settings.pathSegmentSeparator)),dQ.isAppliedFilter(this._settings.entryFilter,e)&&this._emitEntry(e),e.dirent.isDirectory()&&dQ.isAppliedFilter(this._settings.deepFilter,e)&&this._pushToQueue(s,r===void 0?void 0:e.path)}_emitEntry(e){this._emitter.emit("entry",e)}};Q3.default=k3});var Pce=_(F3=>{"use strict";Object.defineProperty(F3,"__esModule",{value:!0});var WZe=T3(),R3=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new WZe.default(this._root,this._settings),this._storage=[]}read(e){this._reader.onError(r=>{YZe(e,r)}),this._reader.onEntry(r=>{this._storage.push(r)}),this._reader.onEnd(()=>{VZe(e,this._storage)}),this._reader.read()}};F3.default=R3;function YZe(t,e){t(e)}function VZe(t,e){t(null,e)}});var xce=_(O3=>{"use strict";Object.defineProperty(O3,"__esModule",{value:!0});var JZe=Ie("stream"),KZe=T3(),N3=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new KZe.default(this._root,this._settings),this._stream=new JZe.Readable({objectMode:!0,read:()=>{},destroy:()=>{this._reader.isDestroyed||this._reader.destroy()}})}read(){return this._reader.onError(e=>{this._stream.emit("error",e)}),this._reader.onEntry(e=>{this._stream.push(e)}),this._reader.onEnd(()=>{this._stream.push(null)}),this._reader.read(),this._stream}};O3.default=N3});var kce=_(M3=>{"use strict";Object.defineProperty(M3,"__esModule",{value:!0});var zZe=hQ(),mQ=gQ(),XZe=x3(),L3=class extends XZe.default{constructor(){super(...arguments),this._scandir=zZe.scandirSync,this._storage=[],this._queue=new Set}read(){return this._pushToQueue(this._root,this._settings.basePath),this._handleQueue(),this._storage}_pushToQueue(e,r){this._queue.add({directory:e,base:r})}_handleQueue(){for(let e of this._queue.values())this._handleDirectory(e.directory,e.base)}_handleDirectory(e,r){try{let s=this._scandir(e,this._settings.fsScandirSettings);for(let a of s)this._handleEntry(a,r)}catch(s){this._handleError(s)}}_handleError(e){if(mQ.isFatalError(this._settings,e))throw e}_handleEntry(e,r){let s=e.path;r!==void 0&&(e.path=mQ.joinPathSegments(r,e.name,this._settings.pathSegmentSeparator)),mQ.isAppliedFilter(this._settings.entryFilter,e)&&this._pushToStorage(e),e.dirent.isDirectory()&&mQ.isAppliedFilter(this._settings.deepFilter,e)&&this._pushToQueue(s,r===void 0?void 0:e.path)}_pushToStorage(e){this._storage.push(e)}};M3.default=L3});var Qce=_(_3=>{"use strict";Object.defineProperty(_3,"__esModule",{value:!0});var ZZe=kce(),U3=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new ZZe.default(this._root,this._settings)}read(){return this._reader.read()}};_3.default=U3});var Tce=_(j3=>{"use strict";Object.defineProperty(j3,"__esModule",{value:!0});var $Ze=Ie("path"),e$e=hQ(),H3=class{constructor(e={}){this._options=e,this.basePath=this._getValue(this._options.basePath,void 0),this.concurrency=this._getValue(this._options.concurrency,Number.POSITIVE_INFINITY),this.deepFilter=this._getValue(this._options.deepFilter,null),this.entryFilter=this._getValue(this._options.entryFilter,null),this.errorFilter=this._getValue(this._options.errorFilter,null),this.pathSegmentSeparator=this._getValue(this._options.pathSegmentSeparator,$Ze.sep),this.fsScandirSettings=new e$e.Settings({followSymbolicLinks:this._options.followSymbolicLinks,fs:this._options.fs,pathSegmentSeparator:this._options.pathSegmentSeparator,stats:this._options.stats,throwErrorOnBrokenSymbolicLink:this._options.throwErrorOnBrokenSymbolicLink})}_getValue(e,r){return e??r}};j3.default=H3});var EQ=_(Xf=>{"use strict";Object.defineProperty(Xf,"__esModule",{value:!0});Xf.Settings=Xf.walkStream=Xf.walkSync=Xf.walk=void 0;var Rce=Pce(),t$e=xce(),r$e=Qce(),G3=Tce();Xf.Settings=G3.default;function n$e(t,e,r){if(typeof e=="function"){new Rce.default(t,yQ()).read(e);return}new Rce.default(t,yQ(e)).read(r)}Xf.walk=n$e;function i$e(t,e){let r=yQ(e);return new r$e.default(t,r).read()}Xf.walkSync=i$e;function s$e(t,e){let r=yQ(e);return new t$e.default(t,r).read()}Xf.walkStream=s$e;function yQ(t={}){return t instanceof G3.default?t:new G3.default(t)}});var IQ=_(W3=>{"use strict";Object.defineProperty(W3,"__esModule",{value:!0});var o$e=Ie("path"),a$e=Zd(),Fce=xp(),q3=class{constructor(e){this._settings=e,this._fsStatSettings=new a$e.Settings({followSymbolicLink:this._settings.followSymbolicLinks,fs:this._settings.fs,throwErrorOnBrokenSymbolicLink:this._settings.followSymbolicLinks})}_getFullEntryPath(e){return o$e.resolve(this._settings.cwd,e)}_makeEntry(e,r){let s={name:r,path:r,dirent:Fce.fs.createDirentFromStats(r,e)};return this._settings.stats&&(s.stats=e),s}_isFatalError(e){return!Fce.errno.isEnoentCodeError(e)&&!this._settings.suppressErrors}};W3.default=q3});var J3=_(V3=>{"use strict";Object.defineProperty(V3,"__esModule",{value:!0});var l$e=Ie("stream"),c$e=Zd(),u$e=EQ(),f$e=IQ(),Y3=class extends f$e.default{constructor(){super(...arguments),this._walkStream=u$e.walkStream,this._stat=c$e.stat}dynamic(e,r){return this._walkStream(e,r)}static(e,r){let s=e.map(this._getFullEntryPath,this),a=new l$e.PassThrough({objectMode:!0});a._write=(n,c,f)=>this._getEntry(s[n],e[n],r).then(p=>{p!==null&&r.entryFilter(p)&&a.push(p),n===s.length-1&&a.end(),f()}).catch(f);for(let n=0;nthis._makeEntry(a,r)).catch(a=>{if(s.errorFilter(a))return null;throw a})}_getStat(e){return new Promise((r,s)=>{this._stat(e,this._fsStatSettings,(a,n)=>a===null?r(n):s(a))})}};V3.default=Y3});var Nce=_(z3=>{"use strict";Object.defineProperty(z3,"__esModule",{value:!0});var A$e=EQ(),p$e=IQ(),h$e=J3(),K3=class extends p$e.default{constructor(){super(...arguments),this._walkAsync=A$e.walk,this._readerStream=new h$e.default(this._settings)}dynamic(e,r){return new Promise((s,a)=>{this._walkAsync(e,r,(n,c)=>{n===null?s(c):a(n)})})}async static(e,r){let s=[],a=this._readerStream.static(e,r);return new Promise((n,c)=>{a.once("error",c),a.on("data",f=>s.push(f)),a.once("end",()=>n(s))})}};z3.default=K3});var Oce=_(Z3=>{"use strict";Object.defineProperty(Z3,"__esModule",{value:!0});var NB=xp(),X3=class{constructor(e,r,s){this._patterns=e,this._settings=r,this._micromatchOptions=s,this._storage=[],this._fillStorage()}_fillStorage(){for(let e of this._patterns){let r=this._getPatternSegments(e),s=this._splitSegmentsIntoSections(r);this._storage.push({complete:s.length<=1,pattern:e,segments:r,sections:s})}}_getPatternSegments(e){return NB.pattern.getPatternParts(e,this._micromatchOptions).map(s=>NB.pattern.isDynamicPattern(s,this._settings)?{dynamic:!0,pattern:s,patternRe:NB.pattern.makeRe(s,this._micromatchOptions)}:{dynamic:!1,pattern:s})}_splitSegmentsIntoSections(e){return NB.array.splitWhen(e,r=>r.dynamic&&NB.pattern.hasGlobStar(r.pattern))}};Z3.default=X3});var Lce=_(e8=>{"use strict";Object.defineProperty(e8,"__esModule",{value:!0});var g$e=Oce(),$3=class extends g$e.default{match(e){let r=e.split("/"),s=r.length,a=this._storage.filter(n=>!n.complete||n.segments.length>s);for(let n of a){let c=n.sections[0];if(!n.complete&&s>c.length||r.every((p,h)=>{let E=n.segments[h];return!!(E.dynamic&&E.patternRe.test(p)||!E.dynamic&&E.pattern===p)}))return!0}return!1}};e8.default=$3});var Mce=_(r8=>{"use strict";Object.defineProperty(r8,"__esModule",{value:!0});var CQ=xp(),d$e=Lce(),t8=class{constructor(e,r){this._settings=e,this._micromatchOptions=r}getFilter(e,r,s){let a=this._getMatcher(r),n=this._getNegativePatternsRe(s);return c=>this._filter(e,c,a,n)}_getMatcher(e){return new d$e.default(e,this._settings,this._micromatchOptions)}_getNegativePatternsRe(e){let r=e.filter(CQ.pattern.isAffectDepthOfReadingPattern);return CQ.pattern.convertPatternsToRe(r,this._micromatchOptions)}_filter(e,r,s,a){if(this._isSkippedByDeep(e,r.path)||this._isSkippedSymbolicLink(r))return!1;let n=CQ.path.removeLeadingDotSegment(r.path);return this._isSkippedByPositivePatterns(n,s)?!1:this._isSkippedByNegativePatterns(n,a)}_isSkippedByDeep(e,r){return this._settings.deep===1/0?!1:this._getEntryLevel(e,r)>=this._settings.deep}_getEntryLevel(e,r){let s=r.split("/").length;if(e==="")return s;let a=e.split("/").length;return s-a}_isSkippedSymbolicLink(e){return!this._settings.followSymbolicLinks&&e.dirent.isSymbolicLink()}_isSkippedByPositivePatterns(e,r){return!this._settings.baseNameMatch&&!r.match(e)}_isSkippedByNegativePatterns(e,r){return!CQ.pattern.matchAny(e,r)}};r8.default=t8});var Uce=_(i8=>{"use strict";Object.defineProperty(i8,"__esModule",{value:!0});var $d=xp(),n8=class{constructor(e,r){this._settings=e,this._micromatchOptions=r,this.index=new Map}getFilter(e,r){let s=$d.pattern.convertPatternsToRe(e,this._micromatchOptions),a=$d.pattern.convertPatternsToRe(r,Object.assign(Object.assign({},this._micromatchOptions),{dot:!0}));return n=>this._filter(n,s,a)}_filter(e,r,s){let a=$d.path.removeLeadingDotSegment(e.path);if(this._settings.unique&&this._isDuplicateEntry(a)||this._onlyFileFilter(e)||this._onlyDirectoryFilter(e)||this._isSkippedByAbsoluteNegativePatterns(a,s))return!1;let n=e.dirent.isDirectory(),c=this._isMatchToPatterns(a,r,n)&&!this._isMatchToPatterns(a,s,n);return this._settings.unique&&c&&this._createIndexRecord(a),c}_isDuplicateEntry(e){return this.index.has(e)}_createIndexRecord(e){this.index.set(e,void 0)}_onlyFileFilter(e){return this._settings.onlyFiles&&!e.dirent.isFile()}_onlyDirectoryFilter(e){return this._settings.onlyDirectories&&!e.dirent.isDirectory()}_isSkippedByAbsoluteNegativePatterns(e,r){if(!this._settings.absolute)return!1;let s=$d.path.makeAbsolute(this._settings.cwd,e);return $d.pattern.matchAny(s,r)}_isMatchToPatterns(e,r,s){let a=$d.pattern.matchAny(e,r);return!a&&s?$d.pattern.matchAny(e+"/",r):a}};i8.default=n8});var _ce=_(o8=>{"use strict";Object.defineProperty(o8,"__esModule",{value:!0});var m$e=xp(),s8=class{constructor(e){this._settings=e}getFilter(){return e=>this._isNonFatalError(e)}_isNonFatalError(e){return m$e.errno.isEnoentCodeError(e)||this._settings.suppressErrors}};o8.default=s8});var jce=_(l8=>{"use strict";Object.defineProperty(l8,"__esModule",{value:!0});var Hce=xp(),a8=class{constructor(e){this._settings=e}getTransformer(){return e=>this._transform(e)}_transform(e){let r=e.path;return this._settings.absolute&&(r=Hce.path.makeAbsolute(this._settings.cwd,r),r=Hce.path.unixify(r)),this._settings.markDirectories&&e.dirent.isDirectory()&&(r+="/"),this._settings.objectMode?Object.assign(Object.assign({},e),{path:r}):r}};l8.default=a8});var wQ=_(u8=>{"use strict";Object.defineProperty(u8,"__esModule",{value:!0});var y$e=Ie("path"),E$e=Mce(),I$e=Uce(),C$e=_ce(),w$e=jce(),c8=class{constructor(e){this._settings=e,this.errorFilter=new C$e.default(this._settings),this.entryFilter=new I$e.default(this._settings,this._getMicromatchOptions()),this.deepFilter=new E$e.default(this._settings,this._getMicromatchOptions()),this.entryTransformer=new w$e.default(this._settings)}_getRootDirectory(e){return y$e.resolve(this._settings.cwd,e.base)}_getReaderOptions(e){let r=e.base==="."?"":e.base;return{basePath:r,pathSegmentSeparator:"/",concurrency:this._settings.concurrency,deepFilter:this.deepFilter.getFilter(r,e.positive,e.negative),entryFilter:this.entryFilter.getFilter(e.positive,e.negative),errorFilter:this.errorFilter.getFilter(),followSymbolicLinks:this._settings.followSymbolicLinks,fs:this._settings.fs,stats:this._settings.stats,throwErrorOnBrokenSymbolicLink:this._settings.throwErrorOnBrokenSymbolicLink,transform:this.entryTransformer.getTransformer()}}_getMicromatchOptions(){return{dot:this._settings.dot,matchBase:this._settings.baseNameMatch,nobrace:!this._settings.braceExpansion,nocase:!this._settings.caseSensitiveMatch,noext:!this._settings.extglob,noglobstar:!this._settings.globstar,posix:!0,strictSlashes:!1}}};u8.default=c8});var Gce=_(A8=>{"use strict";Object.defineProperty(A8,"__esModule",{value:!0});var B$e=Nce(),v$e=wQ(),f8=class extends v$e.default{constructor(){super(...arguments),this._reader=new B$e.default(this._settings)}async read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e);return(await this.api(r,e,s)).map(n=>s.transform(n))}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};A8.default=f8});var qce=_(h8=>{"use strict";Object.defineProperty(h8,"__esModule",{value:!0});var S$e=Ie("stream"),D$e=J3(),b$e=wQ(),p8=class extends b$e.default{constructor(){super(...arguments),this._reader=new D$e.default(this._settings)}read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e),a=this.api(r,e,s),n=new S$e.Readable({objectMode:!0,read:()=>{}});return a.once("error",c=>n.emit("error",c)).on("data",c=>n.emit("data",s.transform(c))).once("end",()=>n.emit("end")),n.once("close",()=>a.destroy()),n}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};h8.default=p8});var Wce=_(d8=>{"use strict";Object.defineProperty(d8,"__esModule",{value:!0});var P$e=Zd(),x$e=EQ(),k$e=IQ(),g8=class extends k$e.default{constructor(){super(...arguments),this._walkSync=x$e.walkSync,this._statSync=P$e.statSync}dynamic(e,r){return this._walkSync(e,r)}static(e,r){let s=[];for(let a of e){let n=this._getFullEntryPath(a),c=this._getEntry(n,a,r);c===null||!r.entryFilter(c)||s.push(c)}return s}_getEntry(e,r,s){try{let a=this._getStat(e);return this._makeEntry(a,r)}catch(a){if(s.errorFilter(a))return null;throw a}}_getStat(e){return this._statSync(e,this._fsStatSettings)}};d8.default=g8});var Yce=_(y8=>{"use strict";Object.defineProperty(y8,"__esModule",{value:!0});var Q$e=Wce(),T$e=wQ(),m8=class extends T$e.default{constructor(){super(...arguments),this._reader=new Q$e.default(this._settings)}read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e);return this.api(r,e,s).map(s.transform)}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};y8.default=m8});var Vce=_(eI=>{"use strict";Object.defineProperty(eI,"__esModule",{value:!0});eI.DEFAULT_FILE_SYSTEM_ADAPTER=void 0;var $E=Ie("fs"),R$e=Ie("os"),F$e=Math.max(R$e.cpus().length,1);eI.DEFAULT_FILE_SYSTEM_ADAPTER={lstat:$E.lstat,lstatSync:$E.lstatSync,stat:$E.stat,statSync:$E.statSync,readdir:$E.readdir,readdirSync:$E.readdirSync};var E8=class{constructor(e={}){this._options=e,this.absolute=this._getValue(this._options.absolute,!1),this.baseNameMatch=this._getValue(this._options.baseNameMatch,!1),this.braceExpansion=this._getValue(this._options.braceExpansion,!0),this.caseSensitiveMatch=this._getValue(this._options.caseSensitiveMatch,!0),this.concurrency=this._getValue(this._options.concurrency,F$e),this.cwd=this._getValue(this._options.cwd,process.cwd()),this.deep=this._getValue(this._options.deep,1/0),this.dot=this._getValue(this._options.dot,!1),this.extglob=this._getValue(this._options.extglob,!0),this.followSymbolicLinks=this._getValue(this._options.followSymbolicLinks,!0),this.fs=this._getFileSystemMethods(this._options.fs),this.globstar=this._getValue(this._options.globstar,!0),this.ignore=this._getValue(this._options.ignore,[]),this.markDirectories=this._getValue(this._options.markDirectories,!1),this.objectMode=this._getValue(this._options.objectMode,!1),this.onlyDirectories=this._getValue(this._options.onlyDirectories,!1),this.onlyFiles=this._getValue(this._options.onlyFiles,!0),this.stats=this._getValue(this._options.stats,!1),this.suppressErrors=this._getValue(this._options.suppressErrors,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!1),this.unique=this._getValue(this._options.unique,!0),this.onlyDirectories&&(this.onlyFiles=!1),this.stats&&(this.objectMode=!0),this.ignore=[].concat(this.ignore)}_getValue(e,r){return e===void 0?r:e}_getFileSystemMethods(e={}){return Object.assign(Object.assign({},eI.DEFAULT_FILE_SYSTEM_ADAPTER),e)}};eI.default=E8});var BQ=_((WLt,Kce)=>{"use strict";var Jce=Kle(),N$e=Gce(),O$e=qce(),L$e=Yce(),I8=Vce(),Qc=xp();async function C8(t,e){ju(t);let r=w8(t,N$e.default,e),s=await Promise.all(r);return Qc.array.flatten(s)}(function(t){t.glob=t,t.globSync=e,t.globStream=r,t.async=t;function e(h,E){ju(h);let C=w8(h,L$e.default,E);return Qc.array.flatten(C)}t.sync=e;function r(h,E){ju(h);let C=w8(h,O$e.default,E);return Qc.stream.merge(C)}t.stream=r;function s(h,E){ju(h);let C=[].concat(h),S=new I8.default(E);return Jce.generate(C,S)}t.generateTasks=s;function a(h,E){ju(h);let C=new I8.default(E);return Qc.pattern.isDynamicPattern(h,C)}t.isDynamicPattern=a;function n(h){return ju(h),Qc.path.escape(h)}t.escapePath=n;function c(h){return ju(h),Qc.path.convertPathToPattern(h)}t.convertPathToPattern=c;let f;(function(h){function E(S){return ju(S),Qc.path.escapePosixPath(S)}h.escapePath=E;function C(S){return ju(S),Qc.path.convertPosixPathToPattern(S)}h.convertPathToPattern=C})(f=t.posix||(t.posix={}));let p;(function(h){function E(S){return ju(S),Qc.path.escapeWindowsPath(S)}h.escapePath=E;function C(S){return ju(S),Qc.path.convertWindowsPathToPattern(S)}h.convertPathToPattern=C})(p=t.win32||(t.win32={}))})(C8||(C8={}));function w8(t,e,r){let s=[].concat(t),a=new I8.default(r),n=Jce.generate(s,a),c=new e(a);return n.map(c.read,c)}function ju(t){if(![].concat(t).every(s=>Qc.string.isString(s)&&!Qc.string.isEmpty(s)))throw new TypeError("Patterns must be a string (non empty) or an array of strings")}Kce.exports=C8});var Nn={};Vt(Nn,{checksumFile:()=>SQ,checksumPattern:()=>DQ,makeHash:()=>us});function us(...t){let e=(0,vQ.createHash)("sha512"),r="";for(let s of t)typeof s=="string"?r+=s:s&&(r&&(e.update(r),r=""),e.update(s));return r&&e.update(r),e.digest("hex")}async function SQ(t,{baseFs:e,algorithm:r}={baseFs:ce,algorithm:"sha512"}){let s=await e.openPromise(t,"r");try{let n=Buffer.allocUnsafeSlow(65536),c=(0,vQ.createHash)(r),f=0;for(;(f=await e.readPromise(s,n,0,65536))!==0;)c.update(f===65536?n:n.slice(0,f));return c.digest("hex")}finally{await e.closePromise(s)}}async function DQ(t,{cwd:e}){let s=(await(0,B8.default)(t,{cwd:fe.fromPortablePath(e),onlyDirectories:!0})).map(f=>`${f}/**/*`),a=await(0,B8.default)([t,...s],{cwd:fe.fromPortablePath(e),onlyFiles:!1});a.sort();let n=await Promise.all(a.map(async f=>{let p=[Buffer.from(f)],h=J.join(e,fe.toPortablePath(f)),E=await ce.lstatPromise(h);return E.isSymbolicLink()?p.push(Buffer.from(await ce.readlinkPromise(h))):E.isFile()&&p.push(await ce.readFilePromise(h)),p.join("\0")})),c=(0,vQ.createHash)("sha512");for(let f of n)c.update(f);return c.digest("hex")}var vQ,B8,I0=Xe(()=>{Dt();vQ=Ie("crypto"),B8=ut(BQ())});var G={};Vt(G,{allPeerRequests:()=>qB,areDescriptorsEqual:()=>eue,areIdentsEqual:()=>UB,areLocatorsEqual:()=>_B,areVirtualPackagesEquivalent:()=>Y$e,bindDescriptor:()=>q$e,bindLocator:()=>W$e,convertDescriptorToLocator:()=>bQ,convertLocatorToDescriptor:()=>S8,convertPackageToLocator:()=>H$e,convertToIdent:()=>_$e,convertToManifestRange:()=>ret,copyPackage:()=>LB,devirtualizeDescriptor:()=>MB,devirtualizeLocator:()=>rI,ensureDevirtualizedDescriptor:()=>j$e,ensureDevirtualizedLocator:()=>G$e,getIdentVendorPath:()=>x8,isPackageCompatible:()=>TQ,isVirtualDescriptor:()=>kp,isVirtualLocator:()=>Gu,makeDescriptor:()=>On,makeIdent:()=>Da,makeLocator:()=>Ws,makeRange:()=>kQ,parseDescriptor:()=>C0,parseFileStyleRange:()=>eet,parseIdent:()=>Sa,parseLocator:()=>Qp,parseRange:()=>em,prettyDependent:()=>t3,prettyDescriptor:()=>ni,prettyIdent:()=>$i,prettyLocator:()=>Yr,prettyLocatorNoColors:()=>e3,prettyRange:()=>iI,prettyReference:()=>jB,prettyResolution:()=>FB,prettyWorkspace:()=>GB,renamePackage:()=>D8,slugifyIdent:()=>v8,slugifyLocator:()=>nI,sortDescriptors:()=>sI,stringifyDescriptor:()=>al,stringifyIdent:()=>un,stringifyLocator:()=>ll,tryParseDescriptor:()=>HB,tryParseIdent:()=>tue,tryParseLocator:()=>xQ,tryParseRange:()=>$$e,unwrapIdentFromScope:()=>iet,virtualizeDescriptor:()=>b8,virtualizePackage:()=>P8,wrapIdentIntoScope:()=>net});function Da(t,e){if(t?.startsWith("@"))throw new Error("Invalid scope: don't prefix it with '@'");return{identHash:us(t,e),scope:t,name:e}}function On(t,e){return{identHash:t.identHash,scope:t.scope,name:t.name,descriptorHash:us(t.identHash,e),range:e}}function Ws(t,e){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:us(t.identHash,e),reference:e}}function _$e(t){return{identHash:t.identHash,scope:t.scope,name:t.name}}function bQ(t){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:t.descriptorHash,reference:t.range}}function S8(t){return{identHash:t.identHash,scope:t.scope,name:t.name,descriptorHash:t.locatorHash,range:t.reference}}function H$e(t){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:t.locatorHash,reference:t.reference}}function D8(t,e){return{identHash:e.identHash,scope:e.scope,name:e.name,locatorHash:e.locatorHash,reference:e.reference,version:t.version,languageName:t.languageName,linkType:t.linkType,conditions:t.conditions,dependencies:new Map(t.dependencies),peerDependencies:new Map(t.peerDependencies),dependenciesMeta:new Map(t.dependenciesMeta),peerDependenciesMeta:new Map(t.peerDependenciesMeta),bin:new Map(t.bin)}}function LB(t){return D8(t,t)}function b8(t,e){if(e.includes("#"))throw new Error("Invalid entropy");return On(t,`virtual:${e}#${t.range}`)}function P8(t,e){if(e.includes("#"))throw new Error("Invalid entropy");return D8(t,Ws(t,`virtual:${e}#${t.reference}`))}function kp(t){return t.range.startsWith(OB)}function Gu(t){return t.reference.startsWith(OB)}function MB(t){if(!kp(t))throw new Error("Not a virtual descriptor");return On(t,t.range.replace(PQ,""))}function rI(t){if(!Gu(t))throw new Error("Not a virtual descriptor");return Ws(t,t.reference.replace(PQ,""))}function j$e(t){return kp(t)?On(t,t.range.replace(PQ,"")):t}function G$e(t){return Gu(t)?Ws(t,t.reference.replace(PQ,"")):t}function q$e(t,e){return t.range.includes("::")?t:On(t,`${t.range}::${tI.default.stringify(e)}`)}function W$e(t,e){return t.reference.includes("::")?t:Ws(t,`${t.reference}::${tI.default.stringify(e)}`)}function UB(t,e){return t.identHash===e.identHash}function eue(t,e){return t.descriptorHash===e.descriptorHash}function _B(t,e){return t.locatorHash===e.locatorHash}function Y$e(t,e){if(!Gu(t))throw new Error("Invalid package type");if(!Gu(e))throw new Error("Invalid package type");if(!UB(t,e)||t.dependencies.size!==e.dependencies.size)return!1;for(let r of t.dependencies.values()){let s=e.dependencies.get(r.identHash);if(!s||!eue(r,s))return!1}return!0}function Sa(t){let e=tue(t);if(!e)throw new Error(`Invalid ident (${t})`);return e}function tue(t){let e=t.match(V$e);if(!e)return null;let[,r,s]=e;return Da(typeof r<"u"?r:null,s)}function C0(t,e=!1){let r=HB(t,e);if(!r)throw new Error(`Invalid descriptor (${t})`);return r}function HB(t,e=!1){let r=e?t.match(J$e):t.match(K$e);if(!r)return null;let[,s,a,n]=r;if(n==="unknown")throw new Error(`Invalid range (${t})`);let c=typeof s<"u"?s:null,f=typeof n<"u"?n:"unknown";return On(Da(c,a),f)}function Qp(t,e=!1){let r=xQ(t,e);if(!r)throw new Error(`Invalid locator (${t})`);return r}function xQ(t,e=!1){let r=e?t.match(z$e):t.match(X$e);if(!r)return null;let[,s,a,n]=r;if(n==="unknown")throw new Error(`Invalid reference (${t})`);let c=typeof s<"u"?s:null,f=typeof n<"u"?n:"unknown";return Ws(Da(c,a),f)}function em(t,e){let r=t.match(Z$e);if(r===null)throw new Error(`Invalid range (${t})`);let s=typeof r[1]<"u"?r[1]:null;if(typeof e?.requireProtocol=="string"&&s!==e.requireProtocol)throw new Error(`Invalid protocol (${s})`);if(e?.requireProtocol&&s===null)throw new Error(`Missing protocol (${s})`);let a=typeof r[3]<"u"?decodeURIComponent(r[2]):null;if(e?.requireSource&&a===null)throw new Error(`Missing source (${t})`);let n=typeof r[3]<"u"?decodeURIComponent(r[3]):decodeURIComponent(r[2]),c=e?.parseSelector?tI.default.parse(n):n,f=typeof r[4]<"u"?tI.default.parse(r[4]):null;return{protocol:s,source:a,selector:c,params:f}}function $$e(t,e){try{return em(t,e)}catch{return null}}function eet(t,{protocol:e}){let{selector:r,params:s}=em(t,{requireProtocol:e,requireBindings:!0});if(typeof s.locator!="string")throw new Error(`Assertion failed: Invalid bindings for ${t}`);return{parentLocator:Qp(s.locator,!0),path:r}}function zce(t){return t=t.replaceAll("%","%25"),t=t.replaceAll(":","%3A"),t=t.replaceAll("#","%23"),t}function tet(t){return t===null?!1:Object.entries(t).length>0}function kQ({protocol:t,source:e,selector:r,params:s}){let a="";return t!==null&&(a+=`${t}`),e!==null&&(a+=`${zce(e)}#`),a+=zce(r),tet(s)&&(a+=`::${tI.default.stringify(s)}`),a}function ret(t){let{params:e,protocol:r,source:s,selector:a}=em(t);for(let n in e)n.startsWith("__")&&delete e[n];return kQ({protocol:r,source:s,params:e,selector:a})}function un(t){return t.scope?`@${t.scope}/${t.name}`:`${t.name}`}function net(t,e){return t.scope?Da(e,`${t.scope}__${t.name}`):Da(e,t.name)}function iet(t,e){if(t.scope!==e)return t;let r=t.name.indexOf("__");if(r===-1)return Da(null,t.name);let s=t.name.slice(0,r),a=t.name.slice(r+2);return Da(s,a)}function al(t){return t.scope?`@${t.scope}/${t.name}@${t.range}`:`${t.name}@${t.range}`}function ll(t){return t.scope?`@${t.scope}/${t.name}@${t.reference}`:`${t.name}@${t.reference}`}function v8(t){return t.scope!==null?`@${t.scope}-${t.name}`:t.name}function nI(t){let{protocol:e,selector:r}=em(t.reference),s=e!==null?e.replace(set,""):"exotic",a=Xce.default.valid(r),n=a!==null?`${s}-${a}`:`${s}`,c=10;return t.scope?`${v8(t)}-${n}-${t.locatorHash.slice(0,c)}`:`${v8(t)}-${n}-${t.locatorHash.slice(0,c)}`}function $i(t,e){return e.scope?`${Ht(t,`@${e.scope}/`,ht.SCOPE)}${Ht(t,e.name,ht.NAME)}`:`${Ht(t,e.name,ht.NAME)}`}function QQ(t){if(t.startsWith(OB)){let e=QQ(t.substring(t.indexOf("#")+1)),r=t.substring(OB.length,OB.length+M$e);return`${e} [${r}]`}else return t.replace(oet,"?[...]")}function iI(t,e){return`${Ht(t,QQ(e),ht.RANGE)}`}function ni(t,e){return`${$i(t,e)}${Ht(t,"@",ht.RANGE)}${iI(t,e.range)}`}function jB(t,e){return`${Ht(t,QQ(e),ht.REFERENCE)}`}function Yr(t,e){return`${$i(t,e)}${Ht(t,"@",ht.REFERENCE)}${jB(t,e.reference)}`}function e3(t){return`${un(t)}@${QQ(t.reference)}`}function sI(t){return qs(t,[e=>un(e),e=>e.range])}function GB(t,e){return $i(t,e.anchoredLocator)}function FB(t,e,r){let s=kp(e)?MB(e):e;return r===null?`${ni(t,s)} \u2192 ${$4(t).Cross}`:s.identHash===r.identHash?`${ni(t,s)} \u2192 ${jB(t,r.reference)}`:`${ni(t,s)} \u2192 ${Yr(t,r)}`}function t3(t,e,r){return r===null?`${Yr(t,e)}`:`${Yr(t,e)} (via ${iI(t,r.range)})`}function x8(t){return`node_modules/${un(t)}`}function TQ(t,e){return t.conditions?U$e(t.conditions,r=>{let[,s,a]=r.match($ce),n=e[s];return n?n.includes(a):!0}):!0}function qB(t){let e=new Set;if("children"in t)e.add(t);else for(let r of t.requests.values())e.add(r);for(let r of e)for(let s of r.children.values())e.add(s);return e}var tI,Xce,Zce,OB,M$e,$ce,U$e,PQ,V$e,J$e,K$e,z$e,X$e,Z$e,set,oet,Wo=Xe(()=>{tI=ut(Ie("querystring")),Xce=ut(Ai()),Zce=ut(Ise());xc();I0();Pc();Wo();OB="virtual:",M$e=5,$ce=/(os|cpu|libc)=([a-z0-9_-]+)/,U$e=(0,Zce.makeParser)($ce);PQ=/^[^#]*#/;V$e=/^(?:@([^/]+?)\/)?([^@/]+)$/;J$e=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))$/,K$e=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))?$/;z$e=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))$/,X$e=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))?$/;Z$e=/^([^#:]*:)?((?:(?!::)[^#])*)(?:#((?:(?!::).)*))?(?:::(.*))?$/;set=/:$/;oet=/\?.*/});var rue,nue=Xe(()=>{Wo();rue={hooks:{reduceDependency:(t,e,r,s,{resolver:a,resolveOptions:n})=>{for(let{pattern:c,reference:f}of e.topLevelWorkspace.manifest.resolutions){if(c.from&&(c.from.fullName!==un(r)||e.configuration.normalizeLocator(Ws(Sa(c.from.fullName),c.from.description??r.reference)).locatorHash!==r.locatorHash)||c.descriptor.fullName!==un(t)||e.configuration.normalizeDependency(On(Qp(c.descriptor.fullName),c.descriptor.description??t.range)).descriptorHash!==t.descriptorHash)continue;return a.bindDescriptor(e.configuration.normalizeDependency(On(t,f)),e.topLevelWorkspace.anchoredLocator,n)}return t},validateProject:async(t,e)=>{for(let r of t.workspaces){let s=GB(t.configuration,r);await t.configuration.triggerHook(a=>a.validateWorkspace,r,{reportWarning:(a,n)=>e.reportWarning(a,`${s}: ${n}`),reportError:(a,n)=>e.reportError(a,`${s}: ${n}`)})}},validateWorkspace:async(t,e)=>{let{manifest:r}=t;r.resolutions.length&&t.cwd!==t.project.cwd&&r.errors.push(new Error("Resolutions field will be ignored"));for(let s of r.errors)e.reportWarning(57,s.message)}}}});var Ei,tm=Xe(()=>{Ei=class t{static{this.protocol="workspace:"}supportsDescriptor(e,r){return!!(e.range.startsWith(t.protocol)||r.project.tryWorkspaceByDescriptor(e)!==null)}supportsLocator(e,r){return!!e.reference.startsWith(t.protocol)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){return[s.project.getWorkspaceByDescriptor(e).anchoredLocator]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let s=r.project.getWorkspaceByCwd(e.reference.slice(t.protocol.length));return{...e,version:s.manifest.version||"0.0.0",languageName:"unknown",linkType:"SOFT",conditions:null,dependencies:r.project.configuration.normalizeDependencyMap(new Map([...s.manifest.dependencies,...s.manifest.devDependencies])),peerDependencies:new Map([...s.manifest.peerDependencies]),dependenciesMeta:s.manifest.dependenciesMeta,peerDependenciesMeta:s.manifest.peerDependenciesMeta,bin:s.manifest.bin}}}});var Fr={};Vt(Fr,{SemVer:()=>lue.SemVer,clean:()=>cet,getComparator:()=>oue,mergeComparators:()=>k8,satisfiesWithPrereleases:()=>Zf,simplifyRanges:()=>Q8,stringifyComparator:()=>aue,validRange:()=>cl});function Zf(t,e,r=!1){if(!t)return!1;let s=`${e}${r}`,a=iue.get(s);if(typeof a>"u")try{a=new Tp.default.Range(e,{includePrerelease:!0,loose:r})}catch{return!1}finally{iue.set(s,a||null)}else if(a===null)return!1;let n;try{n=new Tp.default.SemVer(t,a)}catch{return!1}return a.test(n)?!0:(n.prerelease&&(n.prerelease=[]),a.set.some(c=>{for(let f of c)f.semver.prerelease&&(f.semver.prerelease=[]);return c.every(f=>f.test(n))}))}function cl(t){if(t.indexOf(":")!==-1)return null;let e=sue.get(t);if(typeof e<"u")return e;try{e=new Tp.default.Range(t)}catch{e=null}return sue.set(t,e),e}function cet(t){let e=aet.exec(t);return e?e[1]:null}function oue(t){if(t.semver===Tp.default.Comparator.ANY)return{gt:null,lt:null};switch(t.operator){case"":return{gt:[">=",t.semver],lt:["<=",t.semver]};case">":case">=":return{gt:[t.operator,t.semver],lt:null};case"<":case"<=":return{gt:null,lt:[t.operator,t.semver]};default:throw new Error(`Assertion failed: Unexpected comparator operator (${t.operator})`)}}function k8(t){if(t.length===0)return null;let e=null,r=null;for(let s of t){if(s.gt){let a=e!==null?Tp.default.compare(s.gt[1],e[1]):null;(a===null||a>0||a===0&&s.gt[0]===">")&&(e=s.gt)}if(s.lt){let a=r!==null?Tp.default.compare(s.lt[1],r[1]):null;(a===null||a<0||a===0&&s.lt[0]==="<")&&(r=s.lt)}}if(e&&r){let s=Tp.default.compare(e[1],r[1]);if(s===0&&(e[0]===">"||r[0]==="<")||s>0)return null}return{gt:e,lt:r}}function aue(t){if(t.gt&&t.lt){if(t.gt[0]===">="&&t.lt[0]==="<="&&t.gt[1].version===t.lt[1].version)return t.gt[1].version;if(t.gt[0]===">="&&t.lt[0]==="<"){if(t.lt[1].version===`${t.gt[1].major+1}.0.0-0`)return`^${t.gt[1].version}`;if(t.lt[1].version===`${t.gt[1].major}.${t.gt[1].minor+1}.0-0`)return`~${t.gt[1].version}`}}let e=[];return t.gt&&e.push(t.gt[0]+t.gt[1].version),t.lt&&e.push(t.lt[0]+t.lt[1].version),e.length?e.join(" "):"*"}function Q8(t){let e=t.map(uet).map(s=>cl(s).set.map(a=>a.map(n=>oue(n)))),r=e.shift().map(s=>k8(s)).filter(s=>s!==null);for(let s of e){let a=[];for(let n of r)for(let c of s){let f=k8([n,...c]);f!==null&&a.push(f)}r=a}return r.length===0?null:r.map(s=>aue(s)).join(" || ")}function uet(t){let e=t.split("||");if(e.length>1){let r=new Set;for(let s of e)e.some(a=>a!==s&&Tp.default.subset(s,a))||r.add(s);if(r.size{Tp=ut(Ai()),lue=ut(Ai()),iue=new Map;sue=new Map;aet=/^(?:[\sv=]*?)((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\s*)$/});function cue(t){let e=t.match(/^[ \t]+/m);return e?e[0]:" "}function uue(t){return t.charCodeAt(0)===65279?t.slice(1):t}function ba(t){return t.replace(/\\/g,"/")}function RQ(t,{yamlCompatibilityMode:e}){return e?Y4(t):typeof t>"u"||typeof t=="boolean"?t:null}function fue(t,e){let r=e.search(/[^!]/);if(r===-1)return"invalid";let s=r%2===0?"":"!",a=e.slice(r);return`${s}${t}=${a}`}function T8(t,e){return e.length===1?fue(t,e[0]):`(${e.map(r=>fue(t,r)).join(" | ")})`}var Aue,Ut,oI=Xe(()=>{Dt();wc();Aue=ut(Ai());tm();Pc();Rp();Wo();Ut=class t{constructor(){this.indent=" ";this.name=null;this.version=null;this.os=null;this.cpu=null;this.libc=null;this.type=null;this.packageManager=null;this.private=!1;this.license=null;this.main=null;this.module=null;this.browser=null;this.languageName=null;this.bin=new Map;this.scripts=new Map;this.dependencies=new Map;this.devDependencies=new Map;this.peerDependencies=new Map;this.workspaceDefinitions=[];this.dependenciesMeta=new Map;this.peerDependenciesMeta=new Map;this.resolutions=[];this.files=null;this.publishConfig=null;this.installConfig=null;this.preferUnplugged=null;this.raw={};this.errors=[]}static{this.fileName="package.json"}static{this.allDependencies=["dependencies","devDependencies","peerDependencies"]}static{this.hardDependencies=["dependencies","devDependencies"]}static async tryFind(e,{baseFs:r=new Yn}={}){let s=J.join(e,"package.json");try{return await t.fromFile(s,{baseFs:r})}catch(a){if(a.code==="ENOENT")return null;throw a}}static async find(e,{baseFs:r}={}){let s=await t.tryFind(e,{baseFs:r});if(s===null)throw new Error("Manifest not found");return s}static async fromFile(e,{baseFs:r=new Yn}={}){let s=new t;return await s.loadFile(e,{baseFs:r}),s}static fromText(e){let r=new t;return r.loadFromText(e),r}loadFromText(e){let r;try{r=JSON.parse(uue(e)||"{}")}catch(s){throw s.message+=` (when parsing ${e})`,s}this.load(r),this.indent=cue(e)}async loadFile(e,{baseFs:r=new Yn}){let s=await r.readFilePromise(e,"utf8"),a;try{a=JSON.parse(uue(s)||"{}")}catch(n){throw n.message+=` (when parsing ${e})`,n}this.load(a),this.indent=cue(s)}load(e,{yamlCompatibilityMode:r=!1}={}){if(typeof e!="object"||e===null)throw new Error(`Utterly invalid manifest data (${e})`);this.raw=e;let s=[];if(this.name=null,typeof e.name=="string")try{this.name=Sa(e.name)}catch{s.push(new Error("Parsing failed for the 'name' field"))}if(typeof e.version=="string"?this.version=e.version:this.version=null,Array.isArray(e.os)){let n=[];this.os=n;for(let c of e.os)typeof c!="string"?s.push(new Error("Parsing failed for the 'os' field")):n.push(c)}else this.os=null;if(Array.isArray(e.cpu)){let n=[];this.cpu=n;for(let c of e.cpu)typeof c!="string"?s.push(new Error("Parsing failed for the 'cpu' field")):n.push(c)}else this.cpu=null;if(Array.isArray(e.libc)){let n=[];this.libc=n;for(let c of e.libc)typeof c!="string"?s.push(new Error("Parsing failed for the 'libc' field")):n.push(c)}else this.libc=null;if(typeof e.type=="string"?this.type=e.type:this.type=null,typeof e.packageManager=="string"?this.packageManager=e.packageManager:this.packageManager=null,typeof e.private=="boolean"?this.private=e.private:this.private=!1,typeof e.license=="string"?this.license=e.license:this.license=null,typeof e.languageName=="string"?this.languageName=e.languageName:this.languageName=null,typeof e.main=="string"?this.main=ba(e.main):this.main=null,typeof e.module=="string"?this.module=ba(e.module):this.module=null,e.browser!=null)if(typeof e.browser=="string")this.browser=ba(e.browser);else{this.browser=new Map;for(let[n,c]of Object.entries(e.browser))this.browser.set(ba(n),typeof c=="string"?ba(c):c)}else this.browser=null;if(this.bin=new Map,typeof e.bin=="string")e.bin.trim()===""?s.push(new Error("Invalid bin field")):this.name!==null?this.bin.set(this.name.name,ba(e.bin)):s.push(new Error("String bin field, but no attached package name"));else if(typeof e.bin=="object"&&e.bin!==null)for(let[n,c]of Object.entries(e.bin)){if(typeof c!="string"||c.trim()===""){s.push(new Error(`Invalid bin definition for '${n}'`));continue}let f=Sa(n);this.bin.set(f.name,ba(c))}if(this.scripts=new Map,typeof e.scripts=="object"&&e.scripts!==null)for(let[n,c]of Object.entries(e.scripts)){if(typeof c!="string"){s.push(new Error(`Invalid script definition for '${n}'`));continue}this.scripts.set(n,c)}if(this.dependencies=new Map,typeof e.dependencies=="object"&&e.dependencies!==null)for(let[n,c]of Object.entries(e.dependencies)){if(typeof c!="string"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Sa(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.dependencies.set(p.identHash,p)}if(this.devDependencies=new Map,typeof e.devDependencies=="object"&&e.devDependencies!==null)for(let[n,c]of Object.entries(e.devDependencies)){if(typeof c!="string"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Sa(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.devDependencies.set(p.identHash,p)}if(this.peerDependencies=new Map,typeof e.peerDependencies=="object"&&e.peerDependencies!==null)for(let[n,c]of Object.entries(e.peerDependencies)){let f;try{f=Sa(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}(typeof c!="string"||!c.startsWith(Ei.protocol)&&!cl(c))&&(s.push(new Error(`Invalid dependency range for '${n}'`)),c="*");let p=On(f,c);this.peerDependencies.set(p.identHash,p)}typeof e.workspaces=="object"&&e.workspaces!==null&&e.workspaces.nohoist&&s.push(new Error("'nohoist' is deprecated, please use 'installConfig.hoistingLimits' instead"));let a=Array.isArray(e.workspaces)?e.workspaces:typeof e.workspaces=="object"&&e.workspaces!==null&&Array.isArray(e.workspaces.packages)?e.workspaces.packages:[];this.workspaceDefinitions=[];for(let n of a){if(typeof n!="string"){s.push(new Error(`Invalid workspace definition for '${n}'`));continue}this.workspaceDefinitions.push({pattern:n})}if(this.dependenciesMeta=new Map,typeof e.dependenciesMeta=="object"&&e.dependenciesMeta!==null)for(let[n,c]of Object.entries(e.dependenciesMeta)){if(typeof c!="object"||c===null){s.push(new Error(`Invalid meta field for '${n}`));continue}let f=C0(n),p=this.ensureDependencyMeta(f),h=RQ(c.built,{yamlCompatibilityMode:r});if(h===null){s.push(new Error(`Invalid built meta field for '${n}'`));continue}let E=RQ(c.optional,{yamlCompatibilityMode:r});if(E===null){s.push(new Error(`Invalid optional meta field for '${n}'`));continue}let C=RQ(c.unplugged,{yamlCompatibilityMode:r});if(C===null){s.push(new Error(`Invalid unplugged meta field for '${n}'`));continue}Object.assign(p,{built:h,optional:E,unplugged:C})}if(this.peerDependenciesMeta=new Map,typeof e.peerDependenciesMeta=="object"&&e.peerDependenciesMeta!==null)for(let[n,c]of Object.entries(e.peerDependenciesMeta)){if(typeof c!="object"||c===null){s.push(new Error(`Invalid meta field for '${n}'`));continue}let f=C0(n),p=this.ensurePeerDependencyMeta(f),h=RQ(c.optional,{yamlCompatibilityMode:r});if(h===null){s.push(new Error(`Invalid optional meta field for '${n}'`));continue}Object.assign(p,{optional:h})}if(this.resolutions=[],typeof e.resolutions=="object"&&e.resolutions!==null)for(let[n,c]of Object.entries(e.resolutions)){if(typeof c!="string"){s.push(new Error(`Invalid resolution entry for '${n}'`));continue}try{this.resolutions.push({pattern:px(n),reference:c})}catch(f){s.push(f);continue}}if(Array.isArray(e.files)){this.files=new Set;for(let n of e.files){if(typeof n!="string"){s.push(new Error(`Invalid files entry for '${n}'`));continue}this.files.add(n)}}else this.files=null;if(typeof e.publishConfig=="object"&&e.publishConfig!==null){if(this.publishConfig={},typeof e.publishConfig.access=="string"&&(this.publishConfig.access=e.publishConfig.access),typeof e.publishConfig.main=="string"&&(this.publishConfig.main=ba(e.publishConfig.main)),typeof e.publishConfig.module=="string"&&(this.publishConfig.module=ba(e.publishConfig.module)),e.publishConfig.browser!=null)if(typeof e.publishConfig.browser=="string")this.publishConfig.browser=ba(e.publishConfig.browser);else{this.publishConfig.browser=new Map;for(let[n,c]of Object.entries(e.publishConfig.browser))this.publishConfig.browser.set(ba(n),typeof c=="string"?ba(c):c)}if(typeof e.publishConfig.registry=="string"&&(this.publishConfig.registry=e.publishConfig.registry),typeof e.publishConfig.provenance=="boolean"&&(this.publishConfig.provenance=e.publishConfig.provenance),typeof e.publishConfig.bin=="string")this.name!==null?this.publishConfig.bin=new Map([[this.name.name,ba(e.publishConfig.bin)]]):s.push(new Error("String bin field, but no attached package name"));else if(typeof e.publishConfig.bin=="object"&&e.publishConfig.bin!==null){this.publishConfig.bin=new Map;for(let[n,c]of Object.entries(e.publishConfig.bin)){if(typeof c!="string"){s.push(new Error(`Invalid bin definition for '${n}'`));continue}this.publishConfig.bin.set(n,ba(c))}}if(Array.isArray(e.publishConfig.executableFiles)){this.publishConfig.executableFiles=new Set;for(let n of e.publishConfig.executableFiles){if(typeof n!="string"){s.push(new Error("Invalid executable file definition"));continue}this.publishConfig.executableFiles.add(ba(n))}}}else this.publishConfig=null;if(typeof e.installConfig=="object"&&e.installConfig!==null){this.installConfig={};for(let n of Object.keys(e.installConfig))n==="hoistingLimits"?typeof e.installConfig.hoistingLimits=="string"?this.installConfig.hoistingLimits=e.installConfig.hoistingLimits:s.push(new Error("Invalid hoisting limits definition")):n=="selfReferences"?typeof e.installConfig.selfReferences=="boolean"?this.installConfig.selfReferences=e.installConfig.selfReferences:s.push(new Error("Invalid selfReferences definition, must be a boolean value")):s.push(new Error(`Unrecognized installConfig key: ${n}`))}else this.installConfig=null;if(typeof e.optionalDependencies=="object"&&e.optionalDependencies!==null)for(let[n,c]of Object.entries(e.optionalDependencies)){if(typeof c!="string"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Sa(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.dependencies.set(p.identHash,p);let h=On(f,"unknown"),E=this.ensureDependencyMeta(h);Object.assign(E,{optional:!0})}typeof e.preferUnplugged=="boolean"?this.preferUnplugged=e.preferUnplugged:this.preferUnplugged=null,this.errors=s}getForScope(e){switch(e){case"dependencies":return this.dependencies;case"devDependencies":return this.devDependencies;case"peerDependencies":return this.peerDependencies;default:throw new Error(`Unsupported value ("${e}")`)}}hasConsumerDependency(e){return!!(this.dependencies.has(e.identHash)||this.peerDependencies.has(e.identHash))}hasHardDependency(e){return!!(this.dependencies.has(e.identHash)||this.devDependencies.has(e.identHash))}hasSoftDependency(e){return!!this.peerDependencies.has(e.identHash)}hasDependency(e){return!!(this.hasHardDependency(e)||this.hasSoftDependency(e))}getConditions(){let e=[];return this.os&&this.os.length>0&&e.push(T8("os",this.os)),this.cpu&&this.cpu.length>0&&e.push(T8("cpu",this.cpu)),this.libc&&this.libc.length>0&&e.push(T8("libc",this.libc)),e.length>0?e.join(" & "):null}ensureDependencyMeta(e){if(e.range!=="unknown"&&!Aue.default.valid(e.range))throw new Error(`Invalid meta field range for '${al(e)}'`);let r=un(e),s=e.range!=="unknown"?e.range:null,a=this.dependenciesMeta.get(r);a||this.dependenciesMeta.set(r,a=new Map);let n=a.get(s);return n||a.set(s,n={}),n}ensurePeerDependencyMeta(e){if(e.range!=="unknown")throw new Error(`Invalid meta field range for '${al(e)}'`);let r=un(e),s=this.peerDependenciesMeta.get(r);return s||this.peerDependenciesMeta.set(r,s={}),s}setRawField(e,r,{after:s=[]}={}){let a=new Set(s.filter(n=>Object.hasOwn(this.raw,n)));if(a.size===0||Object.hasOwn(this.raw,e))this.raw[e]=r;else{let n=this.raw,c=this.raw={},f=!1;for(let p of Object.keys(n))c[p]=n[p],f||(a.delete(p),a.size===0&&(c[e]=r,f=!0))}}exportTo(e,{compatibilityMode:r=!0}={}){if(Object.assign(e,this.raw),this.name!==null?e.name=un(this.name):delete e.name,this.version!==null?e.version=this.version:delete e.version,this.os!==null?e.os=this.os:delete e.os,this.cpu!==null?e.cpu=this.cpu:delete e.cpu,this.type!==null?e.type=this.type:delete e.type,this.packageManager!==null?e.packageManager=this.packageManager:delete e.packageManager,this.private?e.private=!0:delete e.private,this.license!==null?e.license=this.license:delete e.license,this.languageName!==null?e.languageName=this.languageName:delete e.languageName,this.main!==null?e.main=this.main:delete e.main,this.module!==null?e.module=this.module:delete e.module,this.browser!==null){let n=this.browser;typeof n=="string"?e.browser=n:n instanceof Map&&(e.browser=Object.assign({},...Array.from(n.keys()).sort().map(c=>({[c]:n.get(c)}))))}else delete e.browser;this.bin.size===1&&this.name!==null&&this.bin.has(this.name.name)?e.bin=this.bin.get(this.name.name):this.bin.size>0?e.bin=Object.assign({},...Array.from(this.bin.keys()).sort().map(n=>({[n]:this.bin.get(n)}))):delete e.bin,this.workspaceDefinitions.length>0?this.raw.workspaces&&!Array.isArray(this.raw.workspaces)?e.workspaces={...this.raw.workspaces,packages:this.workspaceDefinitions.map(({pattern:n})=>n)}:e.workspaces=this.workspaceDefinitions.map(({pattern:n})=>n):this.raw.workspaces&&!Array.isArray(this.raw.workspaces)&&Object.keys(this.raw.workspaces).length>0?e.workspaces=this.raw.workspaces:delete e.workspaces;let s=[],a=[];for(let n of this.dependencies.values()){let c=this.dependenciesMeta.get(un(n)),f=!1;if(r&&c){let p=c.get(null);p&&p.optional&&(f=!0)}f?a.push(n):s.push(n)}s.length>0?e.dependencies=Object.assign({},...sI(s).map(n=>({[un(n)]:n.range}))):delete e.dependencies,a.length>0?e.optionalDependencies=Object.assign({},...sI(a).map(n=>({[un(n)]:n.range}))):delete e.optionalDependencies,this.devDependencies.size>0?e.devDependencies=Object.assign({},...sI(this.devDependencies.values()).map(n=>({[un(n)]:n.range}))):delete e.devDependencies,this.peerDependencies.size>0?e.peerDependencies=Object.assign({},...sI(this.peerDependencies.values()).map(n=>({[un(n)]:n.range}))):delete e.peerDependencies,e.dependenciesMeta={};for(let[n,c]of qs(this.dependenciesMeta.entries(),([f,p])=>f))for(let[f,p]of qs(c.entries(),([h,E])=>h!==null?`0${h}`:"1")){let h=f!==null?al(On(Sa(n),f)):n,E={...p};r&&f===null&&delete E.optional,Object.keys(E).length!==0&&(e.dependenciesMeta[h]=E)}if(Object.keys(e.dependenciesMeta).length===0&&delete e.dependenciesMeta,this.peerDependenciesMeta.size>0?e.peerDependenciesMeta=Object.assign({},...qs(this.peerDependenciesMeta.entries(),([n,c])=>n).map(([n,c])=>({[n]:c}))):delete e.peerDependenciesMeta,this.resolutions.length>0?e.resolutions=Object.assign({},...this.resolutions.map(({pattern:n,reference:c})=>({[hx(n)]:c}))):delete e.resolutions,this.files!==null?e.files=Array.from(this.files):delete e.files,this.preferUnplugged!==null?e.preferUnplugged=this.preferUnplugged:delete e.preferUnplugged,this.scripts!==null&&this.scripts.size>0){e.scripts??={};for(let n of Object.keys(e.scripts))this.scripts.has(n)||delete e.scripts[n];for(let[n,c]of this.scripts.entries())e.scripts[n]=c}else delete e.scripts;return e}}});function Aet(t){return typeof t.reportCode<"u"}var pue,hue,fet,jt,Ao,Tc=Xe(()=>{ql();pue=Ie("stream"),hue=Ie("string_decoder"),fet=15,jt=class extends Error{constructor(r,s,a){super(s);this.reportExtra=a;this.reportCode=r}};Ao=class{constructor(){this.cacheHits=new Set;this.cacheMisses=new Set;this.reportedInfos=new Set;this.reportedWarnings=new Set;this.reportedErrors=new Set}getRecommendedLength(){return 180}reportCacheHit(e){this.cacheHits.add(e.locatorHash)}reportCacheMiss(e,r){this.cacheMisses.add(e.locatorHash)}static progressViaCounter(e){let r=0,s,a=new Promise(p=>{s=p}),n=p=>{let h=s;a=new Promise(E=>{s=E}),r=p,h()},c=(p=0)=>{n(r+1)},f=async function*(){for(;r{r=c}),a=Q4(c=>{let f=r;s=new Promise(p=>{r=p}),e=c,f()},1e3/fet),n=async function*(){for(;;)await s,yield{title:e}}();return{[Symbol.asyncIterator](){return n},hasProgress:!1,hasTitle:!0,setTitle:a}}async startProgressPromise(e,r){let s=this.reportProgress(e);try{return await r(e)}finally{s.stop()}}startProgressSync(e,r){let s=this.reportProgress(e);try{return r(e)}finally{s.stop()}}reportInfoOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedInfos.has(a)||(this.reportedInfos.add(a),this.reportInfo(e,r),s?.reportExtra?.(this))}reportWarningOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedWarnings.has(a)||(this.reportedWarnings.add(a),this.reportWarning(e,r),s?.reportExtra?.(this))}reportErrorOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedErrors.has(a)||(this.reportedErrors.add(a),this.reportError(e,r),s?.reportExtra?.(this))}reportExceptionOnce(e){Aet(e)?this.reportErrorOnce(e.reportCode,e.message,{key:e,reportExtra:e.reportExtra}):this.reportErrorOnce(1,e.stack||e.message,{key:e})}createStreamReporter(e=null){let r=new pue.PassThrough,s=new hue.StringDecoder,a="";return r.on("data",n=>{let c=s.write(n),f;do if(f=c.indexOf(` `),f!==-1){let p=a+c.substring(0,f);c=c.substring(f+1),a="",e!==null?this.reportInfo(null,`${e} ${p}`):this.reportInfo(null,p)}while(f!==-1);a+=c}),r.on("end",()=>{let n=s.end();n!==""&&(e!==null?this.reportInfo(null,`${e} ${n}`):this.reportInfo(null,n))}),r}}});var aI,R8=Xe(()=>{Tc();Wo();aI=class{constructor(e){this.fetchers=e}supports(e,r){return!!this.tryFetcher(e,r)}getLocalPath(e,r){return this.getFetcher(e,r).getLocalPath(e,r)}async fetch(e,r){return await this.getFetcher(e,r).fetch(e,r)}tryFetcher(e,r){let s=this.fetchers.find(a=>a.supports(e,r));return s||null}getFetcher(e,r){let s=this.fetchers.find(a=>a.supports(e,r));if(!s)throw new jt(11,`${Yr(r.project.configuration,e)} isn't supported by any available fetcher`);return s}}});var rm,F8=Xe(()=>{Wo();rm=class{constructor(e){this.resolvers=e.filter(r=>r)}supportsDescriptor(e,r){return!!this.tryResolverByDescriptor(e,r)}supportsLocator(e,r){return!!this.tryResolverByLocator(e,r)}shouldPersistResolution(e,r){return this.getResolverByLocator(e,r).shouldPersistResolution(e,r)}bindDescriptor(e,r,s){return this.getResolverByDescriptor(e,s).bindDescriptor(e,r,s)}getResolutionDependencies(e,r){return this.getResolverByDescriptor(e,r).getResolutionDependencies(e,r)}async getCandidates(e,r,s){return await this.getResolverByDescriptor(e,s).getCandidates(e,r,s)}async getSatisfying(e,r,s,a){return this.getResolverByDescriptor(e,a).getSatisfying(e,r,s,a)}async resolve(e,r){return await this.getResolverByLocator(e,r).resolve(e,r)}tryResolverByDescriptor(e,r){let s=this.resolvers.find(a=>a.supportsDescriptor(e,r));return s||null}getResolverByDescriptor(e,r){let s=this.resolvers.find(a=>a.supportsDescriptor(e,r));if(!s)throw new Error(`${ni(r.project.configuration,e)} isn't supported by any available resolver`);return s}tryResolverByLocator(e,r){let s=this.resolvers.find(a=>a.supportsLocator(e,r));return s||null}getResolverByLocator(e,r){let s=this.resolvers.find(a=>a.supportsLocator(e,r));if(!s)throw new Error(`${Yr(r.project.configuration,e)} isn't supported by any available resolver`);return s}}});var lI,N8=Xe(()=>{Dt();Wo();lI=class{supports(e){return!!e.reference.startsWith("virtual:")}getLocalPath(e,r){let s=e.reference.indexOf("#");if(s===-1)throw new Error("Invalid virtual package reference");let a=e.reference.slice(s+1),n=Ws(e,a);return r.fetcher.getLocalPath(n,r)}async fetch(e,r){let s=e.reference.indexOf("#");if(s===-1)throw new Error("Invalid virtual package reference");let a=e.reference.slice(s+1),n=Ws(e,a),c=await r.fetcher.fetch(n,r);return await this.ensureVirtualLink(e,c,r)}getLocatorFilename(e){return nI(e)}async ensureVirtualLink(e,r,s){let a=r.packageFs.getRealPath(),n=s.project.configuration.get("virtualFolder"),c=this.getLocatorFilename(e),f=uo.makeVirtualPath(n,c,a),p=new _f(f,{baseFs:r.packageFs,pathUtils:J});return{...r,packageFs:p}}}});var FQ,gue=Xe(()=>{FQ=class t{static{this.protocol="virtual:"}static isVirtualDescriptor(e){return!!e.range.startsWith(t.protocol)}static isVirtualLocator(e){return!!e.reference.startsWith(t.protocol)}supportsDescriptor(e,r){return t.isVirtualDescriptor(e)}supportsLocator(e,r){return t.isVirtualLocator(e)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){throw new Error('Assertion failed: calling "bindDescriptor" on a virtual descriptor is unsupported')}getResolutionDependencies(e,r){throw new Error('Assertion failed: calling "getResolutionDependencies" on a virtual descriptor is unsupported')}async getCandidates(e,r,s){throw new Error('Assertion failed: calling "getCandidates" on a virtual descriptor is unsupported')}async getSatisfying(e,r,s,a){throw new Error('Assertion failed: calling "getSatisfying" on a virtual descriptor is unsupported')}async resolve(e,r){throw new Error('Assertion failed: calling "resolve" on a virtual locator is unsupported')}}});var cI,O8=Xe(()=>{Dt();tm();cI=class{supports(e){return!!e.reference.startsWith(Ei.protocol)}getLocalPath(e,r){return this.getWorkspace(e,r).cwd}async fetch(e,r){let s=this.getWorkspace(e,r).cwd;return{packageFs:new Sn(s),prefixPath:vt.dot,localPath:s}}getWorkspace(e,r){return r.project.getWorkspaceByCwd(e.reference.slice(Ei.protocol.length))}}});function WB(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function due(t){return typeof t>"u"?3:WB(t)?0:Array.isArray(t)?1:2}function U8(t,e){return Object.hasOwn(t,e)}function het(t){return WB(t)&&U8(t,"onConflict")&&typeof t.onConflict=="string"}function get(t){if(typeof t>"u")return{onConflict:"default",value:t};if(!het(t))return{onConflict:"default",value:t};if(U8(t,"value"))return t;let{onConflict:e,...r}=t;return{onConflict:e,value:r}}function mue(t,e){let r=WB(t)&&U8(t,e)?t[e]:void 0;return get(r)}function uI(t,e){return[t,e,yue]}function _8(t){return Array.isArray(t)?t[2]===yue:!1}function L8(t,e){if(WB(t)){let r={};for(let s of Object.keys(t))r[s]=L8(t[s],e);return uI(e,r)}return Array.isArray(t)?uI(e,t.map(r=>L8(r,e))):uI(e,t)}function M8(t,e,r,s,a){let n,c=[],f=a,p=0;for(let E=a-1;E>=s;--E){let[C,S]=t[E],{onConflict:P,value:I}=mue(S,r),R=due(I);if(R!==3){if(n??=R,R!==n||P==="hardReset"){p=f;break}if(R===2)return uI(C,I);if(c.unshift([C,I]),P==="reset"){p=E;break}P==="extend"&&E===s&&(s=0),f=E}}if(typeof n>"u")return null;let h=c.map(([E])=>E).join(", ");switch(n){case 1:return uI(h,new Array().concat(...c.map(([E,C])=>C.map(S=>L8(S,E)))));case 0:{let E=Object.assign({},...c.map(([,R])=>R)),C=Object.keys(E),S={},P=t.map(([R,N])=>[R,mue(N,r).value]),I=pet(P,([R,N])=>{let U=due(N);return U!==0&&U!==3});if(I!==-1){let R=P.slice(I+1);for(let N of C)S[N]=M8(R,e,N,0,R.length)}else for(let R of C)S[R]=M8(P,e,R,p,P.length);return uI(h,S)}default:throw new Error("Assertion failed: Non-extendable value type")}}function Eue(t){return M8(t.map(([e,r])=>[e,{".":r}]),[],".",0,t.length)}function YB(t){return _8(t)?t[1]:t}function NQ(t){let e=_8(t)?t[1]:t;if(Array.isArray(e))return e.map(r=>NQ(r));if(WB(e)){let r={};for(let[s,a]of Object.entries(e))r[s]=NQ(a);return r}return e}function H8(t){return _8(t)?t[0]:null}var pet,yue,Iue=Xe(()=>{pet=(t,e,r)=>{let s=[...t];return s.reverse(),s.findIndex(e,r)};yue=Symbol()});var OQ={};Vt(OQ,{getDefaultGlobalFolder:()=>G8,getHomeFolder:()=>fI,isFolderInside:()=>q8});function G8(){if(process.platform==="win32"){let t=fe.toPortablePath(process.env.LOCALAPPDATA||fe.join((0,j8.homedir)(),"AppData","Local"));return J.resolve(t,"Yarn/Berry")}if(process.env.XDG_DATA_HOME){let t=fe.toPortablePath(process.env.XDG_DATA_HOME);return J.resolve(t,"yarn/berry")}return J.resolve(fI(),".yarn/berry")}function fI(){return fe.toPortablePath((0,j8.homedir)()||"/usr/local/share")}function q8(t,e){let r=J.relative(e,t);return r&&!r.startsWith("..")&&!J.isAbsolute(r)}var j8,LQ=Xe(()=>{Dt();j8=Ie("os")});var Bue=_((EMt,wue)=>{"use strict";var W8=Ie("https"),Y8=Ie("http"),{URL:Cue}=Ie("url"),V8=class extends Y8.Agent{constructor(e){let{proxy:r,proxyRequestOptions:s,...a}=e;super(a),this.proxy=typeof r=="string"?new Cue(r):r,this.proxyRequestOptions=s||{}}createConnection(e,r){let s={...this.proxyRequestOptions,method:"CONNECT",host:this.proxy.hostname,port:this.proxy.port,path:`${e.host}:${e.port}`,setHost:!1,headers:{...this.proxyRequestOptions.headers,connection:this.keepAlive?"keep-alive":"close",host:`${e.host}:${e.port}`},agent:!1,timeout:e.timeout||0};if(this.proxy.username||this.proxy.password){let n=Buffer.from(`${decodeURIComponent(this.proxy.username||"")}:${decodeURIComponent(this.proxy.password||"")}`).toString("base64");s.headers["proxy-authorization"]=`Basic ${n}`}this.proxy.protocol==="https:"&&(s.servername=this.proxy.hostname);let a=(this.proxy.protocol==="http:"?Y8:W8).request(s);a.once("connect",(n,c,f)=>{a.removeAllListeners(),c.removeAllListeners(),n.statusCode===200?r(null,c):(c.destroy(),r(new Error(`Bad response: ${n.statusCode}`),null))}),a.once("timeout",()=>{a.destroy(new Error("Proxy timeout"))}),a.once("error",n=>{a.removeAllListeners(),r(n,null)}),a.end()}},J8=class extends W8.Agent{constructor(e){let{proxy:r,proxyRequestOptions:s,...a}=e;super(a),this.proxy=typeof r=="string"?new Cue(r):r,this.proxyRequestOptions=s||{}}createConnection(e,r){let s={...this.proxyRequestOptions,method:"CONNECT",host:this.proxy.hostname,port:this.proxy.port,path:`${e.host}:${e.port}`,setHost:!1,headers:{...this.proxyRequestOptions.headers,connection:this.keepAlive?"keep-alive":"close",host:`${e.host}:${e.port}`},agent:!1,timeout:e.timeout||0};if(this.proxy.username||this.proxy.password){let n=Buffer.from(`${decodeURIComponent(this.proxy.username||"")}:${decodeURIComponent(this.proxy.password||"")}`).toString("base64");s.headers["proxy-authorization"]=`Basic ${n}`}this.proxy.protocol==="https:"&&(s.servername=this.proxy.hostname);let a=(this.proxy.protocol==="http:"?Y8:W8).request(s);a.once("connect",(n,c,f)=>{if(a.removeAllListeners(),c.removeAllListeners(),n.statusCode===200){let p=super.createConnection({...e,socket:c});r(null,p)}else c.destroy(),r(new Error(`Bad response: ${n.statusCode}`),null)}),a.once("timeout",()=>{a.destroy(new Error("Proxy timeout"))}),a.once("error",n=>{a.removeAllListeners(),r(n,null)}),a.end()}};wue.exports={HttpProxyAgent:V8,HttpsProxyAgent:J8}});var K8,vue,Sue,Due=Xe(()=>{K8=ut(Bue(),1),vue=K8.default.HttpProxyAgent,Sue=K8.default.HttpsProxyAgent});var Np=_((Fp,MQ)=>{"use strict";Object.defineProperty(Fp,"__esModule",{value:!0});var bue=["Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Uint16Array","Int32Array","Uint32Array","Float32Array","Float64Array","BigInt64Array","BigUint64Array"];function met(t){return bue.includes(t)}var yet=["Function","Generator","AsyncGenerator","GeneratorFunction","AsyncGeneratorFunction","AsyncFunction","Observable","Array","Buffer","Blob","Object","RegExp","Date","Error","Map","Set","WeakMap","WeakSet","ArrayBuffer","SharedArrayBuffer","DataView","Promise","URL","FormData","URLSearchParams","HTMLElement",...bue];function Eet(t){return yet.includes(t)}var Iet=["null","undefined","string","number","bigint","boolean","symbol"];function Cet(t){return Iet.includes(t)}function AI(t){return e=>typeof e===t}var{toString:Pue}=Object.prototype,VB=t=>{let e=Pue.call(t).slice(8,-1);if(/HTML\w+Element/.test(e)&&be.domElement(t))return"HTMLElement";if(Eet(e))return e},pi=t=>e=>VB(e)===t;function be(t){if(t===null)return"null";switch(typeof t){case"undefined":return"undefined";case"string":return"string";case"number":return"number";case"boolean":return"boolean";case"function":return"Function";case"bigint":return"bigint";case"symbol":return"symbol";default:}if(be.observable(t))return"Observable";if(be.array(t))return"Array";if(be.buffer(t))return"Buffer";let e=VB(t);if(e)return e;if(t instanceof String||t instanceof Boolean||t instanceof Number)throw new TypeError("Please don't use object wrappers for primitive types");return"Object"}be.undefined=AI("undefined");be.string=AI("string");var wet=AI("number");be.number=t=>wet(t)&&!be.nan(t);be.bigint=AI("bigint");be.function_=AI("function");be.null_=t=>t===null;be.class_=t=>be.function_(t)&&t.toString().startsWith("class ");be.boolean=t=>t===!0||t===!1;be.symbol=AI("symbol");be.numericString=t=>be.string(t)&&!be.emptyStringOrWhitespace(t)&&!Number.isNaN(Number(t));be.array=(t,e)=>Array.isArray(t)?be.function_(e)?t.every(e):!0:!1;be.buffer=t=>{var e,r,s,a;return(a=(s=(r=(e=t)===null||e===void 0?void 0:e.constructor)===null||r===void 0?void 0:r.isBuffer)===null||s===void 0?void 0:s.call(r,t))!==null&&a!==void 0?a:!1};be.blob=t=>pi("Blob")(t);be.nullOrUndefined=t=>be.null_(t)||be.undefined(t);be.object=t=>!be.null_(t)&&(typeof t=="object"||be.function_(t));be.iterable=t=>{var e;return be.function_((e=t)===null||e===void 0?void 0:e[Symbol.iterator])};be.asyncIterable=t=>{var e;return be.function_((e=t)===null||e===void 0?void 0:e[Symbol.asyncIterator])};be.generator=t=>{var e,r;return be.iterable(t)&&be.function_((e=t)===null||e===void 0?void 0:e.next)&&be.function_((r=t)===null||r===void 0?void 0:r.throw)};be.asyncGenerator=t=>be.asyncIterable(t)&&be.function_(t.next)&&be.function_(t.throw);be.nativePromise=t=>pi("Promise")(t);var Bet=t=>{var e,r;return be.function_((e=t)===null||e===void 0?void 0:e.then)&&be.function_((r=t)===null||r===void 0?void 0:r.catch)};be.promise=t=>be.nativePromise(t)||Bet(t);be.generatorFunction=pi("GeneratorFunction");be.asyncGeneratorFunction=t=>VB(t)==="AsyncGeneratorFunction";be.asyncFunction=t=>VB(t)==="AsyncFunction";be.boundFunction=t=>be.function_(t)&&!t.hasOwnProperty("prototype");be.regExp=pi("RegExp");be.date=pi("Date");be.error=pi("Error");be.map=t=>pi("Map")(t);be.set=t=>pi("Set")(t);be.weakMap=t=>pi("WeakMap")(t);be.weakSet=t=>pi("WeakSet")(t);be.int8Array=pi("Int8Array");be.uint8Array=pi("Uint8Array");be.uint8ClampedArray=pi("Uint8ClampedArray");be.int16Array=pi("Int16Array");be.uint16Array=pi("Uint16Array");be.int32Array=pi("Int32Array");be.uint32Array=pi("Uint32Array");be.float32Array=pi("Float32Array");be.float64Array=pi("Float64Array");be.bigInt64Array=pi("BigInt64Array");be.bigUint64Array=pi("BigUint64Array");be.arrayBuffer=pi("ArrayBuffer");be.sharedArrayBuffer=pi("SharedArrayBuffer");be.dataView=pi("DataView");be.enumCase=(t,e)=>Object.values(e).includes(t);be.directInstanceOf=(t,e)=>Object.getPrototypeOf(t)===e.prototype;be.urlInstance=t=>pi("URL")(t);be.urlString=t=>{if(!be.string(t))return!1;try{return new URL(t),!0}catch{return!1}};be.truthy=t=>!!t;be.falsy=t=>!t;be.nan=t=>Number.isNaN(t);be.primitive=t=>be.null_(t)||Cet(typeof t);be.integer=t=>Number.isInteger(t);be.safeInteger=t=>Number.isSafeInteger(t);be.plainObject=t=>{if(Pue.call(t)!=="[object Object]")return!1;let e=Object.getPrototypeOf(t);return e===null||e===Object.getPrototypeOf({})};be.typedArray=t=>met(VB(t));var vet=t=>be.safeInteger(t)&&t>=0;be.arrayLike=t=>!be.nullOrUndefined(t)&&!be.function_(t)&&vet(t.length);be.inRange=(t,e)=>{if(be.number(e))return t>=Math.min(0,e)&&t<=Math.max(e,0);if(be.array(e)&&e.length===2)return t>=Math.min(...e)&&t<=Math.max(...e);throw new TypeError(`Invalid range: ${JSON.stringify(e)}`)};var Det=1,bet=["innerHTML","ownerDocument","style","attributes","nodeValue"];be.domElement=t=>be.object(t)&&t.nodeType===Det&&be.string(t.nodeName)&&!be.plainObject(t)&&bet.every(e=>e in t);be.observable=t=>{var e,r,s,a;return t?t===((r=(e=t)[Symbol.observable])===null||r===void 0?void 0:r.call(e))||t===((a=(s=t)["@@observable"])===null||a===void 0?void 0:a.call(s)):!1};be.nodeStream=t=>be.object(t)&&be.function_(t.pipe)&&!be.observable(t);be.infinite=t=>t===1/0||t===-1/0;var xue=t=>e=>be.integer(e)&&Math.abs(e%2)===t;be.evenInteger=xue(0);be.oddInteger=xue(1);be.emptyArray=t=>be.array(t)&&t.length===0;be.nonEmptyArray=t=>be.array(t)&&t.length>0;be.emptyString=t=>be.string(t)&&t.length===0;var Pet=t=>be.string(t)&&!/\S/.test(t);be.emptyStringOrWhitespace=t=>be.emptyString(t)||Pet(t);be.nonEmptyString=t=>be.string(t)&&t.length>0;be.nonEmptyStringAndNotWhitespace=t=>be.string(t)&&!be.emptyStringOrWhitespace(t);be.emptyObject=t=>be.object(t)&&!be.map(t)&&!be.set(t)&&Object.keys(t).length===0;be.nonEmptyObject=t=>be.object(t)&&!be.map(t)&&!be.set(t)&&Object.keys(t).length>0;be.emptySet=t=>be.set(t)&&t.size===0;be.nonEmptySet=t=>be.set(t)&&t.size>0;be.emptyMap=t=>be.map(t)&&t.size===0;be.nonEmptyMap=t=>be.map(t)&&t.size>0;be.propertyKey=t=>be.any([be.string,be.number,be.symbol],t);be.formData=t=>pi("FormData")(t);be.urlSearchParams=t=>pi("URLSearchParams")(t);var kue=(t,e,r)=>{if(!be.function_(e))throw new TypeError(`Invalid predicate: ${JSON.stringify(e)}`);if(r.length===0)throw new TypeError("Invalid number of values");return t.call(r,e)};be.any=(t,...e)=>(be.array(t)?t:[t]).some(s=>kue(Array.prototype.some,s,e));be.all=(t,...e)=>kue(Array.prototype.every,t,e);var _t=(t,e,r,s={})=>{if(!t){let{multipleValues:a}=s,n=a?`received values of types ${[...new Set(r.map(c=>`\`${be(c)}\``))].join(", ")}`:`received value of type \`${be(r)}\``;throw new TypeError(`Expected value which is \`${e}\`, ${n}.`)}};Fp.assert={undefined:t=>_t(be.undefined(t),"undefined",t),string:t=>_t(be.string(t),"string",t),number:t=>_t(be.number(t),"number",t),bigint:t=>_t(be.bigint(t),"bigint",t),function_:t=>_t(be.function_(t),"Function",t),null_:t=>_t(be.null_(t),"null",t),class_:t=>_t(be.class_(t),"Class",t),boolean:t=>_t(be.boolean(t),"boolean",t),symbol:t=>_t(be.symbol(t),"symbol",t),numericString:t=>_t(be.numericString(t),"string with a number",t),array:(t,e)=>{_t(be.array(t),"Array",t),e&&t.forEach(e)},buffer:t=>_t(be.buffer(t),"Buffer",t),blob:t=>_t(be.blob(t),"Blob",t),nullOrUndefined:t=>_t(be.nullOrUndefined(t),"null or undefined",t),object:t=>_t(be.object(t),"Object",t),iterable:t=>_t(be.iterable(t),"Iterable",t),asyncIterable:t=>_t(be.asyncIterable(t),"AsyncIterable",t),generator:t=>_t(be.generator(t),"Generator",t),asyncGenerator:t=>_t(be.asyncGenerator(t),"AsyncGenerator",t),nativePromise:t=>_t(be.nativePromise(t),"native Promise",t),promise:t=>_t(be.promise(t),"Promise",t),generatorFunction:t=>_t(be.generatorFunction(t),"GeneratorFunction",t),asyncGeneratorFunction:t=>_t(be.asyncGeneratorFunction(t),"AsyncGeneratorFunction",t),asyncFunction:t=>_t(be.asyncFunction(t),"AsyncFunction",t),boundFunction:t=>_t(be.boundFunction(t),"Function",t),regExp:t=>_t(be.regExp(t),"RegExp",t),date:t=>_t(be.date(t),"Date",t),error:t=>_t(be.error(t),"Error",t),map:t=>_t(be.map(t),"Map",t),set:t=>_t(be.set(t),"Set",t),weakMap:t=>_t(be.weakMap(t),"WeakMap",t),weakSet:t=>_t(be.weakSet(t),"WeakSet",t),int8Array:t=>_t(be.int8Array(t),"Int8Array",t),uint8Array:t=>_t(be.uint8Array(t),"Uint8Array",t),uint8ClampedArray:t=>_t(be.uint8ClampedArray(t),"Uint8ClampedArray",t),int16Array:t=>_t(be.int16Array(t),"Int16Array",t),uint16Array:t=>_t(be.uint16Array(t),"Uint16Array",t),int32Array:t=>_t(be.int32Array(t),"Int32Array",t),uint32Array:t=>_t(be.uint32Array(t),"Uint32Array",t),float32Array:t=>_t(be.float32Array(t),"Float32Array",t),float64Array:t=>_t(be.float64Array(t),"Float64Array",t),bigInt64Array:t=>_t(be.bigInt64Array(t),"BigInt64Array",t),bigUint64Array:t=>_t(be.bigUint64Array(t),"BigUint64Array",t),arrayBuffer:t=>_t(be.arrayBuffer(t),"ArrayBuffer",t),sharedArrayBuffer:t=>_t(be.sharedArrayBuffer(t),"SharedArrayBuffer",t),dataView:t=>_t(be.dataView(t),"DataView",t),enumCase:(t,e)=>_t(be.enumCase(t,e),"EnumCase",t),urlInstance:t=>_t(be.urlInstance(t),"URL",t),urlString:t=>_t(be.urlString(t),"string with a URL",t),truthy:t=>_t(be.truthy(t),"truthy",t),falsy:t=>_t(be.falsy(t),"falsy",t),nan:t=>_t(be.nan(t),"NaN",t),primitive:t=>_t(be.primitive(t),"primitive",t),integer:t=>_t(be.integer(t),"integer",t),safeInteger:t=>_t(be.safeInteger(t),"integer",t),plainObject:t=>_t(be.plainObject(t),"plain object",t),typedArray:t=>_t(be.typedArray(t),"TypedArray",t),arrayLike:t=>_t(be.arrayLike(t),"array-like",t),domElement:t=>_t(be.domElement(t),"HTMLElement",t),observable:t=>_t(be.observable(t),"Observable",t),nodeStream:t=>_t(be.nodeStream(t),"Node.js Stream",t),infinite:t=>_t(be.infinite(t),"infinite number",t),emptyArray:t=>_t(be.emptyArray(t),"empty array",t),nonEmptyArray:t=>_t(be.nonEmptyArray(t),"non-empty array",t),emptyString:t=>_t(be.emptyString(t),"empty string",t),emptyStringOrWhitespace:t=>_t(be.emptyStringOrWhitespace(t),"empty string or whitespace",t),nonEmptyString:t=>_t(be.nonEmptyString(t),"non-empty string",t),nonEmptyStringAndNotWhitespace:t=>_t(be.nonEmptyStringAndNotWhitespace(t),"non-empty string and not whitespace",t),emptyObject:t=>_t(be.emptyObject(t),"empty object",t),nonEmptyObject:t=>_t(be.nonEmptyObject(t),"non-empty object",t),emptySet:t=>_t(be.emptySet(t),"empty set",t),nonEmptySet:t=>_t(be.nonEmptySet(t),"non-empty set",t),emptyMap:t=>_t(be.emptyMap(t),"empty map",t),nonEmptyMap:t=>_t(be.nonEmptyMap(t),"non-empty map",t),propertyKey:t=>_t(be.propertyKey(t),"PropertyKey",t),formData:t=>_t(be.formData(t),"FormData",t),urlSearchParams:t=>_t(be.urlSearchParams(t),"URLSearchParams",t),evenInteger:t=>_t(be.evenInteger(t),"even integer",t),oddInteger:t=>_t(be.oddInteger(t),"odd integer",t),directInstanceOf:(t,e)=>_t(be.directInstanceOf(t,e),"T",t),inRange:(t,e)=>_t(be.inRange(t,e),"in range",t),any:(t,...e)=>_t(be.any(t,...e),"predicate returns truthy for any value",e,{multipleValues:!0}),all:(t,...e)=>_t(be.all(t,...e),"predicate returns truthy for all values",e,{multipleValues:!0})};Object.defineProperties(be,{class:{value:be.class_},function:{value:be.function_},null:{value:be.null_}});Object.defineProperties(Fp.assert,{class:{value:Fp.assert.class_},function:{value:Fp.assert.function_},null:{value:Fp.assert.null_}});Fp.default=be;MQ.exports=be;MQ.exports.default=be;MQ.exports.assert=Fp.assert});var Que=_((CMt,z8)=>{"use strict";var UQ=class extends Error{constructor(e){super(e||"Promise was canceled"),this.name="CancelError"}get isCanceled(){return!0}},_Q=class t{static fn(e){return(...r)=>new t((s,a,n)=>{r.push(n),e(...r).then(s,a)})}constructor(e){this._cancelHandlers=[],this._isPending=!0,this._isCanceled=!1,this._rejectOnCancel=!0,this._promise=new Promise((r,s)=>{this._reject=s;let a=f=>{this._isPending=!1,r(f)},n=f=>{this._isPending=!1,s(f)},c=f=>{if(!this._isPending)throw new Error("The `onCancel` handler was attached after the promise settled.");this._cancelHandlers.push(f)};return Object.defineProperties(c,{shouldReject:{get:()=>this._rejectOnCancel,set:f=>{this._rejectOnCancel=f}}}),e(a,n,c)})}then(e,r){return this._promise.then(e,r)}catch(e){return this._promise.catch(e)}finally(e){return this._promise.finally(e)}cancel(e){if(!(!this._isPending||this._isCanceled)){if(this._cancelHandlers.length>0)try{for(let r of this._cancelHandlers)r()}catch(r){this._reject(r)}this._isCanceled=!0,this._rejectOnCancel&&this._reject(new UQ(e))}}get isCanceled(){return this._isCanceled}};Object.setPrototypeOf(_Q.prototype,Promise.prototype);z8.exports=_Q;z8.exports.CancelError=UQ});var Tue=_((Z8,$8)=>{"use strict";Object.defineProperty(Z8,"__esModule",{value:!0});function xet(t){return t.encrypted}var X8=(t,e)=>{let r;typeof e=="function"?r={connect:e}:r=e;let s=typeof r.connect=="function",a=typeof r.secureConnect=="function",n=typeof r.close=="function",c=()=>{s&&r.connect(),xet(t)&&a&&(t.authorized?r.secureConnect():t.authorizationError||t.once("secureConnect",r.secureConnect)),n&&t.once("close",r.close)};t.writable&&!t.connecting?c():t.connecting?t.once("connect",c):t.destroyed&&n&&r.close(t._hadError)};Z8.default=X8;$8.exports=X8;$8.exports.default=X8});var Rue=_((tH,rH)=>{"use strict";Object.defineProperty(tH,"__esModule",{value:!0});var ket=Tue(),Qet=Number(process.versions.node.split(".")[0]),eH=t=>{let e={start:Date.now(),socket:void 0,lookup:void 0,connect:void 0,secureConnect:void 0,upload:void 0,response:void 0,end:void 0,error:void 0,abort:void 0,phases:{wait:void 0,dns:void 0,tcp:void 0,tls:void 0,request:void 0,firstByte:void 0,download:void 0,total:void 0}};t.timings=e;let r=c=>{let f=c.emit.bind(c);c.emit=(p,...h)=>(p==="error"&&(e.error=Date.now(),e.phases.total=e.error-e.start,c.emit=f),f(p,...h))};r(t),t.prependOnceListener("abort",()=>{e.abort=Date.now(),(!e.response||Qet>=13)&&(e.phases.total=Date.now()-e.start)});let s=c=>{e.socket=Date.now(),e.phases.wait=e.socket-e.start;let f=()=>{e.lookup=Date.now(),e.phases.dns=e.lookup-e.socket};c.prependOnceListener("lookup",f),ket.default(c,{connect:()=>{e.connect=Date.now(),e.lookup===void 0&&(c.removeListener("lookup",f),e.lookup=e.connect,e.phases.dns=e.lookup-e.socket),e.phases.tcp=e.connect-e.lookup},secureConnect:()=>{e.secureConnect=Date.now(),e.phases.tls=e.secureConnect-e.connect}})};t.socket?s(t.socket):t.prependOnceListener("socket",s);let a=()=>{var c;e.upload=Date.now(),e.phases.request=e.upload-(c=e.secureConnect,c??e.connect)};return(typeof t.writableFinished=="boolean"?t.writableFinished:t.finished&&t.outputSize===0&&(!t.socket||t.socket.writableLength===0))?a():t.prependOnceListener("finish",a),t.prependOnceListener("response",c=>{e.response=Date.now(),e.phases.firstByte=e.response-e.upload,c.timings=e,r(c),c.prependOnceListener("end",()=>{e.end=Date.now(),e.phases.download=e.end-e.response,e.phases.total=e.end-e.start})}),e};tH.default=eH;rH.exports=eH;rH.exports.default=eH});var _ue=_((wMt,sH)=>{"use strict";var{V4MAPPED:Tet,ADDRCONFIG:Ret,ALL:Uue,promises:{Resolver:Fue},lookup:Fet}=Ie("dns"),{promisify:nH}=Ie("util"),Net=Ie("os"),pI=Symbol("cacheableLookupCreateConnection"),iH=Symbol("cacheableLookupInstance"),Nue=Symbol("expires"),Oet=typeof Uue=="number",Oue=t=>{if(!(t&&typeof t.createConnection=="function"))throw new Error("Expected an Agent instance as the first argument")},Let=t=>{for(let e of t)e.family!==6&&(e.address=`::ffff:${e.address}`,e.family=6)},Lue=()=>{let t=!1,e=!1;for(let r of Object.values(Net.networkInterfaces()))for(let s of r)if(!s.internal&&(s.family==="IPv6"?e=!0:t=!0,t&&e))return{has4:t,has6:e};return{has4:t,has6:e}},Met=t=>Symbol.iterator in t,Mue={ttl:!0},Uet={all:!0},HQ=class{constructor({cache:e=new Map,maxTtl:r=1/0,fallbackDuration:s=3600,errorTtl:a=.15,resolver:n=new Fue,lookup:c=Fet}={}){if(this.maxTtl=r,this.errorTtl=a,this._cache=e,this._resolver=n,this._dnsLookup=nH(c),this._resolver instanceof Fue?(this._resolve4=this._resolver.resolve4.bind(this._resolver),this._resolve6=this._resolver.resolve6.bind(this._resolver)):(this._resolve4=nH(this._resolver.resolve4.bind(this._resolver)),this._resolve6=nH(this._resolver.resolve6.bind(this._resolver))),this._iface=Lue(),this._pending={},this._nextRemovalTime=!1,this._hostnamesToFallback=new Set,s<1)this._fallback=!1;else{this._fallback=!0;let f=setInterval(()=>{this._hostnamesToFallback.clear()},s*1e3);f.unref&&f.unref()}this.lookup=this.lookup.bind(this),this.lookupAsync=this.lookupAsync.bind(this)}set servers(e){this.clear(),this._resolver.setServers(e)}get servers(){return this._resolver.getServers()}lookup(e,r,s){if(typeof r=="function"?(s=r,r={}):typeof r=="number"&&(r={family:r}),!s)throw new Error("Callback must be a function.");this.lookupAsync(e,r).then(a=>{r.all?s(null,a):s(null,a.address,a.family,a.expires,a.ttl)},s)}async lookupAsync(e,r={}){typeof r=="number"&&(r={family:r});let s=await this.query(e);if(r.family===6){let a=s.filter(n=>n.family===6);r.hints&Tet&&(Oet&&r.hints&Uue||a.length===0)?Let(s):s=a}else r.family===4&&(s=s.filter(a=>a.family===4));if(r.hints&Ret){let{_iface:a}=this;s=s.filter(n=>n.family===6?a.has6:a.has4)}if(s.length===0){let a=new Error(`cacheableLookup ENOTFOUND ${e}`);throw a.code="ENOTFOUND",a.hostname=e,a}return r.all?s:s[0]}async query(e){let r=await this._cache.get(e);if(!r){let s=this._pending[e];if(s)r=await s;else{let a=this.queryAndCache(e);this._pending[e]=a,r=await a}}return r=r.map(s=>({...s})),r}async _resolve(e){let r=async h=>{try{return await h}catch(E){if(E.code==="ENODATA"||E.code==="ENOTFOUND")return[];throw E}},[s,a]=await Promise.all([this._resolve4(e,Mue),this._resolve6(e,Mue)].map(h=>r(h))),n=0,c=0,f=0,p=Date.now();for(let h of s)h.family=4,h.expires=p+h.ttl*1e3,n=Math.max(n,h.ttl);for(let h of a)h.family=6,h.expires=p+h.ttl*1e3,c=Math.max(c,h.ttl);return s.length>0?a.length>0?f=Math.min(n,c):f=n:f=c,{entries:[...s,...a],cacheTtl:f}}async _lookup(e){try{return{entries:await this._dnsLookup(e,{all:!0}),cacheTtl:0}}catch{return{entries:[],cacheTtl:0}}}async _set(e,r,s){if(this.maxTtl>0&&s>0){s=Math.min(s,this.maxTtl)*1e3,r[Nue]=Date.now()+s;try{await this._cache.set(e,r,s)}catch(a){this.lookupAsync=async()=>{let n=new Error("Cache Error. Please recreate the CacheableLookup instance.");throw n.cause=a,n}}Met(this._cache)&&this._tick(s)}}async queryAndCache(e){if(this._hostnamesToFallback.has(e))return this._dnsLookup(e,Uet);try{let r=await this._resolve(e);r.entries.length===0&&this._fallback&&(r=await this._lookup(e),r.entries.length!==0&&this._hostnamesToFallback.add(e));let s=r.entries.length===0?this.errorTtl:r.cacheTtl;return await this._set(e,r.entries,s),delete this._pending[e],r.entries}catch(r){throw delete this._pending[e],r}}_tick(e){let r=this._nextRemovalTime;(!r||e{this._nextRemovalTime=!1;let s=1/0,a=Date.now();for(let[n,c]of this._cache){let f=c[Nue];a>=f?this._cache.delete(n):f("lookup"in r||(r.lookup=this.lookup),e[pI](r,s))}uninstall(e){if(Oue(e),e[pI]){if(e[iH]!==this)throw new Error("The agent is not owned by this CacheableLookup instance");e.createConnection=e[pI],delete e[pI],delete e[iH]}}updateInterfaceInfo(){let{_iface:e}=this;this._iface=Lue(),(e.has4&&!this._iface.has4||e.has6&&!this._iface.has6)&&this._cache.clear()}clear(e){if(e){this._cache.delete(e);return}this._cache.clear()}};sH.exports=HQ;sH.exports.default=HQ});var Gue=_((BMt,oH)=>{"use strict";var _et=typeof URL>"u"?Ie("url").URL:URL,Het="text/plain",jet="us-ascii",Hue=(t,e)=>e.some(r=>r instanceof RegExp?r.test(t):r===t),Get=(t,{stripHash:e})=>{let r=t.match(/^data:([^,]*?),([^#]*?)(?:#(.*))?$/);if(!r)throw new Error(`Invalid URL: ${t}`);let s=r[1].split(";"),a=r[2],n=e?"":r[3],c=!1;s[s.length-1]==="base64"&&(s.pop(),c=!0);let f=(s.shift()||"").toLowerCase(),h=[...s.map(E=>{let[C,S=""]=E.split("=").map(P=>P.trim());return C==="charset"&&(S=S.toLowerCase(),S===jet)?"":`${C}${S?`=${S}`:""}`}).filter(Boolean)];return c&&h.push("base64"),(h.length!==0||f&&f!==Het)&&h.unshift(f),`data:${h.join(";")},${c?a.trim():a}${n?`#${n}`:""}`},jue=(t,e)=>{if(e={defaultProtocol:"http:",normalizeProtocol:!0,forceHttp:!1,forceHttps:!1,stripAuthentication:!0,stripHash:!1,stripWWW:!0,removeQueryParameters:[/^utm_\w+/i],removeTrailingSlash:!0,removeDirectoryIndex:!1,sortQueryParameters:!0,...e},Reflect.has(e,"normalizeHttps"))throw new Error("options.normalizeHttps is renamed to options.forceHttp");if(Reflect.has(e,"normalizeHttp"))throw new Error("options.normalizeHttp is renamed to options.forceHttps");if(Reflect.has(e,"stripFragment"))throw new Error("options.stripFragment is renamed to options.stripHash");if(t=t.trim(),/^data:/i.test(t))return Get(t,e);let r=t.startsWith("//");!r&&/^\.*\//.test(t)||(t=t.replace(/^(?!(?:\w+:)?\/\/)|^\/\//,e.defaultProtocol));let a=new _et(t);if(e.forceHttp&&e.forceHttps)throw new Error("The `forceHttp` and `forceHttps` options cannot be used together");if(e.forceHttp&&a.protocol==="https:"&&(a.protocol="http:"),e.forceHttps&&a.protocol==="http:"&&(a.protocol="https:"),e.stripAuthentication&&(a.username="",a.password=""),e.stripHash&&(a.hash=""),a.pathname&&(a.pathname=a.pathname.replace(/((?!:).|^)\/{2,}/g,(n,c)=>/^(?!\/)/g.test(c)?`${c}/`:"/")),a.pathname&&(a.pathname=decodeURI(a.pathname)),e.removeDirectoryIndex===!0&&(e.removeDirectoryIndex=[/^index\.[a-z]+$/]),Array.isArray(e.removeDirectoryIndex)&&e.removeDirectoryIndex.length>0){let n=a.pathname.split("/"),c=n[n.length-1];Hue(c,e.removeDirectoryIndex)&&(n=n.slice(0,n.length-1),a.pathname=n.slice(1).join("/")+"/")}if(a.hostname&&(a.hostname=a.hostname.replace(/\.$/,""),e.stripWWW&&/^www\.([a-z\-\d]{2,63})\.([a-z.]{2,5})$/.test(a.hostname)&&(a.hostname=a.hostname.replace(/^www\./,""))),Array.isArray(e.removeQueryParameters))for(let n of[...a.searchParams.keys()])Hue(n,e.removeQueryParameters)&&a.searchParams.delete(n);return e.sortQueryParameters&&a.searchParams.sort(),e.removeTrailingSlash&&(a.pathname=a.pathname.replace(/\/$/,"")),t=a.toString(),(e.removeTrailingSlash||a.pathname==="/")&&a.hash===""&&(t=t.replace(/\/$/,"")),r&&!e.normalizeProtocol&&(t=t.replace(/^http:\/\//,"//")),e.stripProtocol&&(t=t.replace(/^(?:https?:)?\/\//,"")),t};oH.exports=jue;oH.exports.default=jue});var Yue=_((vMt,Wue)=>{Wue.exports=que;function que(t,e){if(t&&e)return que(t)(e);if(typeof t!="function")throw new TypeError("need wrapper function");return Object.keys(t).forEach(function(s){r[s]=t[s]}),r;function r(){for(var s=new Array(arguments.length),a=0;a{var Vue=Yue();aH.exports=Vue(jQ);aH.exports.strict=Vue(Jue);jQ.proto=jQ(function(){Object.defineProperty(Function.prototype,"once",{value:function(){return jQ(this)},configurable:!0}),Object.defineProperty(Function.prototype,"onceStrict",{value:function(){return Jue(this)},configurable:!0})});function jQ(t){var e=function(){return e.called?e.value:(e.called=!0,e.value=t.apply(this,arguments))};return e.called=!1,e}function Jue(t){var e=function(){if(e.called)throw new Error(e.onceError);return e.called=!0,e.value=t.apply(this,arguments)},r=t.name||"Function wrapped with `once`";return e.onceError=r+" shouldn't be called more than once",e.called=!1,e}});var cH=_((DMt,zue)=>{var qet=lH(),Wet=function(){},Yet=function(t){return t.setHeader&&typeof t.abort=="function"},Vet=function(t){return t.stdio&&Array.isArray(t.stdio)&&t.stdio.length===3},Kue=function(t,e,r){if(typeof e=="function")return Kue(t,null,e);e||(e={}),r=qet(r||Wet);var s=t._writableState,a=t._readableState,n=e.readable||e.readable!==!1&&t.readable,c=e.writable||e.writable!==!1&&t.writable,f=function(){t.writable||p()},p=function(){c=!1,n||r.call(t)},h=function(){n=!1,c||r.call(t)},E=function(I){r.call(t,I?new Error("exited with error code: "+I):null)},C=function(I){r.call(t,I)},S=function(){if(n&&!(a&&a.ended))return r.call(t,new Error("premature close"));if(c&&!(s&&s.ended))return r.call(t,new Error("premature close"))},P=function(){t.req.on("finish",p)};return Yet(t)?(t.on("complete",p),t.on("abort",S),t.req?P():t.on("request",P)):c&&!s&&(t.on("end",f),t.on("close",f)),Vet(t)&&t.on("exit",E),t.on("end",h),t.on("finish",p),e.error!==!1&&t.on("error",C),t.on("close",S),function(){t.removeListener("complete",p),t.removeListener("abort",S),t.removeListener("request",P),t.req&&t.req.removeListener("finish",p),t.removeListener("end",f),t.removeListener("close",f),t.removeListener("finish",p),t.removeListener("exit",E),t.removeListener("end",h),t.removeListener("error",C),t.removeListener("close",S)}};zue.exports=Kue});var $ue=_((bMt,Zue)=>{var Jet=lH(),Ket=cH(),uH=Ie("fs"),JB=function(){},zet=/^v?\.0/.test(process.version),GQ=function(t){return typeof t=="function"},Xet=function(t){return!zet||!uH?!1:(t instanceof(uH.ReadStream||JB)||t instanceof(uH.WriteStream||JB))&&GQ(t.close)},Zet=function(t){return t.setHeader&&GQ(t.abort)},$et=function(t,e,r,s){s=Jet(s);var a=!1;t.on("close",function(){a=!0}),Ket(t,{readable:e,writable:r},function(c){if(c)return s(c);a=!0,s()});var n=!1;return function(c){if(!a&&!n){if(n=!0,Xet(t))return t.close(JB);if(Zet(t))return t.abort();if(GQ(t.destroy))return t.destroy();s(c||new Error("stream was destroyed"))}}},Xue=function(t){t()},ett=function(t,e){return t.pipe(e)},ttt=function(){var t=Array.prototype.slice.call(arguments),e=GQ(t[t.length-1]||JB)&&t.pop()||JB;if(Array.isArray(t[0])&&(t=t[0]),t.length<2)throw new Error("pump requires two streams per minimum");var r,s=t.map(function(a,n){var c=n0;return $et(a,c,f,function(p){r||(r=p),p&&s.forEach(Xue),!c&&(s.forEach(Xue),e(r))})});return t.reduce(ett)};Zue.exports=ttt});var tfe=_((PMt,efe)=>{"use strict";var{PassThrough:rtt}=Ie("stream");efe.exports=t=>{t={...t};let{array:e}=t,{encoding:r}=t,s=r==="buffer",a=!1;e?a=!(r||s):r=r||"utf8",s&&(r=null);let n=new rtt({objectMode:a});r&&n.setEncoding(r);let c=0,f=[];return n.on("data",p=>{f.push(p),a?c=f.length:c+=p.length}),n.getBufferedValue=()=>e?f:s?Buffer.concat(f,c):f.join(""),n.getBufferedLength=()=>c,n}});var rfe=_((xMt,hI)=>{"use strict";var ntt=$ue(),itt=tfe(),qQ=class extends Error{constructor(){super("maxBuffer exceeded"),this.name="MaxBufferError"}};async function WQ(t,e){if(!t)return Promise.reject(new Error("Expected a stream"));e={maxBuffer:1/0,...e};let{maxBuffer:r}=e,s;return await new Promise((a,n)=>{let c=f=>{f&&(f.bufferedData=s.getBufferedValue()),n(f)};s=ntt(t,itt(e),f=>{if(f){c(f);return}a()}),s.on("data",()=>{s.getBufferedLength()>r&&c(new qQ)})}),s.getBufferedValue()}hI.exports=WQ;hI.exports.default=WQ;hI.exports.buffer=(t,e)=>WQ(t,{...e,encoding:"buffer"});hI.exports.array=(t,e)=>WQ(t,{...e,array:!0});hI.exports.MaxBufferError=qQ});var ife=_((QMt,nfe)=>{"use strict";var stt=new Set([200,203,204,206,300,301,308,404,405,410,414,501]),ott=new Set([200,203,204,300,301,302,303,307,308,404,405,410,414,501]),att=new Set([500,502,503,504]),ltt={date:!0,connection:!0,"keep-alive":!0,"proxy-authenticate":!0,"proxy-authorization":!0,te:!0,trailer:!0,"transfer-encoding":!0,upgrade:!0},ctt={"content-length":!0,"content-encoding":!0,"transfer-encoding":!0,"content-range":!0};function nm(t){let e=parseInt(t,10);return isFinite(e)?e:0}function utt(t){return t?att.has(t.status):!0}function fH(t){let e={};if(!t)return e;let r=t.trim().split(/,/);for(let s of r){let[a,n]=s.split(/=/,2);e[a.trim()]=n===void 0?!0:n.trim().replace(/^"|"$/g,"")}return e}function ftt(t){let e=[];for(let r in t){let s=t[r];e.push(s===!0?r:r+"="+s)}if(e.length)return e.join(", ")}nfe.exports=class{constructor(e,r,{shared:s,cacheHeuristic:a,immutableMinTimeToLive:n,ignoreCargoCult:c,_fromObject:f}={}){if(f){this._fromObject(f);return}if(!r||!r.headers)throw Error("Response headers missing");this._assertRequestHasHeaders(e),this._responseTime=this.now(),this._isShared=s!==!1,this._cacheHeuristic=a!==void 0?a:.1,this._immutableMinTtl=n!==void 0?n:24*3600*1e3,this._status="status"in r?r.status:200,this._resHeaders=r.headers,this._rescc=fH(r.headers["cache-control"]),this._method="method"in e?e.method:"GET",this._url=e.url,this._host=e.headers.host,this._noAuthorization=!e.headers.authorization,this._reqHeaders=r.headers.vary?e.headers:null,this._reqcc=fH(e.headers["cache-control"]),c&&"pre-check"in this._rescc&&"post-check"in this._rescc&&(delete this._rescc["pre-check"],delete this._rescc["post-check"],delete this._rescc["no-cache"],delete this._rescc["no-store"],delete this._rescc["must-revalidate"],this._resHeaders=Object.assign({},this._resHeaders,{"cache-control":ftt(this._rescc)}),delete this._resHeaders.expires,delete this._resHeaders.pragma),r.headers["cache-control"]==null&&/no-cache/.test(r.headers.pragma)&&(this._rescc["no-cache"]=!0)}now(){return Date.now()}storable(){return!!(!this._reqcc["no-store"]&&(this._method==="GET"||this._method==="HEAD"||this._method==="POST"&&this._hasExplicitExpiration())&&ott.has(this._status)&&!this._rescc["no-store"]&&(!this._isShared||!this._rescc.private)&&(!this._isShared||this._noAuthorization||this._allowsStoringAuthenticated())&&(this._resHeaders.expires||this._rescc["max-age"]||this._isShared&&this._rescc["s-maxage"]||this._rescc.public||stt.has(this._status)))}_hasExplicitExpiration(){return this._isShared&&this._rescc["s-maxage"]||this._rescc["max-age"]||this._resHeaders.expires}_assertRequestHasHeaders(e){if(!e||!e.headers)throw Error("Request headers missing")}satisfiesWithoutRevalidation(e){this._assertRequestHasHeaders(e);let r=fH(e.headers["cache-control"]);return r["no-cache"]||/no-cache/.test(e.headers.pragma)||r["max-age"]&&this.age()>r["max-age"]||r["min-fresh"]&&this.timeToLive()<1e3*r["min-fresh"]||this.stale()&&!(r["max-stale"]&&!this._rescc["must-revalidate"]&&(r["max-stale"]===!0||r["max-stale"]>this.age()-this.maxAge()))?!1:this._requestMatches(e,!1)}_requestMatches(e,r){return(!this._url||this._url===e.url)&&this._host===e.headers.host&&(!e.method||this._method===e.method||r&&e.method==="HEAD")&&this._varyMatches(e)}_allowsStoringAuthenticated(){return this._rescc["must-revalidate"]||this._rescc.public||this._rescc["s-maxage"]}_varyMatches(e){if(!this._resHeaders.vary)return!0;if(this._resHeaders.vary==="*")return!1;let r=this._resHeaders.vary.trim().toLowerCase().split(/\s*,\s*/);for(let s of r)if(e.headers[s]!==this._reqHeaders[s])return!1;return!0}_copyWithoutHopByHopHeaders(e){let r={};for(let s in e)ltt[s]||(r[s]=e[s]);if(e.connection){let s=e.connection.trim().split(/\s*,\s*/);for(let a of s)delete r[a]}if(r.warning){let s=r.warning.split(/,/).filter(a=>!/^\s*1[0-9][0-9]/.test(a));s.length?r.warning=s.join(",").trim():delete r.warning}return r}responseHeaders(){let e=this._copyWithoutHopByHopHeaders(this._resHeaders),r=this.age();return r>3600*24&&!this._hasExplicitExpiration()&&this.maxAge()>3600*24&&(e.warning=(e.warning?`${e.warning}, `:"")+'113 - "rfc7234 5.5.4"'),e.age=`${Math.round(r)}`,e.date=new Date(this.now()).toUTCString(),e}date(){let e=Date.parse(this._resHeaders.date);return isFinite(e)?e:this._responseTime}age(){let e=this._ageValue(),r=(this.now()-this._responseTime)/1e3;return e+r}_ageValue(){return nm(this._resHeaders.age)}maxAge(){if(!this.storable()||this._rescc["no-cache"]||this._isShared&&this._resHeaders["set-cookie"]&&!this._rescc.public&&!this._rescc.immutable||this._resHeaders.vary==="*")return 0;if(this._isShared){if(this._rescc["proxy-revalidate"])return 0;if(this._rescc["s-maxage"])return nm(this._rescc["s-maxage"])}if(this._rescc["max-age"])return nm(this._rescc["max-age"]);let e=this._rescc.immutable?this._immutableMinTtl:0,r=this.date();if(this._resHeaders.expires){let s=Date.parse(this._resHeaders.expires);return Number.isNaN(s)||ss)return Math.max(e,(r-s)/1e3*this._cacheHeuristic)}return e}timeToLive(){let e=this.maxAge()-this.age(),r=e+nm(this._rescc["stale-if-error"]),s=e+nm(this._rescc["stale-while-revalidate"]);return Math.max(0,e,r,s)*1e3}stale(){return this.maxAge()<=this.age()}_useStaleIfError(){return this.maxAge()+nm(this._rescc["stale-if-error"])>this.age()}useStaleWhileRevalidate(){return this.maxAge()+nm(this._rescc["stale-while-revalidate"])>this.age()}static fromObject(e){return new this(void 0,void 0,{_fromObject:e})}_fromObject(e){if(this._responseTime)throw Error("Reinitialized");if(!e||e.v!==1)throw Error("Invalid serialization");this._responseTime=e.t,this._isShared=e.sh,this._cacheHeuristic=e.ch,this._immutableMinTtl=e.imm!==void 0?e.imm:24*3600*1e3,this._status=e.st,this._resHeaders=e.resh,this._rescc=e.rescc,this._method=e.m,this._url=e.u,this._host=e.h,this._noAuthorization=e.a,this._reqHeaders=e.reqh,this._reqcc=e.reqcc}toObject(){return{v:1,t:this._responseTime,sh:this._isShared,ch:this._cacheHeuristic,imm:this._immutableMinTtl,st:this._status,resh:this._resHeaders,rescc:this._rescc,m:this._method,u:this._url,h:this._host,a:this._noAuthorization,reqh:this._reqHeaders,reqcc:this._reqcc}}revalidationHeaders(e){this._assertRequestHasHeaders(e);let r=this._copyWithoutHopByHopHeaders(e.headers);if(delete r["if-range"],!this._requestMatches(e,!0)||!this.storable())return delete r["if-none-match"],delete r["if-modified-since"],r;if(this._resHeaders.etag&&(r["if-none-match"]=r["if-none-match"]?`${r["if-none-match"]}, ${this._resHeaders.etag}`:this._resHeaders.etag),r["accept-ranges"]||r["if-match"]||r["if-unmodified-since"]||this._method&&this._method!="GET"){if(delete r["if-modified-since"],r["if-none-match"]){let a=r["if-none-match"].split(/,/).filter(n=>!/^\s*W\//.test(n));a.length?r["if-none-match"]=a.join(",").trim():delete r["if-none-match"]}}else this._resHeaders["last-modified"]&&!r["if-modified-since"]&&(r["if-modified-since"]=this._resHeaders["last-modified"]);return r}revalidatedPolicy(e,r){if(this._assertRequestHasHeaders(e),this._useStaleIfError()&&utt(r))return{modified:!1,matches:!1,policy:this};if(!r||!r.headers)throw Error("Response headers missing");let s=!1;if(r.status!==void 0&&r.status!=304?s=!1:r.headers.etag&&!/^\s*W\//.test(r.headers.etag)?s=this._resHeaders.etag&&this._resHeaders.etag.replace(/^\s*W\//,"")===r.headers.etag:this._resHeaders.etag&&r.headers.etag?s=this._resHeaders.etag.replace(/^\s*W\//,"")===r.headers.etag.replace(/^\s*W\//,""):this._resHeaders["last-modified"]?s=this._resHeaders["last-modified"]===r.headers["last-modified"]:!this._resHeaders.etag&&!this._resHeaders["last-modified"]&&!r.headers.etag&&!r.headers["last-modified"]&&(s=!0),!s)return{policy:new this.constructor(e,r),modified:r.status!=304,matches:!1};let a={};for(let c in this._resHeaders)a[c]=c in r.headers&&!ctt[c]?r.headers[c]:this._resHeaders[c];let n=Object.assign({},r,{status:this._status,method:this._method,headers:a});return{policy:new this.constructor(e,n,{shared:this._isShared,cacheHeuristic:this._cacheHeuristic,immutableMinTimeToLive:this._immutableMinTtl}),modified:!1,matches:!0}}}});var YQ=_((TMt,sfe)=>{"use strict";sfe.exports=t=>{let e={};for(let[r,s]of Object.entries(t))e[r.toLowerCase()]=s;return e}});var afe=_((RMt,ofe)=>{"use strict";var Att=Ie("stream").Readable,ptt=YQ(),AH=class extends Att{constructor(e,r,s,a){if(typeof e!="number")throw new TypeError("Argument `statusCode` should be a number");if(typeof r!="object")throw new TypeError("Argument `headers` should be an object");if(!(s instanceof Buffer))throw new TypeError("Argument `body` should be a buffer");if(typeof a!="string")throw new TypeError("Argument `url` should be a string");super(),this.statusCode=e,this.headers=ptt(r),this.body=s,this.url=a}_read(){this.push(this.body),this.push(null)}};ofe.exports=AH});var cfe=_((FMt,lfe)=>{"use strict";var htt=["destroy","setTimeout","socket","headers","trailers","rawHeaders","statusCode","httpVersion","httpVersionMinor","httpVersionMajor","rawTrailers","statusMessage"];lfe.exports=(t,e)=>{let r=new Set(Object.keys(t).concat(htt));for(let s of r)s in e||(e[s]=typeof t[s]=="function"?t[s].bind(t):t[s])}});var ffe=_((NMt,ufe)=>{"use strict";var gtt=Ie("stream").PassThrough,dtt=cfe(),mtt=t=>{if(!(t&&t.pipe))throw new TypeError("Parameter `response` must be a response stream.");let e=new gtt;return dtt(t,e),t.pipe(e)};ufe.exports=mtt});var Afe=_(pH=>{pH.stringify=function t(e){if(typeof e>"u")return e;if(e&&Buffer.isBuffer(e))return JSON.stringify(":base64:"+e.toString("base64"));if(e&&e.toJSON&&(e=e.toJSON()),e&&typeof e=="object"){var r="",s=Array.isArray(e);r=s?"[":"{";var a=!0;for(var n in e){var c=typeof e[n]=="function"||!s&&typeof e[n]>"u";Object.hasOwnProperty.call(e,n)&&!c&&(a||(r+=","),a=!1,s?e[n]==null?r+="null":r+=t(e[n]):e[n]!==void 0&&(r+=t(n)+":"+t(e[n])))}return r+=s?"]":"}",r}else return typeof e=="string"?JSON.stringify(/^:/.test(e)?":"+e:e):typeof e>"u"?"null":JSON.stringify(e)};pH.parse=function(t){return JSON.parse(t,function(e,r){return typeof r=="string"?/^:base64:/.test(r)?Buffer.from(r.substring(8),"base64"):/^:/.test(r)?r.substring(1):r:r})}});var dfe=_((LMt,gfe)=>{"use strict";var ytt=Ie("events"),pfe=Afe(),Ett=t=>{let e={redis:"@keyv/redis",rediss:"@keyv/redis",mongodb:"@keyv/mongo",mongo:"@keyv/mongo",sqlite:"@keyv/sqlite",postgresql:"@keyv/postgres",postgres:"@keyv/postgres",mysql:"@keyv/mysql",etcd:"@keyv/etcd",offline:"@keyv/offline",tiered:"@keyv/tiered"};if(t.adapter||t.uri){let r=t.adapter||/^[^:+]*/.exec(t.uri)[0];return new(Ie(e[r]))(t)}return new Map},hfe=["sqlite","postgres","mysql","mongo","redis","tiered"],hH=class extends ytt{constructor(e,{emitErrors:r=!0,...s}={}){if(super(),this.opts={namespace:"keyv",serialize:pfe.stringify,deserialize:pfe.parse,...typeof e=="string"?{uri:e}:e,...s},!this.opts.store){let n={...this.opts};this.opts.store=Ett(n)}if(this.opts.compression){let n=this.opts.compression;this.opts.serialize=n.serialize.bind(n),this.opts.deserialize=n.deserialize.bind(n)}typeof this.opts.store.on=="function"&&r&&this.opts.store.on("error",n=>this.emit("error",n)),this.opts.store.namespace=this.opts.namespace;let a=n=>async function*(){for await(let[c,f]of typeof n=="function"?n(this.opts.store.namespace):n){let p=await this.opts.deserialize(f);if(!(this.opts.store.namespace&&!c.includes(this.opts.store.namespace))){if(typeof p.expires=="number"&&Date.now()>p.expires){this.delete(c);continue}yield[this._getKeyUnprefix(c),p.value]}}};typeof this.opts.store[Symbol.iterator]=="function"&&this.opts.store instanceof Map?this.iterator=a(this.opts.store):typeof this.opts.store.iterator=="function"&&this.opts.store.opts&&this._checkIterableAdaptar()&&(this.iterator=a(this.opts.store.iterator.bind(this.opts.store)))}_checkIterableAdaptar(){return hfe.includes(this.opts.store.opts.dialect)||hfe.findIndex(e=>this.opts.store.opts.url.includes(e))>=0}_getKeyPrefix(e){return`${this.opts.namespace}:${e}`}_getKeyPrefixArray(e){return e.map(r=>`${this.opts.namespace}:${r}`)}_getKeyUnprefix(e){return e.split(":").splice(1).join(":")}get(e,r){let{store:s}=this.opts,a=Array.isArray(e),n=a?this._getKeyPrefixArray(e):this._getKeyPrefix(e);if(a&&s.getMany===void 0){let c=[];for(let f of n)c.push(Promise.resolve().then(()=>s.get(f)).then(p=>typeof p=="string"?this.opts.deserialize(p):this.opts.compression?this.opts.deserialize(p):p).then(p=>{if(p!=null)return typeof p.expires=="number"&&Date.now()>p.expires?this.delete(f).then(()=>{}):r&&r.raw?p:p.value}));return Promise.allSettled(c).then(f=>{let p=[];for(let h of f)p.push(h.value);return p})}return Promise.resolve().then(()=>a?s.getMany(n):s.get(n)).then(c=>typeof c=="string"?this.opts.deserialize(c):this.opts.compression?this.opts.deserialize(c):c).then(c=>{if(c!=null)return a?c.map((f,p)=>{if(typeof f=="string"&&(f=this.opts.deserialize(f)),f!=null){if(typeof f.expires=="number"&&Date.now()>f.expires){this.delete(e[p]).then(()=>{});return}return r&&r.raw?f:f.value}}):typeof c.expires=="number"&&Date.now()>c.expires?this.delete(e).then(()=>{}):r&&r.raw?c:c.value})}set(e,r,s){let a=this._getKeyPrefix(e);typeof s>"u"&&(s=this.opts.ttl),s===0&&(s=void 0);let{store:n}=this.opts;return Promise.resolve().then(()=>{let c=typeof s=="number"?Date.now()+s:null;return typeof r=="symbol"&&this.emit("error","symbol cannot be serialized"),r={value:r,expires:c},this.opts.serialize(r)}).then(c=>n.set(a,c,s)).then(()=>!0)}delete(e){let{store:r}=this.opts;if(Array.isArray(e)){let a=this._getKeyPrefixArray(e);if(r.deleteMany===void 0){let n=[];for(let c of a)n.push(r.delete(c));return Promise.allSettled(n).then(c=>c.every(f=>f.value===!0))}return Promise.resolve().then(()=>r.deleteMany(a))}let s=this._getKeyPrefix(e);return Promise.resolve().then(()=>r.delete(s))}clear(){let{store:e}=this.opts;return Promise.resolve().then(()=>e.clear())}has(e){let r=this._getKeyPrefix(e),{store:s}=this.opts;return Promise.resolve().then(async()=>typeof s.has=="function"?s.has(r):await s.get(r)!==void 0)}disconnect(){let{store:e}=this.opts;if(typeof e.disconnect=="function")return e.disconnect()}};gfe.exports=hH});var Efe=_((UMt,yfe)=>{"use strict";var Itt=Ie("events"),VQ=Ie("url"),Ctt=Gue(),wtt=rfe(),gH=ife(),mfe=afe(),Btt=YQ(),vtt=ffe(),Stt=dfe(),KB=class t{constructor(e,r){if(typeof e!="function")throw new TypeError("Parameter `request` must be a function");return this.cache=new Stt({uri:typeof r=="string"&&r,store:typeof r!="string"&&r,namespace:"cacheable-request"}),this.createCacheableRequest(e)}createCacheableRequest(e){return(r,s)=>{let a;if(typeof r=="string")a=dH(VQ.parse(r)),r={};else if(r instanceof VQ.URL)a=dH(VQ.parse(r.toString())),r={};else{let[C,...S]=(r.path||"").split("?"),P=S.length>0?`?${S.join("?")}`:"";a=dH({...r,pathname:C,search:P})}r={headers:{},method:"GET",cache:!0,strictTtl:!1,automaticFailover:!1,...r,...Dtt(a)},r.headers=Btt(r.headers);let n=new Itt,c=Ctt(VQ.format(a),{stripWWW:!1,removeTrailingSlash:!1,stripAuthentication:!1}),f=`${r.method}:${c}`,p=!1,h=!1,E=C=>{h=!0;let S=!1,P,I=new Promise(N=>{P=()=>{S||(S=!0,N())}}),R=N=>{if(p&&!C.forceRefresh){N.status=N.statusCode;let W=gH.fromObject(p.cachePolicy).revalidatedPolicy(C,N);if(!W.modified){let ee=W.policy.responseHeaders();N=new mfe(p.statusCode,ee,p.body,p.url),N.cachePolicy=W.policy,N.fromCache=!0}}N.fromCache||(N.cachePolicy=new gH(C,N,C),N.fromCache=!1);let U;C.cache&&N.cachePolicy.storable()?(U=vtt(N),(async()=>{try{let W=wtt.buffer(N);if(await Promise.race([I,new Promise(le=>N.once("end",le))]),S)return;let ee=await W,ie={cachePolicy:N.cachePolicy.toObject(),url:N.url,statusCode:N.fromCache?p.statusCode:N.statusCode,body:ee},ue=C.strictTtl?N.cachePolicy.timeToLive():void 0;C.maxTtl&&(ue=ue?Math.min(ue,C.maxTtl):C.maxTtl),await this.cache.set(f,ie,ue)}catch(W){n.emit("error",new t.CacheError(W))}})()):C.cache&&p&&(async()=>{try{await this.cache.delete(f)}catch(W){n.emit("error",new t.CacheError(W))}})(),n.emit("response",U||N),typeof s=="function"&&s(U||N)};try{let N=e(C,R);N.once("error",P),N.once("abort",P),n.emit("request",N)}catch(N){n.emit("error",new t.RequestError(N))}};return(async()=>{let C=async P=>{await Promise.resolve();let I=P.cache?await this.cache.get(f):void 0;if(typeof I>"u")return E(P);let R=gH.fromObject(I.cachePolicy);if(R.satisfiesWithoutRevalidation(P)&&!P.forceRefresh){let N=R.responseHeaders(),U=new mfe(I.statusCode,N,I.body,I.url);U.cachePolicy=R,U.fromCache=!0,n.emit("response",U),typeof s=="function"&&s(U)}else p=I,P.headers=R.revalidationHeaders(P),E(P)},S=P=>n.emit("error",new t.CacheError(P));this.cache.once("error",S),n.on("response",()=>this.cache.removeListener("error",S));try{await C(r)}catch(P){r.automaticFailover&&!h&&E(r),n.emit("error",new t.CacheError(P))}})(),n}}};function Dtt(t){let e={...t};return e.path=`${t.pathname||"/"}${t.search||""}`,delete e.pathname,delete e.search,e}function dH(t){return{protocol:t.protocol,auth:t.auth,hostname:t.hostname||t.host||"localhost",port:t.port,pathname:t.pathname,search:t.search}}KB.RequestError=class extends Error{constructor(t){super(t.message),this.name="RequestError",Object.assign(this,t)}};KB.CacheError=class extends Error{constructor(t){super(t.message),this.name="CacheError",Object.assign(this,t)}};yfe.exports=KB});var Cfe=_((jMt,Ife)=>{"use strict";var btt=["aborted","complete","headers","httpVersion","httpVersionMinor","httpVersionMajor","method","rawHeaders","rawTrailers","setTimeout","socket","statusCode","statusMessage","trailers","url"];Ife.exports=(t,e)=>{if(e._readableState.autoDestroy)throw new Error("The second stream must have the `autoDestroy` option set to `false`");let r=new Set(Object.keys(t).concat(btt)),s={};for(let a of r)a in e||(s[a]={get(){let n=t[a];return typeof n=="function"?n.bind(t):n},set(n){t[a]=n},enumerable:!0,configurable:!1});return Object.defineProperties(e,s),t.once("aborted",()=>{e.destroy(),e.emit("aborted")}),t.once("close",()=>{t.complete&&e.readable?e.once("end",()=>{e.emit("close")}):e.emit("close")}),e}});var Bfe=_((GMt,wfe)=>{"use strict";var{Transform:Ptt,PassThrough:xtt}=Ie("stream"),mH=Ie("zlib"),ktt=Cfe();wfe.exports=t=>{let e=(t.headers["content-encoding"]||"").toLowerCase();if(!["gzip","deflate","br"].includes(e))return t;let r=e==="br";if(r&&typeof mH.createBrotliDecompress!="function")return t.destroy(new Error("Brotli is not supported on Node.js < 12")),t;let s=!0,a=new Ptt({transform(f,p,h){s=!1,h(null,f)},flush(f){f()}}),n=new xtt({autoDestroy:!1,destroy(f,p){t.destroy(),p(f)}}),c=r?mH.createBrotliDecompress():mH.createUnzip();return c.once("error",f=>{if(s&&!t.readable){n.end();return}n.destroy(f)}),ktt(t,n),t.pipe(a).pipe(c).pipe(n),n}});var EH=_((qMt,vfe)=>{"use strict";var yH=class{constructor(e={}){if(!(e.maxSize&&e.maxSize>0))throw new TypeError("`maxSize` must be a number greater than 0");this.maxSize=e.maxSize,this.onEviction=e.onEviction,this.cache=new Map,this.oldCache=new Map,this._size=0}_set(e,r){if(this.cache.set(e,r),this._size++,this._size>=this.maxSize){if(this._size=0,typeof this.onEviction=="function")for(let[s,a]of this.oldCache.entries())this.onEviction(s,a);this.oldCache=this.cache,this.cache=new Map}}get(e){if(this.cache.has(e))return this.cache.get(e);if(this.oldCache.has(e)){let r=this.oldCache.get(e);return this.oldCache.delete(e),this._set(e,r),r}}set(e,r){return this.cache.has(e)?this.cache.set(e,r):this._set(e,r),this}has(e){return this.cache.has(e)||this.oldCache.has(e)}peek(e){if(this.cache.has(e))return this.cache.get(e);if(this.oldCache.has(e))return this.oldCache.get(e)}delete(e){let r=this.cache.delete(e);return r&&this._size--,this.oldCache.delete(e)||r}clear(){this.cache.clear(),this.oldCache.clear(),this._size=0}*keys(){for(let[e]of this)yield e}*values(){for(let[,e]of this)yield e}*[Symbol.iterator](){for(let e of this.cache)yield e;for(let e of this.oldCache){let[r]=e;this.cache.has(r)||(yield e)}}get size(){let e=0;for(let r of this.oldCache.keys())this.cache.has(r)||e++;return Math.min(this._size+e,this.maxSize)}};vfe.exports=yH});var CH=_((WMt,Pfe)=>{"use strict";var Qtt=Ie("events"),Ttt=Ie("tls"),Rtt=Ie("http2"),Ftt=EH(),Pa=Symbol("currentStreamsCount"),Sfe=Symbol("request"),Rc=Symbol("cachedOriginSet"),gI=Symbol("gracefullyClosing"),Ntt=["maxDeflateDynamicTableSize","maxSessionMemory","maxHeaderListPairs","maxOutstandingPings","maxReservedRemoteStreams","maxSendHeaderBlockLength","paddingStrategy","localAddress","path","rejectUnauthorized","minDHSize","ca","cert","clientCertEngine","ciphers","key","pfx","servername","minVersion","maxVersion","secureProtocol","crl","honorCipherOrder","ecdhCurve","dhparam","secureOptions","sessionIdContext"],Ott=(t,e,r)=>{let s=0,a=t.length;for(;s>>1;r(t[n],e)?s=n+1:a=n}return s},Ltt=(t,e)=>t.remoteSettings.maxConcurrentStreams>e.remoteSettings.maxConcurrentStreams,IH=(t,e)=>{for(let r of t)r[Rc].lengthe[Rc].includes(s))&&r[Pa]+e[Pa]<=e.remoteSettings.maxConcurrentStreams&&bfe(r)},Mtt=(t,e)=>{for(let r of t)e[Rc].lengthr[Rc].includes(s))&&e[Pa]+r[Pa]<=r.remoteSettings.maxConcurrentStreams&&bfe(e)},Dfe=({agent:t,isFree:e})=>{let r={};for(let s in t.sessions){let n=t.sessions[s].filter(c=>{let f=c[im.kCurrentStreamsCount]{t[gI]=!0,t[Pa]===0&&t.close()},im=class t extends Qtt{constructor({timeout:e=6e4,maxSessions:r=1/0,maxFreeSessions:s=10,maxCachedTlsSessions:a=100}={}){super(),this.sessions={},this.queue={},this.timeout=e,this.maxSessions=r,this.maxFreeSessions=s,this._freeSessionsCount=0,this._sessionsCount=0,this.settings={enablePush:!1},this.tlsSessionCache=new Ftt({maxSize:a})}static normalizeOrigin(e,r){return typeof e=="string"&&(e=new URL(e)),r&&e.hostname!==r&&(e.hostname=r),e.origin}normalizeOptions(e){let r="";if(e)for(let s of Ntt)e[s]&&(r+=`:${e[s]}`);return r}_tryToCreateNewSession(e,r){if(!(e in this.queue)||!(r in this.queue[e]))return;let s=this.queue[e][r];this._sessionsCount{Array.isArray(s)?(s=[...s],a()):s=[{resolve:a,reject:n}];let c=this.normalizeOptions(r),f=t.normalizeOrigin(e,r&&r.servername);if(f===void 0){for(let{reject:E}of s)E(new TypeError("The `origin` argument needs to be a string or an URL object"));return}if(c in this.sessions){let E=this.sessions[c],C=-1,S=-1,P;for(let I of E){let R=I.remoteSettings.maxConcurrentStreams;if(R=R||I[gI]||I.destroyed)continue;P||(C=R),N>S&&(P=I,S=N)}}if(P){if(s.length!==1){for(let{reject:I}of s){let R=new Error(`Expected the length of listeners to be 1, got ${s.length}. Please report this to https://github.com/szmarczak/http2-wrapper/`);I(R)}return}s[0].resolve(P);return}}if(c in this.queue){if(f in this.queue[c]){this.queue[c][f].listeners.push(...s),this._tryToCreateNewSession(c,f);return}}else this.queue[c]={};let p=()=>{c in this.queue&&this.queue[c][f]===h&&(delete this.queue[c][f],Object.keys(this.queue[c]).length===0&&delete this.queue[c])},h=()=>{let E=`${f}:${c}`,C=!1;try{let S=Rtt.connect(e,{createConnection:this.createConnection,settings:this.settings,session:this.tlsSessionCache.get(E),...r});S[Pa]=0,S[gI]=!1;let P=()=>S[Pa]{this.tlsSessionCache.set(E,N)}),S.once("error",N=>{for(let{reject:U}of s)U(N);this.tlsSessionCache.delete(E)}),S.setTimeout(this.timeout,()=>{S.destroy()}),S.once("close",()=>{if(C){I&&this._freeSessionsCount--,this._sessionsCount--;let N=this.sessions[c];N.splice(N.indexOf(S),1),N.length===0&&delete this.sessions[c]}else{let N=new Error("Session closed without receiving a SETTINGS frame");N.code="HTTP2WRAPPER_NOSETTINGS";for(let{reject:U}of s)U(N);p()}this._tryToCreateNewSession(c,f)});let R=()=>{if(!(!(c in this.queue)||!P())){for(let N of S[Rc])if(N in this.queue[c]){let{listeners:U}=this.queue[c][N];for(;U.length!==0&&P();)U.shift().resolve(S);let W=this.queue[c];if(W[N].listeners.length===0&&(delete W[N],Object.keys(W).length===0)){delete this.queue[c];break}if(!P())break}}};S.on("origin",()=>{S[Rc]=S.originSet,P()&&(R(),IH(this.sessions[c],S))}),S.once("remoteSettings",()=>{if(S.ref(),S.unref(),this._sessionsCount++,h.destroyed){let N=new Error("Agent has been destroyed");for(let U of s)U.reject(N);S.destroy();return}S[Rc]=S.originSet;{let N=this.sessions;if(c in N){let U=N[c];U.splice(Ott(U,S,Ltt),0,S)}else N[c]=[S]}this._freeSessionsCount+=1,C=!0,this.emit("session",S),R(),p(),S[Pa]===0&&this._freeSessionsCount>this.maxFreeSessions&&S.close(),s.length!==0&&(this.getSession(f,r,s),s.length=0),S.on("remoteSettings",()=>{R(),IH(this.sessions[c],S)})}),S[Sfe]=S.request,S.request=(N,U)=>{if(S[gI])throw new Error("The session is gracefully closing. No new streams are allowed.");let W=S[Sfe](N,U);return S.ref(),++S[Pa],S[Pa]===S.remoteSettings.maxConcurrentStreams&&this._freeSessionsCount--,W.once("close",()=>{if(I=P(),--S[Pa],!S.destroyed&&!S.closed&&(Mtt(this.sessions[c],S),P()&&!S.closed)){I||(this._freeSessionsCount++,I=!0);let ee=S[Pa]===0;ee&&S.unref(),ee&&(this._freeSessionsCount>this.maxFreeSessions||S[gI])?S.close():(IH(this.sessions[c],S),R())}}),W}}catch(S){for(let P of s)P.reject(S);p()}};h.listeners=s,h.completed=!1,h.destroyed=!1,this.queue[c][f]=h,this._tryToCreateNewSession(c,f)})}request(e,r,s,a){return new Promise((n,c)=>{this.getSession(e,r,[{reject:c,resolve:f=>{try{n(f.request(s,a))}catch(p){c(p)}}}])})}createConnection(e,r){return t.connect(e,r)}static connect(e,r){r.ALPNProtocols=["h2"];let s=e.port||443,a=e.hostname||e.host;return typeof r.servername>"u"&&(r.servername=a),Ttt.connect(s,a,r)}closeFreeSessions(){for(let e of Object.values(this.sessions))for(let r of e)r[Pa]===0&&r.close()}destroy(e){for(let r of Object.values(this.sessions))for(let s of r)s.destroy(e);for(let r of Object.values(this.queue))for(let s of Object.values(r))s.destroyed=!0;this.queue={}}get freeSessions(){return Dfe({agent:this,isFree:!0})}get busySessions(){return Dfe({agent:this,isFree:!1})}};im.kCurrentStreamsCount=Pa;im.kGracefullyClosing=gI;Pfe.exports={Agent:im,globalAgent:new im}});var BH=_((YMt,xfe)=>{"use strict";var{Readable:Utt}=Ie("stream"),wH=class extends Utt{constructor(e,r){super({highWaterMark:r,autoDestroy:!1}),this.statusCode=null,this.statusMessage="",this.httpVersion="2.0",this.httpVersionMajor=2,this.httpVersionMinor=0,this.headers={},this.trailers={},this.req=null,this.aborted=!1,this.complete=!1,this.upgrade=null,this.rawHeaders=[],this.rawTrailers=[],this.socket=e,this.connection=e,this._dumped=!1}_destroy(e){this.req._request.destroy(e)}setTimeout(e,r){return this.req.setTimeout(e,r),this}_dump(){this._dumped||(this._dumped=!0,this.removeAllListeners("data"),this.resume())}_read(){this.req&&this.req._request.resume()}};xfe.exports=wH});var vH=_((VMt,kfe)=>{"use strict";kfe.exports=t=>{let e={protocol:t.protocol,hostname:typeof t.hostname=="string"&&t.hostname.startsWith("[")?t.hostname.slice(1,-1):t.hostname,host:t.host,hash:t.hash,search:t.search,pathname:t.pathname,href:t.href,path:`${t.pathname||""}${t.search||""}`};return typeof t.port=="string"&&t.port.length!==0&&(e.port=Number(t.port)),(t.username||t.password)&&(e.auth=`${t.username||""}:${t.password||""}`),e}});var Tfe=_((JMt,Qfe)=>{"use strict";Qfe.exports=(t,e,r)=>{for(let s of r)t.on(s,(...a)=>e.emit(s,...a))}});var Ffe=_((KMt,Rfe)=>{"use strict";Rfe.exports=t=>{switch(t){case":method":case":scheme":case":authority":case":path":return!0;default:return!1}}});var Ofe=_((XMt,Nfe)=>{"use strict";var dI=(t,e,r)=>{Nfe.exports[e]=class extends t{constructor(...a){super(typeof r=="string"?r:r(a)),this.name=`${super.name} [${e}]`,this.code=e}}};dI(TypeError,"ERR_INVALID_ARG_TYPE",t=>{let e=t[0].includes(".")?"property":"argument",r=t[1],s=Array.isArray(r);return s&&(r=`${r.slice(0,-1).join(", ")} or ${r.slice(-1)}`),`The "${t[0]}" ${e} must be ${s?"one of":"of"} type ${r}. Received ${typeof t[2]}`});dI(TypeError,"ERR_INVALID_PROTOCOL",t=>`Protocol "${t[0]}" not supported. Expected "${t[1]}"`);dI(Error,"ERR_HTTP_HEADERS_SENT",t=>`Cannot ${t[0]} headers after they are sent to the client`);dI(TypeError,"ERR_INVALID_HTTP_TOKEN",t=>`${t[0]} must be a valid HTTP token [${t[1]}]`);dI(TypeError,"ERR_HTTP_INVALID_HEADER_VALUE",t=>`Invalid value "${t[0]} for header "${t[1]}"`);dI(TypeError,"ERR_INVALID_CHAR",t=>`Invalid character in ${t[0]} [${t[1]}]`)});var xH=_((ZMt,Gfe)=>{"use strict";var _tt=Ie("http2"),{Writable:Htt}=Ie("stream"),{Agent:Lfe,globalAgent:jtt}=CH(),Gtt=BH(),qtt=vH(),Wtt=Tfe(),Ytt=Ffe(),{ERR_INVALID_ARG_TYPE:SH,ERR_INVALID_PROTOCOL:Vtt,ERR_HTTP_HEADERS_SENT:Mfe,ERR_INVALID_HTTP_TOKEN:Jtt,ERR_HTTP_INVALID_HEADER_VALUE:Ktt,ERR_INVALID_CHAR:ztt}=Ofe(),{HTTP2_HEADER_STATUS:Ufe,HTTP2_HEADER_METHOD:_fe,HTTP2_HEADER_PATH:Hfe,HTTP2_METHOD_CONNECT:Xtt}=_tt.constants,Jo=Symbol("headers"),DH=Symbol("origin"),bH=Symbol("session"),jfe=Symbol("options"),JQ=Symbol("flushedHeaders"),zB=Symbol("jobs"),Ztt=/^[\^`\-\w!#$%&*+.|~]+$/,$tt=/[^\t\u0020-\u007E\u0080-\u00FF]/,PH=class extends Htt{constructor(e,r,s){super({autoDestroy:!1});let a=typeof e=="string"||e instanceof URL;if(a&&(e=qtt(e instanceof URL?e:new URL(e))),typeof r=="function"||r===void 0?(s=r,r=a?e:{...e}):r={...e,...r},r.h2session)this[bH]=r.h2session;else if(r.agent===!1)this.agent=new Lfe({maxFreeSessions:0});else if(typeof r.agent>"u"||r.agent===null)typeof r.createConnection=="function"?(this.agent=new Lfe({maxFreeSessions:0}),this.agent.createConnection=r.createConnection):this.agent=jtt;else if(typeof r.agent.request=="function")this.agent=r.agent;else throw new SH("options.agent",["Agent-like Object","undefined","false"],r.agent);if(r.protocol&&r.protocol!=="https:")throw new Vtt(r.protocol,"https:");let n=r.port||r.defaultPort||this.agent&&this.agent.defaultPort||443,c=r.hostname||r.host||"localhost";delete r.hostname,delete r.host,delete r.port;let{timeout:f}=r;if(r.timeout=void 0,this[Jo]=Object.create(null),this[zB]=[],this.socket=null,this.connection=null,this.method=r.method||"GET",this.path=r.path,this.res=null,this.aborted=!1,this.reusedSocket=!1,r.headers)for(let[p,h]of Object.entries(r.headers))this.setHeader(p,h);r.auth&&!("authorization"in this[Jo])&&(this[Jo].authorization="Basic "+Buffer.from(r.auth).toString("base64")),r.session=r.tlsSession,r.path=r.socketPath,this[jfe]=r,n===443?(this[DH]=`https://${c}`,":authority"in this[Jo]||(this[Jo][":authority"]=c)):(this[DH]=`https://${c}:${n}`,":authority"in this[Jo]||(this[Jo][":authority"]=`${c}:${n}`)),f&&this.setTimeout(f),s&&this.once("response",s),this[JQ]=!1}get method(){return this[Jo][_fe]}set method(e){e&&(this[Jo][_fe]=e.toUpperCase())}get path(){return this[Jo][Hfe]}set path(e){e&&(this[Jo][Hfe]=e)}get _mustNotHaveABody(){return this.method==="GET"||this.method==="HEAD"||this.method==="DELETE"}_write(e,r,s){if(this._mustNotHaveABody){s(new Error("The GET, HEAD and DELETE methods must NOT have a body"));return}this.flushHeaders();let a=()=>this._request.write(e,r,s);this._request?a():this[zB].push(a)}_final(e){if(this.destroyed)return;this.flushHeaders();let r=()=>{if(this._mustNotHaveABody){e();return}this._request.end(e)};this._request?r():this[zB].push(r)}abort(){this.res&&this.res.complete||(this.aborted||process.nextTick(()=>this.emit("abort")),this.aborted=!0,this.destroy())}_destroy(e,r){this.res&&this.res._dump(),this._request&&this._request.destroy(),r(e)}async flushHeaders(){if(this[JQ]||this.destroyed)return;this[JQ]=!0;let e=this.method===Xtt,r=s=>{if(this._request=s,this.destroyed){s.destroy();return}e||Wtt(s,this,["timeout","continue","close","error"]);let a=c=>(...f)=>{!this.writable&&!this.destroyed?c(...f):this.once("finish",()=>{c(...f)})};s.once("response",a((c,f,p)=>{let h=new Gtt(this.socket,s.readableHighWaterMark);this.res=h,h.req=this,h.statusCode=c[Ufe],h.headers=c,h.rawHeaders=p,h.once("end",()=>{this.aborted?(h.aborted=!0,h.emit("aborted")):(h.complete=!0,h.socket=null,h.connection=null)}),e?(h.upgrade=!0,this.emit("connect",h,s,Buffer.alloc(0))?this.emit("close"):s.destroy()):(s.on("data",E=>{!h._dumped&&!h.push(E)&&s.pause()}),s.once("end",()=>{h.push(null)}),this.emit("response",h)||h._dump())})),s.once("headers",a(c=>this.emit("information",{statusCode:c[Ufe]}))),s.once("trailers",a((c,f,p)=>{let{res:h}=this;h.trailers=c,h.rawTrailers=p}));let{socket:n}=s.session;this.socket=n,this.connection=n;for(let c of this[zB])c();this.emit("socket",this.socket)};if(this[bH])try{r(this[bH].request(this[Jo]))}catch(s){this.emit("error",s)}else{this.reusedSocket=!0;try{r(await this.agent.request(this[DH],this[jfe],this[Jo]))}catch(s){this.emit("error",s)}}}getHeader(e){if(typeof e!="string")throw new SH("name","string",e);return this[Jo][e.toLowerCase()]}get headersSent(){return this[JQ]}removeHeader(e){if(typeof e!="string")throw new SH("name","string",e);if(this.headersSent)throw new Mfe("remove");delete this[Jo][e.toLowerCase()]}setHeader(e,r){if(this.headersSent)throw new Mfe("set");if(typeof e!="string"||!Ztt.test(e)&&!Ytt(e))throw new Jtt("Header name",e);if(typeof r>"u")throw new Ktt(r,e);if($tt.test(r))throw new ztt("header content",e);this[Jo][e.toLowerCase()]=r}setNoDelay(){}setSocketKeepAlive(){}setTimeout(e,r){let s=()=>this._request.setTimeout(e,r);return this._request?s():this[zB].push(s),this}get maxHeadersCount(){if(!this.destroyed&&this._request)return this._request.session.localSettings.maxHeaderListSize}set maxHeadersCount(e){}};Gfe.exports=PH});var Wfe=_(($Mt,qfe)=>{"use strict";var ert=Ie("tls");qfe.exports=(t={},e=ert.connect)=>new Promise((r,s)=>{let a=!1,n,c=async()=>{await p,n.off("timeout",f),n.off("error",s),t.resolveSocket?(r({alpnProtocol:n.alpnProtocol,socket:n,timeout:a}),a&&(await Promise.resolve(),n.emit("timeout"))):(n.destroy(),r({alpnProtocol:n.alpnProtocol,timeout:a}))},f=async()=>{a=!0,c()},p=(async()=>{try{n=await e(t,c),n.on("error",s),n.once("timeout",f)}catch(h){s(h)}})()})});var Vfe=_((eUt,Yfe)=>{"use strict";var trt=Ie("net");Yfe.exports=t=>{let e=t.host,r=t.headers&&t.headers.host;return r&&(r.startsWith("[")?r.indexOf("]")===-1?e=r:e=r.slice(1,-1):e=r.split(":",1)[0]),trt.isIP(e)?"":e}});var zfe=_((tUt,QH)=>{"use strict";var Jfe=Ie("http"),kH=Ie("https"),rrt=Wfe(),nrt=EH(),irt=xH(),srt=Vfe(),ort=vH(),KQ=new nrt({maxSize:100}),XB=new Map,Kfe=(t,e,r)=>{e._httpMessage={shouldKeepAlive:!0};let s=()=>{t.emit("free",e,r)};e.on("free",s);let a=()=>{t.removeSocket(e,r)};e.on("close",a);let n=()=>{t.removeSocket(e,r),e.off("close",a),e.off("free",s),e.off("agentRemove",n)};e.on("agentRemove",n),t.emit("free",e,r)},art=async t=>{let e=`${t.host}:${t.port}:${t.ALPNProtocols.sort()}`;if(!KQ.has(e)){if(XB.has(e))return(await XB.get(e)).alpnProtocol;let{path:r,agent:s}=t;t.path=t.socketPath;let a=rrt(t);XB.set(e,a);try{let{socket:n,alpnProtocol:c}=await a;if(KQ.set(e,c),t.path=r,c==="h2")n.destroy();else{let{globalAgent:f}=kH,p=kH.Agent.prototype.createConnection;s?s.createConnection===p?Kfe(s,n,t):n.destroy():f.createConnection===p?Kfe(f,n,t):n.destroy()}return XB.delete(e),c}catch(n){throw XB.delete(e),n}}return KQ.get(e)};QH.exports=async(t,e,r)=>{if((typeof t=="string"||t instanceof URL)&&(t=ort(new URL(t))),typeof e=="function"&&(r=e,e=void 0),e={ALPNProtocols:["h2","http/1.1"],...t,...e,resolveSocket:!0},!Array.isArray(e.ALPNProtocols)||e.ALPNProtocols.length===0)throw new Error("The `ALPNProtocols` option must be an Array with at least one entry");e.protocol=e.protocol||"https:";let s=e.protocol==="https:";e.host=e.hostname||e.host||"localhost",e.session=e.tlsSession,e.servername=e.servername||srt(e),e.port=e.port||(s?443:80),e._defaultAgent=s?kH.globalAgent:Jfe.globalAgent;let a=e.agent;if(a){if(a.addRequest)throw new Error("The `options.agent` object can contain only `http`, `https` or `http2` properties");e.agent=a[s?"https":"http"]}return s&&await art(e)==="h2"?(a&&(e.agent=a.http2),new irt(e,r)):Jfe.request(e,r)};QH.exports.protocolCache=KQ});var Zfe=_((rUt,Xfe)=>{"use strict";var lrt=Ie("http2"),crt=CH(),TH=xH(),urt=BH(),frt=zfe(),Art=(t,e,r)=>new TH(t,e,r),prt=(t,e,r)=>{let s=new TH(t,e,r);return s.end(),s};Xfe.exports={...lrt,ClientRequest:TH,IncomingMessage:urt,...crt,request:Art,get:prt,auto:frt}});var FH=_(RH=>{"use strict";Object.defineProperty(RH,"__esModule",{value:!0});var $fe=Np();RH.default=t=>$fe.default.nodeStream(t)&&$fe.default.function_(t.getBoundary)});var nAe=_(NH=>{"use strict";Object.defineProperty(NH,"__esModule",{value:!0});var tAe=Ie("fs"),rAe=Ie("util"),eAe=Np(),hrt=FH(),grt=rAe.promisify(tAe.stat);NH.default=async(t,e)=>{if(e&&"content-length"in e)return Number(e["content-length"]);if(!t)return 0;if(eAe.default.string(t))return Buffer.byteLength(t);if(eAe.default.buffer(t))return t.length;if(hrt.default(t))return rAe.promisify(t.getLength.bind(t))();if(t instanceof tAe.ReadStream){let{size:r}=await grt(t.path);return r===0?void 0:r}}});var LH=_(OH=>{"use strict";Object.defineProperty(OH,"__esModule",{value:!0});function drt(t,e,r){let s={};for(let a of r)s[a]=(...n)=>{e.emit(a,...n)},t.on(a,s[a]);return()=>{for(let a of r)t.off(a,s[a])}}OH.default=drt});var iAe=_(MH=>{"use strict";Object.defineProperty(MH,"__esModule",{value:!0});MH.default=()=>{let t=[];return{once(e,r,s){e.once(r,s),t.push({origin:e,event:r,fn:s})},unhandleAll(){for(let e of t){let{origin:r,event:s,fn:a}=e;r.removeListener(s,a)}t.length=0}}}});var oAe=_(ZB=>{"use strict";Object.defineProperty(ZB,"__esModule",{value:!0});ZB.TimeoutError=void 0;var mrt=Ie("net"),yrt=iAe(),sAe=Symbol("reentry"),Ert=()=>{},zQ=class extends Error{constructor(e,r){super(`Timeout awaiting '${r}' for ${e}ms`),this.event=r,this.name="TimeoutError",this.code="ETIMEDOUT"}};ZB.TimeoutError=zQ;ZB.default=(t,e,r)=>{if(sAe in t)return Ert;t[sAe]=!0;let s=[],{once:a,unhandleAll:n}=yrt.default(),c=(C,S,P)=>{var I;let R=setTimeout(S,C,C,P);(I=R.unref)===null||I===void 0||I.call(R);let N=()=>{clearTimeout(R)};return s.push(N),N},{host:f,hostname:p}=r,h=(C,S)=>{t.destroy(new zQ(C,S))},E=()=>{for(let C of s)C();n()};if(t.once("error",C=>{if(E(),t.listenerCount("error")===0)throw C}),t.once("close",E),a(t,"response",C=>{a(C,"end",E)}),typeof e.request<"u"&&c(e.request,h,"request"),typeof e.socket<"u"){let C=()=>{h(e.socket,"socket")};t.setTimeout(e.socket,C),s.push(()=>{t.removeListener("timeout",C)})}return a(t,"socket",C=>{var S;let{socketPath:P}=t;if(C.connecting){let I=!!(P??mrt.isIP((S=p??f)!==null&&S!==void 0?S:"")!==0);if(typeof e.lookup<"u"&&!I&&typeof C.address().address>"u"){let R=c(e.lookup,h,"lookup");a(C,"lookup",R)}if(typeof e.connect<"u"){let R=()=>c(e.connect,h,"connect");I?a(C,"connect",R()):a(C,"lookup",N=>{N===null&&a(C,"connect",R())})}typeof e.secureConnect<"u"&&r.protocol==="https:"&&a(C,"connect",()=>{let R=c(e.secureConnect,h,"secureConnect");a(C,"secureConnect",R)})}if(typeof e.send<"u"){let I=()=>c(e.send,h,"send");C.connecting?a(C,"connect",()=>{a(t,"upload-complete",I())}):a(t,"upload-complete",I())}}),typeof e.response<"u"&&a(t,"upload-complete",()=>{let C=c(e.response,h,"response");a(t,"response",C)}),E}});var lAe=_(UH=>{"use strict";Object.defineProperty(UH,"__esModule",{value:!0});var aAe=Np();UH.default=t=>{t=t;let e={protocol:t.protocol,hostname:aAe.default.string(t.hostname)&&t.hostname.startsWith("[")?t.hostname.slice(1,-1):t.hostname,host:t.host,hash:t.hash,search:t.search,pathname:t.pathname,href:t.href,path:`${t.pathname||""}${t.search||""}`};return aAe.default.string(t.port)&&t.port.length>0&&(e.port=Number(t.port)),(t.username||t.password)&&(e.auth=`${t.username||""}:${t.password||""}`),e}});var cAe=_(_H=>{"use strict";Object.defineProperty(_H,"__esModule",{value:!0});var Irt=Ie("url"),Crt=["protocol","host","hostname","port","pathname","search"];_H.default=(t,e)=>{var r,s;if(e.path){if(e.pathname)throw new TypeError("Parameters `path` and `pathname` are mutually exclusive.");if(e.search)throw new TypeError("Parameters `path` and `search` are mutually exclusive.");if(e.searchParams)throw new TypeError("Parameters `path` and `searchParams` are mutually exclusive.")}if(e.search&&e.searchParams)throw new TypeError("Parameters `search` and `searchParams` are mutually exclusive.");if(!t){if(!e.protocol)throw new TypeError("No URL protocol specified");t=`${e.protocol}//${(s=(r=e.hostname)!==null&&r!==void 0?r:e.host)!==null&&s!==void 0?s:""}`}let a=new Irt.URL(t);if(e.path){let n=e.path.indexOf("?");n===-1?e.pathname=e.path:(e.pathname=e.path.slice(0,n),e.search=e.path.slice(n+1)),delete e.path}for(let n of Crt)e[n]&&(a[n]=e[n].toString());return a}});var uAe=_(jH=>{"use strict";Object.defineProperty(jH,"__esModule",{value:!0});var HH=class{constructor(){this.weakMap=new WeakMap,this.map=new Map}set(e,r){typeof e=="object"?this.weakMap.set(e,r):this.map.set(e,r)}get(e){return typeof e=="object"?this.weakMap.get(e):this.map.get(e)}has(e){return typeof e=="object"?this.weakMap.has(e):this.map.has(e)}};jH.default=HH});var qH=_(GH=>{"use strict";Object.defineProperty(GH,"__esModule",{value:!0});var wrt=async t=>{let e=[],r=0;for await(let s of t)e.push(s),r+=Buffer.byteLength(s);return Buffer.isBuffer(e[0])?Buffer.concat(e,r):Buffer.from(e.join(""))};GH.default=wrt});var AAe=_(sm=>{"use strict";Object.defineProperty(sm,"__esModule",{value:!0});sm.dnsLookupIpVersionToFamily=sm.isDnsLookupIpVersion=void 0;var fAe={auto:0,ipv4:4,ipv6:6};sm.isDnsLookupIpVersion=t=>t in fAe;sm.dnsLookupIpVersionToFamily=t=>{if(sm.isDnsLookupIpVersion(t))return fAe[t];throw new Error("Invalid DNS lookup IP version")}});var WH=_(XQ=>{"use strict";Object.defineProperty(XQ,"__esModule",{value:!0});XQ.isResponseOk=void 0;XQ.isResponseOk=t=>{let{statusCode:e}=t,r=t.request.options.followRedirect?299:399;return e>=200&&e<=r||e===304}});var hAe=_(YH=>{"use strict";Object.defineProperty(YH,"__esModule",{value:!0});var pAe=new Set;YH.default=t=>{pAe.has(t)||(pAe.add(t),process.emitWarning(`Got: ${t}`,{type:"DeprecationWarning"}))}});var gAe=_(VH=>{"use strict";Object.defineProperty(VH,"__esModule",{value:!0});var Si=Np(),Brt=(t,e)=>{if(Si.default.null_(t.encoding))throw new TypeError("To get a Buffer, set `options.responseType` to `buffer` instead");Si.assert.any([Si.default.string,Si.default.undefined],t.encoding),Si.assert.any([Si.default.boolean,Si.default.undefined],t.resolveBodyOnly),Si.assert.any([Si.default.boolean,Si.default.undefined],t.methodRewriting),Si.assert.any([Si.default.boolean,Si.default.undefined],t.isStream),Si.assert.any([Si.default.string,Si.default.undefined],t.responseType),t.responseType===void 0&&(t.responseType="text");let{retry:r}=t;if(e?t.retry={...e.retry}:t.retry={calculateDelay:s=>s.computedValue,limit:0,methods:[],statusCodes:[],errorCodes:[],maxRetryAfter:void 0},Si.default.object(r)?(t.retry={...t.retry,...r},t.retry.methods=[...new Set(t.retry.methods.map(s=>s.toUpperCase()))],t.retry.statusCodes=[...new Set(t.retry.statusCodes)],t.retry.errorCodes=[...new Set(t.retry.errorCodes)]):Si.default.number(r)&&(t.retry.limit=r),Si.default.undefined(t.retry.maxRetryAfter)&&(t.retry.maxRetryAfter=Math.min(...[t.timeout.request,t.timeout.connect].filter(Si.default.number))),Si.default.object(t.pagination)){e&&(t.pagination={...e.pagination,...t.pagination});let{pagination:s}=t;if(!Si.default.function_(s.transform))throw new Error("`options.pagination.transform` must be implemented");if(!Si.default.function_(s.shouldContinue))throw new Error("`options.pagination.shouldContinue` must be implemented");if(!Si.default.function_(s.filter))throw new TypeError("`options.pagination.filter` must be implemented");if(!Si.default.function_(s.paginate))throw new Error("`options.pagination.paginate` must be implemented")}return t.responseType==="json"&&t.headers.accept===void 0&&(t.headers.accept="application/json"),t};VH.default=Brt});var dAe=_($B=>{"use strict";Object.defineProperty($B,"__esModule",{value:!0});$B.retryAfterStatusCodes=void 0;$B.retryAfterStatusCodes=new Set([413,429,503]);var vrt=({attemptCount:t,retryOptions:e,error:r,retryAfter:s})=>{if(t>e.limit)return 0;let a=e.methods.includes(r.options.method),n=e.errorCodes.includes(r.code),c=r.response&&e.statusCodes.includes(r.response.statusCode);if(!a||!n&&!c)return 0;if(r.response){if(s)return e.maxRetryAfter===void 0||s>e.maxRetryAfter?0:s;if(r.response.statusCode===413)return 0}let f=Math.random()*100;return 2**(t-1)*1e3+f};$B.default=vrt});var rv=_(Ln=>{"use strict";Object.defineProperty(Ln,"__esModule",{value:!0});Ln.UnsupportedProtocolError=Ln.ReadError=Ln.TimeoutError=Ln.UploadError=Ln.CacheError=Ln.HTTPError=Ln.MaxRedirectsError=Ln.RequestError=Ln.setNonEnumerableProperties=Ln.knownHookEvents=Ln.withoutBody=Ln.kIsNormalizedAlready=void 0;var mAe=Ie("util"),yAe=Ie("stream"),Srt=Ie("fs"),w0=Ie("url"),EAe=Ie("http"),JH=Ie("http"),Drt=Ie("https"),brt=Rue(),Prt=_ue(),IAe=Efe(),xrt=Bfe(),krt=Zfe(),Qrt=YQ(),at=Np(),Trt=nAe(),CAe=FH(),Rrt=LH(),wAe=oAe(),Frt=lAe(),BAe=cAe(),Nrt=uAe(),Ort=qH(),vAe=AAe(),Lrt=WH(),B0=hAe(),Mrt=gAe(),Urt=dAe(),KH,po=Symbol("request"),eT=Symbol("response"),mI=Symbol("responseSize"),yI=Symbol("downloadedSize"),EI=Symbol("bodySize"),II=Symbol("uploadedSize"),ZQ=Symbol("serverResponsesPiped"),SAe=Symbol("unproxyEvents"),DAe=Symbol("isFromCache"),zH=Symbol("cancelTimeouts"),bAe=Symbol("startedReading"),CI=Symbol("stopReading"),$Q=Symbol("triggerRead"),v0=Symbol("body"),ev=Symbol("jobs"),PAe=Symbol("originalResponse"),xAe=Symbol("retryTimeout");Ln.kIsNormalizedAlready=Symbol("isNormalizedAlready");var _rt=at.default.string(process.versions.brotli);Ln.withoutBody=new Set(["GET","HEAD"]);Ln.knownHookEvents=["init","beforeRequest","beforeRedirect","beforeError","beforeRetry","afterResponse"];function Hrt(t){for(let e in t){let r=t[e];if(!at.default.string(r)&&!at.default.number(r)&&!at.default.boolean(r)&&!at.default.null_(r)&&!at.default.undefined(r))throw new TypeError(`The \`searchParams\` value '${String(r)}' must be a string, number, boolean or null`)}}function jrt(t){return at.default.object(t)&&!("statusCode"in t)}var XH=new Nrt.default,Grt=async t=>new Promise((e,r)=>{let s=a=>{r(a)};t.pending||e(),t.once("error",s),t.once("ready",()=>{t.off("error",s),e()})}),qrt=new Set([300,301,302,303,304,307,308]),Wrt=["context","body","json","form"];Ln.setNonEnumerableProperties=(t,e)=>{let r={};for(let s of t)if(s)for(let a of Wrt)a in s&&(r[a]={writable:!0,configurable:!0,enumerable:!1,value:s[a]});Object.defineProperties(e,r)};var fs=class extends Error{constructor(e,r,s){var a;if(super(e),Error.captureStackTrace(this,this.constructor),this.name="RequestError",this.code=r.code,s instanceof aT?(Object.defineProperty(this,"request",{enumerable:!1,value:s}),Object.defineProperty(this,"response",{enumerable:!1,value:s[eT]}),Object.defineProperty(this,"options",{enumerable:!1,value:s.options})):Object.defineProperty(this,"options",{enumerable:!1,value:s}),this.timings=(a=this.request)===null||a===void 0?void 0:a.timings,at.default.string(r.stack)&&at.default.string(this.stack)){let n=this.stack.indexOf(this.message)+this.message.length,c=this.stack.slice(n).split(` `).reverse(),f=r.stack.slice(r.stack.indexOf(r.message)+r.message.length).split(` `).reverse();for(;f.length!==0&&f[0]===c[0];)c.shift();this.stack=`${this.stack.slice(0,n)}${c.reverse().join(` `)}${f.reverse().join(` `)}`}}};Ln.RequestError=fs;var tT=class extends fs{constructor(e){super(`Redirected ${e.options.maxRedirects} times. Aborting.`,{},e),this.name="MaxRedirectsError"}};Ln.MaxRedirectsError=tT;var rT=class extends fs{constructor(e){super(`Response code ${e.statusCode} (${e.statusMessage})`,{},e.request),this.name="HTTPError"}};Ln.HTTPError=rT;var nT=class extends fs{constructor(e,r){super(e.message,e,r),this.name="CacheError"}};Ln.CacheError=nT;var iT=class extends fs{constructor(e,r){super(e.message,e,r),this.name="UploadError"}};Ln.UploadError=iT;var sT=class extends fs{constructor(e,r,s){super(e.message,e,s),this.name="TimeoutError",this.event=e.event,this.timings=r}};Ln.TimeoutError=sT;var tv=class extends fs{constructor(e,r){super(e.message,e,r),this.name="ReadError"}};Ln.ReadError=tv;var oT=class extends fs{constructor(e){super(`Unsupported protocol "${e.url.protocol}"`,{},e),this.name="UnsupportedProtocolError"}};Ln.UnsupportedProtocolError=oT;var Yrt=["socket","connect","continue","information","upgrade","timeout"],aT=class extends yAe.Duplex{constructor(e,r={},s){super({autoDestroy:!1,highWaterMark:0}),this[yI]=0,this[II]=0,this.requestInitialized=!1,this[ZQ]=new Set,this.redirects=[],this[CI]=!1,this[$Q]=!1,this[ev]=[],this.retryCount=0,this._progressCallbacks=[];let a=()=>this._unlockWrite(),n=()=>this._lockWrite();this.on("pipe",h=>{h.prependListener("data",a),h.on("data",n),h.prependListener("end",a),h.on("end",n)}),this.on("unpipe",h=>{h.off("data",a),h.off("data",n),h.off("end",a),h.off("end",n)}),this.on("pipe",h=>{h instanceof JH.IncomingMessage&&(this.options.headers={...h.headers,...this.options.headers})});let{json:c,body:f,form:p}=r;if((c||f||p)&&this._lockWrite(),Ln.kIsNormalizedAlready in r)this.options=r;else try{this.options=this.constructor.normalizeArguments(e,r,s)}catch(h){at.default.nodeStream(r.body)&&r.body.destroy(),this.destroy(h);return}(async()=>{var h;try{this.options.body instanceof Srt.ReadStream&&await Grt(this.options.body);let{url:E}=this.options;if(!E)throw new TypeError("Missing `url` property");if(this.requestUrl=E.toString(),decodeURI(this.requestUrl),await this._finalizeBody(),await this._makeRequest(),this.destroyed){(h=this[po])===null||h===void 0||h.destroy();return}for(let C of this[ev])C();this[ev].length=0,this.requestInitialized=!0}catch(E){if(E instanceof fs){this._beforeError(E);return}this.destroyed||this.destroy(E)}})()}static normalizeArguments(e,r,s){var a,n,c,f,p;let h=r;if(at.default.object(e)&&!at.default.urlInstance(e))r={...s,...e,...r};else{if(e&&r&&r.url!==void 0)throw new TypeError("The `url` option is mutually exclusive with the `input` argument");r={...s,...r},e!==void 0&&(r.url=e),at.default.urlInstance(r.url)&&(r.url=new w0.URL(r.url.toString()))}if(r.cache===!1&&(r.cache=void 0),r.dnsCache===!1&&(r.dnsCache=void 0),at.assert.any([at.default.string,at.default.undefined],r.method),at.assert.any([at.default.object,at.default.undefined],r.headers),at.assert.any([at.default.string,at.default.urlInstance,at.default.undefined],r.prefixUrl),at.assert.any([at.default.object,at.default.undefined],r.cookieJar),at.assert.any([at.default.object,at.default.string,at.default.undefined],r.searchParams),at.assert.any([at.default.object,at.default.string,at.default.undefined],r.cache),at.assert.any([at.default.object,at.default.number,at.default.undefined],r.timeout),at.assert.any([at.default.object,at.default.undefined],r.context),at.assert.any([at.default.object,at.default.undefined],r.hooks),at.assert.any([at.default.boolean,at.default.undefined],r.decompress),at.assert.any([at.default.boolean,at.default.undefined],r.ignoreInvalidCookies),at.assert.any([at.default.boolean,at.default.undefined],r.followRedirect),at.assert.any([at.default.number,at.default.undefined],r.maxRedirects),at.assert.any([at.default.boolean,at.default.undefined],r.throwHttpErrors),at.assert.any([at.default.boolean,at.default.undefined],r.http2),at.assert.any([at.default.boolean,at.default.undefined],r.allowGetBody),at.assert.any([at.default.string,at.default.undefined],r.localAddress),at.assert.any([vAe.isDnsLookupIpVersion,at.default.undefined],r.dnsLookupIpVersion),at.assert.any([at.default.object,at.default.undefined],r.https),at.assert.any([at.default.boolean,at.default.undefined],r.rejectUnauthorized),r.https&&(at.assert.any([at.default.boolean,at.default.undefined],r.https.rejectUnauthorized),at.assert.any([at.default.function_,at.default.undefined],r.https.checkServerIdentity),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.certificateAuthority),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.key),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.certificate),at.assert.any([at.default.string,at.default.undefined],r.https.passphrase),at.assert.any([at.default.string,at.default.buffer,at.default.array,at.default.undefined],r.https.pfx)),at.assert.any([at.default.object,at.default.undefined],r.cacheOptions),at.default.string(r.method)?r.method=r.method.toUpperCase():r.method="GET",r.headers===s?.headers?r.headers={...r.headers}:r.headers=Qrt({...s?.headers,...r.headers}),"slashes"in r)throw new TypeError("The legacy `url.Url` has been deprecated. Use `URL` instead.");if("auth"in r)throw new TypeError("Parameter `auth` is deprecated. Use `username` / `password` instead.");if("searchParams"in r&&r.searchParams&&r.searchParams!==s?.searchParams){let P;if(at.default.string(r.searchParams)||r.searchParams instanceof w0.URLSearchParams)P=new w0.URLSearchParams(r.searchParams);else{Hrt(r.searchParams),P=new w0.URLSearchParams;for(let I in r.searchParams){let R=r.searchParams[I];R===null?P.append(I,""):R!==void 0&&P.append(I,R)}}(a=s?.searchParams)===null||a===void 0||a.forEach((I,R)=>{P.has(R)||P.append(R,I)}),r.searchParams=P}if(r.username=(n=r.username)!==null&&n!==void 0?n:"",r.password=(c=r.password)!==null&&c!==void 0?c:"",at.default.undefined(r.prefixUrl)?r.prefixUrl=(f=s?.prefixUrl)!==null&&f!==void 0?f:"":(r.prefixUrl=r.prefixUrl.toString(),r.prefixUrl!==""&&!r.prefixUrl.endsWith("/")&&(r.prefixUrl+="/")),at.default.string(r.url)){if(r.url.startsWith("/"))throw new Error("`input` must not start with a slash when using `prefixUrl`");r.url=BAe.default(r.prefixUrl+r.url,r)}else(at.default.undefined(r.url)&&r.prefixUrl!==""||r.protocol)&&(r.url=BAe.default(r.prefixUrl,r));if(r.url){"port"in r&&delete r.port;let{prefixUrl:P}=r;Object.defineProperty(r,"prefixUrl",{set:R=>{let N=r.url;if(!N.href.startsWith(R))throw new Error(`Cannot change \`prefixUrl\` from ${P} to ${R}: ${N.href}`);r.url=new w0.URL(R+N.href.slice(P.length)),P=R},get:()=>P});let{protocol:I}=r.url;if(I==="unix:"&&(I="http:",r.url=new w0.URL(`http://unix${r.url.pathname}${r.url.search}`)),r.searchParams&&(r.url.search=r.searchParams.toString()),I!=="http:"&&I!=="https:")throw new oT(r);r.username===""?r.username=r.url.username:r.url.username=r.username,r.password===""?r.password=r.url.password:r.url.password=r.password}let{cookieJar:E}=r;if(E){let{setCookie:P,getCookieString:I}=E;at.assert.function_(P),at.assert.function_(I),P.length===4&&I.length===0&&(P=mAe.promisify(P.bind(r.cookieJar)),I=mAe.promisify(I.bind(r.cookieJar)),r.cookieJar={setCookie:P,getCookieString:I})}let{cache:C}=r;if(C&&(XH.has(C)||XH.set(C,new IAe((P,I)=>{let R=P[po](P,I);return at.default.promise(R)&&(R.once=(N,U)=>{if(N==="error")R.catch(U);else if(N==="abort")(async()=>{try{(await R).once("abort",U)}catch{}})();else throw new Error(`Unknown HTTP2 promise event: ${N}`);return R}),R},C))),r.cacheOptions={...r.cacheOptions},r.dnsCache===!0)KH||(KH=new Prt.default),r.dnsCache=KH;else if(!at.default.undefined(r.dnsCache)&&!r.dnsCache.lookup)throw new TypeError(`Parameter \`dnsCache\` must be a CacheableLookup instance or a boolean, got ${at.default(r.dnsCache)}`);at.default.number(r.timeout)?r.timeout={request:r.timeout}:s&&r.timeout!==s.timeout?r.timeout={...s.timeout,...r.timeout}:r.timeout={...r.timeout},r.context||(r.context={});let S=r.hooks===s?.hooks;r.hooks={...r.hooks};for(let P of Ln.knownHookEvents)if(P in r.hooks)if(at.default.array(r.hooks[P]))r.hooks[P]=[...r.hooks[P]];else throw new TypeError(`Parameter \`${P}\` must be an Array, got ${at.default(r.hooks[P])}`);else r.hooks[P]=[];if(s&&!S)for(let P of Ln.knownHookEvents)s.hooks[P].length>0&&(r.hooks[P]=[...s.hooks[P],...r.hooks[P]]);if("family"in r&&B0.default('"options.family" was never documented, please use "options.dnsLookupIpVersion"'),s?.https&&(r.https={...s.https,...r.https}),"rejectUnauthorized"in r&&B0.default('"options.rejectUnauthorized" is now deprecated, please use "options.https.rejectUnauthorized"'),"checkServerIdentity"in r&&B0.default('"options.checkServerIdentity" was never documented, please use "options.https.checkServerIdentity"'),"ca"in r&&B0.default('"options.ca" was never documented, please use "options.https.certificateAuthority"'),"key"in r&&B0.default('"options.key" was never documented, please use "options.https.key"'),"cert"in r&&B0.default('"options.cert" was never documented, please use "options.https.certificate"'),"passphrase"in r&&B0.default('"options.passphrase" was never documented, please use "options.https.passphrase"'),"pfx"in r&&B0.default('"options.pfx" was never documented, please use "options.https.pfx"'),"followRedirects"in r)throw new TypeError("The `followRedirects` option does not exist. Use `followRedirect` instead.");if(r.agent){for(let P in r.agent)if(P!=="http"&&P!=="https"&&P!=="http2")throw new TypeError(`Expected the \`options.agent\` properties to be \`http\`, \`https\` or \`http2\`, got \`${P}\``)}return r.maxRedirects=(p=r.maxRedirects)!==null&&p!==void 0?p:0,Ln.setNonEnumerableProperties([s,h],r),Mrt.default(r,s)}_lockWrite(){let e=()=>{throw new TypeError("The payload has been already provided")};this.write=e,this.end=e}_unlockWrite(){this.write=super.write,this.end=super.end}async _finalizeBody(){let{options:e}=this,{headers:r}=e,s=!at.default.undefined(e.form),a=!at.default.undefined(e.json),n=!at.default.undefined(e.body),c=s||a||n,f=Ln.withoutBody.has(e.method)&&!(e.method==="GET"&&e.allowGetBody);if(this._cannotHaveBody=f,c){if(f)throw new TypeError(`The \`${e.method}\` method cannot be used with a body`);if([n,s,a].filter(p=>p).length>1)throw new TypeError("The `body`, `json` and `form` options are mutually exclusive");if(n&&!(e.body instanceof yAe.Readable)&&!at.default.string(e.body)&&!at.default.buffer(e.body)&&!CAe.default(e.body))throw new TypeError("The `body` option must be a stream.Readable, string or Buffer");if(s&&!at.default.object(e.form))throw new TypeError("The `form` option must be an Object");{let p=!at.default.string(r["content-type"]);n?(CAe.default(e.body)&&p&&(r["content-type"]=`multipart/form-data; boundary=${e.body.getBoundary()}`),this[v0]=e.body):s?(p&&(r["content-type"]="application/x-www-form-urlencoded"),this[v0]=new w0.URLSearchParams(e.form).toString()):(p&&(r["content-type"]="application/json"),this[v0]=e.stringifyJson(e.json));let h=await Trt.default(this[v0],e.headers);at.default.undefined(r["content-length"])&&at.default.undefined(r["transfer-encoding"])&&!f&&!at.default.undefined(h)&&(r["content-length"]=String(h))}}else f?this._lockWrite():this._unlockWrite();this[EI]=Number(r["content-length"])||void 0}async _onResponseBase(e){let{options:r}=this,{url:s}=r;this[PAe]=e,r.decompress&&(e=xrt(e));let a=e.statusCode,n=e;n.statusMessage=n.statusMessage?n.statusMessage:EAe.STATUS_CODES[a],n.url=r.url.toString(),n.requestUrl=this.requestUrl,n.redirectUrls=this.redirects,n.request=this,n.isFromCache=e.fromCache||!1,n.ip=this.ip,n.retryCount=this.retryCount,this[DAe]=n.isFromCache,this[mI]=Number(e.headers["content-length"])||void 0,this[eT]=e,e.once("end",()=>{this[mI]=this[yI],this.emit("downloadProgress",this.downloadProgress)}),e.once("error",f=>{e.destroy(),this._beforeError(new tv(f,this))}),e.once("aborted",()=>{this._beforeError(new tv({name:"Error",message:"The server aborted pending request",code:"ECONNRESET"},this))}),this.emit("downloadProgress",this.downloadProgress);let c=e.headers["set-cookie"];if(at.default.object(r.cookieJar)&&c){let f=c.map(async p=>r.cookieJar.setCookie(p,s.toString()));r.ignoreInvalidCookies&&(f=f.map(async p=>p.catch(()=>{})));try{await Promise.all(f)}catch(p){this._beforeError(p);return}}if(r.followRedirect&&e.headers.location&&qrt.has(a)){if(e.resume(),this[po]&&(this[zH](),delete this[po],this[SAe]()),(a===303&&r.method!=="GET"&&r.method!=="HEAD"||!r.methodRewriting)&&(r.method="GET","body"in r&&delete r.body,"json"in r&&delete r.json,"form"in r&&delete r.form,this[v0]=void 0,delete r.headers["content-length"]),this.redirects.length>=r.maxRedirects){this._beforeError(new tT(this));return}try{let p=Buffer.from(e.headers.location,"binary").toString(),h=new w0.URL(p,s),E=h.toString();decodeURI(E),h.hostname!==s.hostname||h.port!==s.port?("host"in r.headers&&delete r.headers.host,"cookie"in r.headers&&delete r.headers.cookie,"authorization"in r.headers&&delete r.headers.authorization,(r.username||r.password)&&(r.username="",r.password="")):(h.username=r.username,h.password=r.password),this.redirects.push(E),r.url=h;for(let C of r.hooks.beforeRedirect)await C(r,n);this.emit("redirect",n,r),await this._makeRequest()}catch(p){this._beforeError(p);return}return}if(r.isStream&&r.throwHttpErrors&&!Lrt.isResponseOk(n)){this._beforeError(new rT(n));return}e.on("readable",()=>{this[$Q]&&this._read()}),this.on("resume",()=>{e.resume()}),this.on("pause",()=>{e.pause()}),e.once("end",()=>{this.push(null)}),this.emit("response",e);for(let f of this[ZQ])if(!f.headersSent){for(let p in e.headers){let h=r.decompress?p!=="content-encoding":!0,E=e.headers[p];h&&f.setHeader(p,E)}f.statusCode=a}}async _onResponse(e){try{await this._onResponseBase(e)}catch(r){this._beforeError(r)}}_onRequest(e){let{options:r}=this,{timeout:s,url:a}=r;brt.default(e),this[zH]=wAe.default(e,s,a);let n=r.cache?"cacheableResponse":"response";e.once(n,p=>{this._onResponse(p)}),e.once("error",p=>{var h;e.destroy(),(h=e.res)===null||h===void 0||h.removeAllListeners("end"),p=p instanceof wAe.TimeoutError?new sT(p,this.timings,this):new fs(p.message,p,this),this._beforeError(p)}),this[SAe]=Rrt.default(e,this,Yrt),this[po]=e,this.emit("uploadProgress",this.uploadProgress);let c=this[v0],f=this.redirects.length===0?this:e;at.default.nodeStream(c)?(c.pipe(f),c.once("error",p=>{this._beforeError(new iT(p,this))})):(this._unlockWrite(),at.default.undefined(c)?(this._cannotHaveBody||this._noPipe)&&(f.end(),this._lockWrite()):(this._writeRequest(c,void 0,()=>{}),f.end(),this._lockWrite())),this.emit("request",e)}async _createCacheableRequest(e,r){return new Promise((s,a)=>{Object.assign(r,Frt.default(e)),delete r.url;let n,c=XH.get(r.cache)(r,async f=>{f._readableState.autoDestroy=!1,n&&(await n).emit("cacheableResponse",f),s(f)});r.url=e,c.once("error",a),c.once("request",async f=>{n=f,s(n)})})}async _makeRequest(){var e,r,s,a,n;let{options:c}=this,{headers:f}=c;for(let U in f)if(at.default.undefined(f[U]))delete f[U];else if(at.default.null_(f[U]))throw new TypeError(`Use \`undefined\` instead of \`null\` to delete the \`${U}\` header`);if(c.decompress&&at.default.undefined(f["accept-encoding"])&&(f["accept-encoding"]=_rt?"gzip, deflate, br":"gzip, deflate"),c.cookieJar){let U=await c.cookieJar.getCookieString(c.url.toString());at.default.nonEmptyString(U)&&(c.headers.cookie=U)}for(let U of c.hooks.beforeRequest){let W=await U(c);if(!at.default.undefined(W)){c.request=()=>W;break}}c.body&&this[v0]!==c.body&&(this[v0]=c.body);let{agent:p,request:h,timeout:E,url:C}=c;if(c.dnsCache&&!("lookup"in c)&&(c.lookup=c.dnsCache.lookup),C.hostname==="unix"){let U=/(?.+?):(?.+)/.exec(`${C.pathname}${C.search}`);if(U?.groups){let{socketPath:W,path:ee}=U.groups;Object.assign(c,{socketPath:W,path:ee,host:""})}}let S=C.protocol==="https:",P;c.http2?P=krt.auto:P=S?Drt.request:EAe.request;let I=(e=c.request)!==null&&e!==void 0?e:P,R=c.cache?this._createCacheableRequest:I;p&&!c.http2&&(c.agent=p[S?"https":"http"]),c[po]=I,delete c.request,delete c.timeout;let N=c;if(N.shared=(r=c.cacheOptions)===null||r===void 0?void 0:r.shared,N.cacheHeuristic=(s=c.cacheOptions)===null||s===void 0?void 0:s.cacheHeuristic,N.immutableMinTimeToLive=(a=c.cacheOptions)===null||a===void 0?void 0:a.immutableMinTimeToLive,N.ignoreCargoCult=(n=c.cacheOptions)===null||n===void 0?void 0:n.ignoreCargoCult,c.dnsLookupIpVersion!==void 0)try{N.family=vAe.dnsLookupIpVersionToFamily(c.dnsLookupIpVersion)}catch{throw new Error("Invalid `dnsLookupIpVersion` option value")}c.https&&("rejectUnauthorized"in c.https&&(N.rejectUnauthorized=c.https.rejectUnauthorized),c.https.checkServerIdentity&&(N.checkServerIdentity=c.https.checkServerIdentity),c.https.certificateAuthority&&(N.ca=c.https.certificateAuthority),c.https.certificate&&(N.cert=c.https.certificate),c.https.key&&(N.key=c.https.key),c.https.passphrase&&(N.passphrase=c.https.passphrase),c.https.pfx&&(N.pfx=c.https.pfx));try{let U=await R(C,N);at.default.undefined(U)&&(U=P(C,N)),c.request=h,c.timeout=E,c.agent=p,c.https&&("rejectUnauthorized"in c.https&&delete N.rejectUnauthorized,c.https.checkServerIdentity&&delete N.checkServerIdentity,c.https.certificateAuthority&&delete N.ca,c.https.certificate&&delete N.cert,c.https.key&&delete N.key,c.https.passphrase&&delete N.passphrase,c.https.pfx&&delete N.pfx),jrt(U)?this._onRequest(U):this.writable?(this.once("finish",()=>{this._onResponse(U)}),this._unlockWrite(),this.end(),this._lockWrite()):this._onResponse(U)}catch(U){throw U instanceof IAe.CacheError?new nT(U,this):new fs(U.message,U,this)}}async _error(e){try{for(let r of this.options.hooks.beforeError)e=await r(e)}catch(r){e=new fs(r.message,r,this)}this.destroy(e)}_beforeError(e){if(this[CI])return;let{options:r}=this,s=this.retryCount+1;this[CI]=!0,e instanceof fs||(e=new fs(e.message,e,this));let a=e,{response:n}=a;(async()=>{if(n&&!n.body){n.setEncoding(this._readableState.encoding);try{n.rawBody=await Ort.default(n),n.body=n.rawBody.toString()}catch{}}if(this.listenerCount("retry")!==0){let c;try{let f;n&&"retry-after"in n.headers&&(f=Number(n.headers["retry-after"]),Number.isNaN(f)?(f=Date.parse(n.headers["retry-after"])-Date.now(),f<=0&&(f=1)):f*=1e3),c=await r.retry.calculateDelay({attemptCount:s,retryOptions:r.retry,error:a,retryAfter:f,computedValue:Urt.default({attemptCount:s,retryOptions:r.retry,error:a,retryAfter:f,computedValue:0})})}catch(f){this._error(new fs(f.message,f,this));return}if(c){let f=async()=>{try{for(let p of this.options.hooks.beforeRetry)await p(this.options,a,s)}catch(p){this._error(new fs(p.message,e,this));return}this.destroyed||(this.destroy(),this.emit("retry",s,e))};this[xAe]=setTimeout(f,c);return}}this._error(a)})()}_read(){this[$Q]=!0;let e=this[eT];if(e&&!this[CI]){e.readableLength&&(this[$Q]=!1);let r;for(;(r=e.read())!==null;){this[yI]+=r.length,this[bAe]=!0;let s=this.downloadProgress;s.percent<1&&this.emit("downloadProgress",s),this.push(r)}}}_write(e,r,s){let a=()=>{this._writeRequest(e,r,s)};this.requestInitialized?a():this[ev].push(a)}_writeRequest(e,r,s){this[po].destroyed||(this._progressCallbacks.push(()=>{this[II]+=Buffer.byteLength(e,r);let a=this.uploadProgress;a.percent<1&&this.emit("uploadProgress",a)}),this[po].write(e,r,a=>{!a&&this._progressCallbacks.length>0&&this._progressCallbacks.shift()(),s(a)}))}_final(e){let r=()=>{for(;this._progressCallbacks.length!==0;)this._progressCallbacks.shift()();if(!(po in this)){e();return}if(this[po].destroyed){e();return}this[po].end(s=>{s||(this[EI]=this[II],this.emit("uploadProgress",this.uploadProgress),this[po].emit("upload-complete")),e(s)})};this.requestInitialized?r():this[ev].push(r)}_destroy(e,r){var s;this[CI]=!0,clearTimeout(this[xAe]),po in this&&(this[zH](),!((s=this[eT])===null||s===void 0)&&s.complete||this[po].destroy()),e!==null&&!at.default.undefined(e)&&!(e instanceof fs)&&(e=new fs(e.message,e,this)),r(e)}get _isAboutToError(){return this[CI]}get ip(){var e;return(e=this.socket)===null||e===void 0?void 0:e.remoteAddress}get aborted(){var e,r,s;return((r=(e=this[po])===null||e===void 0?void 0:e.destroyed)!==null&&r!==void 0?r:this.destroyed)&&!(!((s=this[PAe])===null||s===void 0)&&s.complete)}get socket(){var e,r;return(r=(e=this[po])===null||e===void 0?void 0:e.socket)!==null&&r!==void 0?r:void 0}get downloadProgress(){let e;return this[mI]?e=this[yI]/this[mI]:this[mI]===this[yI]?e=1:e=0,{percent:e,transferred:this[yI],total:this[mI]}}get uploadProgress(){let e;return this[EI]?e=this[II]/this[EI]:this[EI]===this[II]?e=1:e=0,{percent:e,transferred:this[II],total:this[EI]}}get timings(){var e;return(e=this[po])===null||e===void 0?void 0:e.timings}get isFromCache(){return this[DAe]}pipe(e,r){if(this[bAe])throw new Error("Failed to pipe. The response has been emitted already.");return e instanceof JH.ServerResponse&&this[ZQ].add(e),super.pipe(e,r)}unpipe(e){return e instanceof JH.ServerResponse&&this[ZQ].delete(e),super.unpipe(e),this}};Ln.default=aT});var nv=_(qu=>{"use strict";var Vrt=qu&&qu.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Jrt=qu&&qu.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&Vrt(e,t,r)};Object.defineProperty(qu,"__esModule",{value:!0});qu.CancelError=qu.ParseError=void 0;var kAe=rv(),ZH=class extends kAe.RequestError{constructor(e,r){let{options:s}=r.request;super(`${e.message} in "${s.url.toString()}"`,e,r.request),this.name="ParseError"}};qu.ParseError=ZH;var $H=class extends kAe.RequestError{constructor(e){super("Promise was canceled",{},e),this.name="CancelError"}get isCanceled(){return!0}};qu.CancelError=$H;Jrt(rv(),qu)});var TAe=_(ej=>{"use strict";Object.defineProperty(ej,"__esModule",{value:!0});var QAe=nv(),Krt=(t,e,r,s)=>{let{rawBody:a}=t;try{if(e==="text")return a.toString(s);if(e==="json")return a.length===0?"":r(a.toString());if(e==="buffer")return a;throw new QAe.ParseError({message:`Unknown body type '${e}'`,name:"Error"},t)}catch(n){throw new QAe.ParseError(n,t)}};ej.default=Krt});var tj=_(S0=>{"use strict";var zrt=S0&&S0.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Xrt=S0&&S0.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&zrt(e,t,r)};Object.defineProperty(S0,"__esModule",{value:!0});var Zrt=Ie("events"),$rt=Np(),ent=Que(),lT=nv(),RAe=TAe(),FAe=rv(),tnt=LH(),rnt=qH(),NAe=WH(),nnt=["request","response","redirect","uploadProgress","downloadProgress"];function OAe(t){let e,r,s=new Zrt.EventEmitter,a=new ent((c,f,p)=>{let h=E=>{let C=new FAe.default(void 0,t);C.retryCount=E,C._noPipe=!0,p(()=>C.destroy()),p.shouldReject=!1,p(()=>f(new lT.CancelError(C))),e=C,C.once("response",async I=>{var R;if(I.retryCount=E,I.request.aborted)return;let N;try{N=await rnt.default(C),I.rawBody=N}catch{return}if(C._isAboutToError)return;let U=((R=I.headers["content-encoding"])!==null&&R!==void 0?R:"").toLowerCase(),W=["gzip","deflate","br"].includes(U),{options:ee}=C;if(W&&!ee.decompress)I.body=N;else try{I.body=RAe.default(I,ee.responseType,ee.parseJson,ee.encoding)}catch(ie){if(I.body=N.toString(),NAe.isResponseOk(I)){C._beforeError(ie);return}}try{for(let[ie,ue]of ee.hooks.afterResponse.entries())I=await ue(I,async le=>{let me=FAe.default.normalizeArguments(void 0,{...le,retry:{calculateDelay:()=>0},throwHttpErrors:!1,resolveBodyOnly:!1},ee);me.hooks.afterResponse=me.hooks.afterResponse.slice(0,ie);for(let Be of me.hooks.beforeRetry)await Be(me);let pe=OAe(me);return p(()=>{pe.catch(()=>{}),pe.cancel()}),pe})}catch(ie){C._beforeError(new lT.RequestError(ie.message,ie,C));return}if(!NAe.isResponseOk(I)){C._beforeError(new lT.HTTPError(I));return}r=I,c(C.options.resolveBodyOnly?I.body:I)});let S=I=>{if(a.isCanceled)return;let{options:R}=C;if(I instanceof lT.HTTPError&&!R.throwHttpErrors){let{response:N}=I;c(C.options.resolveBodyOnly?N.body:N);return}f(I)};C.once("error",S);let P=C.options.body;C.once("retry",(I,R)=>{var N,U;if(P===((N=R.request)===null||N===void 0?void 0:N.options.body)&&$rt.default.nodeStream((U=R.request)===null||U===void 0?void 0:U.options.body)){S(R);return}h(I)}),tnt.default(C,s,nnt)};h(0)});a.on=(c,f)=>(s.on(c,f),a);let n=c=>{let f=(async()=>{await a;let{options:p}=r.request;return RAe.default(r,c,p.parseJson,p.encoding)})();return Object.defineProperties(f,Object.getOwnPropertyDescriptors(a)),f};return a.json=()=>{let{headers:c}=e.options;return!e.writableFinished&&c.accept===void 0&&(c.accept="application/json"),n("json")},a.buffer=()=>n("buffer"),a.text=()=>n("text"),a}S0.default=OAe;Xrt(nv(),S0)});var LAe=_(rj=>{"use strict";Object.defineProperty(rj,"__esModule",{value:!0});var int=nv();function snt(t,...e){let r=(async()=>{if(t instanceof int.RequestError)try{for(let a of e)if(a)for(let n of a)t=await n(t)}catch(a){t=a}throw t})(),s=()=>r;return r.json=s,r.text=s,r.buffer=s,r.on=s,r}rj.default=snt});var _Ae=_(nj=>{"use strict";Object.defineProperty(nj,"__esModule",{value:!0});var MAe=Np();function UAe(t){for(let e of Object.values(t))(MAe.default.plainObject(e)||MAe.default.array(e))&&UAe(e);return Object.freeze(t)}nj.default=UAe});var jAe=_(HAe=>{"use strict";Object.defineProperty(HAe,"__esModule",{value:!0})});var ij=_(Nc=>{"use strict";var ont=Nc&&Nc.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),ant=Nc&&Nc.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&ont(e,t,r)};Object.defineProperty(Nc,"__esModule",{value:!0});Nc.defaultHandler=void 0;var GAe=Np(),Fc=tj(),lnt=LAe(),uT=rv(),cnt=_Ae(),unt={RequestError:Fc.RequestError,CacheError:Fc.CacheError,ReadError:Fc.ReadError,HTTPError:Fc.HTTPError,MaxRedirectsError:Fc.MaxRedirectsError,TimeoutError:Fc.TimeoutError,ParseError:Fc.ParseError,CancelError:Fc.CancelError,UnsupportedProtocolError:Fc.UnsupportedProtocolError,UploadError:Fc.UploadError},fnt=async t=>new Promise(e=>{setTimeout(e,t)}),{normalizeArguments:cT}=uT.default,qAe=(...t)=>{let e;for(let r of t)e=cT(void 0,r,e);return e},Ant=t=>t.isStream?new uT.default(void 0,t):Fc.default(t),pnt=t=>"defaults"in t&&"options"in t.defaults,hnt=["get","post","put","patch","head","delete"];Nc.defaultHandler=(t,e)=>e(t);var WAe=(t,e)=>{if(t)for(let r of t)r(e)},YAe=t=>{t._rawHandlers=t.handlers,t.handlers=t.handlers.map(s=>(a,n)=>{let c,f=s(a,p=>(c=n(p),c));if(f!==c&&!a.isStream&&c){let p=f,{then:h,catch:E,finally:C}=p;Object.setPrototypeOf(p,Object.getPrototypeOf(c)),Object.defineProperties(p,Object.getOwnPropertyDescriptors(c)),p.then=h,p.catch=E,p.finally=C}return f});let e=(s,a={},n)=>{var c,f;let p=0,h=E=>t.handlers[p++](E,p===t.handlers.length?Ant:h);if(GAe.default.plainObject(s)){let E={...s,...a};uT.setNonEnumerableProperties([s,a],E),a=E,s=void 0}try{let E;try{WAe(t.options.hooks.init,a),WAe((c=a.hooks)===null||c===void 0?void 0:c.init,a)}catch(S){E=S}let C=cT(s,a,n??t.options);if(C[uT.kIsNormalizedAlready]=!0,E)throw new Fc.RequestError(E.message,E,C);return h(C)}catch(E){if(a.isStream)throw E;return lnt.default(E,t.options.hooks.beforeError,(f=a.hooks)===null||f===void 0?void 0:f.beforeError)}};e.extend=(...s)=>{let a=[t.options],n=[...t._rawHandlers],c;for(let f of s)pnt(f)?(a.push(f.defaults.options),n.push(...f.defaults._rawHandlers),c=f.defaults.mutableDefaults):(a.push(f),"handlers"in f&&n.push(...f.handlers),c=f.mutableDefaults);return n=n.filter(f=>f!==Nc.defaultHandler),n.length===0&&n.push(Nc.defaultHandler),YAe({options:qAe(...a),handlers:n,mutableDefaults:!!c})};let r=async function*(s,a){let n=cT(s,a,t.options);n.resolveBodyOnly=!1;let c=n.pagination;if(!GAe.default.object(c))throw new TypeError("`options.pagination` must be implemented");let f=[],{countLimit:p}=c,h=0;for(;h{let n=[];for await(let c of r(s,a))n.push(c);return n},e.paginate.each=r,e.stream=(s,a)=>e(s,{...a,isStream:!0});for(let s of hnt)e[s]=(a,n)=>e(a,{...n,method:s}),e.stream[s]=(a,n)=>e(a,{...n,method:s,isStream:!0});return Object.assign(e,unt),Object.defineProperty(e,"defaults",{value:t.mutableDefaults?t:cnt.default(t),writable:t.mutableDefaults,configurable:t.mutableDefaults,enumerable:!0}),e.mergeOptions=qAe,e};Nc.default=YAe;ant(jAe(),Nc)});var KAe=_((Op,fT)=>{"use strict";var gnt=Op&&Op.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),VAe=Op&&Op.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&gnt(e,t,r)};Object.defineProperty(Op,"__esModule",{value:!0});var dnt=Ie("url"),JAe=ij(),mnt={options:{method:"GET",retry:{limit:2,methods:["GET","PUT","HEAD","DELETE","OPTIONS","TRACE"],statusCodes:[408,413,429,500,502,503,504,521,522,524],errorCodes:["ETIMEDOUT","ECONNRESET","EADDRINUSE","ECONNREFUSED","EPIPE","ENOTFOUND","ENETUNREACH","EAI_AGAIN"],maxRetryAfter:void 0,calculateDelay:({computedValue:t})=>t},timeout:{},headers:{"user-agent":"got (https://github.com/sindresorhus/got)"},hooks:{init:[],beforeRequest:[],beforeRedirect:[],beforeRetry:[],beforeError:[],afterResponse:[]},cache:void 0,dnsCache:void 0,decompress:!0,throwHttpErrors:!0,followRedirect:!0,isStream:!1,responseType:"text",resolveBodyOnly:!1,maxRedirects:10,prefixUrl:"",methodRewriting:!0,ignoreInvalidCookies:!1,context:{},http2:!1,allowGetBody:!1,https:void 0,pagination:{transform:t=>t.request.options.responseType==="json"?t.body:JSON.parse(t.body),paginate:t=>{if(!Reflect.has(t.headers,"link"))return!1;let e=t.headers.link.split(","),r;for(let s of e){let a=s.split(";");if(a[1].includes("next")){r=a[0].trimStart().trim(),r=r.slice(1,-1);break}}return r?{url:new dnt.URL(r)}:!1},filter:()=>!0,shouldContinue:()=>!0,countLimit:1/0,backoff:0,requestLimit:1e4,stackAllItems:!0},parseJson:t=>JSON.parse(t),stringifyJson:t=>JSON.stringify(t),cacheOptions:{}},handlers:[JAe.defaultHandler],mutableDefaults:!1},sj=JAe.default(mnt);Op.default=sj;fT.exports=sj;fT.exports.default=sj;fT.exports.__esModule=!0;VAe(ij(),Op);VAe(tj(),Op)});var nn={};Vt(nn,{Method:()=>tpe,del:()=>wnt,get:()=>lj,getNetworkSettings:()=>epe,post:()=>cj,put:()=>Cnt,request:()=>iv});async function oj(t){return Yl(XAe,t,()=>ce.readFilePromise(t).then(e=>(XAe.set(t,e),e)))}function Int({statusCode:t,statusMessage:e},r){let s=Ht(r,t,ht.NUMBER),a=`https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/${t}`;return KE(r,`${s}${e?` (${e})`:""}`,a)}async function AT(t,{configuration:e,customErrorMessage:r}){try{return await t}catch(s){if(s.name!=="HTTPError")throw s;let a=r?.(s,e)??s.response.body?.error;a==null&&(s.message.startsWith("Response code")?a="The remote server failed to provide the requested resource":a=s.message),s.code==="ETIMEDOUT"&&s.event==="socket"&&(a+=`(can be increased via ${Ht(e,"httpTimeout",ht.SETTING)})`);let n=new jt(35,a,c=>{s.response&&c.reportError(35,` ${Kf(e,{label:"Response Code",value:_u(ht.NO_HINT,Int(s.response,e))})}`),s.request&&(c.reportError(35,` ${Kf(e,{label:"Request Method",value:_u(ht.NO_HINT,s.request.options.method)})}`),c.reportError(35,` ${Kf(e,{label:"Request URL",value:_u(ht.URL,s.request.requestUrl)})}`)),s.request.redirects.length>0&&c.reportError(35,` ${Kf(e,{label:"Request Redirects",value:_u(ht.NO_HINT,Z4(e,s.request.redirects,ht.URL))})}`),s.request.retryCount===s.request.options.retry.limit&&c.reportError(35,` ${Kf(e,{label:"Request Retry Count",value:_u(ht.NO_HINT,`${Ht(e,s.request.retryCount,ht.NUMBER)} (can be increased via ${Ht(e,"httpRetry",ht.SETTING)})`)})}`)});throw n.originalError=s,n}}function epe(t,e){let r=[...e.configuration.get("networkSettings")].sort(([c],[f])=>f.length-c.length),s={enableNetwork:void 0,httpsCaFilePath:void 0,httpProxy:void 0,httpsProxy:void 0,httpsKeyFilePath:void 0,httpsCertFilePath:void 0},a=Object.keys(s),n=typeof t=="string"?new URL(t):t;for(let[c,f]of r)if(aj.default.isMatch(n.hostname,c))for(let p of a){let h=f.get(p);h!==null&&typeof s[p]>"u"&&(s[p]=h)}for(let c of a)typeof s[c]>"u"&&(s[c]=e.configuration.get(c));return s}async function iv(t,e,{configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c="GET",wrapNetworkRequest:f}){let p={target:t,body:e,configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c},h=async()=>await Bnt(t,e,p),E=typeof f<"u"?await f(h,p):h;return await(await r.reduceHook(S=>S.wrapNetworkRequest,E,p))()}async function lj(t,{configuration:e,jsonResponse:r,customErrorMessage:s,wrapNetworkRequest:a,...n}){let c=()=>AT(iv(t,null,{configuration:e,wrapNetworkRequest:a,...n}),{configuration:e,customErrorMessage:s}).then(p=>p.body),f=await(typeof a<"u"?c():Yl(zAe,t,()=>c().then(p=>(zAe.set(t,p),p))));return r?JSON.parse(f.toString()):f}async function Cnt(t,e,{customErrorMessage:r,...s}){return(await AT(iv(t,e,{...s,method:"PUT"}),{customErrorMessage:r,configuration:s.configuration})).body}async function cj(t,e,{customErrorMessage:r,...s}){return(await AT(iv(t,e,{...s,method:"POST"}),{customErrorMessage:r,configuration:s.configuration})).body}async function wnt(t,{customErrorMessage:e,...r}){return(await AT(iv(t,null,{...r,method:"DELETE"}),{customErrorMessage:e,configuration:r.configuration})).body}async function Bnt(t,e,{configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c="GET"}){let f=typeof t=="string"?new URL(t):t,p=epe(f,{configuration:r});if(p.enableNetwork===!1)throw new jt(80,`Request to '${f.href}' has been blocked because of your configuration settings`);if(f.protocol==="http:"&&!aj.default.isMatch(f.hostname,r.get("unsafeHttpWhitelist")))throw new jt(81,`Unsafe http requests must be explicitly whitelisted in your configuration (${f.hostname})`);let h={headers:s,method:c};h.responseType=n?"json":"buffer",e!==null&&(Buffer.isBuffer(e)||!a&&typeof e=="string"?h.body=e:h.json=e);let E=r.get("httpTimeout"),C=r.get("httpRetry"),S=r.get("enableStrictSsl"),P=p.httpsCaFilePath,I=p.httpsCertFilePath,R=p.httpsKeyFilePath,{default:N}=await Promise.resolve().then(()=>ut(KAe())),U=P?await oj(P):void 0,W=I?await oj(I):void 0,ee=R?await oj(R):void 0,ie={rejectUnauthorized:S,ca:U,cert:W,key:ee},ue={http:p.httpProxy?new vue({proxy:p.httpProxy,proxyRequestOptions:ie}):ynt,https:p.httpsProxy?new Sue({proxy:p.httpsProxy,proxyRequestOptions:ie}):Ent},le=N.extend({timeout:{socket:E},retry:C,agent:ue,https:{rejectUnauthorized:S,certificateAuthority:U,certificate:W,key:ee},...h});return r.getLimit("networkConcurrency")(()=>le(f))}var ZAe,$Ae,aj,zAe,XAe,ynt,Ent,tpe,pT=Xe(()=>{Dt();Due();ZAe=Ie("https"),$Ae=Ie("http"),aj=ut(Go());Tc();xc();Pc();zAe=new Map,XAe=new Map,ynt=new $Ae.Agent({keepAlive:!0}),Ent=new ZAe.Agent({keepAlive:!0});tpe=(a=>(a.GET="GET",a.PUT="PUT",a.POST="POST",a.DELETE="DELETE",a))(tpe||{})});var Ui={};Vt(Ui,{availableParallelism:()=>fj,getArchitecture:()=>sv,getArchitectureName:()=>Pnt,getArchitectureSet:()=>uj,getCaller:()=>Tnt,major:()=>vnt,openUrl:()=>Snt});function bnt(){if(process.platform!=="linux")return null;let t;try{t=ce.readFileSync(Dnt)}catch{}if(typeof t<"u"){if(t&&(t.includes("GLIBC")||t.includes("GNU libc")||t.includes("GNU C Library")))return"glibc";if(t&&t.includes("musl"))return"musl"}let r=(process.report?.getReport()??{}).sharedObjects??[],s=/\/(?:(ld-linux-|[^/]+-linux-gnu\/)|(libc.musl-|ld-musl-))/;return p0(r,a=>{let n=a.match(s);if(!n)return p0.skip;if(n[1])return"glibc";if(n[2])return"musl";throw new Error("Assertion failed: Expected the libc variant to have been detected")})??null}function sv(){return npe=npe??{os:(process.env.YARN_IS_TEST_ENV?process.env.YARN_OS_OVERRIDE:void 0)??process.platform,cpu:(process.env.YARN_IS_TEST_ENV?process.env.YARN_CPU_OVERRIDE:void 0)??process.arch,libc:(process.env.YARN_IS_TEST_ENV?process.env.YARN_LIBC_OVERRIDE:void 0)??bnt()}}function Pnt(t=sv()){return t.libc?`${t.os}-${t.cpu}-${t.libc}`:`${t.os}-${t.cpu}`}function uj(){let t=sv();return ipe=ipe??{os:[t.os],cpu:[t.cpu],libc:t.libc?[t.libc]:[]}}function Qnt(t){let e=xnt.exec(t);if(!e)return null;let r=e[2]&&e[2].indexOf("native")===0,s=e[2]&&e[2].indexOf("eval")===0,a=knt.exec(e[2]);return s&&a!=null&&(e[2]=a[1],e[3]=a[2],e[4]=a[3]),{file:r?null:e[2],methodName:e[1]||"",arguments:r?[e[2]]:[],line:e[3]?+e[3]:null,column:e[4]?+e[4]:null}}function Tnt(){let e=new Error().stack.split(` `)[3];return Qnt(e)}function fj(){return typeof hT.default.availableParallelism<"u"?hT.default.availableParallelism():Math.max(1,hT.default.cpus().length)}var hT,vnt,rpe,Snt,Dnt,npe,ipe,xnt,knt,gT=Xe(()=>{Dt();hT=ut(Ie("os"));dT();Pc();vnt=Number(process.versions.node.split(".")[0]),rpe=new Map([["darwin","open"],["linux","xdg-open"],["win32","explorer.exe"]]).get(process.platform),Snt=typeof rpe<"u"?async t=>{try{return await Aj(rpe,[t],{cwd:J.cwd()}),!0}catch{return!1}}:void 0,Dnt="/usr/bin/ldd";xnt=/^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack||\/|[a-z]:\\|\\\\).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,knt=/\((\S*)(?::(\d+))(?::(\d+))\)/});function yj(t,e,r,s,a){let n=YB(r);if(s.isArray||s.type==="ANY"&&Array.isArray(n))return Array.isArray(n)?n.map((c,f)=>pj(t,`${e}[${f}]`,c,s,a)):String(n).split(/,/).map(c=>pj(t,e,c,s,a));if(Array.isArray(n))throw new Error(`Non-array configuration settings "${e}" cannot be an array`);return pj(t,e,r,s,a)}function pj(t,e,r,s,a){let n=YB(r);switch(s.type){case"ANY":return NQ(n);case"SHAPE":return Ont(t,e,r,s,a);case"MAP":return Lnt(t,e,r,s,a)}if(n===null&&!s.isNullable&&s.default!==null)throw new Error(`Non-nullable configuration settings "${e}" cannot be set to null`);if("values"in s&&s.values?.includes(n))return n;let f=(()=>{if(s.type==="BOOLEAN"&&typeof n!="string")return kB(n);if(typeof n!="string")throw new Error(`Expected configuration setting "${e}" to be a string, got ${typeof n}`);let p=Vk(n,{env:t.env});switch(s.type){case"ABSOLUTE_PATH":{let h=a,E=H8(r);return E&&E[0]!=="<"&&(h=J.dirname(E)),J.resolve(h,fe.toPortablePath(p))}case"LOCATOR_LOOSE":return Qp(p,!1);case"NUMBER":return parseInt(p);case"LOCATOR":return Qp(p);case"BOOLEAN":return kB(p);case"DURATION":return Jk(p,s.unit);default:return p}})();if("values"in s&&s.values&&!s.values.includes(f))throw new Error(`Invalid value, expected one of ${s.values.join(", ")}`);return f}function Ont(t,e,r,s,a){let n=YB(r);if(typeof n!="object"||Array.isArray(n))throw new nt(`Object configuration settings "${e}" must be an object`);let c=Ej(t,s,{ignoreArrays:!0});if(n===null)return c;for(let[f,p]of Object.entries(n)){let h=`${e}.${f}`;if(!s.properties[f])throw new nt(`Unrecognized configuration settings found: ${e}.${f} - run "yarn config" to see the list of settings supported in Yarn`);c.set(f,yj(t,h,p,s.properties[f],a))}return c}function Lnt(t,e,r,s,a){let n=YB(r),c=new Map;if(typeof n!="object"||Array.isArray(n))throw new nt(`Map configuration settings "${e}" must be an object`);if(n===null)return c;for(let[f,p]of Object.entries(n)){let h=s.normalizeKeys?s.normalizeKeys(f):f,E=`${e}['${h}']`,C=s.valueDefinition;c.set(h,yj(t,E,p,C,a))}return c}function Ej(t,e,{ignoreArrays:r=!1}={}){switch(e.type){case"SHAPE":{if(e.isArray&&!r)return[];let s=new Map;for(let[a,n]of Object.entries(e.properties))s.set(a,Ej(t,n));return s}case"MAP":return e.isArray&&!r?[]:new Map;case"ABSOLUTE_PATH":return e.default===null?null:t.projectCwd===null?Array.isArray(e.default)?e.default.map(s=>J.normalize(s)):J.isAbsolute(e.default)?J.normalize(e.default):e.isNullable?null:void 0:Array.isArray(e.default)?e.default.map(s=>J.resolve(t.projectCwd,s)):J.resolve(t.projectCwd,e.default);case"DURATION":return Jk(e.default,e.unit);default:return e.default}}function yT(t,e,r){if(e.type==="SECRET"&&typeof t=="string"&&r.hideSecrets)return Nnt;if(e.type==="ABSOLUTE_PATH"&&typeof t=="string"&&r.getNativePaths)return fe.fromPortablePath(t);if(e.isArray&&Array.isArray(t)){let s=[];for(let a of t)s.push(yT(a,e,r));return s}if(e.type==="MAP"&&t instanceof Map){if(t.size===0)return;let s=new Map;for(let[a,n]of t.entries()){let c=yT(n,e.valueDefinition,r);typeof c<"u"&&s.set(a,c)}return s}if(e.type==="SHAPE"&&t instanceof Map){if(t.size===0)return;let s=new Map;for(let[a,n]of t.entries()){let c=e.properties[a],f=yT(n,c,r);typeof f<"u"&&s.set(a,f)}return s}return t}function Mnt(){let t={};for(let[e,r]of Object.entries(process.env))e=e.toLowerCase(),e.startsWith(ET)&&(e=(0,ope.default)(e.slice(ET.length)),t[e]=r);return t}function gj(){let t=`${ET}rc_filename`;for(let[e,r]of Object.entries(process.env))if(e.toLowerCase()===t&&typeof r=="string")return r;return dj}async function spe(t){try{return await ce.readFilePromise(t)}catch{return Buffer.of()}}async function Unt(t,e){return Buffer.compare(...await Promise.all([spe(t),spe(e)]))===0}async function _nt(t,e){let[r,s]=await Promise.all([ce.statPromise(t),ce.statPromise(e)]);return r.dev===s.dev&&r.ino===s.ino}async function jnt({configuration:t,selfPath:e}){let r=t.get("yarnPath");return t.get("ignorePath")||r===null||r===e||await Hnt(r,e)?null:r}var ope,Lp,ape,lpe,cpe,hj,Rnt,ov,Fnt,Mp,ET,dj,Nnt,wI,upe,mj,IT,mT,Hnt,ze,av=Xe(()=>{Dt();wc();ope=ut(Sre()),Lp=ut(Fd());Yt();ape=ut(yne()),lpe=Ie("module"),cpe=ut(Ld()),hj=Ie("stream");nue();oI();R8();F8();N8();gue();O8();tm();Iue();LQ();xc();I0();pT();Pc();gT();Rp();Wo();Rnt=function(){if(!Lp.GITHUB_ACTIONS||!process.env.GITHUB_EVENT_PATH)return!1;let t=fe.toPortablePath(process.env.GITHUB_EVENT_PATH),e;try{e=ce.readJsonSync(t)}catch{return!1}return!(!("repository"in e)||!e.repository||(e.repository.private??!0))}(),ov=new Set(["@yarnpkg/plugin-constraints","@yarnpkg/plugin-exec","@yarnpkg/plugin-interactive-tools","@yarnpkg/plugin-stage","@yarnpkg/plugin-typescript","@yarnpkg/plugin-version","@yarnpkg/plugin-workspace-tools"]),Fnt=new Set(["isTestEnv","injectNpmUser","injectNpmPassword","injectNpm2FaToken","zipDataEpilogue","cacheCheckpointOverride","cacheVersionOverride","lockfileVersionOverride","osOverride","cpuOverride","libcOverride","binFolder","version","flags","profile","gpg","ignoreNode","wrapOutput","home","confDir","registry","ignoreCwd"]),Mp=/^(?!v)[a-z0-9._-]+$/i,ET="yarn_",dj=".yarnrc.yml",Nnt="********",wI=(C=>(C.ANY="ANY",C.BOOLEAN="BOOLEAN",C.ABSOLUTE_PATH="ABSOLUTE_PATH",C.LOCATOR="LOCATOR",C.LOCATOR_LOOSE="LOCATOR_LOOSE",C.NUMBER="NUMBER",C.STRING="STRING",C.DURATION="DURATION",C.SECRET="SECRET",C.SHAPE="SHAPE",C.MAP="MAP",C))(wI||{}),upe=ht,mj=(c=>(c.MILLISECONDS="ms",c.SECONDS="s",c.MINUTES="m",c.HOURS="h",c.DAYS="d",c.WEEKS="w",c))(mj||{}),IT=(r=>(r.JUNCTIONS="junctions",r.SYMLINKS="symlinks",r))(IT||{}),mT={lastUpdateCheck:{description:"Last timestamp we checked whether new Yarn versions were available",type:"STRING",default:null},yarnPath:{description:"Path to the local executable that must be used over the global one",type:"ABSOLUTE_PATH",default:null},ignorePath:{description:"If true, the local executable will be ignored when using the global one",type:"BOOLEAN",default:!1},globalFolder:{description:"Folder where all system-global files are stored",type:"ABSOLUTE_PATH",default:G8()},cacheFolder:{description:"Folder where the cache files must be written",type:"ABSOLUTE_PATH",default:"./.yarn/cache"},compressionLevel:{description:"Zip files compression level, from 0 to 9 or mixed (a variant of 9, which stores some files uncompressed, when compression doesn't yield good results)",type:"NUMBER",values:["mixed",0,1,2,3,4,5,6,7,8,9],default:0},virtualFolder:{description:"Folder where the virtual packages (cf doc) will be mapped on the disk (must be named __virtual__)",type:"ABSOLUTE_PATH",default:"./.yarn/__virtual__"},installStatePath:{description:"Path of the file where the install state will be persisted",type:"ABSOLUTE_PATH",default:"./.yarn/install-state.gz"},immutablePatterns:{description:"Array of glob patterns; files matching them won't be allowed to change during immutable installs",type:"STRING",default:[],isArray:!0},rcFilename:{description:"Name of the files where the configuration can be found",type:"STRING",default:gj()},enableGlobalCache:{description:"If true, the system-wide cache folder will be used regardless of `cache-folder`",type:"BOOLEAN",default:!0},cacheMigrationMode:{description:"Defines the conditions under which Yarn upgrades should cause the cache archives to be regenerated.",type:"STRING",values:["always","match-spec","required-only"],default:"always"},enableColors:{description:"If true, the CLI is allowed to use colors in its output",type:"BOOLEAN",default:Zk,defaultText:""},enableHyperlinks:{description:"If true, the CLI is allowed to use hyperlinks in its output",type:"BOOLEAN",default:X4,defaultText:""},enableInlineBuilds:{description:"If true, the CLI will print the build output on the command line",type:"BOOLEAN",default:Lp.isCI,defaultText:""},enableMessageNames:{description:"If true, the CLI will prefix most messages with codes suitable for search engines",type:"BOOLEAN",default:!0},enableProgressBars:{description:"If true, the CLI is allowed to show a progress bar for long-running events",type:"BOOLEAN",default:!Lp.isCI,defaultText:""},enableTimers:{description:"If true, the CLI is allowed to print the time spent executing commands",type:"BOOLEAN",default:!0},enableTips:{description:"If true, installs will print a helpful message every day of the week",type:"BOOLEAN",default:!Lp.isCI,defaultText:""},preferInteractive:{description:"If true, the CLI will automatically use the interactive mode when called from a TTY",type:"BOOLEAN",default:!1},preferTruncatedLines:{description:"If true, the CLI will truncate lines that would go beyond the size of the terminal",type:"BOOLEAN",default:!1},progressBarStyle:{description:"Which style of progress bar should be used (only when progress bars are enabled)",type:"STRING",default:void 0,defaultText:""},defaultLanguageName:{description:"Default language mode that should be used when a package doesn't offer any insight",type:"STRING",default:"node"},defaultProtocol:{description:"Default resolution protocol used when resolving pure semver and tag ranges",type:"STRING",default:"npm:"},enableTransparentWorkspaces:{description:"If false, Yarn won't automatically resolve workspace dependencies unless they use the `workspace:` protocol",type:"BOOLEAN",default:!0},supportedArchitectures:{description:"Architectures that Yarn will fetch and inject into the resolver",type:"SHAPE",properties:{os:{description:"Array of supported process.platform strings, or null to target them all",type:"STRING",isArray:!0,isNullable:!0,default:["current"]},cpu:{description:"Array of supported process.arch strings, or null to target them all",type:"STRING",isArray:!0,isNullable:!0,default:["current"]},libc:{description:"Array of supported libc libraries, or null to target them all",type:"STRING",isArray:!0,isNullable:!0,default:["current"]}}},enableMirror:{description:"If true, the downloaded packages will be retrieved and stored in both the local and global folders",type:"BOOLEAN",default:!0},enableNetwork:{description:"If false, Yarn will refuse to use the network if required to",type:"BOOLEAN",default:!0},enableOfflineMode:{description:"If true, Yarn will attempt to retrieve files and metadata from the global cache rather than the network",type:"BOOLEAN",default:!1},httpProxy:{description:"URL of the http proxy that must be used for outgoing http requests",type:"STRING",default:null},httpsProxy:{description:"URL of the http proxy that must be used for outgoing https requests",type:"STRING",default:null},unsafeHttpWhitelist:{description:"List of the hostnames for which http queries are allowed (glob patterns are supported)",type:"STRING",default:[],isArray:!0},httpTimeout:{description:"Timeout of each http request",type:"DURATION",unit:"ms",default:"1m"},httpRetry:{description:"Retry times on http failure",type:"NUMBER",default:3},networkConcurrency:{description:"Maximal number of concurrent requests",type:"NUMBER",default:50},taskPoolConcurrency:{description:"Maximal amount of concurrent heavy task processing",type:"NUMBER",default:fj()},taskPoolMode:{description:"Execution strategy for heavy tasks",type:"STRING",values:["async","workers"],default:"workers"},networkSettings:{description:"Network settings per hostname (glob patterns are supported)",type:"MAP",valueDefinition:{description:"",type:"SHAPE",properties:{httpsCaFilePath:{description:"Path to file containing one or multiple Certificate Authority signing certificates",type:"ABSOLUTE_PATH",default:null},enableNetwork:{description:"If false, the package manager will refuse to use the network if required to",type:"BOOLEAN",default:null},httpProxy:{description:"URL of the http proxy that must be used for outgoing http requests",type:"STRING",default:null},httpsProxy:{description:"URL of the http proxy that must be used for outgoing https requests",type:"STRING",default:null},httpsKeyFilePath:{description:"Path to file containing private key in PEM format",type:"ABSOLUTE_PATH",default:null},httpsCertFilePath:{description:"Path to file containing certificate chain in PEM format",type:"ABSOLUTE_PATH",default:null}}}},httpsCaFilePath:{description:"A path to a file containing one or multiple Certificate Authority signing certificates",type:"ABSOLUTE_PATH",default:null},httpsKeyFilePath:{description:"Path to file containing private key in PEM format",type:"ABSOLUTE_PATH",default:null},httpsCertFilePath:{description:"Path to file containing certificate chain in PEM format",type:"ABSOLUTE_PATH",default:null},enableStrictSsl:{description:"If false, SSL certificate errors will be ignored",type:"BOOLEAN",default:!0},logFilters:{description:"Overrides for log levels",type:"SHAPE",isArray:!0,concatenateValues:!0,properties:{code:{description:"Code of the messages covered by this override",type:"STRING",default:void 0},text:{description:"Code of the texts covered by this override",type:"STRING",default:void 0},pattern:{description:"Code of the patterns covered by this override",type:"STRING",default:void 0},level:{description:"Log level override, set to null to remove override",type:"STRING",values:Object.values(eQ),isNullable:!0,default:void 0}}},enableTelemetry:{description:"If true, telemetry will be periodically sent, following the rules in https://yarnpkg.com/advanced/telemetry",type:"BOOLEAN",default:!0},telemetryInterval:{description:"Minimal amount of time between two telemetry uploads",type:"DURATION",unit:"d",default:"7d"},telemetryUserId:{description:"If you desire to tell us which project you are, you can set this field. Completely optional and opt-in.",type:"STRING",default:null},enableHardenedMode:{description:"If true, automatically enable --check-resolutions --refresh-lockfile on installs",type:"BOOLEAN",default:Lp.isPR&&Rnt,defaultText:""},enableScripts:{description:"If true, packages are allowed to have install scripts by default",type:"BOOLEAN",default:!0},enableStrictSettings:{description:"If true, unknown settings will cause Yarn to abort",type:"BOOLEAN",default:!0},enableImmutableCache:{description:"If true, the cache is reputed immutable and actions that would modify it will throw",type:"BOOLEAN",default:!1},enableCacheClean:{description:"If false, disallows the `cache clean` command",type:"BOOLEAN",default:!0},checksumBehavior:{description:"Enumeration defining what to do when a checksum doesn't match expectations",type:"STRING",default:"throw"},injectEnvironmentFiles:{description:"List of all the environment files that Yarn should inject inside the process when it starts",type:"ABSOLUTE_PATH",default:[".env.yarn?"],isArray:!0},packageExtensions:{description:"Map of package corrections to apply on the dependency tree",type:"MAP",valueDefinition:{description:"The extension that will be applied to any package whose version matches the specified range",type:"SHAPE",properties:{dependencies:{description:"The set of dependencies that must be made available to the current package in order for it to work properly",type:"MAP",valueDefinition:{description:"A range",type:"STRING"}},peerDependencies:{description:"Inherited dependencies - the consumer of the package will be tasked to provide them",type:"MAP",valueDefinition:{description:"A semver range",type:"STRING"}},peerDependenciesMeta:{description:"Extra information related to the dependencies listed in the peerDependencies field",type:"MAP",valueDefinition:{description:"The peerDependency meta",type:"SHAPE",properties:{optional:{description:"If true, the selected peer dependency will be marked as optional by the package manager and the consumer omitting it won't be reported as an error",type:"BOOLEAN",default:!1}}}}}}}};Hnt=process.platform==="win32"?Unt:_nt;ze=class t{constructor(e){this.isCI=Lp.isCI;this.projectCwd=null;this.plugins=new Map;this.settings=new Map;this.values=new Map;this.sources=new Map;this.invalid=new Map;this.env={};this.limits=new Map;this.packageExtensions=null;this.startingCwd=e}static{this.deleteProperty=Symbol()}static{this.telemetry=null}static create(e,r,s){let a=new t(e);typeof r<"u"&&!(r instanceof Map)&&(a.projectCwd=r),a.importSettings(mT);let n=typeof s<"u"?s:r instanceof Map?r:new Map;for(let[c,f]of n)a.activatePlugin(c,f);return a}static async find(e,r,{strict:s=!0,usePathCheck:a=null,useRc:n=!0}={}){let c=Mnt();delete c.rcFilename;let f=new t(e),p=await t.findRcFiles(e),h=await t.findFolderRcFile(fI());h&&(p.find(me=>me.path===h.path)||p.unshift(h));let E=Eue(p.map(le=>[le.path,le.data])),C=vt.dot,S=new Set(Object.keys(mT)),P=({yarnPath:le,ignorePath:me,injectEnvironmentFiles:pe})=>({yarnPath:le,ignorePath:me,injectEnvironmentFiles:pe}),I=({yarnPath:le,ignorePath:me,injectEnvironmentFiles:pe,...Be})=>{let Ce={};for(let[g,we]of Object.entries(Be))S.has(g)&&(Ce[g]=we);return Ce},R=({yarnPath:le,ignorePath:me,...pe})=>{let Be={};for(let[Ce,g]of Object.entries(pe))S.has(Ce)||(Be[Ce]=g);return Be};if(f.importSettings(P(mT)),f.useWithSource("",P(c),e,{strict:!1}),E){let[le,me]=E;f.useWithSource(le,P(me),C,{strict:!1})}if(a){if(await jnt({configuration:f,selfPath:a})!==null)return f;f.useWithSource("",{ignorePath:!0},e,{strict:!1,overwrite:!0})}let N=await t.findProjectCwd(e);f.startingCwd=e,f.projectCwd=N;let U=Object.assign(Object.create(null),process.env);f.env=U;let W=await Promise.all(f.get("injectEnvironmentFiles").map(async le=>{let me=le.endsWith("?")?await ce.readFilePromise(le.slice(0,-1),"utf8").catch(()=>""):await ce.readFilePromise(le,"utf8");return(0,ape.parse)(me)}));for(let le of W)for(let[me,pe]of Object.entries(le))f.env[me]=Vk(pe,{env:U});if(f.importSettings(I(mT)),f.useWithSource("",I(c),e,{strict:s}),E){let[le,me]=E;f.useWithSource(le,I(me),C,{strict:s})}let ee=le=>"default"in le?le.default:le,ie=new Map([["@@core",rue]]);if(r!==null)for(let le of r.plugins.keys())ie.set(le,ee(r.modules.get(le)));for(let[le,me]of ie)f.activatePlugin(le,me);let ue=new Map([]);if(r!==null){let le=new Map;for(let[Be,Ce]of r.modules)le.set(Be,()=>Ce);let me=new Set,pe=async(Be,Ce)=>{let{factory:g,name:we}=Pp(Be);if(!g||me.has(we))return;let ye=new Map(le),Ae=Z=>{if((0,lpe.isBuiltin)(Z))return Pp(Z);if(ye.has(Z))return ye.get(Z)();throw new nt(`This plugin cannot access the package referenced via ${Z} which is neither a builtin, nor an exposed entry`)},se=await qE(async()=>ee(await g(Ae)),Z=>`${Z} (when initializing ${we}, defined in ${Ce})`);le.set(we,()=>se),me.add(we),ue.set(we,se)};if(c.plugins)for(let Be of c.plugins.split(";")){let Ce=J.resolve(e,fe.toPortablePath(Be));await pe(Ce,"")}for(let{path:Be,cwd:Ce,data:g}of p)if(n&&Array.isArray(g.plugins))for(let we of g.plugins){let ye=typeof we!="string"?we.path:we,Ae=we?.spec??"",se=we?.checksum??"";if(ov.has(Ae))continue;let Z=J.resolve(Ce,fe.toPortablePath(ye));if(!await ce.existsPromise(Z)){if(!Ae){let mt=Ht(f,J.basename(Z,".cjs"),ht.NAME),j=Ht(f,".gitignore",ht.NAME),rt=Ht(f,f.values.get("rcFilename"),ht.NAME),Fe=Ht(f,"https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored",ht.URL);throw new nt(`Missing source for the ${mt} plugin - please try to remove the plugin from ${rt} then reinstall it manually. This error usually occurs because ${j} is incorrect, check ${Fe} to make sure your plugin folder isn't gitignored.`)}if(!Ae.match(/^https?:/)){let mt=Ht(f,J.basename(Z,".cjs"),ht.NAME),j=Ht(f,f.values.get("rcFilename"),ht.NAME);throw new nt(`Failed to recognize the source for the ${mt} plugin - please try to delete the plugin from ${j} then reinstall it manually.`)}let De=await lj(Ae,{configuration:f}),Re=us(De);if(se&&se!==Re){let mt=Ht(f,J.basename(Z,".cjs"),ht.NAME),j=Ht(f,f.values.get("rcFilename"),ht.NAME),rt=Ht(f,`yarn plugin import ${Ae}`,ht.CODE);throw new nt(`Failed to fetch the ${mt} plugin from its remote location: its checksum seems to have changed. If this is expected, please remove the plugin from ${j} then run ${rt} to reimport it.`)}await ce.mkdirPromise(J.dirname(Z),{recursive:!0}),await ce.writeFilePromise(Z,De)}await pe(Z,Be)}}for(let[le,me]of ue)f.activatePlugin(le,me);if(f.useWithSource("",R(c),e,{strict:s}),E){let[le,me]=E;f.useWithSource(le,R(me),C,{strict:s})}return f.get("enableGlobalCache")&&(f.values.set("cacheFolder",`${f.get("globalFolder")}/cache`),f.sources.set("cacheFolder","")),f}static async findRcFiles(e){let r=gj(),s=[],a=e,n=null;for(;a!==n;){n=a;let c=J.join(n,r);if(ce.existsSync(c)){let f,p;try{p=await ce.readFilePromise(c,"utf8"),f=ls(p)}catch{let h="";throw p?.match(/^\s+(?!-)[^:]+\s+\S+/m)&&(h=" (in particular, make sure you list the colons after each key name)"),new nt(`Parse error when loading ${c}; please check it's proper Yaml${h}`)}s.unshift({path:c,cwd:n,data:f})}a=J.dirname(n)}return s}static async findFolderRcFile(e){let r=J.join(e,Er.rc),s;try{s=await ce.readFilePromise(r,"utf8")}catch(n){if(n.code==="ENOENT")return null;throw n}let a=ls(s);return{path:r,cwd:e,data:a}}static async findProjectCwd(e){let r=null,s=e,a=null;for(;s!==a;){if(a=s,ce.existsSync(J.join(a,Er.lockfile)))return a;ce.existsSync(J.join(a,Er.manifest))&&(r=a),s=J.dirname(a)}return r}static async updateConfiguration(e,r,s={}){let a=gj(),n=J.join(e,a),c=ce.existsSync(n)?ls(await ce.readFilePromise(n,"utf8")):{},f=!1,p;if(typeof r=="function"){try{p=r(c)}catch{p=r({})}if(p===c)return!1}else{p=c;for(let h of Object.keys(r)){let E=c[h],C=r[h],S;if(typeof C=="function")try{S=C(E)}catch{S=C(void 0)}else S=C;E!==S&&(S===t.deleteProperty?delete p[h]:p[h]=S,f=!0)}if(!f)return!1}return await ce.changeFilePromise(n,nl(p),{automaticNewlines:!0}),!0}static async addPlugin(e,r){r.length!==0&&await t.updateConfiguration(e,s=>{let a=s.plugins??[];if(a.length===0)return{...s,plugins:r};let n=[],c=[...r];for(let f of a){let p=typeof f!="string"?f.path:f,h=c.find(E=>E.path===p);h?(n.push(h),c=c.filter(E=>E!==h)):n.push(f)}return n.push(...c),{...s,plugins:n}})}static async updateHomeConfiguration(e){let r=fI();return await t.updateConfiguration(r,e)}activatePlugin(e,r){this.plugins.set(e,r),typeof r.configuration<"u"&&this.importSettings(r.configuration)}importSettings(e){for(let[r,s]of Object.entries(e))if(s!=null){if(this.settings.has(r))throw new Error(`Cannot redefine settings "${r}"`);this.settings.set(r,s),this.values.set(r,Ej(this,s))}}useWithSource(e,r,s,a){try{this.use(e,r,s,a)}catch(n){throw n.message+=` (in ${Ht(this,e,ht.PATH)})`,n}}use(e,r,s,{strict:a=!0,overwrite:n=!1}={}){a=a&&this.get("enableStrictSettings");for(let c of["enableStrictSettings",...Object.keys(r)]){let f=r[c],p=H8(f);if(p&&(e=p),typeof f>"u"||c==="plugins"||e===""&&Fnt.has(c))continue;if(c==="rcFilename")throw new nt(`The rcFilename settings can only be set via ${`${ET}RC_FILENAME`.toUpperCase()}, not via a rc file`);let h=this.settings.get(c);if(!h){let C=fI(),S=e[0]!=="<"?J.dirname(e):null;if(a&&!(S!==null?C===S:!1))throw new nt(`Unrecognized or legacy configuration settings found: ${c} - run "yarn config" to see the list of settings supported in Yarn`);this.invalid.set(c,e);continue}if(this.sources.has(c)&&!(n||h.type==="MAP"||h.isArray&&h.concatenateValues))continue;let E;try{E=yj(this,c,f,h,s)}catch(C){throw C.message+=` in ${Ht(this,e,ht.PATH)}`,C}if(c==="enableStrictSettings"&&e!==""){a=E;continue}if(h.type==="MAP"){let C=this.values.get(c);this.values.set(c,new Map(n?[...C,...E]:[...E,...C])),this.sources.set(c,`${this.sources.get(c)}, ${e}`)}else if(h.isArray&&h.concatenateValues){let C=this.values.get(c);this.values.set(c,n?[...C,...E]:[...E,...C]),this.sources.set(c,`${this.sources.get(c)}, ${e}`)}else this.values.set(c,E),this.sources.set(c,e)}}get(e){if(!this.values.has(e))throw new Error(`Invalid configuration key "${e}"`);return this.values.get(e)}getSpecial(e,{hideSecrets:r=!1,getNativePaths:s=!1}){let a=this.get(e),n=this.settings.get(e);if(typeof n>"u")throw new nt(`Couldn't find a configuration settings named "${e}"`);return yT(a,n,{hideSecrets:r,getNativePaths:s})}getSubprocessStreams(e,{header:r,prefix:s,report:a}){let n,c,f=ce.createWriteStream(e);if(this.get("enableInlineBuilds")){let p=a.createStreamReporter(`${s} ${Ht(this,"STDOUT","green")}`),h=a.createStreamReporter(`${s} ${Ht(this,"STDERR","red")}`);n=new hj.PassThrough,n.pipe(p),n.pipe(f),c=new hj.PassThrough,c.pipe(h),c.pipe(f)}else n=f,c=f,typeof r<"u"&&n.write(`${r} `);return{stdout:n,stderr:c}}makeResolver(){let e=[];for(let r of this.plugins.values())for(let s of r.resolvers||[])e.push(new s);return new rm([new FQ,new Ei,...e])}makeFetcher(){let e=[];for(let r of this.plugins.values())for(let s of r.fetchers||[])e.push(new s);return new aI([new lI,new cI,...e])}getLinkers(){let e=[];for(let r of this.plugins.values())for(let s of r.linkers||[])e.push(new s);return e}getSupportedArchitectures(){let e=sv(),r=this.get("supportedArchitectures"),s=r.get("os");s!==null&&(s=s.map(c=>c==="current"?e.os:c));let a=r.get("cpu");a!==null&&(a=a.map(c=>c==="current"?e.cpu:c));let n=r.get("libc");return n!==null&&(n=Wl(n,c=>c==="current"?e.libc??Wl.skip:c)),{os:s,cpu:a,libc:n}}isInteractive({interactive:e,stdout:r}){return r.isTTY?e??this.get("preferInteractive"):!1}async getPackageExtensions(){if(this.packageExtensions!==null)return this.packageExtensions;this.packageExtensions=new Map;let e=this.packageExtensions,r=(s,a,{userProvided:n=!1}={})=>{if(!cl(s.range))throw new Error("Only semver ranges are allowed as keys for the packageExtensions setting");let c=new Ut;c.load(a,{yamlCompatibilityMode:!0});let f=xB(e,s.identHash),p=[];f.push([s.range,p]);let h={status:"inactive",userProvided:n,parentDescriptor:s};for(let E of c.dependencies.values())p.push({...h,type:"Dependency",descriptor:E});for(let E of c.peerDependencies.values())p.push({...h,type:"PeerDependency",descriptor:E});for(let[E,C]of c.peerDependenciesMeta)for(let[S,P]of Object.entries(C))p.push({...h,type:"PeerDependencyMeta",selector:E,key:S,value:P})};await this.triggerHook(s=>s.registerPackageExtensions,this,r);for(let[s,a]of this.get("packageExtensions"))r(C0(s,!0),Yk(a),{userProvided:!0});return e}normalizeLocator(e){return cl(e.reference)?Ws(e,`${this.get("defaultProtocol")}${e.reference}`):Mp.test(e.reference)?Ws(e,`${this.get("defaultProtocol")}${e.reference}`):e}normalizeDependency(e){return cl(e.range)?On(e,`${this.get("defaultProtocol")}${e.range}`):Mp.test(e.range)?On(e,`${this.get("defaultProtocol")}${e.range}`):e}normalizeDependencyMap(e){return new Map([...e].map(([r,s])=>[r,this.normalizeDependency(s)]))}normalizePackage(e,{packageExtensions:r}){let s=LB(e),a=r.get(e.identHash);if(typeof a<"u"){let c=e.version;if(c!==null){for(let[f,p]of a)if(Zf(c,f))for(let h of p)switch(h.status==="inactive"&&(h.status="redundant"),h.type){case"Dependency":typeof s.dependencies.get(h.descriptor.identHash)>"u"&&(h.status="active",s.dependencies.set(h.descriptor.identHash,this.normalizeDependency(h.descriptor)));break;case"PeerDependency":typeof s.peerDependencies.get(h.descriptor.identHash)>"u"&&(h.status="active",s.peerDependencies.set(h.descriptor.identHash,h.descriptor));break;case"PeerDependencyMeta":{let E=s.peerDependenciesMeta.get(h.selector);(typeof E>"u"||!Object.hasOwn(E,h.key)||E[h.key]!==h.value)&&(h.status="active",Yl(s.peerDependenciesMeta,h.selector,()=>({}))[h.key]=h.value)}break;default:G4(h)}}}let n=c=>c.scope?`${c.scope}__${c.name}`:`${c.name}`;for(let c of s.peerDependenciesMeta.keys()){let f=Sa(c);s.peerDependencies.has(f.identHash)||s.peerDependencies.set(f.identHash,On(f,"*"))}for(let c of s.peerDependencies.values()){if(c.scope==="types")continue;let f=n(c),p=Da("types",f),h=un(p);s.peerDependencies.has(p.identHash)||s.peerDependenciesMeta.has(h)||s.dependencies.has(p.identHash)||(s.peerDependencies.set(p.identHash,On(p,"*")),s.peerDependenciesMeta.set(h,{optional:!0}))}return s.dependencies=new Map(qs(s.dependencies,([,c])=>al(c))),s.peerDependencies=new Map(qs(s.peerDependencies,([,c])=>al(c))),s}getLimit(e){return Yl(this.limits,e,()=>(0,cpe.default)(this.get(e)))}async triggerHook(e,...r){for(let s of this.plugins.values()){let a=s.hooks;if(!a)continue;let n=e(a);n&&await n(...r)}}async triggerMultipleHooks(e,r){for(let s of r)await this.triggerHook(e,...s)}async reduceHook(e,r,...s){let a=r;for(let n of this.plugins.values()){let c=n.hooks;if(!c)continue;let f=e(c);f&&(a=await f(a,...s))}return a}async firstHook(e,...r){for(let s of this.plugins.values()){let a=s.hooks;if(!a)continue;let n=e(a);if(!n)continue;let c=await n(...r);if(typeof c<"u")return c}return null}}});var qr={};Vt(qr,{EndStrategy:()=>Bj,ExecError:()=>CT,PipeError:()=>lv,execvp:()=>Aj,pipevp:()=>Wu});function om(t){return t!==null&&typeof t.fd=="number"}function Ij(){}function Cj(){for(let t of am)t.kill()}async function Wu(t,e,{cwd:r,env:s=process.env,strict:a=!1,stdin:n=null,stdout:c,stderr:f,end:p=2}){let h=["pipe","pipe","pipe"];n===null?h[0]="ignore":om(n)&&(h[0]=n),om(c)&&(h[1]=c),om(f)&&(h[2]=f);let E=(0,wj.default)(t,e,{cwd:fe.fromPortablePath(r),env:{...s,PWD:fe.fromPortablePath(r)},stdio:h});am.add(E),am.size===1&&(process.on("SIGINT",Ij),process.on("SIGTERM",Cj)),!om(n)&&n!==null&&n.pipe(E.stdin),om(c)||E.stdout.pipe(c,{end:!1}),om(f)||E.stderr.pipe(f,{end:!1});let C=()=>{for(let S of new Set([c,f]))om(S)||S.end()};return new Promise((S,P)=>{E.on("error",I=>{am.delete(E),am.size===0&&(process.off("SIGINT",Ij),process.off("SIGTERM",Cj)),(p===2||p===1)&&C(),P(I)}),E.on("close",(I,R)=>{am.delete(E),am.size===0&&(process.off("SIGINT",Ij),process.off("SIGTERM",Cj)),(p===2||p===1&&I!==0)&&C(),I===0||!a?S({code:vj(I,R)}):P(new lv({fileName:t,code:I,signal:R}))})})}async function Aj(t,e,{cwd:r,env:s=process.env,encoding:a="utf8",strict:n=!1}){let c=["ignore","pipe","pipe"],f=[],p=[],h=fe.fromPortablePath(r);typeof s.PWD<"u"&&(s={...s,PWD:h});let E=(0,wj.default)(t,e,{cwd:h,env:s,stdio:c});return E.stdout.on("data",C=>{f.push(C)}),E.stderr.on("data",C=>{p.push(C)}),await new Promise((C,S)=>{E.on("error",P=>{let I=ze.create(r),R=Ht(I,t,ht.PATH);S(new jt(1,`Process ${R} failed to spawn`,N=>{N.reportError(1,` ${Kf(I,{label:"Thrown Error",value:_u(ht.NO_HINT,P.message)})}`)}))}),E.on("close",(P,I)=>{let R=a==="buffer"?Buffer.concat(f):Buffer.concat(f).toString(a),N=a==="buffer"?Buffer.concat(p):Buffer.concat(p).toString(a);P===0||!n?C({code:vj(P,I),stdout:R,stderr:N}):S(new CT({fileName:t,code:P,signal:I,stdout:R,stderr:N}))})})}function vj(t,e){let r=Gnt.get(e);return typeof r<"u"?128+r:t??1}function qnt(t,e,{configuration:r,report:s}){s.reportError(1,` ${Kf(r,t!==null?{label:"Exit Code",value:_u(ht.NUMBER,t)}:{label:"Exit Signal",value:_u(ht.CODE,e)})}`)}var wj,Bj,lv,CT,am,Gnt,dT=Xe(()=>{Dt();wj=ut(_U());av();Tc();xc();Bj=(s=>(s[s.Never=0]="Never",s[s.ErrorCode=1]="ErrorCode",s[s.Always=2]="Always",s))(Bj||{}),lv=class extends jt{constructor({fileName:e,code:r,signal:s}){let a=ze.create(J.cwd()),n=Ht(a,e,ht.PATH);super(1,`Child ${n} reported an error`,c=>{qnt(r,s,{configuration:a,report:c})}),this.code=vj(r,s)}},CT=class extends lv{constructor({fileName:e,code:r,signal:s,stdout:a,stderr:n}){super({fileName:e,code:r,signal:s}),this.stdout=a,this.stderr=n}};am=new Set;Gnt=new Map([["SIGINT",2],["SIGQUIT",3],["SIGKILL",9],["SIGTERM",15]])});function Ape(t){fpe=t}function cv(){return typeof Sj>"u"&&(Sj=fpe()),Sj}var Sj,fpe,Dj=Xe(()=>{fpe=()=>{throw new Error("Assertion failed: No libzip instance is available, and no factory was configured")}});var ppe=_((wT,Pj)=>{var Wnt=Object.assign({},Ie("fs")),bj=function(){var t=typeof document<"u"&&document.currentScript?document.currentScript.src:void 0;return typeof __filename<"u"&&(t=t||__filename),function(e){e=e||{};var r=typeof e<"u"?e:{},s,a;r.ready=new Promise(function(Ke,st){s=Ke,a=st});var n={},c;for(c in r)r.hasOwnProperty(c)&&(n[c]=r[c]);var f=[],p="./this.program",h=function(Ke,st){throw st},E=!1,C=!0,S="";function P(Ke){return r.locateFile?r.locateFile(Ke,S):S+Ke}var I,R,N,U;C&&(E?S=Ie("path").dirname(S)+"/":S=__dirname+"/",I=function(st,St){var lr=Me(st);return lr?St?lr:lr.toString():(N||(N=Wnt),U||(U=Ie("path")),st=U.normalize(st),N.readFileSync(st,St?null:"utf8"))},R=function(st){var St=I(st,!0);return St.buffer||(St=new Uint8Array(St)),we(St.buffer),St},process.argv.length>1&&(p=process.argv[1].replace(/\\/g,"/")),f=process.argv.slice(2),h=function(Ke){process.exit(Ke)},r.inspect=function(){return"[Emscripten Module object]"});var W=r.print||console.log.bind(console),ee=r.printErr||console.warn.bind(console);for(c in n)n.hasOwnProperty(c)&&(r[c]=n[c]);n=null,r.arguments&&(f=r.arguments),r.thisProgram&&(p=r.thisProgram),r.quit&&(h=r.quit);var ie=0,ue=function(Ke){ie=Ke},le;r.wasmBinary&&(le=r.wasmBinary);var me=r.noExitRuntime||!0;typeof WebAssembly!="object"&&rs("no native wasm support detected");function pe(Ke,st,St){switch(st=st||"i8",st.charAt(st.length-1)==="*"&&(st="i32"),st){case"i1":return Ve[Ke>>0];case"i8":return Ve[Ke>>0];case"i16":return mh((Ke>>1)*2);case"i32":return to((Ke>>2)*4);case"i64":return to((Ke>>2)*4);case"float":return Af((Ke>>2)*4);case"double":return dh((Ke>>3)*8);default:rs("invalid type for getValue: "+st)}return null}var Be,Ce=!1,g;function we(Ke,st){Ke||rs("Assertion failed: "+st)}function ye(Ke){var st=r["_"+Ke];return we(st,"Cannot call unknown function "+Ke+", make sure it is exported"),st}function Ae(Ke,st,St,lr,te){var Ee={string:function(qi){var Tn=0;if(qi!=null&&qi!==0){var Ga=(qi.length<<2)+1;Tn=wi(Ga),mt(qi,Tn,Ga)}return Tn},array:function(qi){var Tn=wi(qi.length);return Fe(qi,Tn),Tn}};function Oe(qi){return st==="string"?De(qi):st==="boolean"?!!qi:qi}var dt=ye(Ke),Et=[],bt=0;if(lr)for(var tr=0;tr=St)&&ke[lr];)++lr;return Z.decode(ke.subarray(Ke,lr))}function Re(Ke,st,St,lr){if(!(lr>0))return 0;for(var te=St,Ee=St+lr-1,Oe=0;Oe=55296&&dt<=57343){var Et=Ke.charCodeAt(++Oe);dt=65536+((dt&1023)<<10)|Et&1023}if(dt<=127){if(St>=Ee)break;st[St++]=dt}else if(dt<=2047){if(St+1>=Ee)break;st[St++]=192|dt>>6,st[St++]=128|dt&63}else if(dt<=65535){if(St+2>=Ee)break;st[St++]=224|dt>>12,st[St++]=128|dt>>6&63,st[St++]=128|dt&63}else{if(St+3>=Ee)break;st[St++]=240|dt>>18,st[St++]=128|dt>>12&63,st[St++]=128|dt>>6&63,st[St++]=128|dt&63}}return st[St]=0,St-te}function mt(Ke,st,St){return Re(Ke,ke,st,St)}function j(Ke){for(var st=0,St=0;St=55296&&lr<=57343&&(lr=65536+((lr&1023)<<10)|Ke.charCodeAt(++St)&1023),lr<=127?++st:lr<=2047?st+=2:lr<=65535?st+=3:st+=4}return st}function rt(Ke){var st=j(Ke)+1,St=La(st);return St&&Re(Ke,Ve,St,st),St}function Fe(Ke,st){Ve.set(Ke,st)}function Ne(Ke,st){return Ke%st>0&&(Ke+=st-Ke%st),Ke}var Pe,Ve,ke,it,Ue,x,w,b,y,F;function z(Ke){Pe=Ke,r.HEAP_DATA_VIEW=F=new DataView(Ke),r.HEAP8=Ve=new Int8Array(Ke),r.HEAP16=it=new Int16Array(Ke),r.HEAP32=x=new Int32Array(Ke),r.HEAPU8=ke=new Uint8Array(Ke),r.HEAPU16=Ue=new Uint16Array(Ke),r.HEAPU32=w=new Uint32Array(Ke),r.HEAPF32=b=new Float32Array(Ke),r.HEAPF64=y=new Float64Array(Ke)}var X=r.INITIAL_MEMORY||16777216,$,oe=[],xe=[],Te=[],lt=!1;function Ct(){if(r.preRun)for(typeof r.preRun=="function"&&(r.preRun=[r.preRun]);r.preRun.length;)Pt(r.preRun.shift());Ts(oe)}function qt(){lt=!0,Ts(xe)}function ir(){if(r.postRun)for(typeof r.postRun=="function"&&(r.postRun=[r.postRun]);r.postRun.length;)Pr(r.postRun.shift());Ts(Te)}function Pt(Ke){oe.unshift(Ke)}function gn(Ke){xe.unshift(Ke)}function Pr(Ke){Te.unshift(Ke)}var Ir=0,Or=null,on=null;function ai(Ke){Ir++,r.monitorRunDependencies&&r.monitorRunDependencies(Ir)}function Io(Ke){if(Ir--,r.monitorRunDependencies&&r.monitorRunDependencies(Ir),Ir==0&&(Or!==null&&(clearInterval(Or),Or=null),on)){var st=on;on=null,st()}}r.preloadedImages={},r.preloadedAudios={};function rs(Ke){r.onAbort&&r.onAbort(Ke),Ke+="",ee(Ke),Ce=!0,g=1,Ke="abort("+Ke+"). Build with -s ASSERTIONS=1 for more info.";var st=new WebAssembly.RuntimeError(Ke);throw a(st),st}var $s="data:application/octet-stream;base64,";function Co(Ke){return Ke.startsWith($s)}var ji="data:application/octet-stream;base64,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QCAAKAJIIgEgASADaiADEAcaAkAgAyAAKAJsIgFNBEAgACABIANrNgJsDAELIABCADcCbAsgACAAKAJoIANrIgE2AmggACAAKAJYIANrNgJYIAEgACgChC5JBEAgACABNgKELgsgAEH8gAEoAgARAwAgAiADaiECCwJAIAAoAgAiASgCBCIERQ0AIAAoAjwhBSAAIAIgBCACIARJGyICBH8gACgCSCAAKAJoaiAFaiEFIAEgBCACazYCBAJAAkACQAJAIAEoAhwiBCgCFEEBaw4CAQACCyAEQaABaiAFIAEoAgAgAkHcgAEoAgARCAAMAgsgASABKAIwIAUgASgCACACQcSAASgCABEEADYCMAwBCyAFIAEoAgAgAhAHGgsgASABKAIAIAJqNgIAIAEgASgCCCACajYCCCAAKAI8BSAFCyACaiICNgI8AkAgACgChC4iASACakEDSQ0AIAAoAmggAWshAQJAIAAoAnRBgQhPBEAgACAAIAAoAkggAWoiAi0AACACLQABIAAoAnwRAAA2AlQMAQsgAUUNACAAIAFBAWsgACgChAERAgAaCyAAKAKELiAAKAI8IgJBAUZrIgRFDQAgACABIAQgACgCgAERBQAgACAAKAKELiAEazYChC4gACgCPCECCyACQYUCSw0AIAAoAgAoAgRFDQAgACgCMCEBDAELCwJAIAAoAkQiAiAAKAJAIgNNDQAgAAJ/IAAoAjwgACgCaGoiASADSwRAIAAoAkggAWpBACACIAFrIgNBggIgA0GCAkkbIgMQGSABIANqDAELIAFBggJqIgEgA00NASAAKAJIIANqQQAgAiADayICIAEgA2siAyACIANJGyIDEBkgACgCQCADags2AkALC50CAQF/AkAgAAJ/IAAoAqAuIgFBwABGBEAgACgCBCAAKAIQaiAAKQOYLjcAACAAQgA3A5guIAAgACgCEEEIajYCEEEADAELIAFBIE4EQCAAKAIEIAAoAhBqIAApA5guPgAAIAAgAEGcLmo1AgA3A5guIAAgACgCEEEEajYCECAAIAAoAqAuQSBrIgE2AqAuCyABQRBOBEAgACgCBCAAKAIQaiAAKQOYLj0AACAAIAAoAhBBAmo2AhAgACAAKQOYLkIQiDcDmC4gACAAKAKgLkEQayIBNgKgLgsgAUEISA0BIAAgACgCECIBQQFqNgIQ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KAJUIgEEfyABQQAgARAiEBkgACgCVAVBAAsQBiAAQQA2AlQLC9wMAgl/AX4jAEFAaiIGJAACQAJAAkACQAJAIAEoAjBBABAjIgVBAkZBACABKAI4QQAQIyIEQQFGGw0AIAVBAUZBACAEQQJGGw0AIAVBAkciAw0BIARBAkcNAQsgASABLwEMQYAQcjsBDEEAIQMMAQsgASABLwEMQf/vA3E7AQxBACEFIANFBEBB9eABIAEoAjAgAEEIahBpIgVFDQILIAJBgAJxBEAgBSEDDAELIARBAkcEQCAFIQMMAQtB9cYBIAEoAjggAEEIahBpIgNFBEAgBRAcDAILIAMgBTYCAAsgASABLwEMQf7/A3EgAS8BUiIFQQBHcjsBDAJAAkACQAJAAn8CQAJAIAEpAyhC/v///w9WDQAgASkDIEL+////D1YNACACQYAEcUUNASABKQNIQv////8PVA0BCyAFQYECa0H//wNxQQNJIQdBAQwBCyAFQYECa0H//wNxIQQgAkGACnFBgApHDQEgBEEDSSEHQQALIQkgBkIcEBciBEUEQCAAQQhqIgAEQCAAQQA2AgQgAEEONgIACyADEBwMBQsgAkGACHEhBQJAAkAgAkGAAnEEQAJAIAUNACABKQMgQv////8PVg0AIAEpAyhCgICAgBBUDQMLIAQgASkDKBAYIAEpAyAhDAwBCwJAAkACQCAFDQAgASkDIEL/////D1YNACABKQMoIgxC/////w9WDQEgASkDSEKAgICAEFQNBAsgASkDKCIMQv////8PVA0BCyAEIAwQGAsgASkDICIMQv////8PWgRAIAQgDBAYCyABKQNIIgxC/////w9UDQELIAQgDBAYCyAELQAARQRAIABBCGoiAARAIABBADYCBCAAQRQ2AgALIAQQCCADEBwMBQtBASEKQQEgBC0AAAR+IAQpAxAFQgALp0H//wNxIAYQRyEFIAQQCCAFIAM2AgAgBw0BDAILIAMhBSAEQQJLDQELIAZCBxAXIgRFBEAgAEEIaiIABEAgAEEANgIEIABBDjYCAAsgBRAcDAMLIARBAhANIARBhxJBAhAsIAQgAS0AUhBwIAQgAS8BEBANIAQtAABFBEAgAEEIaiIABEAgAEEANgIEIABBFDYCAAsgBBAIDAILQYGyAkEHIAYQRyEDIAQQCCADIAU2AgBBASEL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NgIEIABB7A42AhggGiEdDAMLIARB/wFxIgJBwABxRQRAIAggDSADLwECQQJ0aiAdp0F/IAJ0QX9zcUECdGoiAy0AASICayEIIB0gAq2IIR0gAy0AACIERQ0HDAELCyAEQSBxBEAgB0G//gA2AgQgASECDAgLIAdB0f4ANgIEIABB0A42AhggASECDAcLIB1BfyAGdEF/c62DIBt8IhunIQUgCCAEQQ9xIgNrIQggGiAErUIPg4ghHSABIBdrIgYgAjMBAiAaQX8gA3RBf3Otg3ynIgRPDQIgBCAGayIGIBlNDQEgBygCjEdFDQEgB0HR/gA2AgQgAEG5DDYCGAsgASECIAshBQwFCwJAIA5FBEAgDCARIAZraiEDDAELIAYgDk0EQCAMIA4gBmtqIQMMAQsgDCARIAYgDmsiBmtqIQMgBSAGTQ0AIAUgBmshBQJAAkAgASADTSABIA8gAWusIhogBq0iGyAaIBtUGyIapyIGaiICIANLcQ0AIAMgBmogAUsgASADT3ENACABIAMgBhAHGiACIQEMAQsgASADIAMgAWsiASABQR91IgFqIAFzIgIQByACaiEBIBogAq0iHn0iHFANACACIANqIQIDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgASACKQAANwAAIAEgAikAGDcAGCABIAIpABA3ABAgASACKQAINwAIIBpCIH0hGiACQSBqIQIgAUEgaiEBIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAEgAikAADcAACABIAIpABg3ABggASACKQAQNwAQIAEgAikACDcACCABIAIpADg3ADggASACKQAwNwAwIAEgAikAKDcAKCABIAIpACA3ACAgASACKQBYNwBYIAEgAikAUDcAUCABIAIpAEg3AEggASACKQBANwBAIAEgAikAYDcAYCABIAIpAGg3AGggASACKQBwNwBwIAEgAikAeDcAeCACQYABaiECIAFBgAFqIQEgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAEgAikAADcAACABIAIpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCABIAIpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCABIAIoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCABIAIvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCABIAItAAA6AAAgAkEBaiECIAFBAWohAQsgHEIAUg0ACwsgDiEGIAwhAwsgBSAGSwRAAkACQCABIANNIAEgDyABa6wiGiAGrSIbIBogG1QbIhqnIglqIgIgA0txDQAgAyAJaiABSyABIANPcQ0AIAEgAyAJEAcaDAELIAEgAyADIAFrIgEgAUEfdSIBaiABcyIBEAcgAWohAiAaIAGtIh59IhxQDQAgASADaiEBA0ACQCAcIB4gHCAeVBsiG0IgVARAIBshGgwBCyAbIhpCIH0iIEIFiEIBfEIDgyIfUEUEQANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCAaQiB9IRogAUEgaiEBIAJBIGohAiAfQgF9Ih9CAFINAAsLICBC4ABUDQADQCACIAEpAAA3AAAgAiABKQAYNwAYIAIgASkAEDcAECACIAEpAAg3AAggAiABKQA4NwA4IAIgASkAMDcAMCACIAEpACg3ACggAiABKQAgNwAgIAIgASkAWDcAWCACIAEpAFA3AFAgAiABKQBINwBIIAIgASkAQDcAQCACIAEpAGA3AGAgAiABKQBoNwBoIAIgASkAcDcAcCACIAEpAHg3AHggAUGAAWohASACQYABaiECIBpCgAF9IhpCH1YNAAsLIBpCEFoEQCACIAEpAAA3AAAgAiABKQAINwAIIBpCEH0hGiACQRBqIQIgAUEQaiEBCyAaQghaBEAgAiABKQAANwAAIBpCCH0hGiACQQhqIQIgAUEIaiEBCyAaQgRaBEAgAiABKAAANgAAIBpCBH0hGiACQQRqIQIgAUEEaiEBCyAaQgJaBEAgAiABLwAAOwAAIBpCAn0hGiACQQJqIQIgAUECaiEBCyAcIBt9IRwgGlBFBEAgAiABLQAAOgAAIAJBAWohAiABQQFqIQELIBxCAFINAAsLIAUgBmshAUEAIARrIQUCQCAEQQdLBEAgBCEDDAELIAEgBE0EQCAEIQMMAQsgAiAEayEFA0ACQCACIAUpAAA3AAAgBEEBdCEDIAEgBGshASACIARqIQIgBEEDSw0AIAMhBCABIANLDQELC0EAIANrIQULIAIgBWohBAJAIAUgDyACa6wiGiABrSIbIBogG1QbIhqnIgFIIAVBf0pxDQAgBUEBSCABIARqIAJLcQ0AIAIgBCABEAcgAWohAgwDCyACIAQgAyADQR91IgFqIAFzIgEQByABaiECIBogAa0iHn0iHFANAiABIARqIQEDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIBpCIH0hGiABQSBqIQEgAkEgaiECIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCACIAEpADg3ADggAiABKQAwNwAwIAIgASkAKDcAKCACIAEpACA3ACAgAiABKQBYNwBYIAIgASkAUDcAUCACIAEpAEg3AEggAiABKQBANwBAIAIgASkAYDcAYCACIAEpAGg3AGggAiABKQBwNwBwIAIgASkAeDcAeCABQYABaiEBIAJBgAFqIQIgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAIgASkAADcAACACIAEpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCACIAEpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCACIAEoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCACIAEvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCACIAEtAAA6AAAgAkEBaiECIAFBAWohAQsgHFBFDQALDAILAkAgASADTSABIA8gAWusIhogBa0iGyAaIBtUGyIapyIEaiICIANLcQ0AIAMgBGogAUsgASADT3ENACABIAMgBBAHGgwCCyABIAMgAyABayIBIAFBH3UiAWogAXMiARAHIAFqIQIgGiABrSIefSIcUA0BIAEgA2ohAQNAAkAgHCAeIBwgHlQbIhtCIFQEQCAbIRoMAQsgGyIaQiB9IiBCBYhCAXxCA4MiH1BFBEADQCACIAEpAAA3AAAgAiABKQAYNwAYIAIgASkAEDcAECACIAEpAAg3AAggGkIgfSEaIAFBIGohASACQSBqIQIgH0IBfSIfQgBSDQALCyAgQuAAVA0AA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIAIgASkAODcAOCACIAEpADA3ADAgAiABKQAoNwAoIAIgASkAIDcAICACIAEpAFg3AFggAiABKQBQNwBQIAIgASkASDcASCACIAEpAEA3AEAgAiABKQBgNwBgIAIgASkAaDcAaCACIAEpAHA3AHAgAiABKQB4NwB4IAFBgAFqIQEgAkGAAWohAiAaQoABfSIaQh9WDQALCyAaQhBaBEAgAiABKQAANwAAIAIgASkACDcACCAaQhB9IRogAkEQaiECIAFBEGohAQsgGkIIWgRAIAIgASkAADcAACAaQgh9IRogAkEIaiECIAFBCGohAQsgGkIEWgRAIAIgASgAADYAACAaQgR9IRogAkEEaiECIAFBBGohAQsgGkICWgRAIAIgAS8AADsAACAaQgJ9IRogAkECaiECIAFBAmohAQsgHCAbfSEcIBpQRQRAIAIgAS0AADoAACACQQFqIQIgAUEBaiEBCyAcUEUNAAsMAQsCQAJAIBYEQAJAIAQgBUkEQCAHKAKYRyAESw0BCyABIARrIQMCQEEAIARrIgVBf0ogDyABa6wiGiAbIBogG1QbIhqnIgIgBUpxDQAgBUEBSCACIANqIAFLcQ0AIAEgAyACEAcgAmohAgwFCyABIAMgBCAEQR91IgFqIAFzIgEQByABaiECIBogAa0iHn0iHFANBCABIANqIQEDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIBpCIH0hGiABQSBqIQEgAkEgaiECIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCACIAEpADg3ADggAiABKQAwNwAwIAIgASkAKDcAKCACIAEpACA3ACAgAiABKQBYNwBYIAIgASkAUDcAUCACIAEpAEg3AEggAiABKQBANwBAIAIgASkAYDcAYCACIAEpAGg3AGggAiABKQBwNwBwIAIgASkAeDcAeCABQYABaiEBIAJBgAFqIQIgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAIgASkAADcAACACIAEpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCACIAEpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCACIAEoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCACIAEvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCACIAEtAAA6AAAgAkEBaiECIAFBAWohAQsgHFBFDQALDAQLIBAgAWsiCUEBaiIGIAUgBSAGSxshAyABIARrIQIgAUEHcUUNAiADRQ0CIAEgAi0AADoAACACQQFqIQIgAUEBaiIGQQdxQQAgA0EBayIFGw0BIAYhASAFIQMgCSEGDAILAkAgBCAFSQRAIAcoAphHIARLDQELIAEgASAEayIGKQAANwAAIAEgBUEBa0EHcUEBaiIDaiECIAUgA2siBEUNAyADIAZqIQEDQCACIAEpAAA3AAAgAUEIaiEBIAJBCGohAiAEQQhrIgQNAAsMAwsgASAEIAUQPyECDAILIAEgAi0AADoAASAJQQFrIQYgA0ECayEFIAJBAWohAgJAIAFBAmoiCkEHcUUNACAFRQ0AIAEgAi0AADoAAiAJQQJrIQYgA0EDayEFIAJBAWohAgJAIAFBA2oiCkEHcUUNACAFRQ0AIAEgAi0AADoAAyAJQQNrIQYgA0EEayEFIAJBAWohAgJAIAFBBGoiCkEHcUUNACAFRQ0AIAEgAi0AADoABCAJQQRrIQYgA0EFayEFIAJBAWohAgJAIAFBBWoiCkEHcUUNACAFRQ0AIAEgAi0AADoABSAJQQVrIQYgA0EGayEFIAJBAWohAgJAIAFBBmoiCkEHcUUNACAFRQ0AIAEgAi0AADoABiAJQQZrIQYgA0EHayEFIAJBAWohAgJAIAFBB2oiCkEHcUUNACAFRQ0AIAEgAi0AADoAByAJQQdrIQYgA0EIayEDIAFBCGohASACQQFqIQIMBgsgCiEBIAUhAwwFCyAKIQEgBSEDDAQLIAohASAFIQMMAwsgCiEBIAUhAwwCCyAKIQEgBSEDDAELIAohASAFIQMLAkACQCAGQRdNBEAgA0UNASADQQFrIQUgA0EHcSIEBEADQCABIAItAAA6AAAgA0EBayEDIAFBAWohASACQQFqIQIgBEEBayIEDQALCyAFQQdJDQEDQCABIAItAAA6AAAgASACLQABOgABIAEgAi0AAjoAAiABIAItAAM6AAMgASACLQAEOgAEIAEgAi0ABToABSABIAItAAY6AAYgASACLQAHOgAHIAFBCGohASACQQhqIQIgA0EIayIDDQALDAELIAMNAQsgASECDAELIAEgBCADED8hAgsgCyEFDAELIAEgAy0AAjoAACABQQFqIQILIAUgFE8NACACIBNJDQELCyAAIAI2AgwgACAFIAhBA3ZrIgE2AgAgACATIAJrQYMCajYCECAAIBQgAWtBDmo2AgQgByAIQQdxIgA2AogBIAcgHUJ/IACthkJ/hYM+AoQBC+cFAQR/IAMgAiACIANLGyEEIAAgAWshAgJAIABBB3FFDQAgBEUNACAAIAItAAA6AAAgA0EBayEGIAJBAWohAiAAQQFqIgdBB3FBACAEQQFrIgUbRQRAIAchACAFIQQgBiEDDAELIAAgAi0AADoAASADQQJrIQYgBEECayEFIAJBAWohAgJAIABBAmoiB0EHcUUNACAFRQ0AIAAgAi0AADoAAiADQQNrIQYgBEEDayEFIAJBAWohAgJAIABBA2oiB0EHcUUNACAFRQ0AIAAgAi0AADoAAyADQQRrIQYgBEEEayEFIAJBAWohAgJAIABBBGoiB0EHcUUNACAFRQ0AIAAgAi0AADoABCADQQVrIQYgBEEFayEFIAJBAWohAgJAIABBBWoiB0EHcUUNACAFRQ0AIAAgAi0AADoABSADQQZrIQYgBEEGayEFIAJBAWohAgJAIABBBmoiB0EHcUUNACAFRQ0AIAAgAi0AADoABiADQQdrIQYgBEEHayEFIAJBAWohAgJAIABBB2oiB0EHcUUNACAFRQ0AIAAgAi0AADoAByADQQhrIQMgBEEIayEEIABBCGohACACQQFqIQIMBgsgByEAIAUhBCAGIQMMBQsgByEAIAUhBCAGIQMMBAsgByEAIAUhBCAGIQMMAwsgByEAIAUhBCAGIQMMAgsgByEAIAUhBCAGIQMMAQsgByEAIAUhBCAGIQMLAkAgA0EXTQRAIARFDQEgBEEBayEBIARBB3EiAwRAA0AgACACLQAAOgAAIARBAWshBCAAQQFqIQAgAkEBaiECIANBAWsiAw0ACwsgAUEHSQ0BA0AgACACLQAAOgAAIAAgAi0AAToAASAAIAItAAI6AAIgACACLQADOgADIAAgAi0ABDoABCAAIAItAAU6AAUgACACLQAGOgAGIAAgAi0ABzoAByAAQQhqIQAgAkEIaiECIARBCGsiBA0ACwwBCyAERQ0AIAAgASAEED8hAAsgAAvyCAEXfyAAKAJoIgwgACgCMEGGAmsiBWtBACAFIAxJGyENIAAoAnQhAiAAKAKQASEPIAAoAkgiDiAMaiIJIAAoAnAiBUECIAUbIgVBAWsiBmoiAy0AASESIAMtAAAhEyAGIA5qIQZBAyEDIAAoApQBIRYgACgCPCEUIAAoAkwhECAAKAI4IRECQAJ/IAVBA0kEQCANIQggDgwBCyAAIABBACAJLQABIAAoAnwRAAAgCS0AAiAAKAJ8EQAAIQoDQCAAIAogAyAJai0AACAAKAJ8EQAAIQogACgCUCAKQQF0ai8BACIIIAEgCCABQf//A3FJIggbIQEgA0ECayAHIAgbIQcgA0EBaiIDIAVNDQALIAFB//8DcSAHIA1qIghB//8DcU0NASAGIAdB//8DcSIDayEGIA4gA2sLIQMCQAJAIAwgAUH//wNxTQ0AIAIgAkECdiAFIA9JGyEKIA1B//8DcSEVIAlBAmohDyAJQQRrIRcDQAJAAkAgBiABQf//A3EiC2otAAAgE0cNACAGIAtBAWoiAWotAAAgEkcNACADIAtqIgItAAAgCS0AAEcNACABIANqLQAAIAktAAFGDQELIApBAWsiCkUNAiAQIAsgEXFBAXRqLwEAIgEgCEH//wNxSw0BDAILIAJBAmohAUEAIQQgDyECAkADQCACLQAAIAEtAABHDQEgAi0AASABLQABRwRAIARBAXIhBAwCCyACLQACIAEtAAJHBEAgBEECciEEDAILIAItAAMgAS0AA0cEQCAEQQNyIQQMAgsgAi0ABCABLQAERwRAIARBBHIhBAwCCyACLQAFIAEtAAVHBEAgBEEFciEEDAILIAItAAYgAS0ABkcEQCAEQQZyIQQMAgsgAi0AByABLQAHRwRAIARBB3IhBAwCCyABQQhqIQEgAkEIaiECIARB+AFJIRggBEEIaiEEIBgNAAtBgAIhBAsCQAJAIAUgBEECaiICSQRAIAAgCyAHQf//A3FrIgY2AmwgAiAUSwRAIBQPCyACIBZPBEAgAg8LIAkgBEEBaiIFaiIBLQABIRIgAS0AACETAkAgAkEESQ0AIAIgBmogDE8NACAGQf//A3EhCCAEQQFrIQtBACEDQQAhBwNAIBAgAyAIaiARcUEBdGovAQAiASAGQf//A3FJBEAgAyAVaiABTw0IIAMhByABIQYLIANBAWoiAyALTQ0ACyAAIAAgAEEAIAIgF2oiAS0AACAAKAJ8EQAAIAEtAAEgACgCfBEAACABLQACIAAoAnwRAAAhASAAKAJQIAFBAXRqLwEAIgEgBkH//wNxTwRAIAdB//8DcSEDIAYhAQwDCyAEQQJrIgdB//8DcSIDIBVqIAFPDQYMAgsgAyAFaiEGIAIhBQsgCkEBayIKRQ0DIBAgCyARcUEBdGovAQAiASAIQf//A3FNDQMMAQsgByANaiEIIA4gA2siAyAFaiEGIAIhBQsgDCABQf//A3FLDQALCyAFDwsgAiEFCyAFIAAoAjwiACAAIAVLGwuGBQETfyAAKAJ0IgMgA0ECdiAAKAJwIgNBAiADGyIDIAAoApABSRshByAAKAJoIgogACgCMEGGAmsiBWtB//8DcUEAIAUgCkkbIQwgACgCSCIIIApqIgkgA0EBayICaiIFLQABIQ0gBS0AACEOIAlBAmohBSACIAhqIQsgACgClAEhEiAAKAI8IQ8gACgCTCEQIAAoAjghESAAKAKIAUEFSCETA0ACQCAKIAFB//8DcU0NAANAAkACQCALIAFB//8DcSIGai0AACAORw0AIAsgBkEBaiIBai0AACANRw0AIAYgCGoiAi0AACAJLQAARw0AIAEgCGotAAAgCS0AAUYNAQsgB0EBayIHRQ0CIAwgECAGIBFxQQF0ai8BACIBSQ0BDAILCyACQQJqIQRBACECIAUhAQJAA0AgAS0AACAELQAARw0BIAEtAAEgBC0AAUcEQCACQQFyIQIMAgsgAS0AAiAELQACRwRAIAJBAnIhAgwCCyABLQADIAQtAANHBEAgAkEDciECDAILIAEtAAQgBC0ABEcEQCACQQRyIQIMAgsgAS0ABSAELQAFRwRAIAJBBXIhAgwCCyABLQAGIAQtAAZHBEAgAkEGciECDAILIAEtAAcgBC0AB0cEQCACQQdyIQIMAgsgBEEIaiEEIAFBCGohASACQfgBSSEUIAJBCGohAiAUDQALQYACIQILAkAgAyACQQJqIgFJBEAgACAGNgJsIAEgD0sEQCAPDwsgASASTwRAIAEPCyAIIAJBAWoiA2ohCyADIAlqIgMtAAEhDSADLQAAIQ4gASEDDAELIBMNAQsgB0EBayIHRQ0AIAwgECAGIBFxQQF0ai8BACIBSQ0BCwsgAwvLAQECfwJAA0AgAC0AACABLQAARw0BIAAtAAEgAS0AAUcEQCACQQFyDwsgAC0AAiABLQACRwRAIAJBAnIPCyAALQADIAEtAANHBEAgAkEDcg8LIAAtAAQgAS0ABEcEQCACQQRyDwsgAC0ABSABLQAFRwRAIAJBBXIPCyAALQAGIAEtAAZHBEAgAkEGcg8LIAAtAAcgAS0AB0cEQCACQQdyDwsgAUEIaiEBIABBCGohACACQfgBSSEDIAJBCGohAiADDQALQYACIQILIAIL5wwBB38gAEF/cyEAIAJBF08EQAJAIAFBA3FFDQAgAS0AACAAQf8BcXNBAnRB0BhqKAIAIABBCHZzIQAgAkEBayIEQQAgAUEBaiIDQQNxG0UEQCAEIQIgAyEBDAELIAEtAAEgAEH/AXFzQQJ0QdAYaigCACAAQQh2cyEAIAFBAmohAwJAIAJBAmsiBEUNACADQQNxRQ0AIAEtAAIgAEH/AXFzQQJ0QdAYaigCACAAQQh2cyEAIAFBA2ohAwJAIAJBA2siBEUNACADQQNxRQ0AIAEtAAMgAEH/AXFzQQJ0QdAYaigCACAAQQh2cyEAIAFBBGohASACQQRrIQIMAgsgBCECIAMhAQwBCyAEIQIgAyEBCyACQRRuIgNBbGwhCQJAIANBAWsiCEUEQEEAIQQMAQsgA0EUbCABakEUayEDQQAhBANAIAEoAhAgB3MiB0EWdkH8B3FB0DhqKAIAIAdBDnZB/AdxQdAwaigCACAHQQZ2QfwHcUHQKGooAgAgB0H/AXFBAnRB0CBqKAIAc3NzIQcgASgCDCAGcyIGQRZ2QfwHcUHQOGooAgAgBkEOdkH8B3FB0DBqKAIAIAZBBnZB/AdxQdAoaigCACAGQf8BcUECdEHQIGooAgBzc3MhBiABKAIIIAVzIgVBFnZB/AdxQdA4aigCACAFQQ52QfwHcUHQMGooAgAgBUEGdkH8B3FB0ChqKAIAIAVB/wFxQQJ0QdAgaigCAHNzcyEFIAEoAgQgBHMiBEEWdkH8B3FB0DhqKAIAIARBDnZB/AdxQdAwaigCACAEQQZ2QfwHcUHQKGooAgAgBEH/AXFBAnRB0CBqKAIAc3NzIQQgASgCACAAcyIAQRZ2QfwHcUHQOGooAgAgAEEOdkH8B3FB0DBqKAIAIABBBnZB/AdxQdAoaigCACAAQf8BcUECdEHQIGooAgBzc3MhACABQRRqIQEgCEEBayIIDQALIAMhAQsgAiAJaiECIAEoAhAgASgCDCABKAIIIAEoAgQgASgCACAAcyIAQQh2IABB/wFxQQJ0QdAYaigCAHMiAEEIdiAAQf8BcUECdEHQGGooAgBzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyIAQf8BcUECdEHQGGooAgAgBHNzIABBCHZzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyIAQQh2IABB/wFxQQJ0QdAYaigCAHMiAEEIdiAAQf8BcUECdEHQGGooAgBzIgBB/wFxQQJ0QdAYaigCACAFc3MgAEEIdnMiAEEIdiAAQf8BcUECdEHQGGooAgBzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyIAQQh2IABB/wFxQQJ0QdAYaigCAHMiAEH/AXFBAnRB0BhqKAIAIAZzcyAAQQh2cyIAQQh2IABB/wFxQQJ0QdAYaigCAHMiAEEIdiAAQf8BcUECdEHQGGooAgBzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyIAQf8BcUECdEHQGGooAgAgB3NzIABBCHZzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyIAQQh2IABB/wFxQQJ0QdAYaigCAHMiAEEIdiAAQf8BcUECdEHQGGooAgBzIgBBCHYgAEH/AXFBAnRB0BhqKAIAcyEAIAFBFGohAQsgAkEHSwRAA0AgAS0AByABLQAGIAEtAAUgAS0ABCABLQADIAEtAAIgAS0AASABLQAAIABB/wFxc0ECdEHQGGooAgAgAEEIdnMiAEH/AXFzQQJ0QdAYaigCACAAQQh2cyIAQf8BcXNBAnRB0BhqKAIAIABBCHZzIgBB/wFxc0ECdEHQGGooAgAgAEEIdnMiAEH/AXFzQQJ0QdAYaigCACAAQQh2cyIAQf8BcXNBAnRB0BhqKAIAIABBCHZzIgBB/wFxc0ECdEHQGGooAgAgAEEIdnMiAEH/AXFzQQJ0QdAYaigCACAAQQh2cyEAIAFBCGohASACQQhrIgJBB0sNAAsLAkAgAkUNACACQQFxBH8gAS0AACAAQf8BcXNBAnRB0BhqKAIAIABBCHZzIQAgAUEBaiEBIAJBAWsFIAILIQMgAkEBRg0AA0AgAS0AASABLQAAIABB/wFxc0ECdEHQGGooAgAgAEEIdnMiAEH/AXFzQQJ0QdAYaigCACAAQQh2cyEAIAFBAmohASADQQJrIgMNAAsLIABBf3MLwgIBA38jAEEQayIIJAACfwJAIAAEQCAEDQEgBVANAQsgBgRAIAZBADYCBCAGQRI2AgALQQAMAQtBgAEQCSIHRQRAIAYEQCAGQQA2AgQgBkEONgIAC0EADAELIAcgATcDCCAHQgA3AwAgB0EoaiIJECogByAFNwMYIAcgBDYCECAHIAM6AGAgB0EANgJsIAdCADcCZCAAKQMYIQEgCEF/NgIIIAhCjoCAgPAANwMAIAdBECAIECQgAUL/gQGDhCIBNwNwIAcgAadBBnZBAXE6AHgCQCACRQ0AIAkgAhBgQX9KDQAgBxAGQQAMAQsgBhBfIgIEQCAAIAAoAjBBAWo2AjAgAiAHNgIIIAJBATYCBCACIAA2AgAgAkI/IAAgB0EAQgBBDkEBEQoAIgEgAUIAUxs3AxgLIAILIQAgCEEQaiQAIAALYgEBf0E4EAkiAUUEQCAABEAgAEEANgIEIABBDjYCAAtBAA8LIAFBADYCCCABQgA3AwAgAUIANwMgIAFCgICAgBA3AiwgAUEAOgAoIAFBADYCFCABQgA3AgwgAUEAOwE0IAELuwEBAX4gASkDACICQgKDUEUEQCAAIAEpAxA3AxALIAJCBINQRQRAIAAgASkDGDcDGAsgAkIIg1BFBEAgACABKQMgNwMgCyACQhCDUEUEQCAAIAEoAig2AigLIAJCIINQRQRAIAAgASgCLDYCLAsgAkLAAINQRQRAIAAgAS8BMDsBMAsgAkKAAYNQRQRAIAAgAS8BMjsBMgsgAkKAAoNQRQRAIAAgASgCNDYCNAsgACAAKQMAIAKENwMAQQALGQAgAUUEQEEADwsgACABKAIAIAEzAQQQGws3AQJ/IABBACABG0UEQCAAIAFGDwsgAC8BBCIDIAEvAQRGBH8gACgCACABKAIAIAMQPQVBAQtFCyIBAX8gAUUEQEEADwsgARAJIgJFBEBBAA8LIAIgACABEAcLKQAgACABIAIgAyAEEEUiAEUEQEEADwsgACACQQAgBBA1IQEgABAGIAELcQEBfgJ/AkAgAkJ/VwRAIAMEQCADQQA2AgQgA0EUNgIACwwBCyAAIAEgAhARIgRCf1cEQCADBEAgAyAAKAIMNgIAIAMgACgCEDYCBAsMAQtBACACIARXDQEaIAMEQCADQQA2AgQgA0ERNgIACwtBfwsLNQAgACABIAJBABAmIgBFBEBBfw8LIAMEQCADIAAtAAk6AAALIAQEQCAEIAAoAkQ2AgALQQAL/AECAn8BfiMAQRBrIgMkAAJAIAAgA0EOaiABQYAGQQAQRiIARQRAIAIhAAwBCyADLwEOIgFBBUkEQCACIQAMAQsgAC0AAEEBRwRAIAIhAAwBCyAAIAGtQv//A4MQFyIBRQRAIAIhAAwBCyABEH0aAkAgARAVIAIEfwJ/IAIvAQQhAEEAIAIoAgAiBEUNABpBACAEIABB1IABKAIAEQAACwVBAAtHBEAgAiEADAELIAEgAS0AAAR+IAEpAwggASkDEH0FQgALIgVC//8DgxATIAWnQf//A3FBgBBBABA1IgBFBEAgAiEADAELIAIQEAsgARAICyADQRBqJAAgAAvmDwIIfwJ+IwBB4ABrIgckAEEeQS4gAxshCwJAAkAgAgRAIAIiBSIGLQAABH4gBikDCCAGKQMQfQVCAAsgC61aDQEgBARAIARBADYCBCAEQRM2AgALQn8hDQwCCyABIAutIAcgBBAtIgUNAEJ/IQ0MAQsgBUIEEBMoAABBoxJBqBIgAxsoAABHBEAgBARAIARBADYCBCAEQRM2AgALQn8hDSACDQEgBRAIDAELIABCADcDICAAQQA2AhggAEL/////DzcDECAAQQA7AQwgAEG/hig2AgggAEEBOgAGIABBADsBBCAAQQA2AgAgAEIANwNIIABBgIDYjXg2AkQgAEIANwMoIABCADcDMCAAQgA3AzggAEFAa0EAOwEAIABCADcDUCAAIAMEf0EABSAFEAwLOwEIIAAgBRAMOwEKIAAgBRAMOwEMIAAgBRAMNgIQIAUQDCEGIAUQDCEJIAdBADYCWCAHQgA3A1AgB0IANwNIIAcgCUEfcTYCPCAHIAZBC3Y2AjggByAGQQV2QT9xNgI0IAcgBkEBdEE+cTYCMCAHIAlBCXZB0ABqNgJEIAcgCUEFdkEPcUEBazYCQCAAIAdBMGoQBTYCFCAAIAUQFTYCGCAAIAUQFa03AyAgACAFEBWtNwMoIAUQDCEIIAUQDCEGIAACfiADBEBBACEJIABBADYCRCAAQQA7AUAgAEEANgI8QgAMAQsgBRAMIQkgACAFEAw2AjwgACAFEAw7AUAgACAFEBU2AkQgBRAVrQs3A0ggBS0AAEUEQCAEBEAgBEEANgIEIARBFDYCAAtCfyENIAINASAFEAgMAQsCQCAALwEMIgpBAXEEQCAKQcAAcQRAIABB//8DOwFSDAILIABBATsBUgwBCyAAQQA7AVILIABBADYCOCAAQgA3AzAgBiAIaiAJaiEKAkAgAgRAIAUtAAAEfiAFKQMIIAUpAxB9BUIACyAKrVoNASAEBEAgBEEANgIEIARBFTYCAAtCfyENDAILIAUQCCABIAqtQQAgBBAtIgUNAEJ/IQ0MAQsCQCAIRQ0AIAAgBSABIAhBASAEEGQiCDYCMCAIRQRAIAQoAgBBEUYEQCAEBEAgBEEANgIEIARBFTYCAAsLQn8hDSACDQIgBRAIDAILIAAtAA1BCHFFDQAgCEECECNBBUcNACAEBEAgBEEANgIEIARBFTYCAAtCfyENIAINASAFEAgMAQsgAEE0aiEIAkAgBkUNACAFIAEgBkEAIAQQRSIMRQRAQn8hDSACDQIgBRAIDAILIAwgBkGAAkGABCADGyAIIAQQbiEGIAwQBiAGRQRAQn8hDSACDQIgBRAIDAILIANFDQAgAEEBOgAECwJAIAlFDQAgACAFIAEgCUEAIAQQZCIBNgI4IAFFBEBCfyENIAINAiAFEAgMAgsgAC0ADUEIcUUNACABQQIQI0EFRw0AIAQEQCAEQQA2AgQgBEEVNgIAC0J/IQ0gAg0BIAUQCAwBCyAAIAAoAjRB9eABIAAoAjAQZzYCMCAAIAAoAjRB9cYBIAAoAjgQZzYCOAJAAkAgACkDKEL/////D1ENACAAKQMgQv////8PUQ0AIAApA0hC/////w9SDQELAkACQAJAIAgoAgAgB0EwakEBQYACQYAEIAMbIAQQRiIBRQRAIAJFDQEMAgsgASAHMwEwEBciAUUEQCAEBEAgBEEANgIEIARBDjYCAAsgAkUNAQwCCwJAIAApAyhC/////w9RBEAgACABEB03AygMAQsgA0UNAEEAIQYCQCABKQMQIg5CCHwiDSAOVA0AIAEpAwggDVQNACABIA03AxBBASEGCyABIAY6AAALIAApAyBC/////w9RBEAgACABEB03AyALAkAgAw0AIAApA0hC/////w9RBEAgACABEB03A0gLIAAoAjxB//8DRw0AIAAgARAVNgI8CyABLQAABH8gASkDECABKQMIUQVBAAsNAiAEBEAgBEEANgIEIARBFTYCAAsgARAIIAINAQsgBRAIC0J/IQ0MAgsgARAICyAFLQAARQRAIAQEQCAEQQA2AgQgBEEUNgIAC0J/IQ0gAg0BIAUQCAwBCyACRQRAIAUQCAtCfyENIAApA0hCf1cEQCAEBEAgBEEWNgIEIARBBDYCAAsMAQsjAEEQayIDJABBASEBAkAgACgCEEHjAEcNAEEAIQECQCAAKAI0IANBDmpBgbICQYAGQQAQRiICBEAgAy8BDiIFQQZLDQELIAQEQCAEQQA2AgQgBEEVNgIACwwBCyACIAWtQv//A4MQFyICRQRAIAQEQCAEQQA2AgQgBEEUNgIACwwBC0EBIQECQAJAAkAgAhAMQQFrDgICAQALQQAhASAEBEAgBEEANgIEIARBGDYCAAsgAhAIDAILIAApAyhCE1YhAQsgAkICEBMvAABBwYoBRwRAQQAhASAEBEAgBEEANgIEIARBGDYCAAsgAhAIDAELIAIQfUEBayIFQf8BcUEDTwRAQQAhASAEBEAgBEEANgIEIARBGDYCAAsgAhAIDAELIAMvAQ5BB0cEQEEAIQEgBARAIARBADYCBCAEQRU2AgALIAIQCAwBCyAAIAE6AAYgACAFQf8BcUGBAmo7AVIgACACEAw2AhAgAhAIQQEhAQsgA0EQaiQAIAFFDQAgCCAIKAIAEG02AgAgCiALaq0hDQsgB0HgAGokACANC4ECAQR/IwBBEGsiBCQAAkAgASAEQQxqQcAAQQAQJSIGRQ0AIAQoAgxBBWoiA0GAgARPBEAgAgRAIAJBADYCBCACQRI2AgALDAELQQAgA60QFyIDRQRAIAIEQCACQQA2AgQgAkEONgIACwwBCyADQQEQcCADIAEEfwJ/IAEvAQQhBUEAIAEoAgAiAUUNABpBACABIAVB1IABKAIAEQAACwVBAAsQEiADIAYgBCgCDBAsAn8gAy0AAEUEQCACBEAgAkEANgIEIAJBFDYCAAtBAAwBCyAAIAMtAAAEfiADKQMQBUIAC6dB//8DcSADKAIEEEcLIQUgAxAICyAEQRBqJAAgBQvgAQICfwF+QTAQCSICRQRAIAEEQCABQQA2AgQgAUEONgIAC0EADwsgAkIANwMIIAJBADYCACACQgA3AxAgAkIANwMYIAJCADcDICACQgA3ACUgAFAEQCACDwsCQCAAQv////8AVg0AIACnQQR0EAkiA0UNACACIAM2AgBBACEBQgEhBANAIAMgAUEEdGoiAUIANwIAIAFCADcABSAAIARSBEAgBKchASAEQgF8IQQMAQsLIAIgADcDCCACIAA3AxAgAg8LIAEEQCABQQA2AgQgAUEONgIAC0EAEBAgAhAGQQAL7gECA38BfiMAQRBrIgQkAAJAIARBDGpCBBAXIgNFBEBBfyECDAELAkAgAQRAIAJBgAZxIQUDQAJAIAUgASgCBHFFDQACQCADKQMIQgBUBEAgA0EAOgAADAELIANCADcDECADQQE6AAALIAMgAS8BCBANIAMgAS8BChANIAMtAABFBEAgAEEIaiIABEAgAEEANgIEIABBFDYCAAtBfyECDAQLQX8hAiAAIARBDGpCBBAbQQBIDQMgATMBCiIGUA0AIAAgASgCDCAGEBtBAEgNAwsgASgCACIBDQALC0EAIQILIAMQCAsgBEEQaiQAIAILPAEBfyAABEAgAUGABnEhAQNAIAEgACgCBHEEQCACIAAvAQpqQQRqIQILIAAoAgAiAA0ACwsgAkH//wNxC5wBAQN/IABFBEBBAA8LIAAhAwNAAn8CQAJAIAAvAQgiAUH04AFNBEAgAUEBRg0BIAFB9cYBRg0BDAILIAFBgbICRg0AIAFB9eABRw0BCyAAKAIAIQEgAEEANgIAIAAoAgwQBiAAEAYgASADIAAgA0YbIQMCQCACRQRAQQAhAgwBCyACIAE2AgALIAEMAQsgACICKAIACyIADQALIAMLsgQCBX8BfgJAAkACQCAAIAGtEBciAQRAIAEtAAANAUEAIQAMAgsgBARAIARBADYCBCAEQQ42AgALQQAPC0EAIQADQCABLQAABH4gASkDCCABKQMQfQVCAAtCBFQNASABEAwhByABIAEQDCIGrRATIghFBEBBACECIAQEQCAEQQA2AgQgBEEVNgIACyABEAggAEUNAwNAIAAoAgAhASAAKAIMEAYgABAGIAEiAA0ACwwDCwJAAkBBEBAJIgUEQCAFIAY7AQogBSAHOwEIIAUgAjYCBCAFQQA2AgAgBkUNASAFIAggBhBjIgY2AgwgBg0CIAUQBgtBACECIAQEQCAEQQA2AgQgBEEONgIACyABEAggAEUNBANAIAAoAgAhASAAKAIMEAYgABAGIAEiAA0ACwwECyAFQQA2AgwLAkAgAEUEQCAFIQAMAQsgCSAFNgIACyAFIQkgAS0AAA0ACwsCQCABLQAABH8gASkDECABKQMIUQVBAAsNACABIAEtAAAEfiABKQMIIAEpAxB9BUIACyIKQv////8PgxATIQICQCAKpyIFQQNLDQAgAkUNACACQcEUIAUQPUUNAQtBACECIAQEQCAEQQA2AgQgBEEVNgIACyABEAggAEUNAQNAIAAoAgAhASAAKAIMEAYgABAGIAEiAA0ACwwBCyABEAggAwRAIAMgADYCAEEBDwtBASECIABFDQADQCAAKAIAIQEgACgCDBAGIAAQBiABIgANAAsLIAILvgEBBX8gAAR/IAAhAgNAIAIiBCgCACICDQALIAEEQANAIAEiAy8BCCEGIAMoAgAhASAAIQICQAJAA0ACQCACLwEIIAZHDQAgAi8BCiIFIAMvAQpHDQAgBUUNAiACKAIMIAMoAgwgBRA9RQ0CCyACKAIAIgINAAsgA0EANgIAIAQgAzYCACADIQQMAQsgAiACKAIEIAMoAgRBgAZxcjYCBCADQQA2AgAgAygCDBAGIAMQBgsgAQ0ACwsgAAUgAQsLVQICfgF/AkACQCAALQAARQ0AIAApAxAiAkIBfCIDIAJUDQAgAyAAKQMIWA0BCyAAQQA6AAAPCyAAKAIEIgRFBEAPCyAAIAM3AxAgBCACp2ogAToAAAt9AQN/IwBBEGsiAiQAIAIgATYCDEF/IQMCQCAALQAoDQACQCAAKAIAIgRFDQAgBCABEHFBf0oNACAAKAIAIQEgAEEMaiIABEAgACABKAIMNgIAIAAgASgCEDYCBAsMAQsgACACQQxqQgRBExAOQj+HpyEDCyACQRBqJAAgAwvdAQEDfyABIAApAzBaBEAgAEEIagRAIABBADYCDCAAQRI2AggLQX8PCyAAQQhqIQIgAC0AGEECcQRAIAIEQCACQQA2AgQgAkEZNgIAC0F/DwtBfyEDAkAgACABQQAgAhBTIgRFDQAgACgCUCAEIAIQfkUNAAJ/IAEgACkDMFoEQCAAQQhqBEAgAEEANgIMIABBEjYCCAtBfwwBCyABp0EEdCICIAAoAkBqKAIEECAgACgCQCACaiICQQA2AgQgAhBAQQALDQAgACgCQCABp0EEdGpBAToADEEAIQMLIAMLpgIBBX9BfyEFAkAgACABQQBBABAmRQ0AIAAtABhBAnEEQCAAQQhqIgAEQCAAQQA2AgQgAEEZNgIAC0F/DwsCfyAAKAJAIgQgAaciBkEEdGooAgAiBUUEQCADQYCA2I14RyEHQQMMAQsgBSgCRCADRyEHIAUtAAkLIQggBCAGQQR0aiIEIQYgBCgCBCEEQQAgAiAIRiAHG0UEQAJAIAQNACAGIAUQKyIENgIEIAQNACAAQQhqIgAEQCAAQQA2AgQgAEEONgIAC0F/DwsgBCADNgJEIAQgAjoACSAEIAQoAgBBEHI2AgBBAA8LQQAhBSAERQ0AIAQgBCgCAEFvcSIANgIAIABFBEAgBBAgIAZBADYCBEEADwsgBCADNgJEIAQgCDoACQsgBQvjCAIFfwR+IAAtABhBAnEEQCAAQQhqBEAgAEEANgIMIABBGTYCCAtCfw8LIAApAzAhCwJAIANBgMAAcQRAIAAgASADQQAQTCIJQn9SDQELAn4CQAJAIAApAzAiCUIBfCIMIAApAzgiClQEQCAAKAJAIQQMAQsgCkIBhiIJQoAIIAlCgAhUGyIJQhAgCUIQVhsgCnwiCadBBHQiBK0gCkIEhkLw////D4NUDQEgACgCQCAEEDQiBEUNASAAIAk3AzggACAENgJAIAApAzAiCUIBfCEMCyAAIAw3AzAgBCAJp0EEdGoiBEIANwIAIARCADcABSAJDAELIABBCGoEQCAAQQA2AgwgAEEONgIIC0J/CyIJQgBZDQBCfw8LAkAgAUUNAAJ/QQAhBCAJIAApAzBaBEAgAEEIagRAIABBADYCDCAAQRI2AggLQX8MAQsgAC0AGEECcQRAIABBCGoEQCAAQQA2AgwgAEEZNgIIC0F/DAELAkAgAUUNACABLQAARQ0AQX8gASABECJB//8DcSADIABBCGoQNSIERQ0BGiADQYAwcQ0AIARBABAjQQNHDQAgBEECNgIICwJAIAAgAUEAQQAQTCIKQgBTIgENACAJIApRDQAgBBAQIABBCGoEQCAAQQA2AgwgAEEKNgIIC0F/DAELAkAgAUEBIAkgClEbRQ0AAkACfwJAIAAoAkAiASAJpyIFQQR0aiIGKAIAIgMEQCADKAIwIAQQYg0BCyAEIAYoAgQNARogBiAGKAIAECsiAzYCBCAEIAMNARogAEEIagRAIABBADYCDCAAQQ42AggLDAILQQEhByAGKAIAKAIwC0EAQQAgAEEIaiIDECUiCEUNAAJAAkAgASAFQQR0aiIFKAIEIgENACAGKAIAIgENAEEAIQEMAQsgASgCMCIBRQRAQQAhAQwBCyABQQBBACADECUiAUUNAQsgACgCUCAIIAlBACADEE1FDQAgAQRAIAAoAlAgAUEAEH4aCyAFKAIEIQMgBwRAIANFDQIgAy0AAEECcUUNAiADKAIwEBAgBSgCBCIBIAEoAgBBfXEiAzYCACADRQRAIAEQICAFQQA2AgQgBBAQQQAMBAsgASAGKAIAKAIwNgIwIAQQEEEADAMLIAMoAgAiAUECcQRAIAMoAjAQECAFKAIEIgMoAgAhAQsgAyAENgIwIAMgAUECcjYCAEEADAILIAQQEEF/DAELIAQQEEEAC0UNACALIAApAzBRBEBCfw8LIAAoAkAgCadBBHRqED4gACALNwMwQn8PCyAJpyIGQQR0IgEgACgCQGoQQAJAAkAgACgCQCIEIAFqIgMoAgAiBUUNAAJAIAMoAgQiAwRAIAMoAgAiAEEBcUUNAQwCCyAFECshAyAAKAJAIgQgBkEEdGogAzYCBCADRQ0CIAMoAgAhAAsgA0F+NgIQIAMgAEEBcjYCAAsgASAEaiACNgIIIAkPCyAAQQhqBEAgAEEANgIMIABBDjYCCAtCfwteAQF/IwBBEGsiAiQAAn8gACgCJEEBRwRAIABBDGoiAARAIABBADYCBCAAQRI2AgALQX8MAQsgAkEANgIIIAIgATcDACAAIAJCEEEMEA5CP4enCyEAIAJBEGokACAAC9oDAQZ/IwBBEGsiBSQAIAUgAjYCDCMAQaABayIEJAAgBEEIakHA8ABBkAEQBxogBCAANgI0IAQgADYCHCAEQX4gAGsiA0H/////ByADQf////8HSRsiBjYCOCAEIAAgBmoiADYCJCAEIAA2AhggBEEIaiEAIwBB0AFrIgMkACADIAI2AswBIANBoAFqQQBBKBAZIAMgAygCzAE2AsgBAkBBACABIANByAFqIANB0ABqIANBoAFqEEpBAEgNACAAKAJMQQBOIQcgACgCACECIAAsAEpBAEwEQCAAIAJBX3E2AgALIAJBIHEhCAJ/IAAoAjAEQCAAIAEgA0HIAWogA0HQAGogA0GgAWoQSgwBCyAAQdAANgIwIAAgA0HQAGo2AhAgACADNgIcIAAgAzYCFCAAKAIsIQIgACADNgIsIAAgASADQcgBaiADQdAAaiADQaABahBKIAJFDQAaIABBAEEAIAAoAiQRAAAaIABBADYCMCAAIAI2AiwgAEEANgIcIABBADYCECAAKAIUGiAAQQA2AhRBAAsaIAAgACgCACAIcjYCACAHRQ0ACyADQdABaiQAIAYEQCAEKAIcIgAgACAEKAIYRmtBADoAAAsgBEGgAWokACAFQRBqJAALUwEDfwJAIAAoAgAsAABBMGtBCk8NAANAIAAoAgAiAiwAACEDIAAgAkEBajYCACABIANqQTBrIQEgAiwAAUEwa0EKTw0BIAFBCmwhAQwACwALIAELuwIAAkAgAUEUSw0AAkACQAJAAkACQAJAAkACQAJAAkAgAUEJaw4KAAECAwQFBgcICQoLIAIgAigCACIBQQRqNgIAIAAgASgCADYCAA8LIAIgAigCACIBQQRqNgIAIAAgATQCADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATUCADcDAA8LIAIgAigCAEEHakF4cSIBQQhqNgIAIAAgASkDADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATIBADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATMBADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATAAADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATEAADcDAA8LIAIgAigCAEEHakF4cSIBQQhqNgIAIAAgASsDADkDAA8LIAAgAkEAEQcACwubAgAgAEUEQEEADwsCfwJAIAAEfyABQf8ATQ0BAkBB9IIBKAIAKAIARQRAIAFBgH9xQYC/A0YNAwwBCyABQf8PTQRAIAAgAUE/cUGAAXI6AAEgACABQQZ2QcABcjoAAEECDAQLIAFBgLADT0EAIAFBgEBxQYDAA0cbRQRAIAAgAUE/cUGAAXI6AAIgACABQQx2QeABcjoAACAAIAFBBnZBP3FBgAFyOgABQQMMBAsgAUGAgARrQf//P00EQCAAIAFBP3FBgAFyOgADIAAgAUESdkHwAXI6AAAgACABQQZ2QT9xQYABcjoAAiAAIAFBDHZBP3FBgAFyOgABQQQMBAsLQYSEAUEZNgIAQX8FQQELDAELIAAgAToAAEEBCwvjAQECfyACQQBHIQMCQAJAAkAgAEEDcUUNACACRQ0AIAFB/wFxIQQDQCAALQAAIARGDQIgAkEBayICQQBHIQMgAEEBaiIAQQNxRQ0BIAINAAsLIANFDQELAkAgAC0AACABQf8BcUYNACACQQRJDQAgAUH/AXFBgYKECGwhAwNAIAAoAgAgA3MiBEF/cyAEQYGChAhrcUGAgYKEeHENASAAQQRqIQAgAkEEayICQQNLDQALCyACRQ0AIAFB/wFxIQEDQCABIAAtAABGBEAgAA8LIABBAWohACACQQFrIgINAAsLQQALeQEBfAJAIABFDQAgACsDECAAKwMgIgIgAUQAAAAAAAAAACABRAAAAAAAAAAAZBsiAUQAAAAAAADwPyABRAAAAAAAAPA/YxsgACsDKCACoaKgIgEgACsDGKFjRQ0AIAAoAgAgASAAKAIMIAAoAgQRDgAgACABOQMYCwtIAQF8AkAgAEUNACAAKwMQIAArAyAiASAAKwMoIAGhoCIBIAArAxihY0UNACAAKAIAIAEgACgCDCAAKAIEEQ4AIAAgATkDGAsLWgICfgF/An8CQAJAIAAtAABFDQAgACkDECIBQgF8IgIgAVQNACACIAApAwhYDQELIABBADoAAEEADAELQQAgACgCBCIDRQ0AGiAAIAI3AxAgAyABp2otAAALC4IEAgZ/AX4gAEEAIAEbRQRAIAIEQCACQQA2AgQgAkESNgIAC0EADwsCQAJAIAApAwhQDQAgACgCECABLQAAIgQEf0Kl6wohCSABIQMDQCAJIAStQv8Bg3whCSADLQABIgQEQCADQQFqIQMgCUL/////D4NCIX4hCQwBCwsgCacFQYUqCyIEIAAoAgBwQQJ0aiIGKAIAIgNFDQADQAJAIAMoAhwgBEcNACABIAMoAgAQOA0AAkAgAykDCEJ/UQRAIAMoAhghAQJAIAUEQCAFIAE2AhgMAQsgBiABNgIACyADEAYgACAAKQMIQgF9Igk3AwggCbogACgCACIBuER7FK5H4XqEP6JjRQ0BIAFBgQJJDQECf0EAIQMgACgCACIGIAFBAXYiBUcEQCAFEDwiB0UEQCACBEAgAkEANgIEIAJBDjYCAAtBAAwCCwJAIAApAwhCACAGG1AEQCAAKAIQIQQMAQsgACgCECEEA0AgBCADQQJ0aigCACIBBEADQCABKAIYIQIgASAHIAEoAhwgBXBBAnRqIggoAgA2AhggCCABNgIAIAIiAQ0ACwsgA0EBaiIDIAZHDQALCyAEEAYgACAFNgIAIAAgBzYCEAtBAQsNAQwFCyADQn83AxALQQEPCyADIgUoAhgiAw0ACwsgAgRAIAJBADYCBCACQQk2AgALC0EAC6UGAgl/AX4jAEHwAGsiBSQAAkACQCAARQ0AAkAgAQRAIAEpAzAgAlYNAQtBACEDIABBCGoEQCAAQQA2AgwgAEESNgIICwwCCwJAIANBCHENACABKAJAIAKnQQR0aiIGKAIIRQRAIAYtAAxFDQELQQAhAyAAQQhqBEAgAEEANgIMIABBDzYCCAsMAgsgASACIANBCHIgBUE4ahCKAUF/TARAQQAhAyAAQQhqBEAgAEEANgIMIABBFDYCCAsMAgsgA0EDdkEEcSADciIGQQRxIQcgBSkDUCEOIAUvAWghCQJAIANBIHFFIAUvAWpBAEdxIgtFDQAgBA0AIAAoAhwiBA0AQQAhAyAAQQhqBEAgAEEANgIMIABBGjYCCAsMAgsgBSkDWFAEQCAAQQBCAEEAEFIhAwwCCwJAIAdFIgwgCUEAR3EiDUEBckUEQEEAIQMgBUEAOwEwIAUgDjcDICAFIA43AxggBSAFKAJgNgIoIAVC3AA3AwAgASgCACAOIAVBACABIAIgAEEIahBeIgYNAQwDC0EAIQMgASACIAYgAEEIaiIGECYiB0UNAiABKAIAIAUpA1ggBUE4aiAHLwEMQQF2QQNxIAEgAiAGEF4iBkUNAgsCfyAGIAE2AiwCQCABKAJEIghBAWoiCiABKAJIIgdJBEAgASgCTCEHDAELIAEoAkwgB0EKaiIIQQJ0EDQiB0UEQCABQQhqBEAgAUEANgIMIAFBDjYCCAtBfwwCCyABIAc2AkwgASAINgJIIAEoAkQiCEEBaiEKCyABIAo2AkQgByAIQQJ0aiAGNgIAQQALQX9MBEAgBhALDAELAkAgC0UEQCAGIQEMAQtBJkEAIAUvAWpBAUYbIgFFBEAgAEEIagRAIABBADYCDCAAQRg2AggLDAMLIAAgBiAFLwFqQQAgBCABEQYAIQEgBhALIAFFDQILAkAgDUUEQCABIQMMAQsgACABIAUvAWgQgQEhAyABEAsgA0UNAQsCQCAJRSAMckUEQCADIQEMAQsgACADQQEQgAEhASADEAsgAUUNAQsgASEDDAELQQAhAwsgBUHwAGokACADC4UBAQF/IAFFBEAgAEEIaiIABEAgAEEANgIEIABBEjYCAAtBAA8LQTgQCSIDRQRAIABBCGoiAARAIABBADYCBCAAQQ42AgALQQAPCyADQQA2AhAgA0IANwIIIANCADcDKCADQQA2AgQgAyACNgIAIANCADcDGCADQQA2AjAgACABQTsgAxBCCw8AIAAgASACQQBBABCCAQusAgECfyABRQRAIABBCGoiAARAIABBADYCBCAAQRI2AgALQQAPCwJAIAJBfUsNACACQf//A3FBCEYNACAAQQhqIgAEQCAAQQA2AgQgAEEQNgIAC0EADwsCQEGwwAAQCSIFBEAgBUEANgIIIAVCADcCACAFQYiBAUGogQEgAxs2AqhAIAUgAjYCFCAFIAM6ABAgBUEAOgAPIAVBADsBDCAFIAMgAkF9SyIGcToADiAFQQggAiAGG0H//wNxIAQgBUGIgQFBqIEBIAMbKAIAEQAAIgI2AqxAIAINASAFEDEgBRAGCyAAQQhqIgAEQCAAQQA2AgQgAEEONgIAC0EADwsgACABQTogBRBCIgAEfyAABSAFKAKsQCAFKAKoQCgCBBEDACAFEDEgBRAGQQALC6ABAQF/IAIgACgCBCIDIAIgA0kbIgIEQCAAIAMgAms2AgQCQAJAAkACQCAAKAIcIgMoAhRBAWsOAgEAAgsgA0GgAWogASAAKAIAIAJB3IABKAIAEQgADAILIAAgACgCMCABIAAoAgAgAkHEgAEoAgARBAA2AjAMAQsgASAAKAIAIAIQBxoLIAAgACgCACACajYCACAAIAAoAgggAmo2AggLC7cCAQR/QX4hAgJAIABFDQAgACgCIEUNACAAKAIkIgRFDQAgACgCHCIBRQ0AIAEoAgAgAEcNAAJAAkAgASgCICIDQTlrDjkBAgICAgICAgICAgIBAgICAQICAgICAgICAgICAgICAgICAQICAgICAgICAgICAQICAgICAgICAgEACyADQZoFRg0AIANBKkcN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DCABajYCDCAGIAYoAgggAWo2AgggBSAFKAIUIAFqNgIUIAUgBSgCECABazYCECAGIAYoAhAgAWsiATYCECABDQAgBiAGKAIENgIIC0EAIQRBACEJIAMoAhBFDQAMCgsgAygCHCgCJCEGIAMgAygCGCIBQQFqNgIYIAEgBmotAAAhASADIARBAWo2AhAgAygCBCAEaiABOgAAIAEEQCADKAIQIQQMAQsLIAMoAhwoAixFDQAgAygCECIGIAlNDQAgBQJ/IAUoAjAhBEEAIAMoAgQgCWoiAUUNABogBCABIAYgCWtB1IABKAIAEQAACzYCMAsgA0HnADYCIAsCQCADKAIcKAIsBEAgAygCDCADKAIQIgFBAmpJBH8gBRAKIAMoAhANAkEABSABCyADKAIEaiAFKAIwOwAAIAMgAygCEEECajYCECADQaABakHkgAEoAgARAQAaCyADQfEANgIgIAUQCiADKAIQRQ0BDAcLDAYLIAUoAgQNAQsgAygCPA0AIApFDQEgAygCIEGaBUYNAQsCfyADKAKIASIBRQRAIAMgChCFAQwBCwJAAkACQCADKAKMAUECaw4CAAECCwJ/AkADQAJAAkAgAygCPA0AIAMQLyADKAI8DQAgCg0BQQAMBAsgAygCSCADKAJoai0AACEEIAMgAygC8C0iAUEBajYC8C0gASADKALsLWpBADoAACADIAMoAvAtIgFBAWo2AvAtIAEgAygC7C1qQQA6AAAgAyADKALwLSIBQQFqNgLwLSABIAMoAuwtaiAEOgAAIAMgBEECdGoiASABLwHkAUEBajsB5AEgAyADKAI8QQFrNgI8IAMgAygCaEEBaiIBNgJoIAMoAvAtIAMoAvQtRw0BQQAhBCADIAMoAlgiBkEATgR/IAMoAkggBmoFQQALIAEgBmtBABAPIAMgAygCaDYCWCADKAIAEAogAygCACgCEA0BDAILCyADQQA2AoQuIApBBEYEQCADIAMoAlgiAUEATgR/IAMoAkggAWoFQQALIAMoAmggAWtBARAPIAMgAygCaDYCWCADKAIAEApBA0ECIAMoAgAoAhAbDAILIAMoAvAtBEBBACEEIAMgAygCWCIBQQBOBH8gAygCSCABagVBAAsgAygCaCABa0EAEA8gAyADKAJoNgJYIAMoAgAQCiADKAIAKAIQRQ0BC0EBIQQLIAQL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bGl0ZXJhbC9sZW5ndGhzIHNldABpbnZhbGlkIGNvZGUgbGVuZ3RocyBzZXQAdW5rbm93biBoZWFkZXIgZmxhZ3Mgc2V0AGludmFsaWQgZGlzdGFuY2VzIHNldABpbnZhbGlkIGJpdCBsZW5ndGggcmVwZWF0AEZpbGUgYWxyZWFkeSBleGlzdHMAdG9vIG1hbnkgbGVuZ3RoIG9yIGRpc3RhbmNlIHN5bWJvbHMAaW52YWxpZCBzdG9yZWQgYmxvY2sgbGVuZ3RocwAlcyVzJXMAYnVmZmVyIGVycm9yAE5vIGVycm9yAHN0cmVhbSBlcnJvcgBUZWxsIGVycm9yAEludGVybmFsIGVycm9yAFNlZWsgZXJyb3IAV3JpdGUgZXJyb3IAZmlsZSBlcnJvcgBSZWFkIGVycm9yAFpsaWIgZXJyb3IAZGF0YSBlcnJvcgBDUkMgZXJyb3IAaW5jb21wYXRpYmxlIHZlcnNpb24AaW52YWxpZCBjb2RlIC0tIG1pc3NpbmcgZW5kLW9mLWJsb2NrAGluY29ycmVjdCBoZWFkZXIgY2hlY2sAaW5jb3JyZWN0IGxlbmd0aCBjaGVjawBpbmNvcnJlY3QgZGF0YSBjaGVjawBpbnZhbGlkIGRpc3RhbmNlIHRvbyBmYXIgYmFjawBoZWFkZXIgY3JjIG1pc21hdGNoADEuMi4xMy56bGliLW5nAGludmFsaWQgd2luZG93IHNpemUAUmVhZC1vbmx5IGFyY2hpdmUATm90IGEgemlwIGFyY2hpdmUAUmVzb3VyY2Ugc3RpbGwgaW4gdXNlAE1hbGxvYyBmYWlsdXJlAGludmFsaWQgYmxvY2sgdHlwZQBGYWlsdXJlIHRvIGNyZWF0ZSB0ZW1wb3JhcnkgZmlsZQBDYW4ndCBvcGVuIGZpbGUATm8gc3VjaCBmaWxlAFByZW1hdHVyZSBlbmQgb2YgZmlsZQBDYW4ndCByZW1vdmUgZmlsZQBpbnZhbGlkIGxpdGVyYWwvbGVuZ3RoIGNvZGUAaW52YWxpZCBkaXN0YW5jZSBjb2RlAHVua25vd24gY29tcHJlc3Npb24gbWV0aG9kAHN0cmVhbSBlbmQAQ29tcHJlc3NlZCBkYXRhIGludmFsaWQATXVsdGktZGlzayB6aXAgYXJjaGl2ZXMgbm90IHN1cHBvcnRlZABPcGVyYXRpb24gbm90IHN1cHBvcnRlZABFbmNyeXB0aW9uIG1ldGhvZCBub3Qgc3VwcG9ydGVkAENvbXByZXNzaW9uIG1ldGhvZCBub3Qgc3VwcG9ydGVkAEVudHJ5IGhhcyBiZWVuIGRlbGV0ZWQAQ29udGFpbmluZyB6aXAgYXJjaGl2ZSB3YXMgY2xvc2VkAENsb3NpbmcgemlwIGFyY2hpdmUgZmFpbGVkAFJlbmFtaW5nIHRlbXBvcmFyeSBmaWxlIGZhaWxlZABFbnRyeSBoYXMgYmVlbiBjaGFuZ2VkAE5vIHBhc3N3b3JkIHByb3ZpZGVkAFdyb25nIHBhc3N3b3JkIHByb3ZpZGVkAFVua25vd24gZXJyb3IgJWQAQUUAKG51bGwpADogAFBLBgcAUEsGBgBQSwUGAFBLAwQAUEsBAgAAAAA/BQAAwAcAAJMIAAB4CAAAbwUAAJEFAAB6BQAAsgUAAFYIAAAbBwAA1gQAAAsHAADqBgAAnAUAAMgGAACyCAAAHggAACgHAABHBAAAoAYAAGAFAAAuBAAAPgcAAD8IAAD+BwAAjgYAAMkIAADeCAAA5gcAALIGAABVBQAAqAcAACAAQcgTCxEBAAAAAQAAAAEAAAABAAAAAQBB7BMLCQEAAAABAAAAAgBBmBQLAQEAQbgUCwEBAEHSFAukLDomOyZlJmYmYyZgJiIg2CXLJdklQiZAJmomayY8JrolxCWVITwgtgCnAKwlqCGRIZMhkiGQIR8ilCGyJbwlIAAhACIAIwAkACUAJgAnACgAKQAqACsALAAtAC4ALwAwADEAMgAzADQANQA2ADcAOAA5ADoAOwA8AD0APgA/AEAAQQBCAEMARABFAEYARwBIAEkASgBLAEwATQBOAE8AUABRAFIAUwBUAFUAVgBXAFgAWQBaAFsAXABdAF4AXwBgAGEAYgBjAGQAZQBmAGcAaABpAGoAawBsAG0AbgBvAHAAcQByAHMAdAB1AHYAdwB4AHkAegB7AHwAfQB+AAIjxwD8AOkA4gDkAOAA5QDnAOoA6wDoAO8A7gDsAMQAxQDJAOYAxgD0APYA8gD7APkA/wDWANwAogCjAKUApyCSAeEA7QDzAPoA8QDRAKoAugC/ABAjrAC9ALwAoQCrALsAkSWSJZMlAiUkJWElYiVWJVUlYyVRJVclXSVcJVslECUUJTQlLCUcJQAlPCVeJV8lWiVUJWklZiVgJVAlbCVnJWglZCVlJVklWCVSJVMlayVqJRglDCWIJYQljCWQJYAlsQPfAJMDwAOjA8MDtQDEA6YDmAOpA7QDHiLGA7UDKSJhIrEAZSJkIiAjISP3AEgisAAZIrcAGiJ/ILIAoCWgAAAAAACWMAd3LGEO7rpRCZkZxG0Hj/RqcDWlY+mjlWSeMojbDqS43Hke6dXgiNnSlytMtgm9fLF+By2455Edv5BkELcd8iCwakhxufPeQb6EfdTaGuvk3W1RtdT0x4XTg1aYbBPAqGtkevli/ezJZYpPXAEU2WwGY2M9D/r1DQiNyCBuO14QaUzkQWDVcnFnotHkAzxH1ARL/YUN0mu1CqX6qLU1bJiyQtbJu9tA+bys42zYMnVc30XPDdbcWT3Rq6ww2SY6AN5RgFHXyBZh0L+19LQhI8SzVpmVus8Ppb24nrgCKAiIBV+y2QzGJOkLsYd8by8RTGhYqx1hwT0tZraQQdx2BnHbAbwg0pgqENXviYWxcR+1tgal5L+fM9S46KLJB3g0+QAPjqgJlhiYDuG7DWp/LT1tCJdsZJEBXGPm9FFra2JhbBzYMGWFTgBi8u2VBmx7pQEbwfQIglfED/XG2bBlUOm3Euq4vot8iLn83x3dYkkt2hXzfNOMZUzU+1hhsk3OUbU6dAC8o+Iwu9RBpd9K15XYPW3E0aT79NbTaulpQ/zZbjRGiGet0Lhg2nMtBETlHQMzX0wKqsl8Dd08cQVQqkECJxAQC76GIAzJJbVoV7OFbyAJ1Ga5n+Rhzg753l6YydkpIpjQsLSo18cXPbNZgQ20LjtcvbetbLrAIIO47bazv5oM4rYDmtKxdDlH1eqvd9KdFSbbBIMW3HMSC2PjhDtklD5qbQ2oWmp6C88O5J3/CZMnrgAKsZ4HfUSTD/DSowiHaPIBHv7CBmldV2L3y2dlgHE2bBnnBmtudhvU/uAr04laetoQzErdZ2/fufn5776OQ763F9WOsGDoo9bWfpPRocTC2DhS8t9P8We70WdXvKbdBrU/SzaySNorDdhMGwqv9koDNmB6BEHD72DfVd9nqO+ObjF5vmlGjLNhyxqDZryg0m8lNuJoUpV3DMwDRwu7uRYCIi8mBVW+O7rFKAu9spJatCsEarNcp//XwjHP0LWLntksHa7eW7DCZJsm8mPsnKNqdQqTbQKpBgmcPzYO64VnB3ITVwAFgkq/lRR6uOKuK7F7OBu2DJuO0pINvtXlt+/cfCHf2wvU0tOGQuLU8fiz3Whug9ofzRa+gVsmufbhd7Bvd0e3GOZaCIhwag//yjsGZlwLARH/nmWPaa5i+NP/a2FFz2wWeOIKoO7SDddUgwROwrMDOWEmZ6f3FmDQTUdpSdt3bj5KatGu3FrW2WYL30DwO9g3U668qcWeu95/z7JH6f+1MBzyvb2KwrrKMJOzU6ajtCQFNtC6kwbXzSlX3lS/Z9kjLnpms7hKYcQCG2hdlCtvKje+C7ShjgzDG98FWo3vAi0AAAAARjtnZYx2zsrKTamvWevtTh/QiivVnSOEk6ZE4bLW25307bz4PqAVV3ibcjLrPTbTrQZRtmdL+BkhcJ98JavG4GOQoYWp3Qgq7+ZvT3xAK646e0zL8DblZLYNggGXfR190UZ6GBsL07ddMLTSzpbwM4itl1ZC4D75BNtZnAtQ/BpNa5t/hyYy0MEdVbVSuxFUFIB2Md7N356Y9rj7uYYnh/+9QOI18OlNc8uOKOBtysmmVq2sbBsEAyogY2Yu+zr6aMBdn6KN9DDktpNVdxDXtDErsNH7Zhl+vV1+G5wt4WfaFoYCEFsvrVZgSMjFxgwpg/1rTEmwwuMPi6WGFqD4NVCbn1Ca1jb/3O1Rmk9LFXsJcHIewz3bsYUGvNSkdiOo4k1EzSgA7WJuO4oH/Z3O5rumqYNx6wAsN9BnSTMLPtV1MFmwv33wH/lGl3pq4NObLNu0/uaWHVGgrXo0gd3lSMfmgi0NqyuCS5BM59g2CAaeDW9jVEDGzBJ7oakd8AQvW8tjSpGGyuXXva2ARBvpYQIgjgTIbSerjlZAzq8m37LpHbjXI1AReGVrdh32zTL8sPZVmXq7/DY8gJtTOFvCz35gpaq0LQwF8hZrYGGwL4Eni0jk7cbhS6v9hi6KjRlSzLZ+Nwb715hAwLD902b0HJVdk3lfEDrWGStdsyxA8Wtqe5YOoDY/oeYNWMR1qxwlM5B7QPnd0u+/5rWKnpYq9titTZMS4OQ8VNuDWcd9x7iBRqDdSwsJcg0wbhcJ6zeLT9BQ7oWd+UHDpp4kUADaxRY7vaDcdhQPmk1zars97Bb9BotzN0si3HFwRbni1gFYpO1mPW6gz5Iom6j3JxANcWErahSrZsO77V2k3n774D84wIda8o0u9bS2SZCVxtbs0/2xiRmwGCZfi39DzC07oooWXMdAW/VoBmCSDQK7y5FEgKz0js0FW8j2Yj5bUCbfHWtButcm6BWRHY9wsG0QDPZWd2k8G97GeiC5o+mG/UKvvZonZfAziCPLVO064AlefNtuO7aWx5TwraDxYwvkECUwg3XvfSraqUZNv4g20sPODbWmBEAcCUJ7e2zR3T+Nl+ZY6F2r8UcbkJYiH0vPvllwqNuTPQF01QZmEUagIvAAm0WVytbsOozti1+tnRQj66ZzRiHr2uln0L2M9Hb5bbJNngh4ADenPjtQwjGw9UR3i5IhvcY7jvv9XOtoWxgKLmB/b+Qt1sCiFrGlg2Yu2cVdSbwPEOATSSuHdtqNw5ectqTyVvsNXRDAajgUGzOkUiBUwZht/W7eVpoLTfDe6gvLuY/BhhAgh713RabN6Dng9o9cKrsm82yAQZb/JgV3uR1iEnNQy701a6zYAAAAAFiA4tfxBrR0qYZWo+INaOm6jYo+EwvcnUuLPkqFHaEJ3Z1D3nQbFX0sm/eqZxDJ4D+QKzeWFn2UzpafQwo7QhNSu6DE+z32Z6O9FLDoNir6sLbILRkwno5BsHxZjybjGtemAc1+IFduJqC1uW0ri/M1q2kknC0/h8St3VAUdoQmTPZm8eVwMFK98NKF9nvsz677DhgHfVi7X/26bJFrJS/J68f4YG2RWzjtc4xzZk3GK+avEYJg+bLa4BtlHk3GNUbNJOLvS3JBt8uQlvxArtykwEwLDUYaqFXG+H+bUGc8w9CF62pW00gy1jGfeV0P1SHd7QKIW7uh0NtZdijsCE1wbOqa2eq8OYFqXu7K4WCkkmGCczvn1NBjZzYHrfGpRPVxS5Nc9x0wBHf/50/8wa0XfCN6vvp12eZ6lw4i10peeleoidPR/iqLURz9wNoit5hawGAx3JbDaVx0FKfK61f/SgmAVsxfIw5MvfRFx4O+HUdhabTBN8rsQdUdPJqMa2QabrzNnDgflRzayN6X5IKGFwZVL5FQ9ncRsiG5hy1i4QfPtUiBmRYQAXvBW4pFiwMKp1yqjPH/8gwTKDahznhuISyvx6d6DJ8nmNvUrKaRjCxERiWqEuV9KvAys7xvces8jaZCutsFGjo50lGxB5gJMeVPoLez7Pg3UTtQ2BGaCFjzTaHepe75Xkc5stV5c+pVm6RD080HG1Mv0NXFsJONRVJEJMME53xD5jA3yNh6b0g6rcbObA6eTo7ZWuNTiQJjsV6r5ef982UFKrjuO2Dgbtm3SeiPFBFobcPf/vKAh34QVy74RvR2eKQjPfOaaWVzeL7M9S4dlHXMykSulbwcLndrtaghyO0owx+mo/1V/iMfglelSSEPJav2wbM0tZkz1mIwtYDBaDViFiO+XFx7Pr6L0rjoKIo4Cv9OldevFhU1eL+TY9vnE4EMrJi/RvQYXZFdngsyBR7p5cuIdqaTCJRxOo7C0mIOIAUphR5PcQX8mNiDqjuAA0jseDQZ1yC0+wCJMq2j0bJPdJo5cT7CuZPpaz/FSjO/J539KbjepalaCQwvDKpUr+59HyTQN0ekMuDuImRDtqKGlHIPW8Qqj7kTgwnvsNuJDWeQAjMtyILR+mEEh1k5hGWO9xL6za+SGBoGFE65XpSsbhUfkiRNn3Dz5BkmULyZxIdsQp3xNMJ/Jp1EKYXFxMtSjk/1GNbPF89/SUFsJ8mju+lfPPix394vGFmIjEDZalsLUlQRU9K2xvpU4GWi1AKyZnnf4j75PTWXf2uWz/+JQYR0twvc9FXcdXIDfy3y4ajjZH7ru+ScPBJiyp9K4ihIAWkWAlnp9NXwb6J2qO9AoQAAAADhtlLvg2vUBWLdhuoG16gL52H65IW8fA5kCi7hDK5RF+0YA/iPxYUSbnPX/Qp5+Rzrz6vziRItGWikf/YYXKMu+erxwZs3dyt6gSXEHosLJf89Wcqd4N8gfFaNzxTy8jn1RKDWl5kmPHYvdNMSJVoy85MI3ZFOjjdw+NzYMLhGXdEOFLKz05JYUmXAtzZv7lbX2by5tQQ6U1SyaLw8FhdK3aBFpb99w09ey5GgOsG/Qdt37a65qmtEWBw5qyjk5XPJUrecq48xdko5Y5kuM014z4Ufl61YmX1M7suSJEq0ZMX85ounIWBhRpcyjiKdHG/DK06AofbIakBAmoVgcI26gcbfVeMbWb8CrQtQZqclsYcRd17lzPG0BHqjW2ze3K2NaI5C77UIqA4DWkdqCXSmi78mSelioKMI1PJMeCwulJmafHv7R/qRGvGofn77hp+fTdRw/ZBSmhwmAHV0gn+DlTQtbPfpq4YWX/lpclXXiJPjhWfxPgONEIhRYlDIy+exfpkI06Mf4jIVTQ1WH2Pst6kxA9V0t+k0wuUGXGaa8L3QyB/fDU71PrscGlqxMvu7B2AU2drm/jhstBFIlGjJqSI6Jsv/vMwqSe4jTkPAwq/1ki3NKBTHLJ5GKEQ6Od6ljGsxx1Ht2ybnvzRC7ZHVo1vDOsGGRdAgMBc/geZrrmBQOUECjb+r4zvtRIcxw6Vmh5FKBFoXoOXsRU+NSDq5bP5oVg4j7rzvlbxTi5+SsmopwF0I9Ea36UIUWJm6yIB4DJpvGtEchftnTmqfbWCLftsyZBwGtI79sOZhlRSZl3Siy3gWf02S98kffZPDMZxydWNzEKjlmfEet3axXi3zUOh/HDI1+fbTg6sZt4mF+FY/1xc04lH91VQDEr3wfORcRi4LPpuo4d8t+g67J9TvWpGGADhMAOrZ+lIFqQKO3Ui03DIqaVrYy98IN6/VJtZOY3Q5LL7y080IoDylrN/KRBqNJSbHC8/HcVkgo3t3wULNJS4gEKPEwabxK+GW5hQAILT7Yv0yEYNLYP7nQU4fBvcc8GQqmhqFnMj17Ti3AwyO5exuU2MGj+Ux6evvHwgKWU3naITLDYkymeL5ykU6GHwX1XqhkT+bF8PQ/x3tMR6rv958djk0ncBr2/VkFC0U0kbCdg/AKJe5ksfzs7wmEgXuyXDYaCORbjrM0S6gSTCY8qZSRXRMs/Mmo9f5CEI2T1qtVJLcR7UkjqjdgPFePDajsV7rJVu/XXe021dZVTrhC7pYPI1QuYrfv8lyA2coxFGIShnXYquvhY3PpatsLhP5g0zOf2mteC2GxdxScCRqAJ9Gt4Z1pwHUmsML+nsivaiUQGAufqHWfJEAAAAAQ8umh8eQPNSEW5pTzycIc4zsrvQItzSnS3ySIJ5PEObdhLZhWd8sMhoUirVRaBiVEqO+Epb4JEHVM4LGfZlRFz5S95C6CW3D+cLLRLK+WWTxdf/jdS5lsDblwzfj1kHxoB3ndiRGfSVnjduiLPFJgm867wXrYXVWqKrT0foyoy65+QWpPaKf+n5pOX01Fatddt4N2vKFl4mxTjEOZH2zyCe2FU+j7Y8c4CYpm6tau7vokR08bMqHby8BIeiHq/I5xGBUvkA7zu0D8GhqSIz6SgtHXM2PHMaezNdgGRnk4t9aL0RY3nTeC52/eIzWw+qslQhMKxFT1nhSmHD/9GVGXbeu4Noz9XqJcD7cDjtCTi54ieip/NJy+r8Z1H1qKla7KeHwPK26am/ucczopQ1eyObG+E9inWIcIVbEm4n8F0rKN7HNTmwrng2njRlG2x85BRC5voFLI+3CgIVqF7MHrFR4oSvQIzt4k+id/9iUD9+bX6lYHwQzC1zPlYwOV+VzTZxD9MnH2aeKDH8gwXDtAIK7S4cG4NHURSt3U5AY9ZXT01MSV4jJQRRDb8ZfP/3mHPRbYZivwTLbZGe1c860ZDAFEuO0Xoiw95UuN7zpvBf/IhqQe3mAwziyJkTtgaSCrkoCBSoRmFZp2j7RIqas8WFtCnblNpAlpv02oujLjLqrACo9L1uwbmyQFukn7ITJZCciTuB8uB2jtx6adoScXDVPOtuxFKCI8t8GD7mjlC/6aDKofjOo+z34DnyVUt2t1pl7KlLC4XkRCUf+WnXV3hm+c1md5ekK3i5PjQsdzUtI1mvMzI3xn49GVxjEOsU4h/FjvwOq+exAYV9rEvkvlFEyiRPVaRNAlqK1x93eJ+eeFYFgGk4bM1mFvbSMtj9yz32Z9UsmA6YI7aUhQ5E3AQBakYaEAQvVx8qtUm9gfoMsq9gEqPBCV+s75NCgR3bw44zQd2fXSiQkHOyj8S9uZbLkyOI2v1KxdXT0Nj4IZhZ9w8CR+ZhawrpT/EUcrsrnX2VsYNs+9jOY9VC004nClJBCZBMUGf5AV9JYx4Lh2gHBKnyGRXHm1Qa6QFJNxtJyDg109YpW7qbJnUghYTeb8CL8PXemp6ck5WwBo64Qk4Pt2zUEaYCvVypLCdD/eIsWvLMtkTjot8J7IxFFMF+DZXOUJeL3z7+xtAQZNuacacmlV89OIQxVHWLH85opu2G6anDHPe4rXW6t4PvpeNN5LzsY36i/Q0X7/IjjfLf0cVz0P9fbcGRNiDOv6w+bBTje2M6eWVyVBAofXqKNVCIwrRfpliqTsgx50Hmq/gVKKDhGgY6/wtoU7IERsmvKbSBLiaaGzA39HJ9ONroYFAQAAJ0HAAAsCQAAhgUAAEgFAACnBQAAAAQAADIFAAC8BQAALAkAQYDBAAv3CQwACACMAAgATAAIAMwACAAsAAgArAAIAGwACADsAAgAHAAIAJwACABcAAgA3AAIADwACAC8AAgAfAAIAPwACAACAAgAggAIAEIACADCAAgAIgAIAKIACABiAAgA4gAIABIACACSAAgAUgAIANIACAAyAAgAsgAIAHIACADyAAgACgAIAIoACABKAAgAygAIACoACACqAAgAagAIAOoACAAaAAgAmgAIAFoACADaAAgAOgAIALoACAB6AAgA+gAIAAYACACGAAgARgAIAMYACAAmAAgApgAIAGYACADmAAgAFgAIAJYACABWAAgA1gAIADYACAC2AAgAdgAIAPYACAAOAAgAjgAIAE4ACADOAAgALgAIAK4ACABuAAgA7gAIAB4ACACeAAgAXgAIAN4ACAA+AAgAvgAIAH4ACAD+AAgAAQAIAIEACABBAAgAwQAIACEACAChAAgAYQAIAOEACAARAAgAkQAIAFEACADRAAgAMQAIALEACABxAAgA8QAIAAkACACJAAgASQAIAMkACAApAAgAqQAIAGkACADpAAgAGQAIAJkACABZAAgA2QAIADkACAC5AAgAeQAIAPkACAAFAAgAhQAIAEUACADFAAgAJQAIAKUACABlAAgA5QAIABUACACVAAgAVQAIANUACAA1AAgAtQAIAHUACAD1AAgADQAIAI0ACABNAAgAzQAIAC0ACACtAAgAbQAIAO0ACAAdAAgAnQAIAF0ACADdAAgAPQAIAL0ACAB9AAgA/QAIABMACQATAQkAkwAJAJMBCQBTAAkAUwEJANMACQDTAQkAMwAJADMBCQCzAAkAswEJAHMACQBzAQkA8wAJAPMBCQALAAkACwEJAIsACQCLAQkASwAJAEsBCQDLAAkAywEJACsACQArAQkAqwAJAKsBCQBrAAkAawEJAOsACQDrAQkAGwAJABsBCQCbAAkAmwEJAFsACQBbAQkA2wAJANsBCQA7AAkAOwEJALsACQC7AQkAewAJAHsBCQD7AAkA+wEJAAcACQAHAQkAhwAJAIcBCQBHAAkARwEJAMcACQDHAQkAJwAJACcBCQCnAAkApwEJAGcACQBnAQkA5wAJAOcBCQAXAAkAFwEJAJcACQCXAQkAVwAJAFcBCQDXAAkA1wEJADcACQA3AQkAtwAJALcBCQB3AAkAdwEJAPcACQD3AQkADwAJAA8BCQCPAAkAjwEJAE8ACQBPAQkAzwAJAM8BCQAvAAkALwEJAK8ACQCvAQkAbwAJAG8BCQDvAAkA7wEJAB8ACQAfAQkAnwAJAJ8BCQBfAAkAXwEJAN8ACQDfAQkAPwAJAD8BCQC/AAkA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SXMgYSBkaXJlY3RvcnkAVGV4dCBmaWxlIGJ1c3kARXhlYyBmb3JtYXQgZXJyb3IASW52YWxpZCBhcmd1bWVudABBcmd1bWVudCBsaXN0IHRvbyBsb25nAFN5bWJvbGljIGxpbmsgbG9vcABGaWxlbmFtZSB0b28gbG9uZwBUb28gbWFueSBvcGVuIGZpbGVzIGluIHN5c3RlbQBObyBmaWxlIGRlc2NyaXB0b3JzIGF2YWlsYWJsZQBCYWQgZmlsZSBkZXNjcmlwdG9yAE5vIGNoaWxkIHByb2Nlc3MAQmFkIGFkZHJlc3MARmlsZSB0b28gbGFyZ2UAVG9vIG1hbnkgbGlua3MATm8gbG9ja3MgYXZhaWxhYmxlAFJlc291cmNlIGRlYWRsb2NrIHdvdWxkIG9jY3VyAFN0YXRlIG5vdCByZWNvdmVyYWJsZQBQcmV2aW91cyBvd25lciBkaWVkAE9wZXJhdGlvbiBjYW5jZWxlZABGdW5jdGlvbiBub3QgaW1wbGVtZW50ZWQATm8gbWVzc2FnZSBvZiBkZXNpcmVkIHR5cGUASWRlbnRpZmllciByZW1vdmVkAERldmljZSBub3QgYSBzdHJlYW0ATm8gZGF0YSBhdmFpbGFibGUARGV2aWNlIHRpbWVvdXQAT3V0IG9mIHN0cmVhbXMgcmVzb3VyY2VzAExpbmsgaGFzIGJlZW4gc2V2ZXJlZABQcm90b2NvbCBlcnJvcgBCYWQgbWVzc2FnZQBGaWxlIGRlc2NyaXB0b3IgaW4gYmFkIHN0YXRlAE5vdCBhIHNvY2tldABEZXN0aW5hdGlvbiBhZGRyZXNzIHJlcXVpcmVkAE1lc3NhZ2UgdG9vIGxhcmdlAFByb3RvY29sIHdyb25nIHR5cGUgZm9yIHNvY2tldABQcm90b2NvbCBub3QgYXZhaWxhYmxlAFByb3RvY29sIG5vdCBzdXBwb3J0ZWQAU29ja2V0IHR5cGUgbm90IHN1cHBvcnRlZABOb3Qgc3VwcG9ydGVkAFByb3RvY29sIGZhbWlseSBub3Qgc3VwcG9ydGVkAEFkZHJlc3MgZmFtaWx5IG5vdCBzdXBwb3J0ZWQgYnkgcHJvdG9jb2wAQWRkcmVzcyBub3QgYXZhaWxhYmxlAE5ldHdvcmsgaXMgZG93bgBOZXR3b3JrIHVucmVhY2hhYmxlAENvbm5lY3Rpb24gcmVzZXQgYnkgbmV0d29yawBDb25uZWN0aW9uIGFib3J0ZWQATm8gYnVmZmVyIHNwYWNlIGF2YWlsYWJsZQBTb2NrZXQgaXMgY29ubmVjdGVkAFNvY2tldCBub3QgY29ubmVjdGVkAENhbm5vdCBzZW5kIGFmdGVyIHNvY2tldCBzaHV0ZG93bgBPcGVyYXRpb24gYWxyZWFkeSBpbiBwcm9ncmVzcwBPcGVyYXRpb24gaW4gcHJvZ3Jlc3MAU3RhbGUgZmlsZSBoYW5kbGUAUmVtb3RlIEkvTyBlcnJvcgBRdW90YSBleGNlZWRlZABObyBtZWRpdW0gZm91bmQAV3JvbmcgbWVkaXVtIHR5cGUATm8gZXJyb3IgaW5mb3JtYXRpb24AQcCAAQuFARMAAAAUAAAAFQAAABYAAAAXAAAAGAAAABkAAAAaAAAAGwAAABwAAAAdAAAAHgAAAB8AAAAgAAAAIQAAACIAAAAjAAAAgERQADEAAAAyAAAAMwAAADQAAAA1AAAANgAAADcAAAA4AAAAOQAAADIAAAAzAAAANAAAADUAAAA2AAAANwAAADgAQfSCAQsCXEQAQbCDAQsQ/////////////////////w==";Co(ji)||(ji=P(ji));function eo(Ke){try{if(Ke==ji&&le)return new Uint8Array(le);var st=Me(Ke);if(st)return st;if(R)return R(Ke);throw"sync fetching of the wasm failed: you can preload it to Module['wasmBinary'] manually, or emcc.py will do that for you when generating HTML (but not JS)"}catch(St){rs(St)}}function wo(Ke,st){var St,lr,te;try{te=eo(Ke),lr=new WebAssembly.Module(te),St=new WebAssembly.Instance(lr,st)}catch(Oe){var Ee=Oe.toString();throw ee("failed to compile wasm module: "+Ee),(Ee.includes("imported Memory")||Ee.includes("memory import"))&&ee("Memory size incompatibility issues may be due to changing INITIAL_MEMORY at runtime to something too large. Use ALLOW_MEMORY_GROWTH to allow any size memory (and also make sure not to set INITIAL_MEMORY at runtime to something smaller than it was at compile time)."),Oe}return[St,lr]}function QA(){var Ke={a:cu};function st(te,Ee){var Oe=te.exports;r.asm=Oe,Be=r.asm.g,z(Be.buffer),$=r.asm.W,gn(r.asm.h),Io("wasm-instantiate")}if(ai("wasm-instantiate"),r.instantiateWasm)try{var St=r.instantiateWasm(Ke,st);return St}catch(te){return ee("Module.instantiateWasm callback failed with error: "+te),!1}var lr=wo(ji,Ke);return st(lr[0]),r.asm}function Af(Ke){return F.getFloat32(Ke,!0)}function dh(Ke){return F.getFloat64(Ke,!0)}function mh(Ke){return F.getInt16(Ke,!0)}function to(Ke){return F.getInt32(Ke,!0)}function jn(Ke,st){F.setInt32(Ke,st,!0)}function Ts(Ke){for(;Ke.length>0;){var st=Ke.shift();if(typeof st=="function"){st(r);continue}var St=st.func;typeof St=="number"?st.arg===void 0?$.get(St)():$.get(St)(st.arg):St(st.arg===void 0?null:st.arg)}}function ro(Ke,st){var St=new Date(to((Ke>>2)*4)*1e3);jn((st>>2)*4,St.getUTCSeconds()),jn((st+4>>2)*4,St.getUTCMinutes()),jn((st+8>>2)*4,St.getUTCHours()),jn((st+12>>2)*4,St.getUTCDate()),jn((st+16>>2)*4,St.getUTCMonth()),jn((st+20>>2)*4,St.getUTCFullYear()-1900),jn((st+24>>2)*4,St.getUTCDay()),jn((st+36>>2)*4,0),jn((st+32>>2)*4,0);var lr=Date.UTC(St.getUTCFullYear(),0,1,0,0,0,0),te=(St.getTime()-lr)/(1e3*60*60*24)|0;return jn((st+28>>2)*4,te),ro.GMTString||(ro.GMTString=rt("GMT")),jn((st+40>>2)*4,ro.GMTString),st}function ou(Ke,st){return ro(Ke,st)}function au(Ke,st,St){ke.copyWithin(Ke,st,st+St)}function lu(Ke){try{return Be.grow(Ke-Pe.byteLength+65535>>>16),z(Be.buffer),1}catch{}}function TA(Ke){var st=ke.length;Ke=Ke>>>0;var St=2147483648;if(Ke>St)return!1;for(var lr=1;lr<=4;lr*=2){var te=st*(1+.2/lr);te=Math.min(te,Ke+100663296);var Ee=Math.min(St,Ne(Math.max(Ke,te),65536)),Oe=lu(Ee);if(Oe)return!0}return!1}function RA(Ke){ue(Ke)}function oa(Ke){var st=Date.now()/1e3|0;return Ke&&jn((Ke>>2)*4,st),st}function aa(){if(aa.called)return;aa.called=!0;var Ke=new Date().getFullYear(),st=new Date(Ke,0,1),St=new Date(Ke,6,1),lr=st.getTimezoneOffset(),te=St.getTimezoneOffset(),Ee=Math.max(lr,te);jn((vl()>>2)*4,Ee*60),jn((Is()>>2)*4,+(lr!=te));function Oe(An){var li=An.toTimeString().match(/\(([A-Za-z ]+)\)$/);return li?li[1]:"GMT"}var dt=Oe(st),Et=Oe(St),bt=rt(dt),tr=rt(Et);te>2)*4,bt),jn((Mi()+4>>2)*4,tr)):(jn((Mi()>>2)*4,tr),jn((Mi()+4>>2)*4,bt))}function FA(Ke){aa();var st=Date.UTC(to((Ke+20>>2)*4)+1900,to((Ke+16>>2)*4),to((Ke+12>>2)*4),to((Ke+8>>2)*4),to((Ke+4>>2)*4),to((Ke>>2)*4),0),St=new Date(st);jn((Ke+24>>2)*4,St.getUTCDay());var lr=Date.UTC(St.getUTCFullYear(),0,1,0,0,0,0),te=(St.getTime()-lr)/(1e3*60*60*24)|0;return jn((Ke+28>>2)*4,te),St.getTime()/1e3|0}var gr=typeof atob=="function"?atob:function(Ke){var st="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",St="",lr,te,Ee,Oe,dt,Et,bt,tr=0;Ke=Ke.replace(/[^A-Za-z0-9\+\/\=]/g,"");do Oe=st.indexOf(Ke.charAt(tr++)),dt=st.indexOf(Ke.charAt(tr++)),Et=st.indexOf(Ke.charAt(tr++)),bt=st.indexOf(Ke.charAt(tr++)),lr=Oe<<2|dt>>4,te=(dt&15)<<4|Et>>2,Ee=(Et&3)<<6|bt,St=St+String.fromCharCode(lr),Et!==64&&(St=St+String.fromCharCode(te)),bt!==64&&(St=St+String.fromCharCode(Ee));while(tr0||(Ct(),Ir>0))return;function st(){Qn||(Qn=!0,r.calledRun=!0,!Ce&&(qt(),s(r),r.onRuntimeInitialized&&r.onRuntimeInitialized(),ir()))}r.setStatus?(r.setStatus("Running..."),setTimeout(function(){setTimeout(function(){r.setStatus("")},1),st()},1)):st()}if(r.run=Ac,r.preInit)for(typeof r.preInit=="function"&&(r.preInit=[r.preInit]);r.preInit.length>0;)r.preInit.pop()();return Ac(),e}}();typeof wT=="object"&&typeof Pj=="object"?Pj.exports=bj:typeof define=="function"&&define.amd?define([],function(){return bj}):typeof wT=="object"&&(wT.createModule=bj)});var Up,hpe,gpe,dpe=Xe(()=>{Up=["number","number"],hpe=(Z=>(Z[Z.ZIP_ER_OK=0]="ZIP_ER_OK",Z[Z.ZIP_ER_MULTIDISK=1]="ZIP_ER_MULTIDISK",Z[Z.ZIP_ER_RENAME=2]="ZIP_ER_RENAME",Z[Z.ZIP_ER_CLOSE=3]="ZIP_ER_CLOSE",Z[Z.ZIP_ER_SEEK=4]="ZIP_ER_SEEK",Z[Z.ZIP_ER_READ=5]="ZIP_ER_READ",Z[Z.ZIP_ER_WRITE=6]="ZIP_ER_WRITE",Z[Z.ZIP_ER_CRC=7]="ZIP_ER_CRC",Z[Z.ZIP_ER_ZIPCLOSED=8]="ZIP_ER_ZIPCLOSED",Z[Z.ZIP_ER_NOENT=9]="ZIP_ER_NOENT",Z[Z.ZIP_ER_EXISTS=10]="ZIP_ER_EXISTS",Z[Z.ZIP_ER_OPEN=11]="ZIP_ER_OPEN",Z[Z.ZIP_ER_TMPOPEN=12]="ZIP_ER_TMPOPEN",Z[Z.ZIP_ER_ZLIB=13]="ZIP_ER_ZLIB",Z[Z.ZIP_ER_MEMORY=14]="ZIP_ER_MEMORY",Z[Z.ZIP_ER_CHANGED=15]="ZIP_ER_CHANGED",Z[Z.ZIP_ER_COMPNOTSUPP=16]="ZIP_ER_COMPNOTSUPP",Z[Z.ZIP_ER_EOF=17]="ZIP_ER_EOF",Z[Z.ZIP_ER_INVAL=18]="ZIP_ER_INVAL",Z[Z.ZIP_ER_NOZIP=19]="ZIP_ER_NOZIP",Z[Z.ZIP_ER_INTERNAL=20]="ZIP_ER_INTERNAL",Z[Z.ZIP_ER_INCONS=21]="ZIP_ER_INCONS",Z[Z.ZIP_ER_REMOVE=22]="ZIP_ER_REMOVE",Z[Z.ZIP_ER_DELETED=23]="ZIP_ER_DELETED",Z[Z.ZIP_ER_ENCRNOTSUPP=24]="ZIP_ER_ENCRNOTSUPP",Z[Z.ZIP_ER_RDONLY=25]="ZIP_ER_RDONLY",Z[Z.ZIP_ER_NOPASSWD=26]="ZIP_ER_NOPASSWD",Z[Z.ZIP_ER_WRONGPASSWD=27]="ZIP_ER_WRONGPASSWD",Z[Z.ZIP_ER_OPNOTSUPP=28]="ZIP_ER_OPNOTSUPP",Z[Z.ZIP_ER_INUSE=29]="ZIP_ER_INUSE",Z[Z.ZIP_ER_TELL=30]="ZIP_ER_TELL",Z[Z.ZIP_ER_COMPRESSED_DATA=31]="ZIP_ER_COMPRESSED_DATA",Z))(hpe||{}),gpe=t=>({get HEAPU8(){return t.HEAPU8},errors:hpe,SEEK_SET:0,SEEK_CUR:1,SEEK_END:2,ZIP_CHECKCONS:4,ZIP_EXCL:2,ZIP_RDONLY:16,ZIP_FL_OVERWRITE:8192,ZIP_FL_COMPRESSED:4,ZIP_OPSYS_DOS:0,ZIP_OPSYS_AMIGA:1,ZIP_OPSYS_OPENVMS:2,ZIP_OPSYS_UNIX:3,ZIP_OPSYS_VM_CMS:4,ZIP_OPSYS_ATARI_ST:5,ZIP_OPSYS_OS_2:6,ZIP_OPSYS_MACINTOSH:7,ZIP_OPSYS_Z_SYSTEM:8,ZIP_OPSYS_CPM:9,ZIP_OPSYS_WINDOWS_NTFS:10,ZIP_OPSYS_MVS:11,ZIP_OPSYS_VSE:12,ZIP_OPSYS_ACORN_RISC:13,ZIP_OPSYS_VFAT:14,ZIP_OPSYS_ALTERNATE_MVS:15,ZIP_OPSYS_BEOS:16,ZIP_OPSYS_TANDEM:17,ZIP_OPSYS_OS_400:18,ZIP_OPSYS_OS_X:19,ZIP_CM_DEFAULT:-1,ZIP_CM_STORE:0,ZIP_CM_DEFLATE:8,uint08S:t._malloc(1),uint32S:t._malloc(4),malloc:t._malloc,free:t._free,getValue:t.getValue,openFromSource:t.cwrap("zip_open_from_source","number",["number","number","number"]),close:t.cwrap("zip_close","number",["number"]),discard:t.cwrap("zip_discard",null,["number"]),getError:t.cwrap("zip_get_error","number",["number"]),getName:t.cwrap("zip_get_name","string",["number","number","number"]),getNumEntries:t.cwrap("zip_get_num_entries","number",["number","number"]),delete:t.cwrap("zip_delete","number",["number","number"]),statIndex:t.cwrap("zip_stat_index","number",["number",...Up,"number","number"]),fopenIndex:t.cwrap("zip_fopen_index","number",["number",...Up,"number"]),fread:t.cwrap("zip_fread","number",["number","number","number","number"]),fclose:t.cwrap("zip_fclose","number",["number"]),dir:{add:t.cwrap("zip_dir_add","number",["number","string"])},file:{add:t.cwrap("zip_file_add","number",["number","string","number","number"]),getError:t.cwrap("zip_file_get_error","number",["number"]),getExternalAttributes:t.cwrap("zip_file_get_external_attributes","number",["number",...Up,"number","number","number"]),setExternalAttributes:t.cwrap("zip_file_set_external_attributes","number",["number",...Up,"number","number","number"]),setMtime:t.cwrap("zip_file_set_mtime","number",["number",...Up,"number","number"]),setCompression:t.cwrap("zip_set_file_compression","number",["number",...Up,"number","number"])},ext:{countSymlinks:t.cwrap("zip_ext_count_symlinks","number",["number"])},error:{initWithCode:t.cwrap("zip_error_init_with_code",null,["number","number"]),strerror:t.cwrap("zip_error_strerror","string",["number"])},name:{locate:t.cwrap("zip_name_locate","number",["number","string","number"])},source:{fromUnattachedBuffer:t.cwrap("zip_source_buffer_create","number",["number",...Up,"number","number"]),fromBuffer:t.cwrap("zip_source_buffer","number",["number","number",...Up,"number"]),free:t.cwrap("zip_source_free",null,["number"]),keep:t.cwrap("zip_source_keep",null,["number"]),open:t.cwrap("zip_source_open","number",["number"]),close:t.cwrap("zip_source_close","number",["number"]),seek:t.cwrap("zip_source_seek","number",["number",...Up,"number"]),tell:t.cwrap("zip_source_tell","number",["number"]),read:t.cwrap("zip_source_read","number",["number","number","number"]),error:t.cwrap("zip_source_error","number",["number"])},struct:{statS:t.cwrap("zipstruct_statS","number",[]),statSize:t.cwrap("zipstruct_stat_size","number",["number"]),statCompSize:t.cwrap("zipstruct_stat_comp_size","number",["number"]),statCompMethod:t.cwrap("zipstruct_stat_comp_method","number",["number"]),statMtime:t.cwrap("zipstruct_stat_mtime","number",["number"]),statCrc:t.cwrap("zipstruct_stat_crc","number",["number"]),errorS:t.cwrap("zipstruct_errorS","number",[]),errorCodeZip:t.cwrap("zipstruct_error_code_zip","number",["number"])}})});function xj(t,e){let r=t.indexOf(e);if(r<=0)return null;let s=r;for(;r>=0&&(s=r+e.length,t[s]!==J.sep);){if(t[r-1]===J.sep)return null;r=t.indexOf(e,s)}return t.length>s&&t[s]!==J.sep?null:t.slice(0,s)}var $f,mpe=Xe(()=>{Dt();Dt();eA();$f=class t extends e0{static async openPromise(e,r){let s=new t(r);try{return await e(s)}finally{s.saveAndClose()}}constructor(e={}){let r=e.fileExtensions,s=e.readOnlyArchives,a=typeof r>"u"?f=>xj(f,".zip"):f=>{for(let p of r){let h=xj(f,p);if(h)return h}return null},n=(f,p)=>new As(p,{baseFs:f,readOnly:s,stats:f.statSync(p),customZipImplementation:e.customZipImplementation}),c=async(f,p)=>{let h={baseFs:f,readOnly:s,stats:await f.statPromise(p),customZipImplementation:e.customZipImplementation};return()=>new As(p,h)};super({...e,factorySync:n,factoryPromise:c,getMountPoint:a})}}});var kj,BI,Qj=Xe(()=>{Dj();kj=class extends Error{constructor(e,r){super(e),this.name="Libzip Error",this.code=r}},BI=class{constructor(e){this.filesShouldBeCached=!0;let r="buffer"in e?e.buffer:e.baseFs.readFileSync(e.path);this.libzip=cv();let s=this.libzip.malloc(4);try{let c=0;e.readOnly&&(c|=this.libzip.ZIP_RDONLY);let f=this.allocateUnattachedSource(r);try{this.zip=this.libzip.openFromSource(f,c,s),this.lzSource=f}catch(p){throw this.libzip.source.free(f),p}if(this.zip===0){let p=this.libzip.struct.errorS();throw this.libzip.error.initWithCode(p,this.libzip.getValue(s,"i32")),this.makeLibzipError(p)}}finally{this.libzip.free(s)}let a=this.libzip.getNumEntries(this.zip,0),n=new Array(a);for(let c=0;c>>0,n=this.libzip.struct.statMtime(r)>>>0,c=this.libzip.struct.statCrc(r)>>>0;return{size:a,mtime:n,crc:c}}makeLibzipError(e){let r=this.libzip.struct.errorCodeZip(e),s=this.libzip.error.strerror(e),a=new kj(s,this.libzip.errors[r]);if(r===this.libzip.errors.ZIP_ER_CHANGED)throw new Error(`Assertion failed: Unexpected libzip error: ${a.message}`);return a}setFileSource(e,r,s){let a=this.allocateSource(s);try{let n=this.libzip.file.add(this.zip,e,a,this.libzip.ZIP_FL_OVERWRITE);if(n===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));if(r!==null&&this.libzip.file.setCompression(this.zip,n,0,r[0],r[1])===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));return n}catch(n){throw this.libzip.source.free(a),n}}setMtime(e,r){if(this.libzip.file.setMtime(this.zip,e,0,r,0)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}getExternalAttributes(e){if(this.libzip.file.getExternalAttributes(this.zip,e,0,0,this.libzip.uint08S,this.libzip.uint32S)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));let s=this.libzip.getValue(this.libzip.uint08S,"i8")>>>0,a=this.libzip.getValue(this.libzip.uint32S,"i32")>>>0;return[s,a]}setExternalAttributes(e,r,s){if(this.libzip.file.setExternalAttributes(this.zip,e,0,0,r,s)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}locate(e){return this.libzip.name.locate(this.zip,e,0)}getFileSource(e){let r=this.libzip.struct.statS();if(this.libzip.statIndex(this.zip,e,0,0,r)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));let a=this.libzip.struct.statCompSize(r),n=this.libzip.struct.statCompMethod(r),c=this.libzip.malloc(a);try{let f=this.libzip.fopenIndex(this.zip,e,0,this.libzip.ZIP_FL_COMPRESSED);if(f===0)throw this.makeLibzipError(this.libzip.getError(this.zip));try{let p=this.libzip.fread(f,c,a,0);if(p===-1)throw this.makeLibzipError(this.libzip.file.getError(f));if(pa)throw new Error("Overread");let h=this.libzip.HEAPU8.subarray(c,c+a);return{data:Buffer.from(h),compressionMethod:n}}finally{this.libzip.fclose(f)}}finally{this.libzip.free(c)}}deleteEntry(e){if(this.libzip.delete(this.zip,e)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}addDirectory(e){let r=this.libzip.dir.add(this.zip,e);if(r===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));return r}getBufferAndClose(){try{if(this.libzip.source.keep(this.lzSource),this.libzip.close(this.zip)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));if(this.libzip.source.open(this.lzSource)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(this.libzip.source.seek(this.lzSource,0,0,this.libzip.SEEK_END)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));let e=this.libzip.source.tell(this.lzSource);if(e===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(this.libzip.source.seek(this.lzSource,0,0,this.libzip.SEEK_SET)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));let r=this.libzip.malloc(e);if(!r)throw new Error("Couldn't allocate enough memory");try{let s=this.libzip.source.read(this.lzSource,r,e);if(s===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(se)throw new Error("Overread");let a=Buffer.from(this.libzip.HEAPU8.subarray(r,r+e));return process.env.YARN_IS_TEST_ENV&&process.env.YARN_ZIP_DATA_EPILOGUE&&(a=Buffer.concat([a,Buffer.from(process.env.YARN_ZIP_DATA_EPILOGUE)])),a}finally{this.libzip.free(r)}}finally{this.libzip.source.close(this.lzSource),this.libzip.source.free(this.lzSource)}}allocateBuffer(e){Buffer.isBuffer(e)||(e=Buffer.from(e));let r=this.libzip.malloc(e.byteLength);if(!r)throw new Error("Couldn't allocate enough memory");return new Uint8Array(this.libzip.HEAPU8.buffer,r,e.byteLength).set(e),{buffer:r,byteLength:e.byteLength}}allocateUnattachedSource(e){let r=this.libzip.struct.errorS(),{buffer:s,byteLength:a}=this.allocateBuffer(e),n=this.libzip.source.fromUnattachedBuffer(s,a,0,1,r);if(n===0)throw this.libzip.free(r),this.makeLibzipError(r);return n}allocateSource(e){let{buffer:r,byteLength:s}=this.allocateBuffer(e),a=this.libzip.source.fromBuffer(this.zip,r,s,0,1);if(a===0)throw this.libzip.free(r),this.makeLibzipError(this.libzip.getError(this.zip));return a}discard(){this.libzip.discard(this.zip)}}});function Ynt(t){if(typeof t=="string"&&String(+t)===t)return+t;if(typeof t=="number"&&Number.isFinite(t))return t<0?Date.now()/1e3:t;if(ype.types.isDate(t))return t.getTime()/1e3;throw new Error("Invalid time")}function BT(){return Buffer.from([80,75,5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])}var xa,Tj,ype,Rj,lm,Fj,Nj,Epe,As,vT=Xe(()=>{Dt();Dt();Dt();Dt();Dt();Dt();xa=Ie("fs"),Tj=Ie("stream"),ype=Ie("util"),Rj=ut(Ie("zlib"));Qj();lm=3,Fj=0,Nj=8,Epe="mixed";As=class extends Uf{constructor(r,s={}){super();this.listings=new Map;this.entries=new Map;this.fileSources=new Map;this.fds=new Map;this.nextFd=0;this.ready=!1;this.readOnly=!1;s.readOnly&&(this.readOnly=!0);let a=s;this.level=typeof a.level<"u"?a.level:Epe;let n=s.customZipImplementation??BI;if(typeof r=="string"){let{baseFs:f=new Yn}=a;this.baseFs=f,this.path=r}else this.path=null,this.baseFs=null;if(s.stats)this.stats=s.stats;else if(typeof r=="string")try{this.stats=this.baseFs.statSync(r)}catch(f){if(f.code==="ENOENT"&&a.create)this.stats=$a.makeDefaultStats();else throw f}else this.stats=$a.makeDefaultStats();typeof r=="string"?s.create?this.zipImpl=new n({buffer:BT(),readOnly:this.readOnly}):this.zipImpl=new n({path:r,baseFs:this.baseFs,readOnly:this.readOnly,size:this.stats.size}):this.zipImpl=new n({buffer:r??BT(),readOnly:this.readOnly}),this.listings.set(vt.root,new Set);let c=this.zipImpl.getListings();for(let f=0;f{this.closeSync(f)}})}async readPromise(r,s,a,n,c){return this.readSync(r,s,a,n,c)}readSync(r,s,a=0,n=s.byteLength,c=-1){let f=this.fds.get(r);if(typeof f>"u")throw or.EBADF("read");let p=c===-1||c===null?f.cursor:c,h=this.readFileSync(f.p);h.copy(s,a,p,p+n);let E=Math.max(0,Math.min(h.length-p,n));return(c===-1||c===null)&&(f.cursor+=E),E}async writePromise(r,s,a,n,c){return typeof s=="string"?this.writeSync(r,s,c):this.writeSync(r,s,a,n,c)}writeSync(r,s,a,n,c){throw typeof this.fds.get(r)>"u"?or.EBADF("read"):new Error("Unimplemented")}async closePromise(r){return this.closeSync(r)}closeSync(r){if(typeof this.fds.get(r)>"u")throw or.EBADF("read");this.fds.delete(r)}createReadStream(r,{encoding:s}={}){if(r===null)throw new Error("Unimplemented");let a=this.openSync(r,"r"),n=Object.assign(new Tj.PassThrough({emitClose:!0,autoDestroy:!0,destroy:(f,p)=>{clearImmediate(c),this.closeSync(a),p(f)}}),{close(){n.destroy()},bytesRead:0,path:r,pending:!1}),c=setImmediate(async()=>{try{let f=await this.readFilePromise(r,s);n.bytesRead=f.length,n.end(f)}catch(f){n.destroy(f)}});return n}createWriteStream(r,{encoding:s}={}){if(this.readOnly)throw or.EROFS(`open '${r}'`);if(r===null)throw new Error("Unimplemented");let a=[],n=this.openSync(r,"w"),c=Object.assign(new Tj.PassThrough({autoDestroy:!0,emitClose:!0,destroy:(f,p)=>{try{f?p(f):(this.writeFileSync(r,Buffer.concat(a),s),p(null))}catch(h){p(h)}finally{this.closeSync(n)}}}),{close(){c.destroy()},bytesWritten:0,path:r,pending:!1});return c.on("data",f=>{let p=Buffer.from(f);c.bytesWritten+=p.length,a.push(p)}),c}async realpathPromise(r){return this.realpathSync(r)}realpathSync(r){let s=this.resolveFilename(`lstat '${r}'`,r);if(!this.entries.has(s)&&!this.listings.has(s))throw or.ENOENT(`lstat '${r}'`);return s}async existsPromise(r){return this.existsSync(r)}existsSync(r){if(!this.ready)throw or.EBUSY(`archive closed, existsSync '${r}'`);if(this.symlinkCount===0){let a=J.resolve(vt.root,r);return this.entries.has(a)||this.listings.has(a)}let s;try{s=this.resolveFilename(`stat '${r}'`,r,void 0,!1)}catch{return!1}return s===void 0?!1:this.entries.has(s)||this.listings.has(s)}async accessPromise(r,s){return this.accessSync(r,s)}accessSync(r,s=xa.constants.F_OK){let a=this.resolveFilename(`access '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`access '${r}'`);if(this.readOnly&&s&xa.constants.W_OK)throw or.EROFS(`access '${r}'`)}async statPromise(r,s={bigint:!1}){return s.bigint?this.statSync(r,{bigint:!0}):this.statSync(r)}statSync(r,s={bigint:!1,throwIfNoEntry:!0}){let a=this.resolveFilename(`stat '${r}'`,r,void 0,s.throwIfNoEntry);if(a!==void 0){if(!this.entries.has(a)&&!this.listings.has(a)){if(s.throwIfNoEntry===!1)return;throw or.ENOENT(`stat '${r}'`)}if(r[r.length-1]==="/"&&!this.listings.has(a))throw or.ENOTDIR(`stat '${r}'`);return this.statImpl(`stat '${r}'`,a,s)}}async fstatPromise(r,s){return this.fstatSync(r,s)}fstatSync(r,s){let a=this.fds.get(r);if(typeof a>"u")throw or.EBADF("fstatSync");let{p:n}=a,c=this.resolveFilename(`stat '${n}'`,n);if(!this.entries.has(c)&&!this.listings.has(c))throw or.ENOENT(`stat '${n}'`);if(n[n.length-1]==="/"&&!this.listings.has(c))throw or.ENOTDIR(`stat '${n}'`);return this.statImpl(`fstat '${n}'`,c,s)}async lstatPromise(r,s={bigint:!1}){return s.bigint?this.lstatSync(r,{bigint:!0}):this.lstatSync(r)}lstatSync(r,s={bigint:!1,throwIfNoEntry:!0}){let a=this.resolveFilename(`lstat '${r}'`,r,!1,s.throwIfNoEntry);if(a!==void 0){if(!this.entries.has(a)&&!this.listings.has(a)){if(s.throwIfNoEntry===!1)return;throw or.ENOENT(`lstat '${r}'`)}if(r[r.length-1]==="/"&&!this.listings.has(a))throw or.ENOTDIR(`lstat '${r}'`);return this.statImpl(`lstat '${r}'`,a,s)}}statImpl(r,s,a={}){let n=this.entries.get(s);if(typeof n<"u"){let c=this.zipImpl.stat(n),f=c.crc,p=c.size,h=c.mtime*1e3,E=this.stats.uid,C=this.stats.gid,S=512,P=Math.ceil(c.size/S),I=h,R=h,N=h,U=new Date(I),W=new Date(R),ee=new Date(N),ie=new Date(h),ue=this.listings.has(s)?xa.constants.S_IFDIR:this.isSymbolicLink(n)?xa.constants.S_IFLNK:xa.constants.S_IFREG,le=ue===xa.constants.S_IFDIR?493:420,me=ue|this.getUnixMode(n,le)&511,pe=Object.assign(new $a.StatEntry,{uid:E,gid:C,size:p,blksize:S,blocks:P,atime:U,birthtime:W,ctime:ee,mtime:ie,atimeMs:I,birthtimeMs:R,ctimeMs:N,mtimeMs:h,mode:me,crc:f});return a.bigint===!0?$a.convertToBigIntStats(pe):pe}if(this.listings.has(s)){let c=this.stats.uid,f=this.stats.gid,p=0,h=512,E=0,C=this.stats.mtimeMs,S=this.stats.mtimeMs,P=this.stats.mtimeMs,I=this.stats.mtimeMs,R=new Date(C),N=new Date(S),U=new Date(P),W=new Date(I),ee=xa.constants.S_IFDIR|493,ue=Object.assign(new $a.StatEntry,{uid:c,gid:f,size:p,blksize:h,blocks:E,atime:R,birthtime:N,ctime:U,mtime:W,atimeMs:C,birthtimeMs:S,ctimeMs:P,mtimeMs:I,mode:ee,crc:0});return a.bigint===!0?$a.convertToBigIntStats(ue):ue}throw new Error("Unreachable")}getUnixMode(r,s){let[a,n]=this.zipImpl.getExternalAttributes(r);return a!==lm?s:n>>>16}registerListing(r){let s=this.listings.get(r);if(s)return s;this.registerListing(J.dirname(r)).add(J.basename(r));let n=new Set;return this.listings.set(r,n),n}registerEntry(r,s){this.registerListing(J.dirname(r)).add(J.basename(r)),this.entries.set(r,s)}unregisterListing(r){this.listings.delete(r),this.listings.get(J.dirname(r))?.delete(J.basename(r))}unregisterEntry(r){this.unregisterListing(r);let s=this.entries.get(r);this.entries.delete(r),!(typeof s>"u")&&(this.fileSources.delete(s),this.isSymbolicLink(s)&&this.symlinkCount--)}deleteEntry(r,s){this.unregisterEntry(r),this.zipImpl.deleteEntry(s)}resolveFilename(r,s,a=!0,n=!0){if(!this.ready)throw or.EBUSY(`archive closed, ${r}`);let c=J.resolve(vt.root,s);if(c==="/")return vt.root;let f=this.entries.get(c);if(a&&f!==void 0)if(this.symlinkCount!==0&&this.isSymbolicLink(f)){let p=this.getFileSource(f).toString();return this.resolveFilename(r,J.resolve(J.dirname(c),p),!0,n)}else return c;for(;;){let p=this.resolveFilename(r,J.dirname(c),!0,n);if(p===void 0)return p;let h=this.listings.has(p),E=this.entries.has(p);if(!h&&!E){if(n===!1)return;throw or.ENOENT(r)}if(!h)throw or.ENOTDIR(r);if(c=J.resolve(p,J.basename(c)),!a||this.symlinkCount===0)break;let C=this.zipImpl.locate(c.slice(1));if(C===-1)break;if(this.isSymbolicLink(C)){let S=this.getFileSource(C).toString();c=J.resolve(J.dirname(c),S)}else break}return c}setFileSource(r,s){let a=Buffer.isBuffer(s)?s:Buffer.from(s),n=J.relative(vt.root,r),c=null;this.level!=="mixed"&&(c=[this.level===0?Fj:Nj,this.level]);let f=this.zipImpl.setFileSource(n,c,a);return this.fileSources.set(f,a),f}isSymbolicLink(r){if(this.symlinkCount===0)return!1;let[s,a]=this.zipImpl.getExternalAttributes(r);return s!==lm?!1:(a>>>16&xa.constants.S_IFMT)===xa.constants.S_IFLNK}getFileSource(r,s={asyncDecompress:!1}){let a=this.fileSources.get(r);if(typeof a<"u")return a;let{data:n,compressionMethod:c}=this.zipImpl.getFileSource(r);if(c===Fj)return this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,n),n;if(c===Nj){if(s.asyncDecompress)return new Promise((f,p)=>{Rj.default.inflateRaw(n,(h,E)=>{h?p(h):(this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,E),f(E))})});{let f=Rj.default.inflateRawSync(n);return this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,f),f}}else throw new Error(`Unsupported compression method: ${c}`)}async fchmodPromise(r,s){return this.chmodPromise(this.fdToPath(r,"fchmod"),s)}fchmodSync(r,s){return this.chmodSync(this.fdToPath(r,"fchmodSync"),s)}async chmodPromise(r,s){return this.chmodSync(r,s)}chmodSync(r,s){if(this.readOnly)throw or.EROFS(`chmod '${r}'`);s&=493;let a=this.resolveFilename(`chmod '${r}'`,r,!1),n=this.entries.get(a);if(typeof n>"u")throw new Error(`Assertion failed: The entry should have been registered (${a})`);let f=this.getUnixMode(n,xa.constants.S_IFREG|0)&-512|s;this.zipImpl.setExternalAttributes(n,lm,f<<16)}async fchownPromise(r,s,a){return this.chownPromise(this.fdToPath(r,"fchown"),s,a)}fchownSync(r,s,a){return this.chownSync(this.fdToPath(r,"fchownSync"),s,a)}async chownPromise(r,s,a){return this.chownSync(r,s,a)}chownSync(r,s,a){throw new Error("Unimplemented")}async renamePromise(r,s){return this.renameSync(r,s)}renameSync(r,s){throw new Error("Unimplemented")}async copyFilePromise(r,s,a){let{indexSource:n,indexDest:c,resolvedDestP:f}=this.prepareCopyFile(r,s,a),p=await this.getFileSource(n,{asyncDecompress:!0}),h=this.setFileSource(f,p);h!==c&&this.registerEntry(f,h)}copyFileSync(r,s,a=0){let{indexSource:n,indexDest:c,resolvedDestP:f}=this.prepareCopyFile(r,s,a),p=this.getFileSource(n),h=this.setFileSource(f,p);h!==c&&this.registerEntry(f,h)}prepareCopyFile(r,s,a=0){if(this.readOnly)throw or.EROFS(`copyfile '${r} -> '${s}'`);if(a&xa.constants.COPYFILE_FICLONE_FORCE)throw or.ENOSYS("unsupported clone operation",`copyfile '${r}' -> ${s}'`);let n=this.resolveFilename(`copyfile '${r} -> ${s}'`,r),c=this.entries.get(n);if(typeof c>"u")throw or.EINVAL(`copyfile '${r}' -> '${s}'`);let f=this.resolveFilename(`copyfile '${r}' -> ${s}'`,s),p=this.entries.get(f);if(a&(xa.constants.COPYFILE_EXCL|xa.constants.COPYFILE_FICLONE_FORCE)&&typeof p<"u")throw or.EEXIST(`copyfile '${r}' -> '${s}'`);return{indexSource:c,resolvedDestP:f,indexDest:p}}async appendFilePromise(r,s,a){if(this.readOnly)throw or.EROFS(`open '${r}'`);return typeof a>"u"?a={flag:"a"}:typeof a=="string"?a={flag:"a",encoding:a}:typeof a.flag>"u"&&(a={flag:"a",...a}),this.writeFilePromise(r,s,a)}appendFileSync(r,s,a={}){if(this.readOnly)throw or.EROFS(`open '${r}'`);return typeof a>"u"?a={flag:"a"}:typeof a=="string"?a={flag:"a",encoding:a}:typeof a.flag>"u"&&(a={flag:"a",...a}),this.writeFileSync(r,s,a)}fdToPath(r,s){let a=this.fds.get(r)?.p;if(typeof a>"u")throw or.EBADF(s);return a}async writeFilePromise(r,s,a){let{encoding:n,mode:c,index:f,resolvedP:p}=this.prepareWriteFile(r,a);f!==void 0&&typeof a=="object"&&a.flag&&a.flag.includes("a")&&(s=Buffer.concat([await this.getFileSource(f,{asyncDecompress:!0}),Buffer.from(s)])),n!==null&&(s=s.toString(n));let h=this.setFileSource(p,s);h!==f&&this.registerEntry(p,h),c!==null&&await this.chmodPromise(p,c)}writeFileSync(r,s,a){let{encoding:n,mode:c,index:f,resolvedP:p}=this.prepareWriteFile(r,a);f!==void 0&&typeof a=="object"&&a.flag&&a.flag.includes("a")&&(s=Buffer.concat([this.getFileSource(f),Buffer.from(s)])),n!==null&&(s=s.toString(n));let h=this.setFileSource(p,s);h!==f&&this.registerEntry(p,h),c!==null&&this.chmodSync(p,c)}prepareWriteFile(r,s){if(typeof r=="number"&&(r=this.fdToPath(r,"read")),this.readOnly)throw or.EROFS(`open '${r}'`);let a=this.resolveFilename(`open '${r}'`,r);if(this.listings.has(a))throw or.EISDIR(`open '${r}'`);let n=null,c=null;typeof s=="string"?n=s:typeof s=="object"&&({encoding:n=null,mode:c=null}=s);let f=this.entries.get(a);return{encoding:n,mode:c,resolvedP:a,index:f}}async unlinkPromise(r){return this.unlinkSync(r)}unlinkSync(r){if(this.readOnly)throw or.EROFS(`unlink '${r}'`);let s=this.resolveFilename(`unlink '${r}'`,r);if(this.listings.has(s))throw or.EISDIR(`unlink '${r}'`);let a=this.entries.get(s);if(typeof a>"u")throw or.EINVAL(`unlink '${r}'`);this.deleteEntry(s,a)}async utimesPromise(r,s,a){return this.utimesSync(r,s,a)}utimesSync(r,s,a){if(this.readOnly)throw or.EROFS(`utimes '${r}'`);let n=this.resolveFilename(`utimes '${r}'`,r);this.utimesImpl(n,a)}async lutimesPromise(r,s,a){return this.lutimesSync(r,s,a)}lutimesSync(r,s,a){if(this.readOnly)throw or.EROFS(`lutimes '${r}'`);let n=this.resolveFilename(`utimes '${r}'`,r,!1);this.utimesImpl(n,a)}utimesImpl(r,s){this.listings.has(r)&&(this.entries.has(r)||this.hydrateDirectory(r));let a=this.entries.get(r);if(a===void 0)throw new Error("Unreachable");this.zipImpl.setMtime(a,Ynt(s))}async mkdirPromise(r,s){return this.mkdirSync(r,s)}mkdirSync(r,{mode:s=493,recursive:a=!1}={}){if(a)return this.mkdirpSync(r,{chmod:s});if(this.readOnly)throw or.EROFS(`mkdir '${r}'`);let n=this.resolveFilename(`mkdir '${r}'`,r);if(this.entries.has(n)||this.listings.has(n))throw or.EEXIST(`mkdir '${r}'`);this.hydrateDirectory(n),this.chmodSync(n,s)}async rmdirPromise(r,s){return this.rmdirSync(r,s)}rmdirSync(r,{recursive:s=!1}={}){if(this.readOnly)throw or.EROFS(`rmdir '${r}'`);if(s){this.removeSync(r);return}let a=this.resolveFilename(`rmdir '${r}'`,r),n=this.listings.get(a);if(!n)throw or.ENOTDIR(`rmdir '${r}'`);if(n.size>0)throw or.ENOTEMPTY(`rmdir '${r}'`);let c=this.entries.get(a);if(typeof c>"u")throw or.EINVAL(`rmdir '${r}'`);this.deleteEntry(r,c)}async rmPromise(r,s){return this.rmSync(r,s)}rmSync(r,{recursive:s=!1}={}){if(this.readOnly)throw or.EROFS(`rm '${r}'`);if(s){this.removeSync(r);return}let a=this.resolveFilename(`rm '${r}'`,r),n=this.listings.get(a);if(!n)throw or.ENOTDIR(`rm '${r}'`);if(n.size>0)throw or.ENOTEMPTY(`rm '${r}'`);let c=this.entries.get(a);if(typeof c>"u")throw or.EINVAL(`rm '${r}'`);this.deleteEntry(r,c)}hydrateDirectory(r){let s=this.zipImpl.addDirectory(J.relative(vt.root,r));return this.registerListing(r),this.registerEntry(r,s),s}async linkPromise(r,s){return this.linkSync(r,s)}linkSync(r,s){throw or.EOPNOTSUPP(`link '${r}' -> '${s}'`)}async symlinkPromise(r,s){return this.symlinkSync(r,s)}symlinkSync(r,s){if(this.readOnly)throw or.EROFS(`symlink '${r}' -> '${s}'`);let a=this.resolveFilename(`symlink '${r}' -> '${s}'`,s);if(this.listings.has(a))throw or.EISDIR(`symlink '${r}' -> '${s}'`);if(this.entries.has(a))throw or.EEXIST(`symlink '${r}' -> '${s}'`);let n=this.setFileSource(a,r);this.registerEntry(a,n),this.zipImpl.setExternalAttributes(n,lm,(xa.constants.S_IFLNK|511)<<16),this.symlinkCount+=1}async readFilePromise(r,s){typeof s=="object"&&(s=s?s.encoding:void 0);let a=await this.readFileBuffer(r,{asyncDecompress:!0});return s?a.toString(s):a}readFileSync(r,s){typeof s=="object"&&(s=s?s.encoding:void 0);let a=this.readFileBuffer(r);return s?a.toString(s):a}readFileBuffer(r,s={asyncDecompress:!1}){typeof r=="number"&&(r=this.fdToPath(r,"read"));let a=this.resolveFilename(`open '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`open '${r}'`);if(r[r.length-1]==="/"&&!this.listings.has(a))throw or.ENOTDIR(`open '${r}'`);if(this.listings.has(a))throw or.EISDIR("read");let n=this.entries.get(a);if(n===void 0)throw new Error("Unreachable");return this.getFileSource(n,s)}async readdirPromise(r,s){return this.readdirSync(r,s)}readdirSync(r,s){let a=this.resolveFilename(`scandir '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`scandir '${r}'`);let n=this.listings.get(a);if(!n)throw or.ENOTDIR(`scandir '${r}'`);if(s?.recursive)if(s?.withFileTypes){let c=Array.from(n,f=>Object.assign(this.statImpl("lstat",J.join(r,f)),{name:f,path:vt.dot,parentPath:vt.dot}));for(let f of c){if(!f.isDirectory())continue;let p=J.join(f.path,f.name),h=this.listings.get(J.join(a,p));for(let E of h)c.push(Object.assign(this.statImpl("lstat",J.join(r,p,E)),{name:E,path:p,parentPath:p}))}return c}else{let c=[...n];for(let f of c){let p=this.listings.get(J.join(a,f));if(!(typeof p>"u"))for(let h of p)c.push(J.join(f,h))}return c}else return s?.withFileTypes?Array.from(n,c=>Object.assign(this.statImpl("lstat",J.join(r,c)),{name:c,path:void 0,parentPath:void 0})):[...n]}async readlinkPromise(r){let s=this.prepareReadlink(r);return(await this.getFileSource(s,{asyncDecompress:!0})).toString()}readlinkSync(r){let s=this.prepareReadlink(r);return this.getFileSource(s).toString()}prepareReadlink(r){let s=this.resolveFilename(`readlink '${r}'`,r,!1);if(!this.entries.has(s)&&!this.listings.has(s))throw or.ENOENT(`readlink '${r}'`);if(r[r.length-1]==="/"&&!this.listings.has(s))throw or.ENOTDIR(`open '${r}'`);if(this.listings.has(s))throw or.EINVAL(`readlink '${r}'`);let a=this.entries.get(s);if(a===void 0)throw new Error("Unreachable");if(!this.isSymbolicLink(a))throw or.EINVAL(`readlink '${r}'`);return a}async truncatePromise(r,s=0){let a=this.resolveFilename(`open '${r}'`,r),n=this.entries.get(a);if(typeof n>"u")throw or.EINVAL(`open '${r}'`);let c=await this.getFileSource(n,{asyncDecompress:!0}),f=Buffer.alloc(s,0);return c.copy(f),await this.writeFilePromise(r,f)}truncateSync(r,s=0){let a=this.resolveFilename(`open '${r}'`,r),n=this.entries.get(a);if(typeof n>"u")throw or.EINVAL(`open '${r}'`);let c=this.getFileSource(n),f=Buffer.alloc(s,0);return c.copy(f),this.writeFileSync(r,f)}async ftruncatePromise(r,s){return this.truncatePromise(this.fdToPath(r,"ftruncate"),s)}ftruncateSync(r,s){return this.truncateSync(this.fdToPath(r,"ftruncateSync"),s)}watch(r,s,a){let n;switch(typeof s){case"function":case"string":case"undefined":n=!0;break;default:({persistent:n=!0}=s);break}if(!n)return{on:()=>{},close:()=>{}};let c=setInterval(()=>{},24*60*60*1e3);return{on:()=>{},close:()=>{clearInterval(c)}}}watchFile(r,s,a){let n=J.resolve(vt.root,r);return sE(this,n,s,a)}unwatchFile(r,s){let a=J.resolve(vt.root,r);return md(this,a,s)}}});function Cpe(t,e,r=Buffer.alloc(0),s){let a=new As(r),n=C=>C===e||C.startsWith(`${e}/`)?C.slice(0,e.length):null,c=async(C,S)=>()=>a,f=(C,S)=>a,p={...t},h=new Yn(p),E=new e0({baseFs:h,getMountPoint:n,factoryPromise:c,factorySync:f,magicByte:21,maxAge:1/0,typeCheck:s?.typeCheck});return U2(Ipe.default,new t0(E)),a}var Ipe,wpe=Xe(()=>{Dt();Ipe=ut(Ie("fs"));vT()});var Bpe=Xe(()=>{mpe();vT();wpe()});var Oj,uv,ST,vpe=Xe(()=>{Dt();vT();Oj={CENTRAL_DIRECTORY:33639248,END_OF_CENTRAL_DIRECTORY:101010256},uv=22,ST=class t{constructor(e){this.filesShouldBeCached=!1;if("buffer"in e)throw new Error("Buffer based zip archives are not supported");if(!e.readOnly)throw new Error("Writable zip archives are not supported");this.baseFs=e.baseFs,this.fd=this.baseFs.openSync(e.path,"r");try{this.entries=t.readZipSync(this.fd,this.baseFs,e.size)}catch(r){throw this.baseFs.closeSync(this.fd),this.fd="closed",r}}static readZipSync(e,r,s){if(s=0;N--)if(n.readUInt32LE(N)===Oj.END_OF_CENTRAL_DIRECTORY){a=N;break}if(a===-1)throw new Error("Not a zip archive")}let c=n.readUInt16LE(a+10),f=n.readUInt32LE(a+12),p=n.readUInt32LE(a+16),h=n.readUInt16LE(a+20);if(a+h+uv>n.length)throw new Error("Zip archive inconsistent");if(c==65535||f==4294967295||p==4294967295)throw new Error("Zip 64 is not supported");if(f>s)throw new Error("Zip archive inconsistent");if(c>f/46)throw new Error("Zip archive inconsistent");let E=Buffer.alloc(f);if(r.readSync(e,E,0,E.length,p)!==E.length)throw new Error("Zip archive inconsistent");let C=[],S=0,P=0,I=0;for(;PE.length)throw new Error("Zip archive inconsistent");if(E.readUInt32LE(S)!==Oj.CENTRAL_DIRECTORY)throw new Error("Zip archive inconsistent");let N=E.readUInt16LE(S+4)>>>8;if(E.readUInt16LE(S+8)&1)throw new Error("Encrypted zip files are not supported");let W=E.readUInt16LE(S+10),ee=E.readUInt32LE(S+16),ie=E.readUInt16LE(S+28),ue=E.readUInt16LE(S+30),le=E.readUInt16LE(S+32),me=E.readUInt32LE(S+42),pe=E.toString("utf8",S+46,S+46+ie).replaceAll("\0"," ");if(pe.includes("\0"))throw new Error("Invalid ZIP file");let Be=E.readUInt32LE(S+20),Ce=E.readUInt32LE(S+38);C.push({name:pe,os:N,mtime:fi.SAFE_TIME,crc:ee,compressionMethod:W,isSymbolicLink:N===lm&&(Ce>>>16&fi.S_IFMT)===fi.S_IFLNK,size:E.readUInt32LE(S+24),compressedSize:Be,externalAttributes:Ce,localHeaderOffset:me}),I+=Be,P+=1,S+=46+ie+ue+le}if(I>s)throw new Error("Zip archive inconsistent");if(S!==E.length)throw new Error("Zip archive inconsistent");return C}getExternalAttributes(e){let r=this.entries[e];return[r.os,r.externalAttributes]}getListings(){return this.entries.map(e=>e.name)}getSymlinkCount(){let e=0;for(let r of this.entries)r.isSymbolicLink&&(e+=1);return e}stat(e){let r=this.entries[e];return{crc:r.crc,mtime:r.mtime,size:r.size}}locate(e){for(let r=0;rEpe,DEFLATE:()=>Nj,JsZipImpl:()=>ST,LibZipImpl:()=>BI,STORE:()=>Fj,ZIP_UNIX:()=>lm,ZipFS:()=>As,ZipOpenFS:()=>$f,getArchivePart:()=>xj,getLibzipPromise:()=>Jnt,getLibzipSync:()=>Vnt,makeEmptyArchive:()=>BT,mountMemoryDrive:()=>Cpe});function Vnt(){return cv()}async function Jnt(){return cv()}var Spe,eA=Xe(()=>{Dj();Spe=ut(ppe());dpe();Bpe();vpe();Qj();Ape(()=>{let t=(0,Spe.default)();return gpe(t)})});var Av,Dpe=Xe(()=>{Dt();Yt();pv();Av=class extends ot{constructor(){super(...arguments);this.cwd=ge.String("--cwd",process.cwd(),{description:"The directory to run the command in"});this.commandName=ge.String();this.args=ge.Proxy()}static{this.usage={description:"run a command using yarn's portable shell",details:` This command will run a command using Yarn's portable shell. Make sure to escape glob patterns, redirections, and other features that might be expanded by your own shell. Note: To escape something from Yarn's shell, you might have to escape it twice, the first time from your own shell. Note: Don't use this command in Yarn scripts, as Yarn's shell is automatically used. For a list of features, visit: https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-shell/README.md. `,examples:[["Run a simple command","$0 echo Hello"],["Run a command with a glob pattern","$0 echo '*.js'"],["Run a command with a redirection","$0 echo Hello World '>' hello.txt"],["Run a command with an escaped glob pattern (The double escape is needed in Unix shells)",`$0 echo '"*.js"'`],["Run a command with a variable (Double quotes are needed in Unix shells, to prevent them from expanding the variable)",'$0 "GREETING=Hello echo $GREETING World"']]}}async execute(){let r=this.args.length>0?`${this.commandName} ${this.args.join(" ")}`:this.commandName;return await vI(r,[],{cwd:fe.toPortablePath(this.cwd),stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})}}});var Vl,bpe=Xe(()=>{Vl=class extends Error{constructor(e){super(e),this.name="ShellError"}}});var PT={};Vt(PT,{fastGlobOptions:()=>kpe,isBraceExpansion:()=>Lj,isGlobPattern:()=>Knt,match:()=>znt,micromatchOptions:()=>bT});function Knt(t){if(!DT.default.scan(t,bT).isGlob)return!1;try{DT.default.parse(t,bT)}catch{return!1}return!0}function znt(t,{cwd:e,baseFs:r}){return(0,Ppe.default)(t,{...kpe,cwd:fe.fromPortablePath(e),fs:ax(xpe.default,new t0(r))})}function Lj(t){return DT.default.scan(t,bT).isBrace}var Ppe,xpe,DT,bT,kpe,Qpe=Xe(()=>{Dt();Ppe=ut(BQ()),xpe=ut(Ie("fs")),DT=ut(Go()),bT={strictBrackets:!0},kpe={onlyDirectories:!1,onlyFiles:!1}});function Mj(){}function Uj(){for(let t of cm)t.kill()}function Npe(t,e,r,s){return a=>{let n=a[0]instanceof tA.Transform?"pipe":a[0],c=a[1]instanceof tA.Transform?"pipe":a[1],f=a[2]instanceof tA.Transform?"pipe":a[2],p=(0,Rpe.default)(t,e,{...s,stdio:[n,c,f]});return cm.add(p),cm.size===1&&(process.on("SIGINT",Mj),process.on("SIGTERM",Uj)),a[0]instanceof tA.Transform&&a[0].pipe(p.stdin),a[1]instanceof tA.Transform&&p.stdout.pipe(a[1],{end:!1}),a[2]instanceof tA.Transform&&p.stderr.pipe(a[2],{end:!1}),{stdin:p.stdin,promise:new Promise(h=>{p.on("error",E=>{switch(cm.delete(p),cm.size===0&&(process.off("SIGINT",Mj),process.off("SIGTERM",Uj)),E.code){case"ENOENT":a[2].write(`command not found: ${t} `),h(127);break;case"EACCES":a[2].write(`permission denied: ${t} `),h(128);break;default:a[2].write(`uncaught error: ${E.message} `),h(1);break}}),p.on("close",E=>{cm.delete(p),cm.size===0&&(process.off("SIGINT",Mj),process.off("SIGTERM",Uj)),h(E!==null?E:129)})})}}}function Ope(t){return e=>{let r=e[0]==="pipe"?new tA.PassThrough:e[0];return{stdin:r,promise:Promise.resolve().then(()=>t({stdin:r,stdout:e[1],stderr:e[2]}))}}}function xT(t,e){return Hj.start(t,e)}function Tpe(t,e=null){let r=new tA.PassThrough,s=new Fpe.StringDecoder,a="";return r.on("data",n=>{let c=s.write(n),f;do if(f=c.indexOf(` `),f!==-1){let p=a+c.substring(0,f);c=c.substring(f+1),a="",t(e!==null?`${e} ${p}`:p)}while(f!==-1);a+=c}),r.on("end",()=>{let n=s.end();n!==""&&t(e!==null?`${e} ${n}`:n)}),r}function Lpe(t,{prefix:e}){return{stdout:Tpe(r=>t.stdout.write(`${r} `),t.stdout.isTTY?e:null),stderr:Tpe(r=>t.stderr.write(`${r} `),t.stderr.isTTY?e:null)}}var Rpe,tA,Fpe,cm,Oc,_j,Hj,jj=Xe(()=>{Rpe=ut(_U()),tA=Ie("stream"),Fpe=Ie("string_decoder"),cm=new Set;Oc=class{constructor(e){this.stream=e}close(){}get(){return this.stream}},_j=class{constructor(){this.stream=null}close(){if(this.stream===null)throw new Error("Assertion failed: No stream attached");this.stream.end()}attach(e){this.stream=e}get(){if(this.stream===null)throw new Error("Assertion failed: No stream attached");return this.stream}},Hj=class t{constructor(e,r){this.stdin=null;this.stdout=null;this.stderr=null;this.pipe=null;this.ancestor=e,this.implementation=r}static start(e,{stdin:r,stdout:s,stderr:a}){let n=new t(null,e);return n.stdin=r,n.stdout=s,n.stderr=a,n}pipeTo(e,r=1){let s=new t(this,e),a=new _j;return s.pipe=a,s.stdout=this.stdout,s.stderr=this.stderr,(r&1)===1?this.stdout=a:this.ancestor!==null&&(this.stderr=this.ancestor.stdout),(r&2)===2?this.stderr=a:this.ancestor!==null&&(this.stderr=this.ancestor.stderr),s}async exec(){let e=["ignore","ignore","ignore"];if(this.pipe)e[0]="pipe";else{if(this.stdin===null)throw new Error("Assertion failed: No input stream registered");e[0]=this.stdin.get()}let r;if(this.stdout===null)throw new Error("Assertion failed: No output stream registered");r=this.stdout,e[1]=r.get();let s;if(this.stderr===null)throw new Error("Assertion failed: No error stream registered");s=this.stderr,e[2]=s.get();let a=this.implementation(e);return this.pipe&&this.pipe.attach(a.stdin),await a.promise.then(n=>(r.close(),s.close(),n))}async run(){let e=[];for(let s=this;s;s=s.ancestor)e.push(s.exec());return(await Promise.all(e))[0]}}});var mv={};Vt(mv,{EntryCommand:()=>Av,ShellError:()=>Vl,execute:()=>vI,globUtils:()=>PT});function Mpe(t,e,r){let s=new Jl.PassThrough({autoDestroy:!0});switch(t){case 0:(e&1)===1&&r.stdin.pipe(s,{end:!1}),(e&2)===2&&r.stdin instanceof Jl.Writable&&s.pipe(r.stdin,{end:!1});break;case 1:(e&1)===1&&r.stdout.pipe(s,{end:!1}),(e&2)===2&&s.pipe(r.stdout,{end:!1});break;case 2:(e&1)===1&&r.stderr.pipe(s,{end:!1}),(e&2)===2&&s.pipe(r.stderr,{end:!1});break;default:throw new Vl(`Bad file descriptor: "${t}"`)}return s}function QT(t,e={}){let r={...t,...e};return r.environment={...t.environment,...e.environment},r.variables={...t.variables,...e.variables},r}async function Znt(t,e,r){let s=[],a=new Jl.PassThrough;return a.on("data",n=>s.push(n)),await TT(t,e,QT(r,{stdout:a})),Buffer.concat(s).toString().replace(/[\r\n]+$/,"")}async function Upe(t,e,r){let s=t.map(async n=>{let c=await um(n.args,e,r);return{name:n.name,value:c.join(" ")}});return(await Promise.all(s)).reduce((n,c)=>(n[c.name]=c.value,n),{})}function kT(t){return t.match(/[^ \r\n\t]+/g)||[]}async function Wpe(t,e,r,s,a=s){switch(t.name){case"$":s(String(process.pid));break;case"#":s(String(e.args.length));break;case"@":if(t.quoted)for(let n of e.args)a(n);else for(let n of e.args){let c=kT(n);for(let f=0;f=0&&n"u"&&(t.defaultValue?c=(await um(t.defaultValue,e,r)).join(" "):t.alternativeValue&&(c="")),typeof c>"u")throw f?new Vl(`Unbound argument #${n}`):new Vl(`Unbound variable "${t.name}"`);if(t.quoted)s(c);else{let p=kT(c);for(let E=0;Es.push(n));let a=Number(s.join(" "));return Number.isNaN(a)?hv({type:"variable",name:s.join(" ")},e,r):hv({type:"number",value:a},e,r)}else return $nt[t.type](await hv(t.left,e,r),await hv(t.right,e,r))}async function um(t,e,r){let s=new Map,a=[],n=[],c=E=>{n.push(E)},f=()=>{n.length>0&&a.push(n.join("")),n=[]},p=E=>{c(E),f()},h=(E,C,S)=>{let P=JSON.stringify({type:E,fd:C}),I=s.get(P);typeof I>"u"&&s.set(P,I=[]),I.push(S)};for(let E of t){let C=!1;switch(E.type){case"redirection":{let S=await um(E.args,e,r);for(let P of S)h(E.subtype,E.fd,P)}break;case"argument":for(let S of E.segments)switch(S.type){case"text":c(S.text);break;case"glob":c(S.pattern),C=!0;break;case"shell":{let P=await Znt(S.shell,e,r);if(S.quoted)c(P);else{let I=kT(P);for(let R=0;R"u")throw new Error("Assertion failed: Expected a glob pattern to have been set");let P=await e.glob.match(S,{cwd:r.cwd,baseFs:e.baseFs});if(P.length===0){let I=Lj(S)?". Note: Brace expansion of arbitrary strings isn't currently supported. For more details, please read this issue: https://github.com/yarnpkg/berry/issues/22":"";throw new Vl(`No matches found: "${S}"${I}`)}for(let I of P.sort())p(I)}}if(s.size>0){let E=[];for(let[C,S]of s.entries())E.splice(E.length,0,C,String(S.length),...S);a.splice(0,0,"__ysh_set_redirects",...E,"--")}return a}function gv(t,e,r){e.builtins.has(t[0])||(t=["command",...t]);let s=fe.fromPortablePath(r.cwd),a=r.environment;typeof a.PWD<"u"&&(a={...a,PWD:s});let[n,...c]=t;if(n==="command")return Npe(c[0],c.slice(1),e,{cwd:s,env:a});let f=e.builtins.get(n);if(typeof f>"u")throw new Error(`Assertion failed: A builtin should exist for "${n}"`);return Ope(async({stdin:p,stdout:h,stderr:E})=>{let{stdin:C,stdout:S,stderr:P}=r;r.stdin=p,r.stdout=h,r.stderr=E;try{return await f(c,e,r)}finally{r.stdin=C,r.stdout=S,r.stderr=P}})}function eit(t,e,r){return s=>{let a=new Jl.PassThrough,n=TT(t,e,QT(r,{stdin:a}));return{stdin:a,promise:n}}}function tit(t,e,r){return s=>{let a=new Jl.PassThrough,n=TT(t,e,r);return{stdin:a,promise:n}}}function _pe(t,e,r,s){if(e.length===0)return t;{let a;do a=String(Math.random());while(Object.hasOwn(s.procedures,a));return s.procedures={...s.procedures},s.procedures[a]=t,gv([...e,"__ysh_run_procedure",a],r,s)}}async function Hpe(t,e,r){let s=t,a=null,n=null;for(;s;){let c=s.then?{...r}:r,f;switch(s.type){case"command":{let p=await um(s.args,e,r),h=await Upe(s.envs,e,r);f=s.envs.length?gv(p,e,QT(c,{environment:h})):gv(p,e,c)}break;case"subshell":{let p=await um(s.args,e,r),h=eit(s.subshell,e,c);f=_pe(h,p,e,c)}break;case"group":{let p=await um(s.args,e,r),h=tit(s.group,e,c);f=_pe(h,p,e,c)}break;case"envs":{let p=await Upe(s.envs,e,r);c.environment={...c.environment,...p},f=gv(["true"],e,c)}break}if(typeof f>"u")throw new Error("Assertion failed: An action should have been generated");if(a===null)n=xT(f,{stdin:new Oc(c.stdin),stdout:new Oc(c.stdout),stderr:new Oc(c.stderr)});else{if(n===null)throw new Error("Assertion failed: The execution pipeline should have been setup");switch(a){case"|":n=n.pipeTo(f,1);break;case"|&":n=n.pipeTo(f,3);break}}s.then?(a=s.then.type,s=s.then.chain):s=null}if(n===null)throw new Error("Assertion failed: The execution pipeline should have been setup");return await n.run()}async function rit(t,e,r,{background:s=!1}={}){function a(n){let c=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],f=c[n%c.length];return jpe.default.hex(f)}if(s){let n=r.nextBackgroundJobIndex++,c=a(n),f=`[${n}]`,p=c(f),{stdout:h,stderr:E}=Lpe(r,{prefix:p});return r.backgroundJobs.push(Hpe(t,e,QT(r,{stdout:h,stderr:E})).catch(C=>E.write(`${C.message} `)).finally(()=>{r.stdout.isTTY&&r.stdout.write(`Job ${p}, '${c(AE(t))}' has ended `)})),0}return await Hpe(t,e,r)}async function nit(t,e,r,{background:s=!1}={}){let a,n=f=>{a=f,r.variables["?"]=String(f)},c=async f=>{try{return await rit(f.chain,e,r,{background:s&&typeof f.then>"u"})}catch(p){if(!(p instanceof Vl))throw p;return r.stderr.write(`${p.message} `),1}};for(n(await c(t));t.then;){if(r.exitCode!==null)return r.exitCode;switch(t.then.type){case"&&":a===0&&n(await c(t.then.line));break;case"||":a!==0&&n(await c(t.then.line));break;default:throw new Error(`Assertion failed: Unsupported command type: "${t.then.type}"`)}t=t.then.line}return a}async function TT(t,e,r){let s=r.backgroundJobs;r.backgroundJobs=[];let a=0;for(let{command:n,type:c}of t){if(a=await nit(n,e,r,{background:c==="&"}),r.exitCode!==null)return r.exitCode;r.variables["?"]=String(a)}return await Promise.all(r.backgroundJobs),r.backgroundJobs=s,a}function Ype(t){switch(t.type){case"variable":return t.name==="@"||t.name==="#"||t.name==="*"||Number.isFinite(parseInt(t.name,10))||"defaultValue"in t&&!!t.defaultValue&&t.defaultValue.some(e=>dv(e))||"alternativeValue"in t&&!!t.alternativeValue&&t.alternativeValue.some(e=>dv(e));case"arithmetic":return Gj(t.arithmetic);case"shell":return qj(t.shell);default:return!1}}function dv(t){switch(t.type){case"redirection":return t.args.some(e=>dv(e));case"argument":return t.segments.some(e=>Ype(e));default:throw new Error(`Assertion failed: Unsupported argument type: "${t.type}"`)}}function Gj(t){switch(t.type){case"variable":return Ype(t);case"number":return!1;default:return Gj(t.left)||Gj(t.right)}}function qj(t){return t.some(({command:e})=>{for(;e;){let r=e.chain;for(;r;){let s;switch(r.type){case"subshell":s=qj(r.subshell);break;case"command":s=r.envs.some(a=>a.args.some(n=>dv(n)))||r.args.some(a=>dv(a));break}if(s)return!0;if(!r.then)break;r=r.then.chain}if(!e.then)break;e=e.then.line}return!1})}async function vI(t,e=[],{baseFs:r=new Yn,builtins:s={},cwd:a=fe.toPortablePath(process.cwd()),env:n=process.env,stdin:c=process.stdin,stdout:f=process.stdout,stderr:p=process.stderr,variables:h={},glob:E=PT}={}){let C={};for(let[I,R]of Object.entries(n))typeof R<"u"&&(C[I]=R);let S=new Map(Xnt);for(let[I,R]of Object.entries(s))S.set(I,R);c===null&&(c=new Jl.PassThrough,c.end());let P=ux(t,E);if(!qj(P)&&P.length>0&&e.length>0){let{command:I}=P[P.length-1];for(;I.then;)I=I.then.line;let R=I.chain;for(;R.then;)R=R.then.chain;R.type==="command"&&(R.args=R.args.concat(e.map(N=>({type:"argument",segments:[{type:"text",text:N}]}))))}return await TT(P,{args:e,baseFs:r,builtins:S,initialStdin:c,initialStdout:f,initialStderr:p,glob:E},{cwd:a,environment:C,exitCode:null,procedures:{},stdin:c,stdout:f,stderr:p,variables:Object.assign({},h,{"?":0}),nextBackgroundJobIndex:1,backgroundJobs:[]})}var jpe,Gpe,Jl,qpe,Xnt,$nt,pv=Xe(()=>{Dt();wc();jpe=ut(TE()),Gpe=Ie("os"),Jl=Ie("stream"),qpe=Ie("timers/promises");Dpe();bpe();Qpe();jj();jj();Xnt=new Map([["cd",async([t=(0,Gpe.homedir)(),...e],r,s)=>{let a=J.resolve(s.cwd,fe.toPortablePath(t));if(!(await r.baseFs.statPromise(a).catch(c=>{throw c.code==="ENOENT"?new Vl(`cd: no such file or directory: ${t}`):c})).isDirectory())throw new Vl(`cd: not a directory: ${t}`);return s.cwd=a,0}],["pwd",async(t,e,r)=>(r.stdout.write(`${fe.fromPortablePath(r.cwd)} `),0)],[":",async(t,e,r)=>0],["true",async(t,e,r)=>0],["false",async(t,e,r)=>1],["exit",async([t,...e],r,s)=>s.exitCode=parseInt(t??s.variables["?"],10)],["echo",async(t,e,r)=>(r.stdout.write(`${t.join(" ")} `),0)],["sleep",async([t],e,r)=>{if(typeof t>"u")throw new Vl("sleep: missing operand");let s=Number(t);if(Number.isNaN(s))throw new Vl(`sleep: invalid time interval '${t}'`);return await(0,qpe.setTimeout)(1e3*s,0)}],["unset",async(t,e,r)=>{for(let s of t)delete r.environment[s],delete r.variables[s];return 0}],["__ysh_run_procedure",async(t,e,r)=>{let s=r.procedures[t[0]];return await xT(s,{stdin:new Oc(r.stdin),stdout:new Oc(r.stdout),stderr:new Oc(r.stderr)}).run()}],["__ysh_set_redirects",async(t,e,r)=>{let s=r.stdin,a=r.stdout,n=r.stderr,c=[],f=[],p=[],h=0;for(;t[h]!=="--";){let C=t[h++],{type:S,fd:P}=JSON.parse(C),I=W=>{switch(P){case null:case 0:c.push(W);break;default:throw new Error(`Unsupported file descriptor: "${P}"`)}},R=W=>{switch(P){case null:case 1:f.push(W);break;case 2:p.push(W);break;default:throw new Error(`Unsupported file descriptor: "${P}"`)}},N=Number(t[h++]),U=h+N;for(let W=h;We.baseFs.createReadStream(J.resolve(r.cwd,fe.toPortablePath(t[W]))));break;case"<<<":I(()=>{let ee=new Jl.PassThrough;return process.nextTick(()=>{ee.write(`${t[W]} `),ee.end()}),ee});break;case"<&":I(()=>Mpe(Number(t[W]),1,r));break;case">":case">>":{let ee=J.resolve(r.cwd,fe.toPortablePath(t[W]));R(ee==="/dev/null"?new Jl.Writable({autoDestroy:!0,emitClose:!0,write(ie,ue,le){setImmediate(le)}}):e.baseFs.createWriteStream(ee,S===">>"?{flags:"a"}:void 0))}break;case">&":R(Mpe(Number(t[W]),2,r));break;default:throw new Error(`Assertion failed: Unsupported redirection type: "${S}"`)}}if(c.length>0){let C=new Jl.PassThrough;s=C;let S=P=>{if(P===c.length)C.end();else{let I=c[P]();I.pipe(C,{end:!1}),I.on("end",()=>{S(P+1)})}};S(0)}if(f.length>0){let C=new Jl.PassThrough;a=C;for(let S of f)C.pipe(S)}if(p.length>0){let C=new Jl.PassThrough;n=C;for(let S of p)C.pipe(S)}let E=await xT(gv(t.slice(h+1),e,r),{stdin:new Oc(s),stdout:new Oc(a),stderr:new Oc(n)}).run();return await Promise.all(f.map(C=>new Promise((S,P)=>{C.on("error",I=>{P(I)}),C.on("close",()=>{S()}),C.end()}))),await Promise.all(p.map(C=>new Promise((S,P)=>{C.on("error",I=>{P(I)}),C.on("close",()=>{S()}),C.end()}))),E}]]);$nt={addition:(t,e)=>t+e,subtraction:(t,e)=>t-e,multiplication:(t,e)=>t*e,division:(t,e)=>Math.trunc(t/e)}});var Vpe=_((S4t,RT)=>{function iit(){var t=0,e=1,r=2,s=3,a=4,n=5,c=6,f=7,p=8,h=9,E=10,C=11,S=12,P=13,I=14,R=15,N=16,U=17,W=0,ee=1,ie=2,ue=3,le=4;function me(g,we){return 55296<=g.charCodeAt(we)&&g.charCodeAt(we)<=56319&&56320<=g.charCodeAt(we+1)&&g.charCodeAt(we+1)<=57343}function pe(g,we){we===void 0&&(we=0);var ye=g.charCodeAt(we);if(55296<=ye&&ye<=56319&&we=1){var Ae=g.charCodeAt(we-1),se=ye;return 55296<=Ae&&Ae<=56319?(Ae-55296)*1024+(se-56320)+65536:se}return ye}function Be(g,we,ye){var Ae=[g].concat(we).concat([ye]),se=Ae[Ae.length-2],Z=ye,De=Ae.lastIndexOf(I);if(De>1&&Ae.slice(1,De).every(function(j){return j==s})&&[s,P,U].indexOf(g)==-1)return ie;var Re=Ae.lastIndexOf(a);if(Re>0&&Ae.slice(1,Re).every(function(j){return j==a})&&[S,a].indexOf(se)==-1)return Ae.filter(function(j){return j==a}).length%2==1?ue:le;if(se==t&&Z==e)return W;if(se==r||se==t||se==e)return Z==I&&we.every(function(j){return j==s})?ie:ee;if(Z==r||Z==t||Z==e)return ee;if(se==c&&(Z==c||Z==f||Z==h||Z==E))return W;if((se==h||se==f)&&(Z==f||Z==p))return W;if((se==E||se==p)&&Z==p)return W;if(Z==s||Z==R)return W;if(Z==n)return W;if(se==S)return W;var mt=Ae.indexOf(s)!=-1?Ae.lastIndexOf(s)-1:Ae.length-2;return[P,U].indexOf(Ae[mt])!=-1&&Ae.slice(mt+1,-1).every(function(j){return j==s})&&Z==I||se==R&&[N,U].indexOf(Z)!=-1?W:we.indexOf(a)!=-1?ie:se==a&&Z==a?W:ee}this.nextBreak=function(g,we){if(we===void 0&&(we=0),we<0)return 0;if(we>=g.length-1)return g.length;for(var ye=Ce(pe(g,we)),Ae=[],se=we+1;se{var sit=/^(.*?)(\x1b\[[^m]+m|\x1b\]8;;.*?(\x1b\\|\u0007))/,FT;function oit(){if(FT)return FT;if(typeof Intl.Segmenter<"u"){let t=new Intl.Segmenter("en",{granularity:"grapheme"});return FT=e=>Array.from(t.segment(e),({segment:r})=>r)}else{let t=Vpe(),e=new t;return FT=r=>e.splitGraphemes(r)}}Jpe.exports=(t,e=0,r=t.length)=>{if(e<0||r<0)throw new RangeError("Negative indices aren't supported by this implementation");let s=r-e,a="",n=0,c=0;for(;t.length>0;){let f=t.match(sit)||[t,t,void 0],p=oit()(f[1]),h=Math.min(e-n,p.length);p=p.slice(h);let E=Math.min(s-c,p.length);a+=p.slice(0,E).join(""),n+=h,c+=E,typeof f[2]<"u"&&(a+=f[2]),t=t.slice(f[0].length)}return a}});var fn,yv=Xe(()=>{fn=process.env.YARN_IS_TEST_ENV?"0.0.0":"4.12.0"});function the(t,{configuration:e,json:r}){if(!e.get("enableMessageNames"))return"";let a=Yf(t===null?0:t);return!r&&t===null?Ht(e,a,"grey"):a}function Wj(t,{configuration:e,json:r}){let s=the(t,{configuration:e,json:r});if(!s||t===null||t===0)return s;let a=Br[t],n=`https://yarnpkg.com/advanced/error-codes#${s}---${a}`.toLowerCase();return KE(e,s,n)}async function SI({configuration:t,stdout:e,forceError:r},s){let a=await Ot.start({configuration:t,stdout:e,includeFooter:!1},async n=>{let c=!1,f=!1;for(let p of s)typeof p.option<"u"&&(p.error||r?(f=!0,n.reportError(50,p.message)):(c=!0,n.reportWarning(50,p.message)),p.callback?.());c&&!f&&n.reportSeparator()});return a.hasErrors()?a.exitCode():null}var $pe,NT,ait,zpe,Xpe,D0,ehe,Zpe,lit,cit,OT,uit,Ot,Ev=Xe(()=>{$pe=ut(Kpe()),NT=ut(Fd());Gx();Tc();yv();xc();ait="\xB7",zpe=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],Xpe=80,D0=NT.default.GITHUB_ACTIONS?{start:t=>`::group::${t} `,end:t=>`::endgroup:: `}:NT.default.TRAVIS?{start:t=>`travis_fold:start:${t} `,end:t=>`travis_fold:end:${t} `}:NT.default.GITLAB?{start:t=>`section_start:${Math.floor(Date.now()/1e3)}:${t.toLowerCase().replace(/\W+/g,"_")}[collapsed=true]\r\x1B[0K${t} `,end:t=>`section_end:${Math.floor(Date.now()/1e3)}:${t.toLowerCase().replace(/\W+/g,"_")}\r\x1B[0K`}:null,ehe=D0!==null,Zpe=new Date,lit=["iTerm.app","Apple_Terminal","WarpTerminal","vscode"].includes(process.env.TERM_PROGRAM)||!!process.env.WT_SESSION,cit=t=>t,OT=cit({patrick:{date:[17,3],chars:["\u{1F340}","\u{1F331}"],size:40},simba:{date:[19,7],chars:["\u{1F981}","\u{1F334}"],size:40},jack:{date:[31,10],chars:["\u{1F383}","\u{1F987}"],size:40},hogsfather:{date:[31,12],chars:["\u{1F389}","\u{1F384}"],size:40},default:{chars:["=","-"],size:80}}),uit=lit&&Object.keys(OT).find(t=>{let e=OT[t];return!(e.date&&(e.date[0]!==Zpe.getDate()||e.date[1]!==Zpe.getMonth()+1))})||"default";Ot=class extends Ao{constructor({configuration:r,stdout:s,json:a=!1,forceSectionAlignment:n=!1,includeNames:c=!0,includePrefix:f=!0,includeFooter:p=!0,includeLogs:h=!a,includeInfos:E=h,includeWarnings:C=h}){super();this.uncommitted=new Set;this.warningCount=0;this.errorCount=0;this.timerFooter=[];this.startTime=Date.now();this.indent=0;this.level=0;this.progress=new Map;this.progressTime=0;this.progressFrame=0;this.progressTimeout=null;this.progressStyle=null;this.progressMaxScaledSize=null;if(RB(this,{configuration:r}),this.configuration=r,this.forceSectionAlignment=n,this.includeNames=c,this.includePrefix=f,this.includeFooter=p,this.includeInfos=E,this.includeWarnings=C,this.json=a,this.stdout=s,r.get("enableProgressBars")&&!a&&s.isTTY&&s.columns>22){let S=r.get("progressBarStyle")||uit;if(!Object.hasOwn(OT,S))throw new Error("Assertion failed: Invalid progress bar style");this.progressStyle=OT[S];let P=Math.min(this.getRecommendedLength(),80);this.progressMaxScaledSize=Math.floor(this.progressStyle.size*P/80)}}static async start(r,s){let a=new this(r),n=process.emitWarning;process.emitWarning=(c,f)=>{if(typeof c!="string"){let h=c;c=h.message,f=f??h.name}let p=typeof f<"u"?`${f}: ${c}`:c;a.reportWarning(0,p)},r.includeVersion&&a.reportInfo(0,zd(r.configuration,`Yarn ${fn}`,2));try{await s(a)}catch(c){a.reportExceptionOnce(c)}finally{await a.finalize(),process.emitWarning=n}return a}hasErrors(){return this.errorCount>0}exitCode(){return this.hasErrors()?1:0}getRecommendedLength(){let s=this.progressStyle!==null?this.stdout.columns-1:super.getRecommendedLength();return Math.max(40,s-12-this.indent*2)}startSectionSync({reportHeader:r,reportFooter:s,skipIfEmpty:a},n){let c={committed:!1,action:()=>{r?.()}};a?this.uncommitted.add(c):(c.action(),c.committed=!0);let f=Date.now();try{return n()}catch(p){throw this.reportExceptionOnce(p),p}finally{let p=Date.now();this.uncommitted.delete(c),c.committed&&s?.(p-f)}}async startSectionPromise({reportHeader:r,reportFooter:s,skipIfEmpty:a},n){let c={committed:!1,action:()=>{r?.()}};a?this.uncommitted.add(c):(c.action(),c.committed=!0);let f=Date.now();try{return await n()}catch(p){throw this.reportExceptionOnce(p),p}finally{let p=Date.now();this.uncommitted.delete(c),c.committed&&s?.(p-f)}}startTimerImpl(r,s,a){return{cb:typeof s=="function"?s:a,reportHeader:()=>{this.level+=1,this.reportInfo(null,`\u250C ${r}`),this.indent+=1,D0!==null&&!this.json&&this.includeInfos&&this.stdout.write(D0.start(r))},reportFooter:f=>{if(this.indent-=1,D0!==null&&!this.json&&this.includeInfos){this.stdout.write(D0.end(r));for(let p of this.timerFooter)p()}this.configuration.get("enableTimers")&&f>200?this.reportInfo(null,`\u2514 Completed in ${Ht(this.configuration,f,ht.DURATION)}`):this.reportInfo(null,"\u2514 Completed"),this.level-=1},skipIfEmpty:(typeof s=="function"?{}:s).skipIfEmpty}}startTimerSync(r,s,a){let{cb:n,...c}=this.startTimerImpl(r,s,a);return this.startSectionSync(c,n)}async startTimerPromise(r,s,a){let{cb:n,...c}=this.startTimerImpl(r,s,a);return this.startSectionPromise(c,n)}reportSeparator(){this.indent===0?this.writeLine(""):this.reportInfo(null,"")}reportInfo(r,s){if(!this.includeInfos)return;this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:"",c=`${this.formatPrefix(n,"blueBright")}${s}`;this.json?this.reportJson({type:"info",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(c)}reportWarning(r,s){if(this.warningCount+=1,!this.includeWarnings)return;this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:"";this.json?this.reportJson({type:"warning",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(`${this.formatPrefix(n,"yellowBright")}${s}`)}reportError(r,s){this.errorCount+=1,this.timerFooter.push(()=>this.reportErrorImpl(r,s)),this.reportErrorImpl(r,s)}reportErrorImpl(r,s){this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:"";this.json?this.reportJson({type:"error",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(`${this.formatPrefix(n,"redBright")}${s}`,{truncate:!1})}reportFold(r,s){if(!D0)return;let a=`${D0.start(r)}${s}${D0.end(r)}`;this.timerFooter.push(()=>this.stdout.write(a))}reportProgress(r){if(this.progressStyle===null)return{...Promise.resolve(),stop:()=>{}};if(r.hasProgress&&r.hasTitle)throw new Error("Unimplemented: Progress bars can't have both progress and titles.");let s=!1,a=Promise.resolve().then(async()=>{let c={progress:r.hasProgress?0:void 0,title:r.hasTitle?"":void 0};this.progress.set(r,{definition:c,lastScaledSize:r.hasProgress?-1:void 0,lastTitle:void 0}),this.refreshProgress({delta:-1});for await(let{progress:f,title:p}of r)s||c.progress===f&&c.title===p||(c.progress=f,c.title=p,this.refreshProgress());n()}),n=()=>{s||(s=!0,this.progress.delete(r),this.refreshProgress({delta:1}))};return{...a,stop:n}}reportJson(r){this.json&&this.writeLine(`${JSON.stringify(r)}`)}async finalize(){if(!this.includeFooter)return;let r="";this.errorCount>0?r="Failed with errors":this.warningCount>0?r="Done with warnings":r="Done";let s=Ht(this.configuration,Date.now()-this.startTime,ht.DURATION),a=this.configuration.get("enableTimers")?`${r} in ${s}`:r;this.errorCount>0?this.reportError(0,a):this.warningCount>0?this.reportWarning(0,a):this.reportInfo(0,a)}writeLine(r,{truncate:s}={}){this.clearProgress({clear:!0}),this.stdout.write(`${this.truncate(r,{truncate:s})} `),this.writeProgress()}writeLines(r,{truncate:s}={}){this.clearProgress({delta:r.length});for(let a of r)this.stdout.write(`${this.truncate(a,{truncate:s})} `);this.writeProgress()}commit(){let r=this.uncommitted;this.uncommitted=new Set;for(let s of r)s.committed=!0,s.action()}clearProgress({delta:r=0,clear:s=!1}){this.progressStyle!==null&&this.progress.size+r>0&&(this.stdout.write(`\x1B[${this.progress.size+r}A`),(r>0||s)&&this.stdout.write("\x1B[0J"))}writeProgress(){if(this.progressStyle===null||(this.progressTimeout!==null&&clearTimeout(this.progressTimeout),this.progressTimeout=null,this.progress.size===0))return;let r=Date.now();r-this.progressTime>Xpe&&(this.progressFrame=(this.progressFrame+1)%zpe.length,this.progressTime=r);let s=zpe[this.progressFrame];for(let a of this.progress.values()){let n="";if(typeof a.lastScaledSize<"u"){let h=this.progressStyle.chars[0].repeat(a.lastScaledSize),E=this.progressStyle.chars[1].repeat(this.progressMaxScaledSize-a.lastScaledSize);n=` ${h}${E}`}let c=this.formatName(null),f=c?`${c}: `:"",p=a.definition.title?` ${a.definition.title}`:"";this.stdout.write(`${Ht(this.configuration,"\u27A4","blueBright")} ${f}${s}${n}${p} `)}this.progressTimeout=setTimeout(()=>{this.refreshProgress({force:!0})},Xpe)}refreshProgress({delta:r=0,force:s=!1}={}){let a=!1,n=!1;if(s||this.progress.size===0)a=!0;else for(let c of this.progress.values()){let f=typeof c.definition.progress<"u"?Math.trunc(this.progressMaxScaledSize*c.definition.progress):void 0,p=c.lastScaledSize;c.lastScaledSize=f;let h=c.lastTitle;if(c.lastTitle=c.definition.title,f!==p||(n=h!==c.definition.title)){a=!0;break}}a&&(this.clearProgress({delta:r,clear:n}),this.writeProgress())}truncate(r,{truncate:s}={}){return this.progressStyle===null&&(s=!1),typeof s>"u"&&(s=this.configuration.get("preferTruncatedLines")),s&&(r=(0,$pe.default)(r,0,this.stdout.columns-1)),r}formatName(r){return this.includeNames?the(r,{configuration:this.configuration,json:this.json}):""}formatPrefix(r,s){return this.includePrefix?`${Ht(this.configuration,"\u27A4",s)} ${r}${this.formatIndent()}`:""}formatNameWithHyperlink(r){return this.includeNames?Wj(r,{configuration:this.configuration,json:this.json}):""}formatIndent(){return this.level>0||!this.forceSectionAlignment?"\u2502 ".repeat(this.indent):`${ait} `}}});var In={};Vt(In,{PackageManager:()=>nhe,detectPackageManager:()=>ihe,executePackageAccessibleBinary:()=>che,executePackageScript:()=>LT,executePackageShellcode:()=>Yj,executeWorkspaceAccessibleBinary:()=>mit,executeWorkspaceLifecycleScript:()=>ahe,executeWorkspaceScript:()=>ohe,getPackageAccessibleBinaries:()=>MT,getWorkspaceAccessibleBinaries:()=>lhe,hasPackageScript:()=>hit,hasWorkspaceScript:()=>Vj,isNodeScript:()=>Jj,makeScriptEnv:()=>Iv,maybeExecuteWorkspaceLifecycleScript:()=>dit,prepareExternalProject:()=>pit});async function b0(t,e,r,s=[]){if(process.platform==="win32"){let a=`@goto #_undefined_# 2>NUL || @title %COMSPEC% & @setlocal & @"${r}" ${s.map(n=>`"${n.replace('"','""')}"`).join(" ")} %*`;await ce.writeFilePromise(J.format({dir:t,name:e,ext:".cmd"}),a)}await ce.writeFilePromise(J.join(t,e),`#!/bin/sh exec "${r}" ${s.map(a=>`'${a.replace(/'/g,`'"'"'`)}'`).join(" ")} "$@" `,{mode:493})}async function ihe(t){let e=await Ut.tryFind(t);if(e?.packageManager){let s=xQ(e.packageManager);if(s?.name){let a=`found ${JSON.stringify({packageManager:e.packageManager})} in manifest`,[n]=s.reference.split(".");switch(s.name){case"yarn":return{packageManagerField:!0,packageManager:Number(n)===1?"Yarn Classic":"Yarn",reason:a};case"npm":return{packageManagerField:!0,packageManager:"npm",reason:a};case"pnpm":return{packageManagerField:!0,packageManager:"pnpm",reason:a}}}}let r;try{r=await ce.readFilePromise(J.join(t,Er.lockfile),"utf8")}catch{}return r!==void 0?r.match(/^__metadata:$/m)?{packageManager:"Yarn",reason:'"__metadata" key found in yarn.lock'}:{packageManager:"Yarn Classic",reason:'"__metadata" key not found in yarn.lock, must be a Yarn classic lockfile'}:ce.existsSync(J.join(t,"package-lock.json"))?{packageManager:"npm",reason:`found npm's "package-lock.json" lockfile`}:ce.existsSync(J.join(t,"pnpm-lock.yaml"))?{packageManager:"pnpm",reason:`found pnpm's "pnpm-lock.yaml" lockfile`}:null}async function Iv({project:t,locator:e,binFolder:r,ignoreCorepack:s,lifecycleScript:a,baseEnv:n=t?.configuration.env??process.env}){let c={};for(let[E,C]of Object.entries(n))typeof C<"u"&&(c[E.toLowerCase()!=="path"?E:"PATH"]=C);let f=fe.fromPortablePath(r);c.BERRY_BIN_FOLDER=fe.fromPortablePath(f);let p=process.env.COREPACK_ROOT&&!s?fe.join(process.env.COREPACK_ROOT,"dist/yarn.js"):process.argv[1];if(await Promise.all([b0(r,"node",process.execPath),...fn!==null?[b0(r,"run",process.execPath,[p,"run"]),b0(r,"yarn",process.execPath,[p]),b0(r,"yarnpkg",process.execPath,[p]),b0(r,"node-gyp",process.execPath,[p,"run","--top-level","node-gyp"])]:[]]),t&&(c.INIT_CWD=fe.fromPortablePath(t.configuration.startingCwd),c.PROJECT_CWD=fe.fromPortablePath(t.cwd)),c.PATH=c.PATH?`${f}${fe.delimiter}${c.PATH}`:`${f}`,c.npm_execpath=`${f}${fe.sep}yarn`,c.npm_node_execpath=`${f}${fe.sep}node`,e){if(!t)throw new Error("Assertion failed: Missing project");let E=t.tryWorkspaceByLocator(e),C=E?E.manifest.version??"":t.storedPackages.get(e.locatorHash).version??"";c.npm_package_name=un(e),c.npm_package_version=C;let S;if(E)S=E.cwd;else{let P=t.storedPackages.get(e.locatorHash);if(!P)throw new Error(`Package for ${Yr(t.configuration,e)} not found in the project`);let I=t.configuration.getLinkers(),R={project:t,report:new Ot({stdout:new P0.PassThrough,configuration:t.configuration})},N=I.find(U=>U.supportsPackage(P,R));if(!N)throw new Error(`The package ${Yr(t.configuration,P)} isn't supported by any of the available linkers`);S=await N.findPackageLocation(P,R)}c.npm_package_json=fe.fromPortablePath(J.join(S,Er.manifest))}let h=fn!==null?`yarn/${fn}`:`yarn/${Pp("@yarnpkg/core").version}-core`;return c.npm_config_user_agent=`${h} npm/? node/${process.version} ${process.platform} ${process.arch}`,a&&(c.npm_lifecycle_event=a),t&&await t.configuration.triggerHook(E=>E.setupScriptEnvironment,t,c,async(E,C,S)=>await b0(r,E,C,S)),c}async function pit(t,e,{configuration:r,report:s,workspace:a=null,locator:n=null}){await Ait(async()=>{await ce.mktempPromise(async c=>{let f=J.join(c,"pack.log"),p=null,{stdout:h,stderr:E}=r.getSubprocessStreams(f,{prefix:fe.fromPortablePath(t),report:s}),C=n&&Gu(n)?rI(n):n,S=C?ll(C):"an external project";h.write(`Packing ${S} from sources `);let P=await ihe(t),I;P!==null?(h.write(`Using ${P.packageManager} for bootstrap. Reason: ${P.reason} `),I=P.packageManager):(h.write(`No package manager configuration detected; defaulting to Yarn `),I="Yarn");let R=I==="Yarn"&&!P?.packageManagerField;await ce.mktempPromise(async N=>{let U=await Iv({binFolder:N,ignoreCorepack:R,baseEnv:{...process.env,COREPACK_ENABLE_AUTO_PIN:"0"}}),ee=new Map([["Yarn Classic",async()=>{let ue=a!==null?["workspace",a]:[],le=J.join(t,Er.manifest),me=await ce.readFilePromise(le),pe=await Wu(process.execPath,[process.argv[1],"set","version","classic","--only-if-needed","--yarn-path"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(pe.code!==0)return pe.code;await ce.writeFilePromise(le,me),await ce.appendFilePromise(J.join(t,".npmignore"),`/.yarn `),h.write(` `),delete U.NODE_ENV;let Be=await Wu("yarn",["install"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(Be.code!==0)return Be.code;h.write(` `);let Ce=await Wu("yarn",[...ue,"pack","--filename",fe.fromPortablePath(e)],{cwd:t,env:U,stdin:p,stdout:h,stderr:E});return Ce.code!==0?Ce.code:0}],["Yarn",async()=>{let ue=a!==null?["workspace",a]:[];U.YARN_ENABLE_INLINE_BUILDS="1";let le=J.join(t,Er.lockfile);await ce.existsPromise(le)||await ce.writeFilePromise(le,"");let me=await Wu("yarn",[...ue,"pack","--install-if-needed","--filename",fe.fromPortablePath(e)],{cwd:t,env:U,stdin:p,stdout:h,stderr:E});return me.code!==0?me.code:0}],["npm",async()=>{if(a!==null){let we=new P0.PassThrough,ye=WE(we);we.pipe(h,{end:!1});let Ae=await Wu("npm",["--version"],{cwd:t,env:U,stdin:p,stdout:we,stderr:E,end:0});if(we.end(),Ae.code!==0)return h.end(),E.end(),Ae.code;let se=(await ye).toString().trim();if(!Zf(se,">=7.x")){let Z=Da(null,"npm"),De=On(Z,se),Re=On(Z,">=7.x");throw new Error(`Workspaces aren't supported by ${ni(r,De)}; please upgrade to ${ni(r,Re)} (npm has been detected as the primary package manager for ${Ht(r,t,ht.PATH)})`)}}let ue=a!==null?["--workspace",a]:[];delete U.npm_config_user_agent,delete U.npm_config_production,delete U.NPM_CONFIG_PRODUCTION,delete U.NODE_ENV;let le=await Wu("npm",["install","--legacy-peer-deps"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(le.code!==0)return le.code;let me=new P0.PassThrough,pe=WE(me);me.pipe(h);let Be=await Wu("npm",["pack","--silent",...ue],{cwd:t,env:U,stdin:p,stdout:me,stderr:E});if(Be.code!==0)return Be.code;let Ce=(await pe).toString().trim().replace(/^.*\n/s,""),g=J.resolve(t,fe.toPortablePath(Ce));return await ce.renamePromise(g,e),0}]]).get(I);if(typeof ee>"u")throw new Error("Assertion failed: Unsupported workflow");let ie=await ee();if(!(ie===0||typeof ie>"u"))throw ce.detachTemp(c),new jt(58,`Packing the package failed (exit code ${ie}, logs can be found here: ${Ht(r,f,ht.PATH)})`)})})})}async function hit(t,e,{project:r}){let s=r.tryWorkspaceByLocator(t);if(s!==null)return Vj(s,e);let a=r.storedPackages.get(t.locatorHash);if(!a)throw new Error(`Package for ${Yr(r.configuration,t)} not found in the project`);return await $f.openPromise(async n=>{let c=r.configuration,f=r.configuration.getLinkers(),p={project:r,report:new Ot({stdout:new P0.PassThrough,configuration:c})},h=f.find(P=>P.supportsPackage(a,p));if(!h)throw new Error(`The package ${Yr(r.configuration,a)} isn't supported by any of the available linkers`);let E=await h.findPackageLocation(a,p),C=new Sn(E,{baseFs:n});return(await Ut.find(vt.dot,{baseFs:C})).scripts.has(e)})}async function LT(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f}){return await ce.mktempPromise(async p=>{let{manifest:h,env:E,cwd:C}=await she(t,{project:a,binFolder:p,cwd:s,lifecycleScript:e}),S=h.scripts.get(e);if(typeof S>"u")return 1;let P=async()=>await vI(S,r,{cwd:C,env:E,stdin:n,stdout:c,stderr:f});return await(await a.configuration.reduceHook(R=>R.wrapScriptExecution,P,a,t,e,{script:S,args:r,cwd:C,env:E,stdin:n,stdout:c,stderr:f}))()})}async function Yj(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f}){return await ce.mktempPromise(async p=>{let{env:h,cwd:E}=await she(t,{project:a,binFolder:p,cwd:s});return await vI(e,r,{cwd:E,env:h,stdin:n,stdout:c,stderr:f})})}async function git(t,{binFolder:e,cwd:r,lifecycleScript:s}){let a=await Iv({project:t.project,locator:t.anchoredLocator,binFolder:e,lifecycleScript:s});return await Kj(e,await lhe(t)),typeof r>"u"&&(r=J.dirname(await ce.realpathPromise(J.join(t.cwd,"package.json")))),{manifest:t.manifest,binFolder:e,env:a,cwd:r}}async function she(t,{project:e,binFolder:r,cwd:s,lifecycleScript:a}){let n=e.tryWorkspaceByLocator(t);if(n!==null)return git(n,{binFolder:r,cwd:s,lifecycleScript:a});let c=e.storedPackages.get(t.locatorHash);if(!c)throw new Error(`Package for ${Yr(e.configuration,t)} not found in the project`);return await $f.openPromise(async f=>{let p=e.configuration,h=e.configuration.getLinkers(),E={project:e,report:new Ot({stdout:new P0.PassThrough,configuration:p})},C=h.find(N=>N.supportsPackage(c,E));if(!C)throw new Error(`The package ${Yr(e.configuration,c)} isn't supported by any of the available linkers`);let S=await Iv({project:e,locator:t,binFolder:r,lifecycleScript:a});await Kj(r,await MT(t,{project:e}));let P=await C.findPackageLocation(c,E),I=new Sn(P,{baseFs:f}),R=await Ut.find(vt.dot,{baseFs:I});return typeof s>"u"&&(s=P),{manifest:R,binFolder:r,env:S,cwd:s}})}async function ohe(t,e,r,{cwd:s,stdin:a,stdout:n,stderr:c}){return await LT(t.anchoredLocator,e,r,{cwd:s,project:t.project,stdin:a,stdout:n,stderr:c})}function Vj(t,e){return t.manifest.scripts.has(e)}async function ahe(t,e,{cwd:r,report:s}){let{configuration:a}=t.project,n=null;await ce.mktempPromise(async c=>{let f=J.join(c,`${e}.log`),p=`# This file contains the result of Yarn calling the "${e}" lifecycle script inside a workspace ("${fe.fromPortablePath(t.cwd)}") `,{stdout:h,stderr:E}=a.getSubprocessStreams(f,{report:s,prefix:Yr(a,t.anchoredLocator),header:p});s.reportInfo(36,`Calling the "${e}" lifecycle script`);let C=await ohe(t,e,[],{cwd:r,stdin:n,stdout:h,stderr:E});if(h.end(),E.end(),C!==0)throw ce.detachTemp(c),new jt(36,`${bB(e)} script failed (exit code ${Ht(a,C,ht.NUMBER)}, logs can be found here: ${Ht(a,f,ht.PATH)}); run ${Ht(a,`yarn ${e}`,ht.CODE)} to investigate`)})}async function dit(t,e,r){Vj(t,e)&&await ahe(t,e,r)}function Jj(t){let e=J.extname(t);if(e.match(/\.[cm]?[jt]sx?$/))return!0;if(e===".exe"||e===".bin")return!1;let r=Buffer.alloc(4),s;try{s=ce.openSync(t,"r")}catch{return!0}try{ce.readSync(s,r,0,r.length,0)}finally{ce.closeSync(s)}let a=r.readUint32BE();return!(a===3405691582||a===3489328638||a===2135247942||(a&4294901760)===1297743872)}async function MT(t,{project:e}){let r=e.configuration,s=new Map,a=e.storedPackages.get(t.locatorHash);if(!a)throw new Error(`Package for ${Yr(r,t)} not found in the project`);let n=new P0.Writable,c=r.getLinkers(),f={project:e,report:new Ot({configuration:r,stdout:n})},p=new Set([t.locatorHash]);for(let E of a.dependencies.values()){let C=e.storedResolutions.get(E.descriptorHash);if(!C)throw new Error(`Assertion failed: The resolution (${ni(r,E)}) should have been registered`);p.add(C)}let h=await Promise.all(Array.from(p,async E=>{let C=e.storedPackages.get(E);if(!C)throw new Error(`Assertion failed: The package (${E}) should have been registered`);if(C.bin.size===0)return Wl.skip;let S=c.find(I=>I.supportsPackage(C,f));if(!S)return Wl.skip;let P=null;try{P=await S.findPackageLocation(C,f)}catch(I){if(I.code==="LOCATOR_NOT_INSTALLED")return Wl.skip;throw I}return{dependency:C,packageLocation:P}}));for(let E of h){if(E===Wl.skip)continue;let{dependency:C,packageLocation:S}=E;for(let[P,I]of C.bin){let R=J.resolve(S,I);s.set(P,[C,fe.fromPortablePath(R),Jj(R)])}}return s}async function lhe(t){return await MT(t.anchoredLocator,{project:t.project})}async function Kj(t,e){await Promise.all(Array.from(e,([r,[,s,a]])=>a?b0(t,r,process.execPath,[s]):b0(t,r,s,[])))}async function che(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f,nodeArgs:p=[],packageAccessibleBinaries:h}){h??=await MT(t,{project:a});let E=h.get(e);if(!E)throw new Error(`Binary not found (${e}) for ${Yr(a.configuration,t)}`);return await ce.mktempPromise(async C=>{let[,S]=E,P=await Iv({project:a,locator:t,binFolder:C});await Kj(P.BERRY_BIN_FOLDER,h);let I=Jj(fe.toPortablePath(S))?Wu(process.execPath,[...p,S,...r],{cwd:s,env:P,stdin:n,stdout:c,stderr:f}):Wu(S,r,{cwd:s,env:P,stdin:n,stdout:c,stderr:f}),R;try{R=await I}finally{await ce.removePromise(P.BERRY_BIN_FOLDER)}return R.code})}async function mit(t,e,r,{cwd:s,stdin:a,stdout:n,stderr:c,packageAccessibleBinaries:f}){return await che(t.anchoredLocator,e,r,{project:t.project,cwd:s,stdin:a,stdout:n,stderr:c,packageAccessibleBinaries:f})}var rhe,P0,nhe,fit,Ait,zj=Xe(()=>{Dt();Dt();eA();pv();ql();rhe=ut(Ld()),P0=Ie("stream");oI();Tc();Ev();yv();dT();xc();Pc();Rp();Wo();nhe=(a=>(a.Yarn1="Yarn Classic",a.Yarn2="Yarn",a.Npm="npm",a.Pnpm="pnpm",a))(nhe||{});fit=2,Ait=(0,rhe.default)(fit)});var DI=_((J4t,fhe)=>{"use strict";var uhe=new Map([["C","cwd"],["f","file"],["z","gzip"],["P","preservePaths"],["U","unlink"],["strip-components","strip"],["stripComponents","strip"],["keep-newer","newer"],["keepNewer","newer"],["keep-newer-files","newer"],["keepNewerFiles","newer"],["k","keep"],["keep-existing","keep"],["keepExisting","keep"],["m","noMtime"],["no-mtime","noMtime"],["p","preserveOwner"],["L","follow"],["h","follow"]]);fhe.exports=t=>t?Object.keys(t).map(e=>[uhe.has(e)?uhe.get(e):e,t[e]]).reduce((e,r)=>(e[r[0]]=r[1],e),Object.create(null)):{}});var PI=_((K4t,Ihe)=>{"use strict";var Ahe=typeof process=="object"&&process?process:{stdout:null,stderr:null},yit=Ie("events"),phe=Ie("stream"),hhe=Ie("string_decoder").StringDecoder,_p=Symbol("EOF"),Hp=Symbol("maybeEmitEnd"),x0=Symbol("emittedEnd"),UT=Symbol("emittingEnd"),Cv=Symbol("emittedError"),_T=Symbol("closed"),ghe=Symbol("read"),HT=Symbol("flush"),dhe=Symbol("flushChunk"),ul=Symbol("encoding"),jp=Symbol("decoder"),jT=Symbol("flowing"),wv=Symbol("paused"),bI=Symbol("resume"),Ys=Symbol("bufferLength"),Xj=Symbol("bufferPush"),Zj=Symbol("bufferShift"),Ko=Symbol("objectMode"),zo=Symbol("destroyed"),$j=Symbol("emitData"),mhe=Symbol("emitEnd"),e6=Symbol("emitEnd2"),Gp=Symbol("async"),Bv=t=>Promise.resolve().then(t),yhe=global._MP_NO_ITERATOR_SYMBOLS_!=="1",Eit=yhe&&Symbol.asyncIterator||Symbol("asyncIterator not implemented"),Iit=yhe&&Symbol.iterator||Symbol("iterator not implemented"),Cit=t=>t==="end"||t==="finish"||t==="prefinish",wit=t=>t instanceof ArrayBuffer||typeof t=="object"&&t.constructor&&t.constructor.name==="ArrayBuffer"&&t.byteLength>=0,Bit=t=>!Buffer.isBuffer(t)&&ArrayBuffer.isView(t),GT=class{constructor(e,r,s){this.src=e,this.dest=r,this.opts=s,this.ondrain=()=>e[bI](),r.on("drain",this.ondrain)}unpipe(){this.dest.removeListener("drain",this.ondrain)}proxyErrors(){}end(){this.unpipe(),this.opts.end&&this.dest.end()}},t6=class extends GT{unpipe(){this.src.removeListener("error",this.proxyErrors),super.unpipe()}constructor(e,r,s){super(e,r,s),this.proxyErrors=a=>r.emit("error",a),e.on("error",this.proxyErrors)}};Ihe.exports=class Ehe extends phe{constructor(e){super(),this[jT]=!1,this[wv]=!1,this.pipes=[],this.buffer=[],this[Ko]=e&&e.objectMode||!1,this[Ko]?this[ul]=null:this[ul]=e&&e.encoding||null,this[ul]==="buffer"&&(this[ul]=null),this[Gp]=e&&!!e.async||!1,this[jp]=this[ul]?new hhe(this[ul]):null,this[_p]=!1,this[x0]=!1,this[UT]=!1,this[_T]=!1,this[Cv]=null,this.writable=!0,this.readable=!0,this[Ys]=0,this[zo]=!1}get bufferLength(){return this[Ys]}get encoding(){return this[ul]}set encoding(e){if(this[Ko])throw new Error("cannot set encoding in objectMode");if(this[ul]&&e!==this[ul]&&(this[jp]&&this[jp].lastNeed||this[Ys]))throw new Error("cannot change encoding");this[ul]!==e&&(this[jp]=e?new hhe(e):null,this.buffer.length&&(this.buffer=this.buffer.map(r=>this[jp].write(r)))),this[ul]=e}setEncoding(e){this.encoding=e}get objectMode(){return this[Ko]}set objectMode(e){this[Ko]=this[Ko]||!!e}get async(){return this[Gp]}set async(e){this[Gp]=this[Gp]||!!e}write(e,r,s){if(this[_p])throw new Error("write after end");if(this[zo])return this.emit("error",Object.assign(new Error("Cannot call write after a stream was destroyed"),{code:"ERR_STREAM_DESTROYED"})),!0;typeof r=="function"&&(s=r,r="utf8"),r||(r="utf8");let a=this[Gp]?Bv:n=>n();return!this[Ko]&&!Buffer.isBuffer(e)&&(Bit(e)?e=Buffer.from(e.buffer,e.byteOffset,e.byteLength):wit(e)?e=Buffer.from(e):typeof e!="string"&&(this.objectMode=!0)),this[Ko]?(this.flowing&&this[Ys]!==0&&this[HT](!0),this.flowing?this.emit("data",e):this[Xj](e),this[Ys]!==0&&this.emit("readable"),s&&a(s),this.flowing):e.length?(typeof e=="string"&&!(r===this[ul]&&!this[jp].lastNeed)&&(e=Buffer.from(e,r)),Buffer.isBuffer(e)&&this[ul]&&(e=this[jp].write(e)),this.flowing&&this[Ys]!==0&&this[HT](!0),this.flowing?this.emit("data",e):this[Xj](e),this[Ys]!==0&&this.emit("readable"),s&&a(s),this.flowing):(this[Ys]!==0&&this.emit("readable"),s&&a(s),this.flowing)}read(e){if(this[zo])return null;if(this[Ys]===0||e===0||e>this[Ys])return this[Hp](),null;this[Ko]&&(e=null),this.buffer.length>1&&!this[Ko]&&(this.encoding?this.buffer=[this.buffer.join("")]:this.buffer=[Buffer.concat(this.buffer,this[Ys])]);let r=this[ghe](e||null,this.buffer[0]);return this[Hp](),r}[ghe](e,r){return e===r.length||e===null?this[Zj]():(this.buffer[0]=r.slice(e),r=r.slice(0,e),this[Ys]-=e),this.emit("data",r),!this.buffer.length&&!this[_p]&&this.emit("drain"),r}end(e,r,s){return typeof e=="function"&&(s=e,e=null),typeof r=="function"&&(s=r,r="utf8"),e&&this.write(e,r),s&&this.once("end",s),this[_p]=!0,this.writable=!1,(this.flowing||!this[wv])&&this[Hp](),this}[bI](){this[zo]||(this[wv]=!1,this[jT]=!0,this.emit("resume"),this.buffer.length?this[HT]():this[_p]?this[Hp]():this.emit("drain"))}resume(){return this[bI]()}pause(){this[jT]=!1,this[wv]=!0}get destroyed(){return this[zo]}get flowing(){return this[jT]}get paused(){return this[wv]}[Xj](e){this[Ko]?this[Ys]+=1:this[Ys]+=e.length,this.buffer.push(e)}[Zj](){return this.buffer.length&&(this[Ko]?this[Ys]-=1:this[Ys]-=this.buffer[0].length),this.buffer.shift()}[HT](e){do;while(this[dhe](this[Zj]()));!e&&!this.buffer.length&&!this[_p]&&this.emit("drain")}[dhe](e){return e?(this.emit("data",e),this.flowing):!1}pipe(e,r){if(this[zo])return;let s=this[x0];return r=r||{},e===Ahe.stdout||e===Ahe.stderr?r.end=!1:r.end=r.end!==!1,r.proxyErrors=!!r.proxyErrors,s?r.end&&e.end():(this.pipes.push(r.proxyErrors?new t6(this,e,r):new GT(this,e,r)),this[Gp]?Bv(()=>this[bI]()):this[bI]()),e}unpipe(e){let r=this.pipes.find(s=>s.dest===e);r&&(this.pipes.splice(this.pipes.indexOf(r),1),r.unpipe())}addListener(e,r){return this.on(e,r)}on(e,r){let s=super.on(e,r);return e==="data"&&!this.pipes.length&&!this.flowing?this[bI]():e==="readable"&&this[Ys]!==0?super.emit("readable"):Cit(e)&&this[x0]?(super.emit(e),this.removeAllListeners(e)):e==="error"&&this[Cv]&&(this[Gp]?Bv(()=>r.call(this,this[Cv])):r.call(this,this[Cv])),s}get emittedEnd(){return this[x0]}[Hp](){!this[UT]&&!this[x0]&&!this[zo]&&this.buffer.length===0&&this[_p]&&(this[UT]=!0,this.emit("end"),this.emit("prefinish"),this.emit("finish"),this[_T]&&this.emit("close"),this[UT]=!1)}emit(e,r,...s){if(e!=="error"&&e!=="close"&&e!==zo&&this[zo])return;if(e==="data")return r?this[Gp]?Bv(()=>this[$j](r)):this[$j](r):!1;if(e==="end")return this[mhe]();if(e==="close"){if(this[_T]=!0,!this[x0]&&!this[zo])return;let n=super.emit("close");return this.removeAllListeners("close"),n}else if(e==="error"){this[Cv]=r;let n=super.emit("error",r);return this[Hp](),n}else if(e==="resume"){let n=super.emit("resume");return this[Hp](),n}else if(e==="finish"||e==="prefinish"){let n=super.emit(e);return this.removeAllListeners(e),n}let a=super.emit(e,r,...s);return this[Hp](),a}[$j](e){for(let s of this.pipes)s.dest.write(e)===!1&&this.pause();let r=super.emit("data",e);return this[Hp](),r}[mhe](){this[x0]||(this[x0]=!0,this.readable=!1,this[Gp]?Bv(()=>this[e6]()):this[e6]())}[e6](){if(this[jp]){let r=this[jp].end();if(r){for(let s of this.pipes)s.dest.write(r);super.emit("data",r)}}for(let r of this.pipes)r.end();let e=super.emit("end");return this.removeAllListeners("end"),e}collect(){let e=[];this[Ko]||(e.dataLength=0);let r=this.promise();return this.on("data",s=>{e.push(s),this[Ko]||(e.dataLength+=s.length)}),r.then(()=>e)}concat(){return this[Ko]?Promise.reject(new Error("cannot concat in objectMode")):this.collect().then(e=>this[Ko]?Promise.reject(new Error("cannot concat in objectMode")):this[ul]?e.join(""):Buffer.concat(e,e.dataLength))}promise(){return new Promise((e,r)=>{this.on(zo,()=>r(new Error("stream destroyed"))),this.on("error",s=>r(s)),this.on("end",()=>e())})}[Eit](){return{next:()=>{let r=this.read();if(r!==null)return Promise.resolve({done:!1,value:r});if(this[_p])return Promise.resolve({done:!0});let s=null,a=null,n=h=>{this.removeListener("data",c),this.removeListener("end",f),a(h)},c=h=>{this.removeListener("error",n),this.removeListener("end",f),this.pause(),s({value:h,done:!!this[_p]})},f=()=>{this.removeListener("error",n),this.removeListener("data",c),s({done:!0})},p=()=>n(new Error("stream destroyed"));return new Promise((h,E)=>{a=E,s=h,this.once(zo,p),this.once("error",n),this.once("end",f),this.once("data",c)})}}}[Iit](){return{next:()=>{let r=this.read();return{value:r,done:r===null}}}}destroy(e){return this[zo]?(e?this.emit("error",e):this.emit(zo),this):(this[zo]=!0,this.buffer.length=0,this[Ys]=0,typeof this.close=="function"&&!this[_T]&&this.close(),e?this.emit("error",e):this.emit(zo),this)}static isStream(e){return!!e&&(e instanceof Ehe||e instanceof phe||e instanceof yit&&(typeof e.pipe=="function"||typeof e.write=="function"&&typeof e.end=="function"))}}});var whe=_((z4t,Che)=>{var vit=Ie("zlib").constants||{ZLIB_VERNUM:4736};Che.exports=Object.freeze(Object.assign(Object.create(null),{Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_VERSION_ERROR:-6,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,DEFLATE:1,INFLATE:2,GZIP:3,GUNZIP:4,DEFLATERAW:5,INFLATERAW:6,UNZIP:7,BROTLI_DECODE:8,BROTLI_ENCODE:9,Z_MIN_WINDOWBITS:8,Z_MAX_WINDOWBITS:15,Z_DEFAULT_WINDOWBITS:15,Z_MIN_CHUNK:64,Z_MAX_CHUNK:1/0,Z_DEFAULT_CHUNK:16384,Z_MIN_MEMLEVEL:1,Z_MAX_MEMLEVEL:9,Z_DEFAULT_MEMLEVEL:8,Z_MIN_LEVEL:-1,Z_MAX_LEVEL:9,Z_DEFAULT_LEVEL:-1,BROTLI_OPERATION_PROCESS:0,BROTLI_OPERATION_FLUSH:1,BROTLI_OPERATION_FINISH:2,BROTLI_OPERATION_EMIT_METADATA:3,BROTLI_MODE_GENERIC:0,BROTLI_MODE_TEXT:1,BROTLI_MODE_FONT:2,BROTLI_DEFAULT_MODE:0,BROTLI_MIN_QUALITY:0,BROTLI_MAX_QUALITY:11,BROTLI_DEFAULT_QUALITY:11,BROTLI_MIN_WINDOW_BITS:10,BROTLI_MAX_WINDOW_BITS:24,BROTLI_LARGE_MAX_WINDOW_BITS:30,BROTLI_DEFAULT_WINDOW:22,BROTLI_MIN_INPUT_BLOCK_BITS:16,BROTLI_MAX_INPUT_BLOCK_BITS:24,BROTLI_PARAM_MODE:0,BROTLI_PARAM_QUALITY:1,BROTLI_PARAM_LGWIN:2,BROTLI_PARAM_LGBLOCK:3,BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING:4,BROTLI_PARAM_SIZE_HINT:5,BROTLI_PARAM_LARGE_WINDOW:6,BROTLI_PARAM_NPOSTFIX:7,BROTLI_PARAM_NDIRECT:8,BROTLI_DECODER_RESULT_ERROR:0,BROTLI_DECODER_RESULT_SUCCESS:1,BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:2,BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION:0,BROTLI_DECODER_PARAM_LARGE_WINDOW:1,BROTLI_DECODER_NO_ERROR:0,BROTLI_DECODER_SUCCESS:1,BROTLI_DECODER_NEEDS_MORE_INPUT:2,BROTLI_DECODER_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE:-1,BROTLI_DECODER_ERROR_FORMAT_RESERVED:-2,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE:-3,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET:-4,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME:-5,BROTLI_DECODER_ERROR_FORMAT_CL_SPACE:-6,BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE:-7,BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT:-8,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1:-9,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2:-10,BROTLI_DECODER_ERROR_FORMAT_TRANSFORM:-11,BROTLI_DECODER_ERROR_FORMAT_DICTIONARY:-12,BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS:-13,BROTLI_DECODER_ERROR_FORMAT_PADDING_1:-14,BROTLI_DECODER_ERROR_FORMAT_PADDING_2:-15,BROTLI_DECODER_ERROR_FORMAT_DISTANCE:-16,BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET:-19,BROTLI_DECODER_ERROR_INVALID_ARGUMENTS:-20,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES:-21,BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS:-22,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP:-25,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1:-26,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2:-27,BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES:-30,BROTLI_DECODER_ERROR_UNREACHABLE:-31},vit))});var m6=_(Kl=>{"use strict";var o6=Ie("assert"),k0=Ie("buffer").Buffer,She=Ie("zlib"),fm=Kl.constants=whe(),Sit=PI(),Bhe=k0.concat,Am=Symbol("_superWrite"),kI=class extends Error{constructor(e){super("zlib: "+e.message),this.code=e.code,this.errno=e.errno,this.code||(this.code="ZLIB_ERROR"),this.message="zlib: "+e.message,Error.captureStackTrace(this,this.constructor)}get name(){return"ZlibError"}},Dit=Symbol("opts"),vv=Symbol("flushFlag"),vhe=Symbol("finishFlushFlag"),d6=Symbol("fullFlushFlag"),Ii=Symbol("handle"),qT=Symbol("onError"),xI=Symbol("sawError"),r6=Symbol("level"),n6=Symbol("strategy"),i6=Symbol("ended"),X4t=Symbol("_defaultFullFlush"),WT=class extends Sit{constructor(e,r){if(!e||typeof e!="object")throw new TypeError("invalid options for ZlibBase constructor");super(e),this[xI]=!1,this[i6]=!1,this[Dit]=e,this[vv]=e.flush,this[vhe]=e.finishFlush;try{this[Ii]=new She[r](e)}catch(s){throw new kI(s)}this[qT]=s=>{this[xI]||(this[xI]=!0,this.close(),this.emit("error",s))},this[Ii].on("error",s=>this[qT](new kI(s))),this.once("end",()=>this.close)}close(){this[Ii]&&(this[Ii].close(),this[Ii]=null,this.emit("close"))}reset(){if(!this[xI])return o6(this[Ii],"zlib binding closed"),this[Ii].reset()}flush(e){this.ended||(typeof e!="number"&&(e=this[d6]),this.write(Object.assign(k0.alloc(0),{[vv]:e})))}end(e,r,s){return e&&this.write(e,r),this.flush(this[vhe]),this[i6]=!0,super.end(null,null,s)}get ended(){return this[i6]}write(e,r,s){if(typeof r=="function"&&(s=r,r="utf8"),typeof e=="string"&&(e=k0.from(e,r)),this[xI])return;o6(this[Ii],"zlib binding closed");let a=this[Ii]._handle,n=a.close;a.close=()=>{};let c=this[Ii].close;this[Ii].close=()=>{},k0.concat=h=>h;let f;try{let h=typeof e[vv]=="number"?e[vv]:this[vv];f=this[Ii]._processChunk(e,h),k0.concat=Bhe}catch(h){k0.concat=Bhe,this[qT](new kI(h))}finally{this[Ii]&&(this[Ii]._handle=a,a.close=n,this[Ii].close=c,this[Ii].removeAllListeners("error"))}this[Ii]&&this[Ii].on("error",h=>this[qT](new kI(h)));let p;if(f)if(Array.isArray(f)&&f.length>0){p=this[Am](k0.from(f[0]));for(let h=1;h{this.flush(a),n()};try{this[Ii].params(e,r)}finally{this[Ii].flush=s}this[Ii]&&(this[r6]=e,this[n6]=r)}}}},a6=class extends qp{constructor(e){super(e,"Deflate")}},l6=class extends qp{constructor(e){super(e,"Inflate")}},s6=Symbol("_portable"),c6=class extends qp{constructor(e){super(e,"Gzip"),this[s6]=e&&!!e.portable}[Am](e){return this[s6]?(this[s6]=!1,e[9]=255,super[Am](e)):super[Am](e)}},u6=class extends qp{constructor(e){super(e,"Gunzip")}},f6=class extends qp{constructor(e){super(e,"DeflateRaw")}},A6=class extends qp{constructor(e){super(e,"InflateRaw")}},p6=class extends qp{constructor(e){super(e,"Unzip")}},YT=class extends WT{constructor(e,r){e=e||{},e.flush=e.flush||fm.BROTLI_OPERATION_PROCESS,e.finishFlush=e.finishFlush||fm.BROTLI_OPERATION_FINISH,super(e,r),this[d6]=fm.BROTLI_OPERATION_FLUSH}},h6=class extends YT{constructor(e){super(e,"BrotliCompress")}},g6=class extends YT{constructor(e){super(e,"BrotliDecompress")}};Kl.Deflate=a6;Kl.Inflate=l6;Kl.Gzip=c6;Kl.Gunzip=u6;Kl.DeflateRaw=f6;Kl.InflateRaw=A6;Kl.Unzip=p6;typeof She.BrotliCompress=="function"?(Kl.BrotliCompress=h6,Kl.BrotliDecompress=g6):Kl.BrotliCompress=Kl.BrotliDecompress=class{constructor(){throw new Error("Brotli is not supported in this version of Node.js")}}});var QI=_((e3t,Dhe)=>{var bit=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform;Dhe.exports=bit!=="win32"?t=>t:t=>t&&t.replace(/\\/g,"/")});var VT=_((r3t,bhe)=>{"use strict";var Pit=PI(),y6=QI(),E6=Symbol("slurp");bhe.exports=class extends Pit{constructor(e,r,s){switch(super(),this.pause(),this.extended=r,this.globalExtended=s,this.header=e,this.startBlockSize=512*Math.ceil(e.size/512),this.blockRemain=this.startBlockSize,this.remain=e.size,this.type=e.type,this.meta=!1,this.ignore=!1,this.type){case"File":case"OldFile":case"Link":case"SymbolicLink":case"CharacterDevice":case"BlockDevice":case"Directory":case"FIFO":case"ContiguousFile":case"GNUDumpDir":break;case"NextFileHasLongLinkpath":case"NextFileHasLongPath":case"OldGnuLongPath":case"GlobalExtendedHeader":case"ExtendedHeader":case"OldExtendedHeader":this.meta=!0;break;default:this.ignore=!0}this.path=y6(e.path),this.mode=e.mode,this.mode&&(this.mode=this.mode&4095),this.uid=e.uid,this.gid=e.gid,this.uname=e.uname,this.gname=e.gname,this.size=e.size,this.mtime=e.mtime,this.atime=e.atime,this.ctime=e.ctime,this.linkpath=y6(e.linkpath),this.uname=e.uname,this.gname=e.gname,r&&this[E6](r),s&&this[E6](s,!0)}write(e){let r=e.length;if(r>this.blockRemain)throw new Error("writing more to entry than is appropriate");let s=this.remain,a=this.blockRemain;return this.remain=Math.max(0,s-r),this.blockRemain=Math.max(0,a-r),this.ignore?!0:s>=r?super.write(e):super.write(e.slice(0,s))}[E6](e,r){for(let s in e)e[s]!==null&&e[s]!==void 0&&!(r&&s==="path")&&(this[s]=s==="path"||s==="linkpath"?y6(e[s]):e[s])}}});var I6=_(JT=>{"use strict";JT.name=new Map([["0","File"],["","OldFile"],["1","Link"],["2","SymbolicLink"],["3","CharacterDevice"],["4","BlockDevice"],["5","Directory"],["6","FIFO"],["7","ContiguousFile"],["g","GlobalExtendedHeader"],["x","ExtendedHeader"],["A","SolarisACL"],["D","GNUDumpDir"],["I","Inode"],["K","NextFileHasLongLinkpath"],["L","NextFileHasLongPath"],["M","ContinuationFile"],["N","OldGnuLongPath"],["S","SparseFile"],["V","TapeVolumeHeader"],["X","OldExtendedHeader"]]);JT.code=new Map(Array.from(JT.name).map(t=>[t[1],t[0]]))});var Qhe=_((i3t,khe)=>{"use strict";var xit=(t,e)=>{if(Number.isSafeInteger(t))t<0?Qit(t,e):kit(t,e);else throw Error("cannot encode number outside of javascript safe integer range");return e},kit=(t,e)=>{e[0]=128;for(var r=e.length;r>1;r--)e[r-1]=t&255,t=Math.floor(t/256)},Qit=(t,e)=>{e[0]=255;var r=!1;t=t*-1;for(var s=e.length;s>1;s--){var a=t&255;t=Math.floor(t/256),r?e[s-1]=Phe(a):a===0?e[s-1]=0:(r=!0,e[s-1]=xhe(a))}},Tit=t=>{let e=t[0],r=e===128?Fit(t.slice(1,t.length)):e===255?Rit(t):null;if(r===null)throw Error("invalid base256 encoding");if(!Number.isSafeInteger(r))throw Error("parsed number outside of javascript safe integer range");return r},Rit=t=>{for(var e=t.length,r=0,s=!1,a=e-1;a>-1;a--){var n=t[a],c;s?c=Phe(n):n===0?c=n:(s=!0,c=xhe(n)),c!==0&&(r-=c*Math.pow(256,e-a-1))}return r},Fit=t=>{for(var e=t.length,r=0,s=e-1;s>-1;s--){var a=t[s];a!==0&&(r+=a*Math.pow(256,e-s-1))}return r},Phe=t=>(255^t)&255,xhe=t=>(255^t)+1&255;khe.exports={encode:xit,parse:Tit}});var RI=_((s3t,Rhe)=>{"use strict";var C6=I6(),TI=Ie("path").posix,The=Qhe(),w6=Symbol("slurp"),zl=Symbol("type"),S6=class{constructor(e,r,s,a){this.cksumValid=!1,this.needPax=!1,this.nullBlock=!1,this.block=null,this.path=null,this.mode=null,this.uid=null,this.gid=null,this.size=null,this.mtime=null,this.cksum=null,this[zl]="0",this.linkpath=null,this.uname=null,this.gname=null,this.devmaj=0,this.devmin=0,this.atime=null,this.ctime=null,Buffer.isBuffer(e)?this.decode(e,r||0,s,a):e&&this.set(e)}decode(e,r,s,a){if(r||(r=0),!e||!(e.length>=r+512))throw new Error("need 512 bytes for header");if(this.path=pm(e,r,100),this.mode=Q0(e,r+100,8),this.uid=Q0(e,r+108,8),this.gid=Q0(e,r+116,8),this.size=Q0(e,r+124,12),this.mtime=B6(e,r+136,12),this.cksum=Q0(e,r+148,12),this[w6](s),this[w6](a,!0),this[zl]=pm(e,r+156,1),this[zl]===""&&(this[zl]="0"),this[zl]==="0"&&this.path.substr(-1)==="/"&&(this[zl]="5"),this[zl]==="5"&&(this.size=0),this.linkpath=pm(e,r+157,100),e.slice(r+257,r+265).toString()==="ustar\x0000")if(this.uname=pm(e,r+265,32),this.gname=pm(e,r+297,32),this.devmaj=Q0(e,r+329,8),this.devmin=Q0(e,r+337,8),e[r+475]!==0){let c=pm(e,r+345,155);this.path=c+"/"+this.path}else{let c=pm(e,r+345,130);c&&(this.path=c+"/"+this.path),this.atime=B6(e,r+476,12),this.ctime=B6(e,r+488,12)}let n=8*32;for(let c=r;c=r+512))throw new Error("need 512 bytes for header");let s=this.ctime||this.atime?130:155,a=Nit(this.path||"",s),n=a[0],c=a[1];this.needPax=a[2],this.needPax=hm(e,r,100,n)||this.needPax,this.needPax=T0(e,r+100,8,this.mode)||this.needPax,this.needPax=T0(e,r+108,8,this.uid)||this.needPax,this.needPax=T0(e,r+116,8,this.gid)||this.needPax,this.needPax=T0(e,r+124,12,this.size)||this.needPax,this.needPax=v6(e,r+136,12,this.mtime)||this.needPax,e[r+156]=this[zl].charCodeAt(0),this.needPax=hm(e,r+157,100,this.linkpath)||this.needPax,e.write("ustar\x0000",r+257,8),this.needPax=hm(e,r+265,32,this.uname)||this.needPax,this.needPax=hm(e,r+297,32,this.gname)||this.needPax,this.needPax=T0(e,r+329,8,this.devmaj)||this.needPax,this.needPax=T0(e,r+337,8,this.devmin)||this.needPax,this.needPax=hm(e,r+345,s,c)||this.needPax,e[r+475]!==0?this.needPax=hm(e,r+345,155,c)||this.needPax:(this.needPax=hm(e,r+345,130,c)||this.needPax,this.needPax=v6(e,r+476,12,this.atime)||this.needPax,this.needPax=v6(e,r+488,12,this.ctime)||this.needPax);let f=8*32;for(let p=r;p{let s=t,a="",n,c=TI.parse(t).root||".";if(Buffer.byteLength(s)<100)n=[s,a,!1];else{a=TI.dirname(s),s=TI.basename(s);do Buffer.byteLength(s)<=100&&Buffer.byteLength(a)<=e?n=[s,a,!1]:Buffer.byteLength(s)>100&&Buffer.byteLength(a)<=e?n=[s.substr(0,99),a,!0]:(s=TI.join(TI.basename(a),s),a=TI.dirname(a));while(a!==c&&!n);n||(n=[t.substr(0,99),"",!0])}return n},pm=(t,e,r)=>t.slice(e,e+r).toString("utf8").replace(/\0.*/,""),B6=(t,e,r)=>Oit(Q0(t,e,r)),Oit=t=>t===null?null:new Date(t*1e3),Q0=(t,e,r)=>t[e]&128?The.parse(t.slice(e,e+r)):Mit(t,e,r),Lit=t=>isNaN(t)?null:t,Mit=(t,e,r)=>Lit(parseInt(t.slice(e,e+r).toString("utf8").replace(/\0.*$/,"").trim(),8)),Uit={12:8589934591,8:2097151},T0=(t,e,r,s)=>s===null?!1:s>Uit[r]||s<0?(The.encode(s,t.slice(e,e+r)),!0):(_it(t,e,r,s),!1),_it=(t,e,r,s)=>t.write(Hit(s,r),e,r,"ascii"),Hit=(t,e)=>jit(Math.floor(t).toString(8),e),jit=(t,e)=>(t.length===e-1?t:new Array(e-t.length-1).join("0")+t+" ")+"\0",v6=(t,e,r,s)=>s===null?!1:T0(t,e,r,s.getTime()/1e3),Git=new Array(156).join("\0"),hm=(t,e,r,s)=>s===null?!1:(t.write(s+Git,e,r,"utf8"),s.length!==Buffer.byteLength(s)||s.length>r);Rhe.exports=S6});var KT=_((o3t,Fhe)=>{"use strict";var qit=RI(),Wit=Ie("path"),Sv=class{constructor(e,r){this.atime=e.atime||null,this.charset=e.charset||null,this.comment=e.comment||null,this.ctime=e.ctime||null,this.gid=e.gid||null,this.gname=e.gname||null,this.linkpath=e.linkpath||null,this.mtime=e.mtime||null,this.path=e.path||null,this.size=e.size||null,this.uid=e.uid||null,this.uname=e.uname||null,this.dev=e.dev||null,this.ino=e.ino||null,this.nlink=e.nlink||null,this.global=r||!1}encode(){let e=this.encodeBody();if(e==="")return null;let r=Buffer.byteLength(e),s=512*Math.ceil(1+r/512),a=Buffer.allocUnsafe(s);for(let n=0;n<512;n++)a[n]=0;new qit({path:("PaxHeader/"+Wit.basename(this.path)).slice(0,99),mode:this.mode||420,uid:this.uid||null,gid:this.gid||null,size:r,mtime:this.mtime||null,type:this.global?"GlobalExtendedHeader":"ExtendedHeader",linkpath:"",uname:this.uname||"",gname:this.gname||"",devmaj:0,devmin:0,atime:this.atime||null,ctime:this.ctime||null}).encode(a),a.write(e,512,r,"utf8");for(let n=r+512;n=Math.pow(10,n)&&(n+=1),n+a+s}};Sv.parse=(t,e,r)=>new Sv(Yit(Vit(t),e),r);var Yit=(t,e)=>e?Object.keys(t).reduce((r,s)=>(r[s]=t[s],r),e):t,Vit=t=>t.replace(/\n$/,"").split(` `).reduce(Jit,Object.create(null)),Jit=(t,e)=>{let r=parseInt(e,10);if(r!==Buffer.byteLength(e)+1)return t;e=e.substr((r+" ").length);let s=e.split("="),a=s.shift().replace(/^SCHILY\.(dev|ino|nlink)/,"$1");if(!a)return t;let n=s.join("=");return t[a]=/^([A-Z]+\.)?([mac]|birth|creation)time$/.test(a)?new Date(n*1e3):/^[0-9]+$/.test(n)?+n:n,t};Fhe.exports=Sv});var FI=_((a3t,Nhe)=>{Nhe.exports=t=>{let e=t.length-1,r=-1;for(;e>-1&&t.charAt(e)==="/";)r=e,e--;return r===-1?t:t.slice(0,r)}});var zT=_((l3t,Ohe)=>{"use strict";Ohe.exports=t=>class extends t{warn(e,r,s={}){this.file&&(s.file=this.file),this.cwd&&(s.cwd=this.cwd),s.code=r instanceof Error&&r.code||e,s.tarCode=e,!this.strict&&s.recoverable!==!1?(r instanceof Error&&(s=Object.assign(r,s),r=r.message),this.emit("warn",s.tarCode,r,s)):r instanceof Error?this.emit("error",Object.assign(r,s)):this.emit("error",Object.assign(new Error(`${e}: ${r}`),s))}}});var b6=_((u3t,Lhe)=>{"use strict";var XT=["|","<",">","?",":"],D6=XT.map(t=>String.fromCharCode(61440+t.charCodeAt(0))),Kit=new Map(XT.map((t,e)=>[t,D6[e]])),zit=new Map(D6.map((t,e)=>[t,XT[e]]));Lhe.exports={encode:t=>XT.reduce((e,r)=>e.split(r).join(Kit.get(r)),t),decode:t=>D6.reduce((e,r)=>e.split(r).join(zit.get(r)),t)}});var P6=_((f3t,Uhe)=>{var{isAbsolute:Xit,parse:Mhe}=Ie("path").win32;Uhe.exports=t=>{let e="",r=Mhe(t);for(;Xit(t)||r.root;){let s=t.charAt(0)==="/"&&t.slice(0,4)!=="//?/"?"/":r.root;t=t.substr(s.length),e+=s,r=Mhe(t)}return[e,t]}});var Hhe=_((A3t,_he)=>{"use strict";_he.exports=(t,e,r)=>(t&=4095,r&&(t=(t|384)&-19),e&&(t&256&&(t|=64),t&32&&(t|=8),t&4&&(t|=1)),t)});var M6=_((g3t,t0e)=>{"use strict";var Jhe=PI(),Khe=KT(),zhe=RI(),nA=Ie("fs"),jhe=Ie("path"),rA=QI(),Zit=FI(),Xhe=(t,e)=>e?(t=rA(t).replace(/^\.(\/|$)/,""),Zit(e)+"/"+t):rA(t),$it=16*1024*1024,Ghe=Symbol("process"),qhe=Symbol("file"),Whe=Symbol("directory"),k6=Symbol("symlink"),Yhe=Symbol("hardlink"),Dv=Symbol("header"),ZT=Symbol("read"),Q6=Symbol("lstat"),$T=Symbol("onlstat"),T6=Symbol("onread"),R6=Symbol("onreadlink"),F6=Symbol("openfile"),N6=Symbol("onopenfile"),R0=Symbol("close"),eR=Symbol("mode"),O6=Symbol("awaitDrain"),x6=Symbol("ondrain"),iA=Symbol("prefix"),Vhe=Symbol("hadError"),Zhe=zT(),est=b6(),$he=P6(),e0e=Hhe(),tR=Zhe(class extends Jhe{constructor(e,r){if(r=r||{},super(r),typeof e!="string")throw new TypeError("path is required");this.path=rA(e),this.portable=!!r.portable,this.myuid=process.getuid&&process.getuid()||0,this.myuser=process.env.USER||"",this.maxReadSize=r.maxReadSize||$it,this.linkCache=r.linkCache||new Map,this.statCache=r.statCache||new Map,this.preservePaths=!!r.preservePaths,this.cwd=rA(r.cwd||process.cwd()),this.strict=!!r.strict,this.noPax=!!r.noPax,this.noMtime=!!r.noMtime,this.mtime=r.mtime||null,this.prefix=r.prefix?rA(r.prefix):null,this.fd=null,this.blockLen=null,this.blockRemain=null,this.buf=null,this.offset=null,this.length=null,this.pos=null,this.remain=null,typeof r.onwarn=="function"&&this.on("warn",r.onwarn);let s=!1;if(!this.preservePaths){let[a,n]=$he(this.path);a&&(this.path=n,s=a)}this.win32=!!r.win32||process.platform==="win32",this.win32&&(this.path=est.decode(this.path.replace(/\\/g,"/")),e=e.replace(/\\/g,"/")),this.absolute=rA(r.absolute||jhe.resolve(this.cwd,e)),this.path===""&&(this.path="./"),s&&this.warn("TAR_ENTRY_INFO",`stripping ${s} from absolute path`,{entry:this,path:s+this.path}),this.statCache.has(this.absolute)?this[$T](this.statCache.get(this.absolute)):this[Q6]()}emit(e,...r){return e==="error"&&(this[Vhe]=!0),super.emit(e,...r)}[Q6](){nA.lstat(this.absolute,(e,r)=>{if(e)return this.emit("error",e);this[$T](r)})}[$T](e){this.statCache.set(this.absolute,e),this.stat=e,e.isFile()||(e.size=0),this.type=rst(e),this.emit("stat",e),this[Ghe]()}[Ghe](){switch(this.type){case"File":return this[qhe]();case"Directory":return this[Whe]();case"SymbolicLink":return this[k6]();default:return this.end()}}[eR](e){return e0e(e,this.type==="Directory",this.portable)}[iA](e){return Xhe(e,this.prefix)}[Dv](){this.type==="Directory"&&this.portable&&(this.noMtime=!0),this.header=new zhe({path:this[iA](this.path),linkpath:this.type==="Link"?this[iA](this.linkpath):this.linkpath,mode:this[eR](this.stat.mode),uid:this.portable?null:this.stat.uid,gid:this.portable?null:this.stat.gid,size:this.stat.size,mtime:this.noMtime?null:this.mtime||this.stat.mtime,type:this.type,uname:this.portable?null:this.stat.uid===this.myuid?this.myuser:"",atime:this.portable?null:this.stat.atime,ctime:this.portable?null:this.stat.ctime}),this.header.encode()&&!this.noPax&&super.write(new Khe({atime:this.portable?null:this.header.atime,ctime:this.portable?null:this.header.ctime,gid:this.portable?null:this.header.gid,mtime:this.noMtime?null:this.mtime||this.header.mtime,path:this[iA](this.path),linkpath:this.type==="Link"?this[iA](this.linkpath):this.linkpath,size:this.header.size,uid:this.portable?null:this.header.uid,uname:this.portable?null:this.header.uname,dev:this.portable?null:this.stat.dev,ino:this.portable?null:this.stat.ino,nlink:this.portable?null:this.stat.nlink}).encode()),super.write(this.header.block)}[Whe](){this.path.substr(-1)!=="/"&&(this.path+="/"),this.stat.size=0,this[Dv](),this.end()}[k6](){nA.readlink(this.absolute,(e,r)=>{if(e)return this.emit("error",e);this[R6](r)})}[R6](e){this.linkpath=rA(e),this[Dv](),this.end()}[Yhe](e){this.type="Link",this.linkpath=rA(jhe.relative(this.cwd,e)),this.stat.size=0,this[Dv](),this.end()}[qhe](){if(this.stat.nlink>1){let e=this.stat.dev+":"+this.stat.ino;if(this.linkCache.has(e)){let r=this.linkCache.get(e);if(r.indexOf(this.cwd)===0)return this[Yhe](r)}this.linkCache.set(e,this.absolute)}if(this[Dv](),this.stat.size===0)return this.end();this[F6]()}[F6](){nA.open(this.absolute,"r",(e,r)=>{if(e)return this.emit("error",e);this[N6](r)})}[N6](e){if(this.fd=e,this[Vhe])return this[R0]();this.blockLen=512*Math.ceil(this.stat.size/512),this.blockRemain=this.blockLen;let r=Math.min(this.blockLen,this.maxReadSize);this.buf=Buffer.allocUnsafe(r),this.offset=0,this.pos=0,this.remain=this.stat.size,this.length=this.buf.length,this[ZT]()}[ZT](){let{fd:e,buf:r,offset:s,length:a,pos:n}=this;nA.read(e,r,s,a,n,(c,f)=>{if(c)return this[R0](()=>this.emit("error",c));this[T6](f)})}[R0](e){nA.close(this.fd,e)}[T6](e){if(e<=0&&this.remain>0){let a=new Error("encountered unexpected EOF");return a.path=this.absolute,a.syscall="read",a.code="EOF",this[R0](()=>this.emit("error",a))}if(e>this.remain){let a=new Error("did not encounter expected EOF");return a.path=this.absolute,a.syscall="read",a.code="EOF",this[R0](()=>this.emit("error",a))}if(e===this.remain)for(let a=e;athis[x6]())}[O6](e){this.once("drain",e)}write(e){if(this.blockRemaine?this.emit("error",e):this.end());this.offset>=this.length&&(this.buf=Buffer.allocUnsafe(Math.min(this.blockRemain,this.buf.length)),this.offset=0),this.length=this.buf.length-this.offset,this[ZT]()}}),L6=class extends tR{[Q6](){this[$T](nA.lstatSync(this.absolute))}[k6](){this[R6](nA.readlinkSync(this.absolute))}[F6](){this[N6](nA.openSync(this.absolute,"r"))}[ZT](){let e=!0;try{let{fd:r,buf:s,offset:a,length:n,pos:c}=this,f=nA.readSync(r,s,a,n,c);this[T6](f),e=!1}finally{if(e)try{this[R0](()=>{})}catch{}}}[O6](e){e()}[R0](e){nA.closeSync(this.fd),e()}},tst=Zhe(class extends Jhe{constructor(e,r){r=r||{},super(r),this.preservePaths=!!r.preservePaths,this.portable=!!r.portable,this.strict=!!r.strict,this.noPax=!!r.noPax,this.noMtime=!!r.noMtime,this.readEntry=e,this.type=e.type,this.type==="Directory"&&this.portable&&(this.noMtime=!0),this.prefix=r.prefix||null,this.path=rA(e.path),this.mode=this[eR](e.mode),this.uid=this.portable?null:e.uid,this.gid=this.portable?null:e.gid,this.uname=this.portable?null:e.uname,this.gname=this.portable?null:e.gname,this.size=e.size,this.mtime=this.noMtime?null:r.mtime||e.mtime,this.atime=this.portable?null:e.atime,this.ctime=this.portable?null:e.ctime,this.linkpath=rA(e.linkpath),typeof r.onwarn=="function"&&this.on("warn",r.onwarn);let s=!1;if(!this.preservePaths){let[a,n]=$he(this.path);a&&(this.path=n,s=a)}this.remain=e.size,this.blockRemain=e.startBlockSize,this.header=new zhe({path:this[iA](this.path),linkpath:this.type==="Link"?this[iA](this.linkpath):this.linkpath,mode:this.mode,uid:this.portable?null:this.uid,gid:this.portable?null:this.gid,size:this.size,mtime:this.noMtime?null:this.mtime,type:this.type,uname:this.portable?null:this.uname,atime:this.portable?null:this.atime,ctime:this.portable?null:this.ctime}),s&&this.warn("TAR_ENTRY_INFO",`stripping ${s} from absolute path`,{entry:this,path:s+this.path}),this.header.encode()&&!this.noPax&&super.write(new Khe({atime:this.portable?null:this.atime,ctime:this.portable?null:this.ctime,gid:this.portable?null:this.gid,mtime:this.noMtime?null:this.mtime,path:this[iA](this.path),linkpath:this.type==="Link"?this[iA](this.linkpath):this.linkpath,size:this.size,uid:this.portable?null:this.uid,uname:this.portable?null:this.uname,dev:this.portable?null:this.readEntry.dev,ino:this.portable?null:this.readEntry.ino,nlink:this.portable?null:this.readEntry.nlink}).encode()),super.write(this.header.block),e.pipe(this)}[iA](e){return Xhe(e,this.prefix)}[eR](e){return e0e(e,this.type==="Directory",this.portable)}write(e){let r=e.length;if(r>this.blockRemain)throw new Error("writing more to entry than is appropriate");return this.blockRemain-=r,super.write(e)}end(){return this.blockRemain&&super.write(Buffer.alloc(this.blockRemain)),super.end()}});tR.Sync=L6;tR.Tar=tst;var rst=t=>t.isFile()?"File":t.isDirectory()?"Directory":t.isSymbolicLink()?"SymbolicLink":"Unsupported";t0e.exports=tR});var uR=_((m3t,l0e)=>{"use strict";var lR=class{constructor(e,r){this.path=e||"./",this.absolute=r,this.entry=null,this.stat=null,this.readdir=null,this.pending=!1,this.ignore=!1,this.piped=!1}},nst=PI(),ist=m6(),sst=VT(),V6=M6(),ost=V6.Sync,ast=V6.Tar,lst=$x(),r0e=Buffer.alloc(1024),iR=Symbol("onStat"),rR=Symbol("ended"),sA=Symbol("queue"),NI=Symbol("current"),gm=Symbol("process"),nR=Symbol("processing"),n0e=Symbol("processJob"),oA=Symbol("jobs"),U6=Symbol("jobDone"),sR=Symbol("addFSEntry"),i0e=Symbol("addTarEntry"),G6=Symbol("stat"),q6=Symbol("readdir"),oR=Symbol("onreaddir"),aR=Symbol("pipe"),s0e=Symbol("entry"),_6=Symbol("entryOpt"),W6=Symbol("writeEntryClass"),a0e=Symbol("write"),H6=Symbol("ondrain"),cR=Ie("fs"),o0e=Ie("path"),cst=zT(),j6=QI(),J6=cst(class extends nst{constructor(e){super(e),e=e||Object.create(null),this.opt=e,this.file=e.file||"",this.cwd=e.cwd||process.cwd(),this.maxReadSize=e.maxReadSize,this.preservePaths=!!e.preservePaths,this.strict=!!e.strict,this.noPax=!!e.noPax,this.prefix=j6(e.prefix||""),this.linkCache=e.linkCache||new Map,this.statCache=e.statCache||new Map,this.readdirCache=e.readdirCache||new Map,this[W6]=V6,typeof e.onwarn=="function"&&this.on("warn",e.onwarn),this.portable=!!e.portable,this.zip=null,e.gzip?(typeof e.gzip!="object"&&(e.gzip={}),this.portable&&(e.gzip.portable=!0),this.zip=new ist.Gzip(e.gzip),this.zip.on("data",r=>super.write(r)),this.zip.on("end",r=>super.end()),this.zip.on("drain",r=>this[H6]()),this.on("resume",r=>this.zip.resume())):this.on("drain",this[H6]),this.noDirRecurse=!!e.noDirRecurse,this.follow=!!e.follow,this.noMtime=!!e.noMtime,this.mtime=e.mtime||null,this.filter=typeof e.filter=="function"?e.filter:r=>!0,this[sA]=new lst,this[oA]=0,this.jobs=+e.jobs||4,this[nR]=!1,this[rR]=!1}[a0e](e){return super.write(e)}add(e){return this.write(e),this}end(e){return e&&this.write(e),this[rR]=!0,this[gm](),this}write(e){if(this[rR])throw new Error("write after end");return e instanceof sst?this[i0e](e):this[sR](e),this.flowing}[i0e](e){let r=j6(o0e.resolve(this.cwd,e.path));if(!this.filter(e.path,e))e.resume();else{let s=new lR(e.path,r,!1);s.entry=new ast(e,this[_6](s)),s.entry.on("end",a=>this[U6](s)),this[oA]+=1,this[sA].push(s)}this[gm]()}[sR](e){let r=j6(o0e.resolve(this.cwd,e));this[sA].push(new lR(e,r)),this[gm]()}[G6](e){e.pending=!0,this[oA]+=1;let r=this.follow?"stat":"lstat";cR[r](e.absolute,(s,a)=>{e.pending=!1,this[oA]-=1,s?this.emit("error",s):this[iR](e,a)})}[iR](e,r){this.statCache.set(e.absolute,r),e.stat=r,this.filter(e.path,r)||(e.ignore=!0),this[gm]()}[q6](e){e.pending=!0,this[oA]+=1,cR.readdir(e.absolute,(r,s)=>{if(e.pending=!1,this[oA]-=1,r)return this.emit("error",r);this[oR](e,s)})}[oR](e,r){this.readdirCache.set(e.absolute,r),e.readdir=r,this[gm]()}[gm](){if(!this[nR]){this[nR]=!0;for(let e=this[sA].head;e!==null&&this[oA]this.warn(r,s,a),noPax:this.noPax,cwd:this.cwd,absolute:e.absolute,preservePaths:this.preservePaths,maxReadSize:this.maxReadSize,strict:this.strict,portable:this.portable,linkCache:this.linkCache,statCache:this.statCache,noMtime:this.noMtime,mtime:this.mtime,prefix:this.prefix}}[s0e](e){this[oA]+=1;try{return new this[W6](e.path,this[_6](e)).on("end",()=>this[U6](e)).on("error",r=>this.emit("error",r))}catch(r){this.emit("error",r)}}[H6](){this[NI]&&this[NI].entry&&this[NI].entry.resume()}[aR](e){e.piped=!0,e.readdir&&e.readdir.forEach(a=>{let n=e.path,c=n==="./"?"":n.replace(/\/*$/,"/");this[sR](c+a)});let r=e.entry,s=this.zip;s?r.on("data",a=>{s.write(a)||r.pause()}):r.on("data",a=>{super.write(a)||r.pause()})}pause(){return this.zip&&this.zip.pause(),super.pause()}}),Y6=class extends J6{constructor(e){super(e),this[W6]=ost}pause(){}resume(){}[G6](e){let r=this.follow?"statSync":"lstatSync";this[iR](e,cR[r](e.absolute))}[q6](e,r){this[oR](e,cR.readdirSync(e.absolute))}[aR](e){let r=e.entry,s=this.zip;e.readdir&&e.readdir.forEach(a=>{let n=e.path,c=n==="./"?"":n.replace(/\/*$/,"/");this[sR](c+a)}),s?r.on("data",a=>{s.write(a)}):r.on("data",a=>{super[a0e](a)})}};J6.Sync=Y6;l0e.exports=J6});var GI=_(Pv=>{"use strict";var ust=PI(),fst=Ie("events").EventEmitter,fl=Ie("fs"),X6=fl.writev;if(!X6){let t=process.binding("fs"),e=t.FSReqWrap||t.FSReqCallback;X6=(r,s,a,n)=>{let c=(p,h)=>n(p,h,s),f=new e;f.oncomplete=c,t.writeBuffers(r,s,a,f)}}var HI=Symbol("_autoClose"),Yu=Symbol("_close"),bv=Symbol("_ended"),ii=Symbol("_fd"),c0e=Symbol("_finished"),N0=Symbol("_flags"),K6=Symbol("_flush"),Z6=Symbol("_handleChunk"),$6=Symbol("_makeBuf"),gR=Symbol("_mode"),fR=Symbol("_needDrain"),UI=Symbol("_onerror"),jI=Symbol("_onopen"),z6=Symbol("_onread"),LI=Symbol("_onwrite"),O0=Symbol("_open"),Wp=Symbol("_path"),dm=Symbol("_pos"),aA=Symbol("_queue"),MI=Symbol("_read"),u0e=Symbol("_readSize"),F0=Symbol("_reading"),AR=Symbol("_remain"),f0e=Symbol("_size"),pR=Symbol("_write"),OI=Symbol("_writing"),hR=Symbol("_defaultFlag"),_I=Symbol("_errored"),dR=class extends ust{constructor(e,r){if(r=r||{},super(r),this.readable=!0,this.writable=!1,typeof e!="string")throw new TypeError("path must be a string");this[_I]=!1,this[ii]=typeof r.fd=="number"?r.fd:null,this[Wp]=e,this[u0e]=r.readSize||16*1024*1024,this[F0]=!1,this[f0e]=typeof r.size=="number"?r.size:1/0,this[AR]=this[f0e],this[HI]=typeof r.autoClose=="boolean"?r.autoClose:!0,typeof this[ii]=="number"?this[MI]():this[O0]()}get fd(){return this[ii]}get path(){return this[Wp]}write(){throw new TypeError("this is a readable stream")}end(){throw new TypeError("this is a readable stream")}[O0](){fl.open(this[Wp],"r",(e,r)=>this[jI](e,r))}[jI](e,r){e?this[UI](e):(this[ii]=r,this.emit("open",r),this[MI]())}[$6](){return Buffer.allocUnsafe(Math.min(this[u0e],this[AR]))}[MI](){if(!this[F0]){this[F0]=!0;let e=this[$6]();if(e.length===0)return process.nextTick(()=>this[z6](null,0,e));fl.read(this[ii],e,0,e.length,null,(r,s,a)=>this[z6](r,s,a))}}[z6](e,r,s){this[F0]=!1,e?this[UI](e):this[Z6](r,s)&&this[MI]()}[Yu](){if(this[HI]&&typeof this[ii]=="number"){let e=this[ii];this[ii]=null,fl.close(e,r=>r?this.emit("error",r):this.emit("close"))}}[UI](e){this[F0]=!0,this[Yu](),this.emit("error",e)}[Z6](e,r){let s=!1;return this[AR]-=e,e>0&&(s=super.write(ethis[jI](e,r))}[jI](e,r){this[hR]&&this[N0]==="r+"&&e&&e.code==="ENOENT"?(this[N0]="w",this[O0]()):e?this[UI](e):(this[ii]=r,this.emit("open",r),this[K6]())}end(e,r){return e&&this.write(e,r),this[bv]=!0,!this[OI]&&!this[aA].length&&typeof this[ii]=="number"&&this[LI](null,0),this}write(e,r){return typeof e=="string"&&(e=Buffer.from(e,r)),this[bv]?(this.emit("error",new Error("write() after end()")),!1):this[ii]===null||this[OI]||this[aA].length?(this[aA].push(e),this[fR]=!0,!1):(this[OI]=!0,this[pR](e),!0)}[pR](e){fl.write(this[ii],e,0,e.length,this[dm],(r,s)=>this[LI](r,s))}[LI](e,r){e?this[UI](e):(this[dm]!==null&&(this[dm]+=r),this[aA].length?this[K6]():(this[OI]=!1,this[bv]&&!this[c0e]?(this[c0e]=!0,this[Yu](),this.emit("finish")):this[fR]&&(this[fR]=!1,this.emit("drain"))))}[K6](){if(this[aA].length===0)this[bv]&&this[LI](null,0);else if(this[aA].length===1)this[pR](this[aA].pop());else{let e=this[aA];this[aA]=[],X6(this[ii],e,this[dm],(r,s)=>this[LI](r,s))}}[Yu](){if(this[HI]&&typeof this[ii]=="number"){let e=this[ii];this[ii]=null,fl.close(e,r=>r?this.emit("error",r):this.emit("close"))}}},tG=class extends mR{[O0](){let e;if(this[hR]&&this[N0]==="r+")try{e=fl.openSync(this[Wp],this[N0],this[gR])}catch(r){if(r.code==="ENOENT")return this[N0]="w",this[O0]();throw r}else e=fl.openSync(this[Wp],this[N0],this[gR]);this[jI](null,e)}[Yu](){if(this[HI]&&typeof this[ii]=="number"){let e=this[ii];this[ii]=null,fl.closeSync(e),this.emit("close")}}[pR](e){let r=!0;try{this[LI](null,fl.writeSync(this[ii],e,0,e.length,this[dm])),r=!1}finally{if(r)try{this[Yu]()}catch{}}}};Pv.ReadStream=dR;Pv.ReadStreamSync=eG;Pv.WriteStream=mR;Pv.WriteStreamSync=tG});var vR=_((I3t,y0e)=>{"use strict";var Ast=zT(),pst=RI(),hst=Ie("events"),gst=$x(),dst=1024*1024,mst=VT(),A0e=KT(),yst=m6(),rG=Buffer.from([31,139]),Lc=Symbol("state"),mm=Symbol("writeEntry"),Yp=Symbol("readEntry"),nG=Symbol("nextEntry"),p0e=Symbol("processEntry"),Mc=Symbol("extendedHeader"),xv=Symbol("globalExtendedHeader"),L0=Symbol("meta"),h0e=Symbol("emitMeta"),Di=Symbol("buffer"),Vp=Symbol("queue"),ym=Symbol("ended"),g0e=Symbol("emittedEnd"),Em=Symbol("emit"),Al=Symbol("unzip"),yR=Symbol("consumeChunk"),ER=Symbol("consumeChunkSub"),iG=Symbol("consumeBody"),d0e=Symbol("consumeMeta"),m0e=Symbol("consumeHeader"),IR=Symbol("consuming"),sG=Symbol("bufferConcat"),oG=Symbol("maybeEnd"),kv=Symbol("writing"),M0=Symbol("aborted"),CR=Symbol("onDone"),Im=Symbol("sawValidEntry"),wR=Symbol("sawNullBlock"),BR=Symbol("sawEOF"),Est=t=>!0;y0e.exports=Ast(class extends hst{constructor(e){e=e||{},super(e),this.file=e.file||"",this[Im]=null,this.on(CR,r=>{(this[Lc]==="begin"||this[Im]===!1)&&this.warn("TAR_BAD_ARCHIVE","Unrecognized archive format")}),e.ondone?this.on(CR,e.ondone):this.on(CR,r=>{this.emit("prefinish"),this.emit("finish"),this.emit("end"),this.emit("close")}),this.strict=!!e.strict,this.maxMetaEntrySize=e.maxMetaEntrySize||dst,this.filter=typeof e.filter=="function"?e.filter:Est,this.writable=!0,this.readable=!1,this[Vp]=new gst,this[Di]=null,this[Yp]=null,this[mm]=null,this[Lc]="begin",this[L0]="",this[Mc]=null,this[xv]=null,this[ym]=!1,this[Al]=null,this[M0]=!1,this[wR]=!1,this[BR]=!1,typeof e.onwarn=="function"&&this.on("warn",e.onwarn),typeof e.onentry=="function"&&this.on("entry",e.onentry)}[m0e](e,r){this[Im]===null&&(this[Im]=!1);let s;try{s=new pst(e,r,this[Mc],this[xv])}catch(a){return this.warn("TAR_ENTRY_INVALID",a)}if(s.nullBlock)this[wR]?(this[BR]=!0,this[Lc]==="begin"&&(this[Lc]="header"),this[Em]("eof")):(this[wR]=!0,this[Em]("nullBlock"));else if(this[wR]=!1,!s.cksumValid)this.warn("TAR_ENTRY_INVALID","checksum failure",{header:s});else if(!s.path)this.warn("TAR_ENTRY_INVALID","path is required",{header:s});else{let a=s.type;if(/^(Symbolic)?Link$/.test(a)&&!s.linkpath)this.warn("TAR_ENTRY_INVALID","linkpath required",{header:s});else if(!/^(Symbolic)?Link$/.test(a)&&s.linkpath)this.warn("TAR_ENTRY_INVALID","linkpath forbidden",{header:s});else{let n=this[mm]=new mst(s,this[Mc],this[xv]);if(!this[Im])if(n.remain){let c=()=>{n.invalid||(this[Im]=!0)};n.on("end",c)}else this[Im]=!0;n.meta?n.size>this.maxMetaEntrySize?(n.ignore=!0,this[Em]("ignoredEntry",n),this[Lc]="ignore",n.resume()):n.size>0&&(this[L0]="",n.on("data",c=>this[L0]+=c),this[Lc]="meta"):(this[Mc]=null,n.ignore=n.ignore||!this.filter(n.path,n),n.ignore?(this[Em]("ignoredEntry",n),this[Lc]=n.remain?"ignore":"header",n.resume()):(n.remain?this[Lc]="body":(this[Lc]="header",n.end()),this[Yp]?this[Vp].push(n):(this[Vp].push(n),this[nG]())))}}}[p0e](e){let r=!0;return e?Array.isArray(e)?this.emit.apply(this,e):(this[Yp]=e,this.emit("entry",e),e.emittedEnd||(e.on("end",s=>this[nG]()),r=!1)):(this[Yp]=null,r=!1),r}[nG](){do;while(this[p0e](this[Vp].shift()));if(!this[Vp].length){let e=this[Yp];!e||e.flowing||e.size===e.remain?this[kv]||this.emit("drain"):e.once("drain",s=>this.emit("drain"))}}[iG](e,r){let s=this[mm],a=s.blockRemain,n=a>=e.length&&r===0?e:e.slice(r,r+a);return s.write(n),s.blockRemain||(this[Lc]="header",this[mm]=null,s.end()),n.length}[d0e](e,r){let s=this[mm],a=this[iG](e,r);return this[mm]||this[h0e](s),a}[Em](e,r,s){!this[Vp].length&&!this[Yp]?this.emit(e,r,s):this[Vp].push([e,r,s])}[h0e](e){switch(this[Em]("meta",this[L0]),e.type){case"ExtendedHeader":case"OldExtendedHeader":this[Mc]=A0e.parse(this[L0],this[Mc],!1);break;case"GlobalExtendedHeader":this[xv]=A0e.parse(this[L0],this[xv],!0);break;case"NextFileHasLongPath":case"OldGnuLongPath":this[Mc]=this[Mc]||Object.create(null),this[Mc].path=this[L0].replace(/\0.*/,"");break;case"NextFileHasLongLinkpath":this[Mc]=this[Mc]||Object.create(null),this[Mc].linkpath=this[L0].replace(/\0.*/,"");break;default:throw new Error("unknown meta: "+e.type)}}abort(e){this[M0]=!0,this.emit("abort",e),this.warn("TAR_ABORT",e,{recoverable:!1})}write(e){if(this[M0])return;if(this[Al]===null&&e){if(this[Di]&&(e=Buffer.concat([this[Di],e]),this[Di]=null),e.lengththis[yR](n)),this[Al].on("error",n=>this.abort(n)),this[Al].on("end",n=>{this[ym]=!0,this[yR]()}),this[kv]=!0;let a=this[Al][s?"end":"write"](e);return this[kv]=!1,a}}this[kv]=!0,this[Al]?this[Al].write(e):this[yR](e),this[kv]=!1;let r=this[Vp].length?!1:this[Yp]?this[Yp].flowing:!0;return!r&&!this[Vp].length&&this[Yp].once("drain",s=>this.emit("drain")),r}[sG](e){e&&!this[M0]&&(this[Di]=this[Di]?Buffer.concat([this[Di],e]):e)}[oG](){if(this[ym]&&!this[g0e]&&!this[M0]&&!this[IR]){this[g0e]=!0;let e=this[mm];if(e&&e.blockRemain){let r=this[Di]?this[Di].length:0;this.warn("TAR_BAD_ARCHIVE",`Truncated input (needed ${e.blockRemain} more bytes, only ${r} available)`,{entry:e}),this[Di]&&e.write(this[Di]),e.end()}this[Em](CR)}}[yR](e){if(this[IR])this[sG](e);else if(!e&&!this[Di])this[oG]();else{if(this[IR]=!0,this[Di]){this[sG](e);let r=this[Di];this[Di]=null,this[ER](r)}else this[ER](e);for(;this[Di]&&this[Di].length>=512&&!this[M0]&&!this[BR];){let r=this[Di];this[Di]=null,this[ER](r)}this[IR]=!1}(!this[Di]||this[ym])&&this[oG]()}[ER](e){let r=0,s=e.length;for(;r+512<=s&&!this[M0]&&!this[BR];)switch(this[Lc]){case"begin":case"header":this[m0e](e,r),r+=512;break;case"ignore":case"body":r+=this[iG](e,r);break;case"meta":r+=this[d0e](e,r);break;default:throw new Error("invalid state: "+this[Lc])}r{"use strict";var Ist=DI(),I0e=vR(),qI=Ie("fs"),Cst=GI(),E0e=Ie("path"),aG=FI();w0e.exports=(t,e,r)=>{typeof t=="function"?(r=t,e=null,t={}):Array.isArray(t)&&(e=t,t={}),typeof e=="function"&&(r=e,e=null),e?e=Array.from(e):e=[];let s=Ist(t);if(s.sync&&typeof r=="function")throw new TypeError("callback not supported for sync tar functions");if(!s.file&&typeof r=="function")throw new TypeError("callback only supported with file option");return e.length&&Bst(s,e),s.noResume||wst(s),s.file&&s.sync?vst(s):s.file?Sst(s,r):C0e(s)};var wst=t=>{let e=t.onentry;t.onentry=e?r=>{e(r),r.resume()}:r=>r.resume()},Bst=(t,e)=>{let r=new Map(e.map(n=>[aG(n),!0])),s=t.filter,a=(n,c)=>{let f=c||E0e.parse(n).root||".",p=n===f?!1:r.has(n)?r.get(n):a(E0e.dirname(n),f);return r.set(n,p),p};t.filter=s?(n,c)=>s(n,c)&&a(aG(n)):n=>a(aG(n))},vst=t=>{let e=C0e(t),r=t.file,s=!0,a;try{let n=qI.statSync(r),c=t.maxReadSize||16*1024*1024;if(n.size{let r=new I0e(t),s=t.maxReadSize||16*1024*1024,a=t.file,n=new Promise((c,f)=>{r.on("error",f),r.on("end",c),qI.stat(a,(p,h)=>{if(p)f(p);else{let E=new Cst.ReadStream(a,{readSize:s,size:h.size});E.on("error",f),E.pipe(r)}})});return e?n.then(e,e):n},C0e=t=>new I0e(t)});var P0e=_((w3t,b0e)=>{"use strict";var Dst=DI(),DR=uR(),B0e=GI(),v0e=SR(),S0e=Ie("path");b0e.exports=(t,e,r)=>{if(typeof e=="function"&&(r=e),Array.isArray(t)&&(e=t,t={}),!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");e=Array.from(e);let s=Dst(t);if(s.sync&&typeof r=="function")throw new TypeError("callback not supported for sync tar functions");if(!s.file&&typeof r=="function")throw new TypeError("callback only supported with file option");return s.file&&s.sync?bst(s,e):s.file?Pst(s,e,r):s.sync?xst(s,e):kst(s,e)};var bst=(t,e)=>{let r=new DR.Sync(t),s=new B0e.WriteStreamSync(t.file,{mode:t.mode||438});r.pipe(s),D0e(r,e)},Pst=(t,e,r)=>{let s=new DR(t),a=new B0e.WriteStream(t.file,{mode:t.mode||438});s.pipe(a);let n=new Promise((c,f)=>{a.on("error",f),a.on("close",c),s.on("error",f)});return lG(s,e),r?n.then(r,r):n},D0e=(t,e)=>{e.forEach(r=>{r.charAt(0)==="@"?v0e({file:S0e.resolve(t.cwd,r.substr(1)),sync:!0,noResume:!0,onentry:s=>t.add(s)}):t.add(r)}),t.end()},lG=(t,e)=>{for(;e.length;){let r=e.shift();if(r.charAt(0)==="@")return v0e({file:S0e.resolve(t.cwd,r.substr(1)),noResume:!0,onentry:s=>t.add(s)}).then(s=>lG(t,e));t.add(r)}t.end()},xst=(t,e)=>{let r=new DR.Sync(t);return D0e(r,e),r},kst=(t,e)=>{let r=new DR(t);return lG(r,e),r}});var cG=_((B3t,N0e)=>{"use strict";var Qst=DI(),x0e=uR(),Xl=Ie("fs"),k0e=GI(),Q0e=SR(),T0e=Ie("path"),R0e=RI();N0e.exports=(t,e,r)=>{let s=Qst(t);if(!s.file)throw new TypeError("file is required");if(s.gzip)throw new TypeError("cannot append to compressed archives");if(!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");return e=Array.from(e),s.sync?Tst(s,e):Fst(s,e,r)};var Tst=(t,e)=>{let r=new x0e.Sync(t),s=!0,a,n;try{try{a=Xl.openSync(t.file,"r+")}catch(p){if(p.code==="ENOENT")a=Xl.openSync(t.file,"w+");else throw p}let c=Xl.fstatSync(a),f=Buffer.alloc(512);e:for(n=0;nc.size)break;n+=h,t.mtimeCache&&t.mtimeCache.set(p.path,p.mtime)}s=!1,Rst(t,r,n,a,e)}finally{if(s)try{Xl.closeSync(a)}catch{}}},Rst=(t,e,r,s,a)=>{let n=new k0e.WriteStreamSync(t.file,{fd:s,start:r});e.pipe(n),Nst(e,a)},Fst=(t,e,r)=>{e=Array.from(e);let s=new x0e(t),a=(c,f,p)=>{let h=(I,R)=>{I?Xl.close(c,N=>p(I)):p(null,R)},E=0;if(f===0)return h(null,0);let C=0,S=Buffer.alloc(512),P=(I,R)=>{if(I)return h(I);if(C+=R,C<512&&R)return Xl.read(c,S,C,S.length-C,E+C,P);if(E===0&&S[0]===31&&S[1]===139)return h(new Error("cannot append to compressed archives"));if(C<512)return h(null,E);let N=new R0e(S);if(!N.cksumValid)return h(null,E);let U=512*Math.ceil(N.size/512);if(E+U+512>f||(E+=U+512,E>=f))return h(null,E);t.mtimeCache&&t.mtimeCache.set(N.path,N.mtime),C=0,Xl.read(c,S,0,512,E,P)};Xl.read(c,S,0,512,E,P)},n=new Promise((c,f)=>{s.on("error",f);let p="r+",h=(E,C)=>{if(E&&E.code==="ENOENT"&&p==="r+")return p="w+",Xl.open(t.file,p,h);if(E)return f(E);Xl.fstat(C,(S,P)=>{if(S)return Xl.close(C,()=>f(S));a(C,P.size,(I,R)=>{if(I)return f(I);let N=new k0e.WriteStream(t.file,{fd:C,start:R});s.pipe(N),N.on("error",f),N.on("close",c),F0e(s,e)})})};Xl.open(t.file,p,h)});return r?n.then(r,r):n},Nst=(t,e)=>{e.forEach(r=>{r.charAt(0)==="@"?Q0e({file:T0e.resolve(t.cwd,r.substr(1)),sync:!0,noResume:!0,onentry:s=>t.add(s)}):t.add(r)}),t.end()},F0e=(t,e)=>{for(;e.length;){let r=e.shift();if(r.charAt(0)==="@")return Q0e({file:T0e.resolve(t.cwd,r.substr(1)),noResume:!0,onentry:s=>t.add(s)}).then(s=>F0e(t,e));t.add(r)}t.end()}});var L0e=_((v3t,O0e)=>{"use strict";var Ost=DI(),Lst=cG();O0e.exports=(t,e,r)=>{let s=Ost(t);if(!s.file)throw new TypeError("file is required");if(s.gzip)throw new TypeError("cannot append to compressed archives");if(!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");return e=Array.from(e),Mst(s),Lst(s,e,r)};var Mst=t=>{let e=t.filter;t.mtimeCache||(t.mtimeCache=new Map),t.filter=e?(r,s)=>e(r,s)&&!(t.mtimeCache.get(r)>s.mtime):(r,s)=>!(t.mtimeCache.get(r)>s.mtime)}});var _0e=_((S3t,U0e)=>{var{promisify:M0e}=Ie("util"),U0=Ie("fs"),Ust=t=>{if(!t)t={mode:511,fs:U0};else if(typeof t=="object")t={mode:511,fs:U0,...t};else if(typeof t=="number")t={mode:t,fs:U0};else if(typeof t=="string")t={mode:parseInt(t,8),fs:U0};else throw new TypeError("invalid options argument");return t.mkdir=t.mkdir||t.fs.mkdir||U0.mkdir,t.mkdirAsync=M0e(t.mkdir),t.stat=t.stat||t.fs.stat||U0.stat,t.statAsync=M0e(t.stat),t.statSync=t.statSync||t.fs.statSync||U0.statSync,t.mkdirSync=t.mkdirSync||t.fs.mkdirSync||U0.mkdirSync,t};U0e.exports=Ust});var j0e=_((D3t,H0e)=>{var _st=process.platform,{resolve:Hst,parse:jst}=Ie("path"),Gst=t=>{if(/\0/.test(t))throw Object.assign(new TypeError("path must be a string without null bytes"),{path:t,code:"ERR_INVALID_ARG_VALUE"});if(t=Hst(t),_st==="win32"){let e=/[*|"<>?:]/,{root:r}=jst(t);if(e.test(t.substr(r.length)))throw Object.assign(new Error("Illegal characters in path."),{path:t,code:"EINVAL"})}return t};H0e.exports=Gst});var V0e=_((b3t,Y0e)=>{var{dirname:G0e}=Ie("path"),q0e=(t,e,r=void 0)=>r===e?Promise.resolve():t.statAsync(e).then(s=>s.isDirectory()?r:void 0,s=>s.code==="ENOENT"?q0e(t,G0e(e),e):void 0),W0e=(t,e,r=void 0)=>{if(r!==e)try{return t.statSync(e).isDirectory()?r:void 0}catch(s){return s.code==="ENOENT"?W0e(t,G0e(e),e):void 0}};Y0e.exports={findMade:q0e,findMadeSync:W0e}});var AG=_((P3t,K0e)=>{var{dirname:J0e}=Ie("path"),uG=(t,e,r)=>{e.recursive=!1;let s=J0e(t);return s===t?e.mkdirAsync(t,e).catch(a=>{if(a.code!=="EISDIR")throw a}):e.mkdirAsync(t,e).then(()=>r||t,a=>{if(a.code==="ENOENT")return uG(s,e).then(n=>uG(t,e,n));if(a.code!=="EEXIST"&&a.code!=="EROFS")throw a;return e.statAsync(t).then(n=>{if(n.isDirectory())return r;throw a},()=>{throw a})})},fG=(t,e,r)=>{let s=J0e(t);if(e.recursive=!1,s===t)try{return e.mkdirSync(t,e)}catch(a){if(a.code!=="EISDIR")throw a;return}try{return e.mkdirSync(t,e),r||t}catch(a){if(a.code==="ENOENT")return fG(t,e,fG(s,e,r));if(a.code!=="EEXIST"&&a.code!=="EROFS")throw a;try{if(!e.statSync(t).isDirectory())throw a}catch{throw a}}};K0e.exports={mkdirpManual:uG,mkdirpManualSync:fG}});var Z0e=_((x3t,X0e)=>{var{dirname:z0e}=Ie("path"),{findMade:qst,findMadeSync:Wst}=V0e(),{mkdirpManual:Yst,mkdirpManualSync:Vst}=AG(),Jst=(t,e)=>(e.recursive=!0,z0e(t)===t?e.mkdirAsync(t,e):qst(e,t).then(s=>e.mkdirAsync(t,e).then(()=>s).catch(a=>{if(a.code==="ENOENT")return Yst(t,e);throw a}))),Kst=(t,e)=>{if(e.recursive=!0,z0e(t)===t)return e.mkdirSync(t,e);let s=Wst(e,t);try{return e.mkdirSync(t,e),s}catch(a){if(a.code==="ENOENT")return Vst(t,e);throw a}};X0e.exports={mkdirpNative:Jst,mkdirpNativeSync:Kst}});var rge=_((k3t,tge)=>{var $0e=Ie("fs"),zst=process.version,pG=zst.replace(/^v/,"").split("."),ege=+pG[0]>10||+pG[0]==10&&+pG[1]>=12,Xst=ege?t=>t.mkdir===$0e.mkdir:()=>!1,Zst=ege?t=>t.mkdirSync===$0e.mkdirSync:()=>!1;tge.exports={useNative:Xst,useNativeSync:Zst}});var lge=_((Q3t,age)=>{var WI=_0e(),YI=j0e(),{mkdirpNative:nge,mkdirpNativeSync:ige}=Z0e(),{mkdirpManual:sge,mkdirpManualSync:oge}=AG(),{useNative:$st,useNativeSync:eot}=rge(),VI=(t,e)=>(t=YI(t),e=WI(e),$st(e)?nge(t,e):sge(t,e)),tot=(t,e)=>(t=YI(t),e=WI(e),eot(e)?ige(t,e):oge(t,e));VI.sync=tot;VI.native=(t,e)=>nge(YI(t),WI(e));VI.manual=(t,e)=>sge(YI(t),WI(e));VI.nativeSync=(t,e)=>ige(YI(t),WI(e));VI.manualSync=(t,e)=>oge(YI(t),WI(e));age.exports=VI});var gge=_((T3t,hge)=>{"use strict";var Uc=Ie("fs"),Cm=Ie("path"),rot=Uc.lchown?"lchown":"chown",not=Uc.lchownSync?"lchownSync":"chownSync",uge=Uc.lchown&&!process.version.match(/v1[1-9]+\./)&&!process.version.match(/v10\.[6-9]/),cge=(t,e,r)=>{try{return Uc[not](t,e,r)}catch(s){if(s.code!=="ENOENT")throw s}},iot=(t,e,r)=>{try{return Uc.chownSync(t,e,r)}catch(s){if(s.code!=="ENOENT")throw s}},sot=uge?(t,e,r,s)=>a=>{!a||a.code!=="EISDIR"?s(a):Uc.chown(t,e,r,s)}:(t,e,r,s)=>s,hG=uge?(t,e,r)=>{try{return cge(t,e,r)}catch(s){if(s.code!=="EISDIR")throw s;iot(t,e,r)}}:(t,e,r)=>cge(t,e,r),oot=process.version,fge=(t,e,r)=>Uc.readdir(t,e,r),aot=(t,e)=>Uc.readdirSync(t,e);/^v4\./.test(oot)&&(fge=(t,e,r)=>Uc.readdir(t,r));var bR=(t,e,r,s)=>{Uc[rot](t,e,r,sot(t,e,r,a=>{s(a&&a.code!=="ENOENT"?a:null)}))},Age=(t,e,r,s,a)=>{if(typeof e=="string")return Uc.lstat(Cm.resolve(t,e),(n,c)=>{if(n)return a(n.code!=="ENOENT"?n:null);c.name=e,Age(t,c,r,s,a)});if(e.isDirectory())gG(Cm.resolve(t,e.name),r,s,n=>{if(n)return a(n);let c=Cm.resolve(t,e.name);bR(c,r,s,a)});else{let n=Cm.resolve(t,e.name);bR(n,r,s,a)}},gG=(t,e,r,s)=>{fge(t,{withFileTypes:!0},(a,n)=>{if(a){if(a.code==="ENOENT")return s();if(a.code!=="ENOTDIR"&&a.code!=="ENOTSUP")return s(a)}if(a||!n.length)return bR(t,e,r,s);let c=n.length,f=null,p=h=>{if(!f){if(h)return s(f=h);if(--c===0)return bR(t,e,r,s)}};n.forEach(h=>Age(t,h,e,r,p))})},lot=(t,e,r,s)=>{if(typeof e=="string")try{let a=Uc.lstatSync(Cm.resolve(t,e));a.name=e,e=a}catch(a){if(a.code==="ENOENT")return;throw a}e.isDirectory()&&pge(Cm.resolve(t,e.name),r,s),hG(Cm.resolve(t,e.name),r,s)},pge=(t,e,r)=>{let s;try{s=aot(t,{withFileTypes:!0})}catch(a){if(a.code==="ENOENT")return;if(a.code==="ENOTDIR"||a.code==="ENOTSUP")return hG(t,e,r);throw a}return s&&s.length&&s.forEach(a=>lot(t,a,e,r)),hG(t,e,r)};hge.exports=gG;gG.sync=pge});var Ege=_((R3t,dG)=>{"use strict";var dge=lge(),_c=Ie("fs"),PR=Ie("path"),mge=gge(),Vu=QI(),xR=class extends Error{constructor(e,r){super("Cannot extract through symbolic link"),this.path=r,this.symlink=e}get name(){return"SylinkError"}},kR=class extends Error{constructor(e,r){super(r+": Cannot cd into '"+e+"'"),this.path=e,this.code=r}get name(){return"CwdError"}},QR=(t,e)=>t.get(Vu(e)),Qv=(t,e,r)=>t.set(Vu(e),r),cot=(t,e)=>{_c.stat(t,(r,s)=>{(r||!s.isDirectory())&&(r=new kR(t,r&&r.code||"ENOTDIR")),e(r)})};dG.exports=(t,e,r)=>{t=Vu(t);let s=e.umask,a=e.mode|448,n=(a&s)!==0,c=e.uid,f=e.gid,p=typeof c=="number"&&typeof f=="number"&&(c!==e.processUid||f!==e.processGid),h=e.preserve,E=e.unlink,C=e.cache,S=Vu(e.cwd),P=(N,U)=>{N?r(N):(Qv(C,t,!0),U&&p?mge(U,c,f,W=>P(W)):n?_c.chmod(t,a,r):r())};if(C&&QR(C,t)===!0)return P();if(t===S)return cot(t,P);if(h)return dge(t,{mode:a}).then(N=>P(null,N),P);let R=Vu(PR.relative(S,t)).split("/");TR(S,R,a,C,E,S,null,P)};var TR=(t,e,r,s,a,n,c,f)=>{if(!e.length)return f(null,c);let p=e.shift(),h=Vu(PR.resolve(t+"/"+p));if(QR(s,h))return TR(h,e,r,s,a,n,c,f);_c.mkdir(h,r,yge(h,e,r,s,a,n,c,f))},yge=(t,e,r,s,a,n,c,f)=>p=>{p?_c.lstat(t,(h,E)=>{if(h)h.path=h.path&&Vu(h.path),f(h);else if(E.isDirectory())TR(t,e,r,s,a,n,c,f);else if(a)_c.unlink(t,C=>{if(C)return f(C);_c.mkdir(t,r,yge(t,e,r,s,a,n,c,f))});else{if(E.isSymbolicLink())return f(new xR(t,t+"/"+e.join("/")));f(p)}}):(c=c||t,TR(t,e,r,s,a,n,c,f))},uot=t=>{let e=!1,r="ENOTDIR";try{e=_c.statSync(t).isDirectory()}catch(s){r=s.code}finally{if(!e)throw new kR(t,r)}};dG.exports.sync=(t,e)=>{t=Vu(t);let r=e.umask,s=e.mode|448,a=(s&r)!==0,n=e.uid,c=e.gid,f=typeof n=="number"&&typeof c=="number"&&(n!==e.processUid||c!==e.processGid),p=e.preserve,h=e.unlink,E=e.cache,C=Vu(e.cwd),S=N=>{Qv(E,t,!0),N&&f&&mge.sync(N,n,c),a&&_c.chmodSync(t,s)};if(E&&QR(E,t)===!0)return S();if(t===C)return uot(C),S();if(p)return S(dge.sync(t,s));let I=Vu(PR.relative(C,t)).split("/"),R=null;for(let N=I.shift(),U=C;N&&(U+="/"+N);N=I.shift())if(U=Vu(PR.resolve(U)),!QR(E,U))try{_c.mkdirSync(U,s),R=R||U,Qv(E,U,!0)}catch{let ee=_c.lstatSync(U);if(ee.isDirectory()){Qv(E,U,!0);continue}else if(h){_c.unlinkSync(U),_c.mkdirSync(U,s),R=R||U,Qv(E,U,!0);continue}else if(ee.isSymbolicLink())return new xR(U,U+"/"+I.join("/"))}return S(R)}});var yG=_((F3t,Ige)=>{var mG=Object.create(null),{hasOwnProperty:fot}=Object.prototype;Ige.exports=t=>(fot.call(mG,t)||(mG[t]=t.normalize("NFKD")),mG[t])});var vge=_((N3t,Bge)=>{var Cge=Ie("assert"),Aot=yG(),pot=FI(),{join:wge}=Ie("path"),hot=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform,got=hot==="win32";Bge.exports=()=>{let t=new Map,e=new Map,r=h=>h.split("/").slice(0,-1).reduce((C,S)=>(C.length&&(S=wge(C[C.length-1],S)),C.push(S||"/"),C),[]),s=new Set,a=h=>{let E=e.get(h);if(!E)throw new Error("function does not have any path reservations");return{paths:E.paths.map(C=>t.get(C)),dirs:[...E.dirs].map(C=>t.get(C))}},n=h=>{let{paths:E,dirs:C}=a(h);return E.every(S=>S[0]===h)&&C.every(S=>S[0]instanceof Set&&S[0].has(h))},c=h=>s.has(h)||!n(h)?!1:(s.add(h),h(()=>f(h)),!0),f=h=>{if(!s.has(h))return!1;let{paths:E,dirs:C}=e.get(h),S=new Set;return E.forEach(P=>{let I=t.get(P);Cge.equal(I[0],h),I.length===1?t.delete(P):(I.shift(),typeof I[0]=="function"?S.add(I[0]):I[0].forEach(R=>S.add(R)))}),C.forEach(P=>{let I=t.get(P);Cge(I[0]instanceof Set),I[0].size===1&&I.length===1?t.delete(P):I[0].size===1?(I.shift(),S.add(I[0])):I[0].delete(h)}),s.delete(h),S.forEach(P=>c(P)),!0};return{check:n,reserve:(h,E)=>{h=got?["win32 parallelization disabled"]:h.map(S=>Aot(pot(wge(S))).toLowerCase());let C=new Set(h.map(S=>r(S)).reduce((S,P)=>S.concat(P)));return e.set(E,{dirs:C,paths:h}),h.forEach(S=>{let P=t.get(S);P?P.push(E):t.set(S,[E])}),C.forEach(S=>{let P=t.get(S);P?P[P.length-1]instanceof Set?P[P.length-1].add(E):P.push(new Set([E])):t.set(S,[new Set([E])])}),c(E)}}}});var bge=_((O3t,Dge)=>{var dot=process.platform,mot=dot==="win32",yot=global.__FAKE_TESTING_FS__||Ie("fs"),{O_CREAT:Eot,O_TRUNC:Iot,O_WRONLY:Cot,UV_FS_O_FILEMAP:Sge=0}=yot.constants,wot=mot&&!!Sge,Bot=512*1024,vot=Sge|Iot|Eot|Cot;Dge.exports=wot?t=>t"w"});var bG=_((L3t,Hge)=>{"use strict";var Sot=Ie("assert"),Dot=vR(),Mn=Ie("fs"),bot=GI(),Jp=Ie("path"),Mge=Ege(),Pge=b6(),Pot=vge(),xot=P6(),Zl=QI(),kot=FI(),Qot=yG(),xge=Symbol("onEntry"),CG=Symbol("checkFs"),kge=Symbol("checkFs2"),NR=Symbol("pruneCache"),wG=Symbol("isReusable"),Hc=Symbol("makeFs"),BG=Symbol("file"),vG=Symbol("directory"),OR=Symbol("link"),Qge=Symbol("symlink"),Tge=Symbol("hardlink"),Rge=Symbol("unsupported"),Fge=Symbol("checkPath"),_0=Symbol("mkdir"),Xo=Symbol("onError"),RR=Symbol("pending"),Nge=Symbol("pend"),JI=Symbol("unpend"),EG=Symbol("ended"),IG=Symbol("maybeClose"),SG=Symbol("skip"),Tv=Symbol("doChown"),Rv=Symbol("uid"),Fv=Symbol("gid"),Nv=Symbol("checkedCwd"),Uge=Ie("crypto"),_ge=bge(),Tot=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform,Ov=Tot==="win32",Rot=(t,e)=>{if(!Ov)return Mn.unlink(t,e);let r=t+".DELETE."+Uge.randomBytes(16).toString("hex");Mn.rename(t,r,s=>{if(s)return e(s);Mn.unlink(r,e)})},Fot=t=>{if(!Ov)return Mn.unlinkSync(t);let e=t+".DELETE."+Uge.randomBytes(16).toString("hex");Mn.renameSync(t,e),Mn.unlinkSync(e)},Oge=(t,e,r)=>t===t>>>0?t:e===e>>>0?e:r,Lge=t=>Qot(kot(Zl(t))).toLowerCase(),Not=(t,e)=>{e=Lge(e);for(let r of t.keys()){let s=Lge(r);(s===e||s.indexOf(e+"/")===0)&&t.delete(r)}},Oot=t=>{for(let e of t.keys())t.delete(e)},Lv=class extends Dot{constructor(e){if(e||(e={}),e.ondone=r=>{this[EG]=!0,this[IG]()},super(e),this[Nv]=!1,this.reservations=Pot(),this.transform=typeof e.transform=="function"?e.transform:null,this.writable=!0,this.readable=!1,this[RR]=0,this[EG]=!1,this.dirCache=e.dirCache||new Map,typeof e.uid=="number"||typeof e.gid=="number"){if(typeof e.uid!="number"||typeof e.gid!="number")throw new TypeError("cannot set owner without number uid and gid");if(e.preserveOwner)throw new TypeError("cannot preserve owner in archive and also set owner explicitly");this.uid=e.uid,this.gid=e.gid,this.setOwner=!0}else this.uid=null,this.gid=null,this.setOwner=!1;e.preserveOwner===void 0&&typeof e.uid!="number"?this.preserveOwner=process.getuid&&process.getuid()===0:this.preserveOwner=!!e.preserveOwner,this.processUid=(this.preserveOwner||this.setOwner)&&process.getuid?process.getuid():null,this.processGid=(this.preserveOwner||this.setOwner)&&process.getgid?process.getgid():null,this.forceChown=e.forceChown===!0,this.win32=!!e.win32||Ov,this.newer=!!e.newer,this.keep=!!e.keep,this.noMtime=!!e.noMtime,this.preservePaths=!!e.preservePaths,this.unlink=!!e.unlink,this.cwd=Zl(Jp.resolve(e.cwd||process.cwd())),this.strip=+e.strip||0,this.processUmask=e.noChmod?0:process.umask(),this.umask=typeof e.umask=="number"?e.umask:this.processUmask,this.dmode=e.dmode||511&~this.umask,this.fmode=e.fmode||438&~this.umask,this.on("entry",r=>this[xge](r))}warn(e,r,s={}){return(e==="TAR_BAD_ARCHIVE"||e==="TAR_ABORT")&&(s.recoverable=!1),super.warn(e,r,s)}[IG](){this[EG]&&this[RR]===0&&(this.emit("prefinish"),this.emit("finish"),this.emit("end"),this.emit("close"))}[Fge](e){if(this.strip){let r=Zl(e.path).split("/");if(r.length=this.strip)e.linkpath=s.slice(this.strip).join("/");else return!1}}if(!this.preservePaths){let r=Zl(e.path),s=r.split("/");if(s.includes("..")||Ov&&/^[a-z]:\.\.$/i.test(s[0]))return this.warn("TAR_ENTRY_ERROR","path contains '..'",{entry:e,path:r}),!1;let[a,n]=xot(r);a&&(e.path=n,this.warn("TAR_ENTRY_INFO",`stripping ${a} from absolute path`,{entry:e,path:r}))}if(Jp.isAbsolute(e.path)?e.absolute=Zl(Jp.resolve(e.path)):e.absolute=Zl(Jp.resolve(this.cwd,e.path)),!this.preservePaths&&e.absolute.indexOf(this.cwd+"/")!==0&&e.absolute!==this.cwd)return this.warn("TAR_ENTRY_ERROR","path escaped extraction target",{entry:e,path:Zl(e.path),resolvedPath:e.absolute,cwd:this.cwd}),!1;if(e.absolute===this.cwd&&e.type!=="Directory"&&e.type!=="GNUDumpDir")return!1;if(this.win32){let{root:r}=Jp.win32.parse(e.absolute);e.absolute=r+Pge.encode(e.absolute.substr(r.length));let{root:s}=Jp.win32.parse(e.path);e.path=s+Pge.encode(e.path.substr(s.length))}return!0}[xge](e){if(!this[Fge](e))return e.resume();switch(Sot.equal(typeof e.absolute,"string"),e.type){case"Directory":case"GNUDumpDir":e.mode&&(e.mode=e.mode|448);case"File":case"OldFile":case"ContiguousFile":case"Link":case"SymbolicLink":return this[CG](e);case"CharacterDevice":case"BlockDevice":case"FIFO":default:return this[Rge](e)}}[Xo](e,r){e.name==="CwdError"?this.emit("error",e):(this.warn("TAR_ENTRY_ERROR",e,{entry:r}),this[JI](),r.resume())}[_0](e,r,s){Mge(Zl(e),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cache:this.dirCache,cwd:this.cwd,mode:r,noChmod:this.noChmod},s)}[Tv](e){return this.forceChown||this.preserveOwner&&(typeof e.uid=="number"&&e.uid!==this.processUid||typeof e.gid=="number"&&e.gid!==this.processGid)||typeof this.uid=="number"&&this.uid!==this.processUid||typeof this.gid=="number"&&this.gid!==this.processGid}[Rv](e){return Oge(this.uid,e.uid,this.processUid)}[Fv](e){return Oge(this.gid,e.gid,this.processGid)}[BG](e,r){let s=e.mode&4095||this.fmode,a=new bot.WriteStream(e.absolute,{flags:_ge(e.size),mode:s,autoClose:!1});a.on("error",p=>{a.fd&&Mn.close(a.fd,()=>{}),a.write=()=>!0,this[Xo](p,e),r()});let n=1,c=p=>{if(p){a.fd&&Mn.close(a.fd,()=>{}),this[Xo](p,e),r();return}--n===0&&Mn.close(a.fd,h=>{h?this[Xo](h,e):this[JI](),r()})};a.on("finish",p=>{let h=e.absolute,E=a.fd;if(e.mtime&&!this.noMtime){n++;let C=e.atime||new Date,S=e.mtime;Mn.futimes(E,C,S,P=>P?Mn.utimes(h,C,S,I=>c(I&&P)):c())}if(this[Tv](e)){n++;let C=this[Rv](e),S=this[Fv](e);Mn.fchown(E,C,S,P=>P?Mn.chown(h,C,S,I=>c(I&&P)):c())}c()});let f=this.transform&&this.transform(e)||e;f!==e&&(f.on("error",p=>{this[Xo](p,e),r()}),e.pipe(f)),f.pipe(a)}[vG](e,r){let s=e.mode&4095||this.dmode;this[_0](e.absolute,s,a=>{if(a){this[Xo](a,e),r();return}let n=1,c=f=>{--n===0&&(r(),this[JI](),e.resume())};e.mtime&&!this.noMtime&&(n++,Mn.utimes(e.absolute,e.atime||new Date,e.mtime,c)),this[Tv](e)&&(n++,Mn.chown(e.absolute,this[Rv](e),this[Fv](e),c)),c()})}[Rge](e){e.unsupported=!0,this.warn("TAR_ENTRY_UNSUPPORTED",`unsupported entry type: ${e.type}`,{entry:e}),e.resume()}[Qge](e,r){this[OR](e,e.linkpath,"symlink",r)}[Tge](e,r){let s=Zl(Jp.resolve(this.cwd,e.linkpath));this[OR](e,s,"link",r)}[Nge](){this[RR]++}[JI](){this[RR]--,this[IG]()}[SG](e){this[JI](),e.resume()}[wG](e,r){return e.type==="File"&&!this.unlink&&r.isFile()&&r.nlink<=1&&!Ov}[CG](e){this[Nge]();let r=[e.path];e.linkpath&&r.push(e.linkpath),this.reservations.reserve(r,s=>this[kge](e,s))}[NR](e){e.type==="SymbolicLink"?Oot(this.dirCache):e.type!=="Directory"&&Not(this.dirCache,e.absolute)}[kge](e,r){this[NR](e);let s=f=>{this[NR](e),r(f)},a=()=>{this[_0](this.cwd,this.dmode,f=>{if(f){this[Xo](f,e),s();return}this[Nv]=!0,n()})},n=()=>{if(e.absolute!==this.cwd){let f=Zl(Jp.dirname(e.absolute));if(f!==this.cwd)return this[_0](f,this.dmode,p=>{if(p){this[Xo](p,e),s();return}c()})}c()},c=()=>{Mn.lstat(e.absolute,(f,p)=>{if(p&&(this.keep||this.newer&&p.mtime>e.mtime)){this[SG](e),s();return}if(f||this[wG](e,p))return this[Hc](null,e,s);if(p.isDirectory()){if(e.type==="Directory"){let h=!this.noChmod&&e.mode&&(p.mode&4095)!==e.mode,E=C=>this[Hc](C,e,s);return h?Mn.chmod(e.absolute,e.mode,E):E()}if(e.absolute!==this.cwd)return Mn.rmdir(e.absolute,h=>this[Hc](h,e,s))}if(e.absolute===this.cwd)return this[Hc](null,e,s);Rot(e.absolute,h=>this[Hc](h,e,s))})};this[Nv]?n():a()}[Hc](e,r,s){if(e){this[Xo](e,r),s();return}switch(r.type){case"File":case"OldFile":case"ContiguousFile":return this[BG](r,s);case"Link":return this[Tge](r,s);case"SymbolicLink":return this[Qge](r,s);case"Directory":case"GNUDumpDir":return this[vG](r,s)}}[OR](e,r,s,a){Mn[s](r,e.absolute,n=>{n?this[Xo](n,e):(this[JI](),e.resume()),a()})}},FR=t=>{try{return[null,t()]}catch(e){return[e,null]}},DG=class extends Lv{[Hc](e,r){return super[Hc](e,r,()=>{})}[CG](e){if(this[NR](e),!this[Nv]){let n=this[_0](this.cwd,this.dmode);if(n)return this[Xo](n,e);this[Nv]=!0}if(e.absolute!==this.cwd){let n=Zl(Jp.dirname(e.absolute));if(n!==this.cwd){let c=this[_0](n,this.dmode);if(c)return this[Xo](c,e)}}let[r,s]=FR(()=>Mn.lstatSync(e.absolute));if(s&&(this.keep||this.newer&&s.mtime>e.mtime))return this[SG](e);if(r||this[wG](e,s))return this[Hc](null,e);if(s.isDirectory()){if(e.type==="Directory"){let c=!this.noChmod&&e.mode&&(s.mode&4095)!==e.mode,[f]=c?FR(()=>{Mn.chmodSync(e.absolute,e.mode)}):[];return this[Hc](f,e)}let[n]=FR(()=>Mn.rmdirSync(e.absolute));this[Hc](n,e)}let[a]=e.absolute===this.cwd?[]:FR(()=>Fot(e.absolute));this[Hc](a,e)}[BG](e,r){let s=e.mode&4095||this.fmode,a=f=>{let p;try{Mn.closeSync(n)}catch(h){p=h}(f||p)&&this[Xo](f||p,e),r()},n;try{n=Mn.openSync(e.absolute,_ge(e.size),s)}catch(f){return a(f)}let c=this.transform&&this.transform(e)||e;c!==e&&(c.on("error",f=>this[Xo](f,e)),e.pipe(c)),c.on("data",f=>{try{Mn.writeSync(n,f,0,f.length)}catch(p){a(p)}}),c.on("end",f=>{let p=null;if(e.mtime&&!this.noMtime){let h=e.atime||new Date,E=e.mtime;try{Mn.futimesSync(n,h,E)}catch(C){try{Mn.utimesSync(e.absolute,h,E)}catch{p=C}}}if(this[Tv](e)){let h=this[Rv](e),E=this[Fv](e);try{Mn.fchownSync(n,h,E)}catch(C){try{Mn.chownSync(e.absolute,h,E)}catch{p=p||C}}}a(p)})}[vG](e,r){let s=e.mode&4095||this.dmode,a=this[_0](e.absolute,s);if(a){this[Xo](a,e),r();return}if(e.mtime&&!this.noMtime)try{Mn.utimesSync(e.absolute,e.atime||new Date,e.mtime)}catch{}if(this[Tv](e))try{Mn.chownSync(e.absolute,this[Rv](e),this[Fv](e))}catch{}r(),e.resume()}[_0](e,r){try{return Mge.sync(Zl(e),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cache:this.dirCache,cwd:this.cwd,mode:r})}catch(s){return s}}[OR](e,r,s,a){try{Mn[s+"Sync"](r,e.absolute),a(),e.resume()}catch(n){return this[Xo](n,e)}}};Lv.Sync=DG;Hge.exports=Lv});var Yge=_((M3t,Wge)=>{"use strict";var Lot=DI(),LR=bG(),Gge=Ie("fs"),qge=GI(),jge=Ie("path"),PG=FI();Wge.exports=(t,e,r)=>{typeof t=="function"?(r=t,e=null,t={}):Array.isArray(t)&&(e=t,t={}),typeof e=="function"&&(r=e,e=null),e?e=Array.from(e):e=[];let s=Lot(t);if(s.sync&&typeof r=="function")throw new TypeError("callback not supported for sync tar functions");if(!s.file&&typeof r=="function")throw new TypeError("callback only supported with file option");return e.length&&Mot(s,e),s.file&&s.sync?Uot(s):s.file?_ot(s,r):s.sync?Hot(s):jot(s)};var Mot=(t,e)=>{let r=new Map(e.map(n=>[PG(n),!0])),s=t.filter,a=(n,c)=>{let f=c||jge.parse(n).root||".",p=n===f?!1:r.has(n)?r.get(n):a(jge.dirname(n),f);return r.set(n,p),p};t.filter=s?(n,c)=>s(n,c)&&a(PG(n)):n=>a(PG(n))},Uot=t=>{let e=new LR.Sync(t),r=t.file,s=Gge.statSync(r),a=t.maxReadSize||16*1024*1024;new qge.ReadStreamSync(r,{readSize:a,size:s.size}).pipe(e)},_ot=(t,e)=>{let r=new LR(t),s=t.maxReadSize||16*1024*1024,a=t.file,n=new Promise((c,f)=>{r.on("error",f),r.on("close",c),Gge.stat(a,(p,h)=>{if(p)f(p);else{let E=new qge.ReadStream(a,{readSize:s,size:h.size});E.on("error",f),E.pipe(r)}})});return e?n.then(e,e):n},Hot=t=>new LR.Sync(t),jot=t=>new LR(t)});var Vge=_(Ps=>{"use strict";Ps.c=Ps.create=P0e();Ps.r=Ps.replace=cG();Ps.t=Ps.list=SR();Ps.u=Ps.update=L0e();Ps.x=Ps.extract=Yge();Ps.Pack=uR();Ps.Unpack=bG();Ps.Parse=vR();Ps.ReadEntry=VT();Ps.WriteEntry=M6();Ps.Header=RI();Ps.Pax=KT();Ps.types=I6()});var xG,Jge,H0,Mv,Uv,Kge=Xe(()=>{xG=ut(Ld()),Jge=Ie("worker_threads"),H0=Symbol("kTaskInfo"),Mv=class{constructor(e,r){this.fn=e;this.limit=(0,xG.default)(r.poolSize)}run(e){return this.limit(()=>this.fn(e))}},Uv=class{constructor(e,r){this.source=e;this.workers=[];this.limit=(0,xG.default)(r.poolSize),this.cleanupInterval=setInterval(()=>{if(this.limit.pendingCount===0&&this.limit.activeCount===0){let s=this.workers.pop();s?s.terminate():clearInterval(this.cleanupInterval)}},5e3).unref()}createWorker(){this.cleanupInterval.refresh();let e=new Jge.Worker(this.source,{eval:!0,execArgv:[...process.execArgv,"--unhandled-rejections=strict"]});return e.on("message",r=>{if(!e[H0])throw new Error("Assertion failed: Worker sent a result without having a task assigned");e[H0].resolve(r),e[H0]=null,e.unref(),this.workers.push(e)}),e.on("error",r=>{e[H0]?.reject(r),e[H0]=null}),e.on("exit",r=>{r!==0&&e[H0]?.reject(new Error(`Worker exited with code ${r}`)),e[H0]=null}),e}run(e){return this.limit(()=>{let r=this.workers.pop()??this.createWorker();return r.ref(),new Promise((s,a)=>{r[H0]={resolve:s,reject:a},r.postMessage(e)})})}}});var Xge=_((j3t,zge)=>{var kG;zge.exports.getContent=()=>(typeof kG>"u"&&(kG=Ie("zlib").brotliDecompressSync(Buffer.from("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","base64")).toString()),kG)});var ps={};Vt(ps,{convertToZip:()=>Yot,convertToZipWorker:()=>RG,extractArchiveTo:()=>rde,getDefaultTaskPool:()=>ede,getTaskPoolForConfiguration:()=>tde,makeArchiveFromDirectory:()=>Wot});function Got(t,e){switch(t){case"async":return new Mv(RG,{poolSize:e});case"workers":return new Uv((0,TG.getContent)(),{poolSize:e});default:throw new Error(`Assertion failed: Unknown value ${t} for taskPoolMode`)}}function ede(){return typeof QG>"u"&&(QG=Got("workers",Ui.availableParallelism())),QG}function tde(t){return typeof t>"u"?ede():Yl(qot,t,()=>{let e=t.get("taskPoolMode"),r=t.get("taskPoolConcurrency");switch(e){case"async":return new Mv(RG,{poolSize:r});case"workers":return new Uv((0,TG.getContent)(),{poolSize:r});default:throw new Error(`Assertion failed: Unknown value ${e} for taskPoolMode`)}})}async function RG(t){let{tmpFile:e,tgz:r,compressionLevel:s,extractBufferOpts:a}=t,n=new As(e,{create:!0,level:s,stats:$a.makeDefaultStats()}),c=Buffer.from(r.buffer,r.byteOffset,r.byteLength);return await rde(c,n,a),n.saveAndClose(),e}async function Wot(t,{baseFs:e=new Yn,prefixPath:r=vt.root,compressionLevel:s,inMemory:a=!1}={}){let n;if(a)n=new As(null,{level:s});else{let f=await ce.mktempPromise(),p=J.join(f,"archive.zip");n=new As(p,{create:!0,level:s})}let c=J.resolve(vt.root,r);return await n.copyPromise(c,t,{baseFs:e,stableTime:!0,stableSort:!0}),n}async function Yot(t,e={}){let r=await ce.mktempPromise(),s=J.join(r,"archive.zip"),a=e.compressionLevel??e.configuration?.get("compressionLevel")??"mixed",n={prefixPath:e.prefixPath,stripComponents:e.stripComponents};return await(e.taskPool??tde(e.configuration)).run({tmpFile:s,tgz:t,compressionLevel:a,extractBufferOpts:n}),new As(s,{level:e.compressionLevel})}async function*Vot(t){let e=new $ge.default.Parse,r=new Zge.PassThrough({objectMode:!0,autoDestroy:!0,emitClose:!0});e.on("entry",s=>{r.write(s)}),e.on("error",s=>{r.destroy(s)}),e.on("close",()=>{r.destroyed||r.end()}),e.end(t);for await(let s of r){let a=s;yield a,a.resume()}}async function rde(t,e,{stripComponents:r=0,prefixPath:s=vt.dot}={}){function a(n){if(n.path[0]==="/")return!0;let c=n.path.split(/\//g);return!!(c.some(f=>f==="..")||c.length<=r)}for await(let n of Vot(t)){if(a(n))continue;let c=J.normalize(fe.toPortablePath(n.path)).replace(/\/$/,"").split(/\//g);if(c.length<=r)continue;let f=c.slice(r).join("/"),p=J.join(s,f),h=420;switch((n.type==="Directory"||(n.mode??0)&73)&&(h|=73),n.type){case"Directory":e.mkdirpSync(J.dirname(p),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),e.mkdirSync(p,{mode:h}),e.utimesSync(p,fi.SAFE_TIME,fi.SAFE_TIME);break;case"OldFile":case"File":e.mkdirpSync(J.dirname(p),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),e.writeFileSync(p,await WE(n),{mode:h}),e.utimesSync(p,fi.SAFE_TIME,fi.SAFE_TIME);break;case"SymbolicLink":e.mkdirpSync(J.dirname(p),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),e.symlinkSync(n.linkpath,p),e.lutimesSync(p,fi.SAFE_TIME,fi.SAFE_TIME);break}}return e}var Zge,$ge,TG,QG,qot,nde=Xe(()=>{Ge();Dt();eA();Zge=Ie("stream"),$ge=ut(Vge());Kge();Pc();TG=ut(Xge());qot=new WeakMap});var sde=_((FG,ide)=>{(function(t,e){typeof FG=="object"?ide.exports=e():typeof define=="function"&&define.amd?define(e):t.treeify=e()})(FG,function(){function t(a,n){var c=n?"\u2514":"\u251C";return a?c+="\u2500 ":c+="\u2500\u2500\u2510",c}function e(a,n){var c=[];for(var f in a)a.hasOwnProperty(f)&&(n&&typeof a[f]=="function"||c.push(f));return c}function r(a,n,c,f,p,h,E){var C="",S=0,P,I,R=f.slice(0);if(R.push([n,c])&&f.length>0&&(f.forEach(function(U,W){W>0&&(C+=(U[1]?" ":"\u2502")+" "),!I&&U[0]===n&&(I=!0)}),C+=t(a,c)+a,p&&(typeof n!="object"||n instanceof Date)&&(C+=": "+n),I&&(C+=" (circular ref.)"),E(C)),!I&&typeof n=="object"){var N=e(n,h);N.forEach(function(U){P=++S===N.length,r(U,n[U],P,R,p,h,E)})}}var s={};return s.asLines=function(a,n,c,f){var p=typeof c!="function"?c:!1;r(".",a,!1,[],n,p,f||c)},s.asTree=function(a,n,c){var f="";return r(".",a,!1,[],n,c,function(p){f+=p+` `}),f},s})});var xs={};Vt(xs,{emitList:()=>Jot,emitTree:()=>cde,treeNodeToJson:()=>lde,treeNodeToTreeify:()=>ade});function ade(t,{configuration:e}){let r={},s=0,a=(n,c)=>{let f=Array.isArray(n)?n.entries():Object.entries(n);for(let[p,h]of f){if(!h)continue;let{label:E,value:C,children:S}=h,P=[];typeof E<"u"&&P.push(zd(e,E,2)),typeof C<"u"&&P.push(Ht(e,C[0],C[1])),P.length===0&&P.push(zd(e,`${p}`,2));let I=P.join(": ").trim(),R=`\0${s++}\0`,N=c[`${R}${I}`]={};typeof S<"u"&&a(S,N)}};if(typeof t.children>"u")throw new Error("The root node must only contain children");return a(t.children,r),r}function lde(t){let e=r=>{if(typeof r.children>"u"){if(typeof r.value>"u")throw new Error("Assertion failed: Expected a value to be set if the children are missing");return Xd(r.value[0],r.value[1])}let s=Array.isArray(r.children)?r.children.entries():Object.entries(r.children??{}),a=Array.isArray(r.children)?[]:{};for(let[n,c]of s)c&&(a[Kot(n)]=e(c));return typeof r.value>"u"?a:{value:Xd(r.value[0],r.value[1]),children:a}};return e(t)}function Jot(t,{configuration:e,stdout:r,json:s}){let a=t.map(n=>({value:n}));cde({children:a},{configuration:e,stdout:r,json:s})}function cde(t,{configuration:e,stdout:r,json:s,separators:a=0}){if(s){let c=Array.isArray(t.children)?t.children.values():Object.values(t.children??{});for(let f of c)f&&r.write(`${JSON.stringify(lde(f))} `);return}let n=(0,ode.asTree)(ade(t,{configuration:e}),!1,!1);if(n=n.replace(/\0[0-9]+\0/g,""),a>=1&&(n=n.replace(/^([├└]─)/gm,`\u2502 $1`).replace(/^│\n/,"")),a>=2)for(let c=0;c<2;++c)n=n.replace(/^([│ ].{2}[├│ ].{2}[^\n]+\n)(([│ ]).{2}[├└].{2}[^\n]*\n[│ ].{2}[│ ].{2}[├└]─)/gm,`$1$3 \u2502 $2`).replace(/^│\n/,"");if(a>=3)throw new Error("Only the first two levels are accepted by treeUtils.emitTree");r.write(n)}function Kot(t){return typeof t=="string"?t.replace(/^\0[0-9]+\0/,""):t}var ode,ude=Xe(()=>{ode=ut(sde());xc()});var MR,fde=Xe(()=>{MR=class{constructor(e){this.releaseFunction=e;this.map=new Map}addOrCreate(e,r){let s=this.map.get(e);if(typeof s<"u"){if(s.refCount<=0)throw new Error(`Race condition in RefCountedMap. While adding a new key the refCount is: ${s.refCount} for ${JSON.stringify(e)}`);return s.refCount++,{value:s.value,release:()=>this.release(e)}}else{let a=r();return this.map.set(e,{refCount:1,value:a}),{value:a,release:()=>this.release(e)}}}release(e){let r=this.map.get(e);if(!r)throw new Error(`Unbalanced calls to release. No known instances of: ${JSON.stringify(e)}`);let s=r.refCount;if(s<=0)throw new Error(`Unbalanced calls to release. Too many release vs alloc refcount would become: ${s-1} of ${JSON.stringify(e)}`);s==1?(this.map.delete(e),this.releaseFunction(r.value)):r.refCount--}}});function _v(t){let e=t.match(zot);if(!e?.groups)throw new Error("Assertion failed: Expected the checksum to match the requested pattern");let r=e.groups.cacheVersion?parseInt(e.groups.cacheVersion):null;return{cacheKey:e.groups.cacheKey??null,cacheVersion:r,cacheSpec:e.groups.cacheSpec??null,hash:e.groups.hash}}var Ade,NG,OG,UR,Kr,zot,LG=Xe(()=>{Ge();Dt();Dt();eA();Ade=Ie("crypto"),NG=ut(Ie("fs"));fde();Tc();I0();Pc();Wo();OG=YE(process.env.YARN_CACHE_CHECKPOINT_OVERRIDE??process.env.YARN_CACHE_VERSION_OVERRIDE??9),UR=YE(process.env.YARN_CACHE_VERSION_OVERRIDE??10),Kr=class t{constructor(e,{configuration:r,immutable:s=r.get("enableImmutableCache"),check:a=!1}){this.markedFiles=new Set;this.mutexes=new Map;this.refCountedZipFsCache=new MR(e=>{e.discardAndClose()});this.cacheId=`-${(0,Ade.randomBytes)(8).toString("hex")}.tmp`;this.configuration=r,this.cwd=e,this.immutable=s,this.check=a;let{cacheSpec:n,cacheKey:c}=t.getCacheKey(r);this.cacheSpec=n,this.cacheKey=c}static async find(e,{immutable:r,check:s}={}){let a=new t(e.get("cacheFolder"),{configuration:e,immutable:r,check:s});return await a.setup(),a}static getCacheKey(e){let r=e.get("compressionLevel"),s=r!=="mixed"?`c${r}`:"";return{cacheKey:[UR,s].join(""),cacheSpec:s}}get mirrorCwd(){if(!this.configuration.get("enableMirror"))return null;let e=`${this.configuration.get("globalFolder")}/cache`;return e!==this.cwd?e:null}getVersionFilename(e){return`${nI(e)}-${this.cacheKey}.zip`}getChecksumFilename(e,r){let a=_v(r).hash.slice(0,10);return`${nI(e)}-${a}.zip`}isChecksumCompatible(e){if(e===null)return!1;let{cacheVersion:r,cacheSpec:s}=_v(e);if(r===null||r{let pe=new As,Be=J.join(vt.root,x8(e));return pe.mkdirSync(Be,{recursive:!0}),pe.writeJsonSync(J.join(Be,Er.manifest),{name:un(e),mocked:!0}),pe},E=async(pe,{isColdHit:Be,controlPath:Ce=null})=>{if(Ce===null&&c.unstablePackages?.has(e.locatorHash))return{isValid:!0,hash:null};let g=r&&!Be?_v(r).cacheKey:this.cacheKey,we=!c.skipIntegrityCheck||!r?`${g}/${await SQ(pe)}`:r;if(Ce!==null){let Ae=!c.skipIntegrityCheck||!r?`${this.cacheKey}/${await SQ(Ce)}`:r;if(we!==Ae)throw new jt(18,"The remote archive doesn't match the local checksum - has the local cache been corrupted?")}let ye=null;switch(r!==null&&we!==r&&(this.check?ye="throw":_v(r).cacheKey!==_v(we).cacheKey?ye="update":ye=this.configuration.get("checksumBehavior")),ye){case null:case"update":return{isValid:!0,hash:we};case"ignore":return{isValid:!0,hash:r};case"reset":return{isValid:!1,hash:r};default:case"throw":throw new jt(18,"The remote archive doesn't match the expected checksum")}},C=async pe=>{if(!n)throw new Error(`Cache check required but no loader configured for ${Yr(this.configuration,e)}`);let Be=await n(),Ce=Be.getRealPath();Be.saveAndClose(),await ce.chmodPromise(Ce,420);let g=await E(pe,{controlPath:Ce,isColdHit:!1});if(!g.isValid)throw new Error("Assertion failed: Expected a valid checksum");return g.hash},S=async()=>{if(f===null||!await ce.existsPromise(f)){let pe=await n(),Be=pe.getRealPath();return pe.saveAndClose(),{source:"loader",path:Be}}return{source:"mirror",path:f}},P=async()=>{if(!n)throw new Error(`Cache entry required but missing for ${Yr(this.configuration,e)}`);if(this.immutable)throw new jt(56,`Cache entry required but missing for ${Yr(this.configuration,e)}`);let{path:pe,source:Be}=await S(),{hash:Ce}=await E(pe,{isColdHit:!0}),g=this.getLocatorPath(e,Ce),we=[];Be!=="mirror"&&f!==null&&we.push(async()=>{let Ae=`${f}${this.cacheId}`;await ce.copyFilePromise(pe,Ae,NG.default.constants.COPYFILE_FICLONE),await ce.chmodPromise(Ae,420),await ce.renamePromise(Ae,f)}),(!c.mirrorWriteOnly||f===null)&&we.push(async()=>{let Ae=`${g}${this.cacheId}`;await ce.copyFilePromise(pe,Ae,NG.default.constants.COPYFILE_FICLONE),await ce.chmodPromise(Ae,420),await ce.renamePromise(Ae,g)});let ye=c.mirrorWriteOnly?f??g:g;return await Promise.all(we.map(Ae=>Ae())),[!1,ye,Ce]},I=async()=>{let Be=(async()=>{let Ce=c.unstablePackages?.has(e.locatorHash),g=Ce||!r||this.isChecksumCompatible(r)?this.getLocatorPath(e,r):null,we=g!==null?this.markedFiles.has(g)||await p.existsPromise(g):!1,ye=!!c.mockedPackages?.has(e.locatorHash)&&(!this.check||!we),Ae=ye||we,se=Ae?s:a;if(se&&se(),Ae){let Z=null,De=g;if(!ye)if(this.check)Z=await C(De);else{let Re=await E(De,{isColdHit:!1});if(Re.isValid)Z=Re.hash;else return P()}return[ye,De,Z]}else{if(this.immutable&&Ce)throw new jt(56,`Cache entry required but missing for ${Yr(this.configuration,e)}; consider defining ${he.pretty(this.configuration,"supportedArchitectures",he.Type.CODE)} to cache packages for multiple systems`);return P()}})();this.mutexes.set(e.locatorHash,Be);try{return await Be}finally{this.mutexes.delete(e.locatorHash)}};for(let pe;pe=this.mutexes.get(e.locatorHash);)await pe;let[R,N,U]=await I();R||this.markedFiles.add(N);let W=()=>this.refCountedZipFsCache.addOrCreate(N,()=>R?h():new As(N,{baseFs:p,readOnly:!0})),ee,ie=new oE(()=>W4(()=>(ee=W(),ee.value),pe=>`Failed to open the cache entry for ${Yr(this.configuration,e)}: ${pe}`),J),ue=new _f(N,{baseFs:ie,pathUtils:J}),le=()=>{ee?.release()},me=c.unstablePackages?.has(e.locatorHash)?null:U;return[ue,le,me]}},zot=/^(?:(?(?[0-9]+)(?.*))\/)?(?.*)$/});var _R,pde=Xe(()=>{_R=(r=>(r[r.SCRIPT=0]="SCRIPT",r[r.SHELLCODE=1]="SHELLCODE",r))(_R||{})});var Xot,KI,MG=Xe(()=>{Dt();wc();Rp();Wo();Xot=[[/^(git(?:\+(?:https|ssh))?:\/\/.*(?:\.git)?)#(.*)$/,(t,e,r,s)=>`${r}#commit=${s}`],[/^https:\/\/((?:[^/]+?)@)?codeload\.github\.com\/([^/]+\/[^/]+)\/tar\.gz\/([0-9a-f]+)$/,(t,e,r="",s,a)=>`https://${r}github.com/${s}.git#commit=${a}`],[/^https:\/\/((?:[^/]+?)@)?github\.com\/([^/]+\/[^/]+?)(?:\.git)?#([0-9a-f]+)$/,(t,e,r="",s,a)=>`https://${r}github.com/${s}.git#commit=${a}`],[/^https?:\/\/[^/]+\/(?:[^/]+\/)*(?:@.+(?:\/|(?:%2f)))?([^/]+)\/(?:-|download)\/\1-[^/]+\.tgz(?:#|$)/,t=>`npm:${t}`],[/^https:\/\/npm\.pkg\.github\.com\/download\/(?:@[^/]+)\/(?:[^/]+)\/(?:[^/]+)\/(?:[0-9a-f]+)(?:#|$)/,t=>`npm:${t}`],[/^https:\/\/npm\.fontawesome\.com\/(?:@[^/]+)\/([^/]+)\/-\/([^/]+)\/\1-\2.tgz(?:#|$)/,t=>`npm:${t}`],[/^https?:\/\/[^/]+\/.*\/(@[^/]+)\/([^/]+)\/-\/\1\/\2-(?:[.\d\w-]+)\.tgz(?:#|$)/,(t,e)=>kQ({protocol:"npm:",source:null,selector:t,params:{__archiveUrl:e}})],[/^[^/]+\.tgz#[0-9a-f]+$/,t=>`npm:${t}`]],KI=class{constructor(e){this.resolver=e;this.resolutions=null}async setup(e,{report:r}){let s=J.join(e.cwd,Er.lockfile);if(!ce.existsSync(s))return;let a=await ce.readFilePromise(s,"utf8"),n=ls(a);if(Object.hasOwn(n,"__metadata"))return;let c=this.resolutions=new Map;for(let f of Object.keys(n)){let p=HB(f);if(!p){r.reportWarning(14,`Failed to parse the string "${f}" into a proper descriptor`);continue}let h=cl(p.range)?On(p,`npm:${p.range}`):p,{version:E,resolved:C}=n[f];if(!C)continue;let S;for(let[I,R]of Xot){let N=C.match(I);if(N){S=R(E,...N);break}}if(!S){r.reportWarning(14,`${ni(e.configuration,h)}: Only some patterns can be imported from legacy lockfiles (not "${C}")`);continue}let P=h;try{let I=em(h.range),R=HB(I.selector,!0);R&&(P=R)}catch{}c.set(h.descriptorHash,Ws(P,S))}}supportsDescriptor(e,r){return this.resolutions?this.resolutions.has(e.descriptorHash):!1}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error("Assertion failed: This resolver doesn't support resolving locators to packages")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!this.resolutions)throw new Error("Assertion failed: The resolution store should have been setup");let a=this.resolutions.get(e.descriptorHash);if(!a)throw new Error("Assertion failed: The resolution should have been registered");let n=S8(a),c=s.project.configuration.normalizeDependency(n);return await this.resolver.getCandidates(c,r,s)}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){throw new Error("Assertion failed: This resolver doesn't support resolving locators to packages")}}});var lA,hde=Xe(()=>{Tc();Ev();xc();lA=class extends Ao{constructor({configuration:r,stdout:s,suggestInstall:a=!0}){super();this.errorCount=0;RB(this,{configuration:r}),this.configuration=r,this.stdout=s,this.suggestInstall=a}static async start(r,s){let a=new this(r);try{await s(a)}catch(n){a.reportExceptionOnce(n)}finally{await a.finalize()}return a}hasErrors(){return this.errorCount>0}exitCode(){return this.hasErrors()?1:0}reportCacheHit(r){}reportCacheMiss(r){}startSectionSync(r,s){return s()}async startSectionPromise(r,s){return await s()}startTimerSync(r,s,a){return(typeof s=="function"?s:a)()}async startTimerPromise(r,s,a){return await(typeof s=="function"?s:a)()}reportSeparator(){}reportInfo(r,s){}reportWarning(r,s){}reportError(r,s){this.errorCount+=1,this.stdout.write(`${Ht(this.configuration,"\u27A4","redBright")} ${this.formatNameWithHyperlink(r)}: ${s} `)}reportProgress(r){return{...Promise.resolve().then(async()=>{for await(let{}of r);}),stop:()=>{}}}reportJson(r){}reportFold(r,s){}async finalize(){this.errorCount>0&&(this.stdout.write(` `),this.stdout.write(`${Ht(this.configuration,"\u27A4","redBright")} Errors happened when preparing the environment required to run this command. `),this.suggestInstall&&this.stdout.write(`${Ht(this.configuration,"\u27A4","redBright")} This might be caused by packages being missing from the lockfile, in which case running "yarn install" might help. `))}formatNameWithHyperlink(r){return Wj(r,{configuration:this.configuration,json:!1})}}});var zI,UG=Xe(()=>{Wo();zI=class{constructor(e){this.resolver=e}supportsDescriptor(e,r){return!!(r.project.storedResolutions.get(e.descriptorHash)||r.project.originalPackages.has(bQ(e).locatorHash))}supportsLocator(e,r){return!!(r.project.originalPackages.has(e.locatorHash)&&!r.project.lockfileNeedsRefresh)}shouldPersistResolution(e,r){throw new Error("The shouldPersistResolution method shouldn't be called on the lockfile resolver, which would always answer yes")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return this.resolver.getResolutionDependencies(e,r)}async getCandidates(e,r,s){let a=s.project.storedResolutions.get(e.descriptorHash);if(a){let c=s.project.originalPackages.get(a);if(c)return[c]}let n=s.project.originalPackages.get(bQ(e).locatorHash);if(n)return[n];throw new Error("Resolution expected from the lockfile data")}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let s=r.project.originalPackages.get(e.locatorHash);if(!s)throw new Error("The lockfile resolver isn't meant to resolve packages - they should already have been stored into a cache");return s}}});function Kp(){}function Zot(t,e,r,s,a){for(var n=0,c=e.length,f=0,p=0;nP.length?R:P}),h.value=t.join(E)}else h.value=t.join(r.slice(f,f+h.count));f+=h.count,h.added||(p+=h.count)}}var S=e[c-1];return c>1&&typeof S.value=="string"&&(S.added||S.removed)&&t.equals("",S.value)&&(e[c-2].value+=S.value,e.pop()),e}function $ot(t){return{newPos:t.newPos,components:t.components.slice(0)}}function eat(t,e){if(typeof t=="function")e.callback=t;else if(t)for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r]);return e}function mde(t,e,r){return r=eat(r,{ignoreWhitespace:!0}),qG.diff(t,e,r)}function tat(t,e,r){return WG.diff(t,e,r)}function HR(t){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?HR=function(e){return typeof e}:HR=function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},HR(t)}function _G(t){return iat(t)||sat(t)||oat(t)||aat()}function iat(t){if(Array.isArray(t))return HG(t)}function sat(t){if(typeof Symbol<"u"&&Symbol.iterator in Object(t))return Array.from(t)}function oat(t,e){if(t){if(typeof t=="string")return HG(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if(r==="Object"&&t.constructor&&(r=t.constructor.name),r==="Map"||r==="Set")return Array.from(t);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return HG(t,e)}}function HG(t,e){(e==null||e>t.length)&&(e=t.length);for(var r=0,s=new Array(e);r"u"&&(c.context=4);var f=tat(r,s,c);if(!f)return;f.push({value:"",lines:[]});function p(U){return U.map(function(W){return" "+W})}for(var h=[],E=0,C=0,S=[],P=1,I=1,R=function(W){var ee=f[W],ie=ee.lines||ee.value.replace(/\n$/,"").split(` `);if(ee.lines=ie,ee.added||ee.removed){var ue;if(!E){var le=f[W-1];E=P,C=I,le&&(S=c.context>0?p(le.lines.slice(-c.context)):[],E-=S.length,C-=S.length)}(ue=S).push.apply(ue,_G(ie.map(function(Ae){return(ee.added?"+":"-")+Ae}))),ee.added?I+=ie.length:P+=ie.length}else{if(E)if(ie.length<=c.context*2&&W=f.length-2&&ie.length<=c.context){var g=/\n$/.test(r),we=/\n$/.test(s),ye=ie.length==0&&S.length>Ce.oldLines;!g&&ye&&r.length>0&&S.splice(Ce.oldLines,0,"\\ No newline at end of file"),(!g&&!ye||!we)&&S.push("\\ No newline at end of file")}h.push(Ce),E=0,C=0,S=[]}P+=ie.length,I+=ie.length}},N=0;N{Kp.prototype={diff:function(e,r){var s=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},a=s.callback;typeof s=="function"&&(a=s,s={}),this.options=s;var n=this;function c(R){return a?(setTimeout(function(){a(void 0,R)},0),!0):R}e=this.castInput(e),r=this.castInput(r),e=this.removeEmpty(this.tokenize(e)),r=this.removeEmpty(this.tokenize(r));var f=r.length,p=e.length,h=1,E=f+p;s.maxEditLength&&(E=Math.min(E,s.maxEditLength));var C=[{newPos:-1,components:[]}],S=this.extractCommon(C[0],r,e,0);if(C[0].newPos+1>=f&&S+1>=p)return c([{value:this.join(r),count:r.length}]);function P(){for(var R=-1*h;R<=h;R+=2){var N=void 0,U=C[R-1],W=C[R+1],ee=(W?W.newPos:0)-R;U&&(C[R-1]=void 0);var ie=U&&U.newPos+1=f&&ee+1>=p)return c(Zot(n,N.components,r,e,n.useLongestToken));C[R]=N}h++}if(a)(function R(){setTimeout(function(){if(h>E)return a();P()||R()},0)})();else for(;h<=E;){var I=P();if(I)return I}},pushComponent:function(e,r,s){var a=e[e.length-1];a&&a.added===r&&a.removed===s?e[e.length-1]={count:a.count+1,added:r,removed:s}:e.push({count:1,added:r,removed:s})},extractCommon:function(e,r,s,a){for(var n=r.length,c=s.length,f=e.newPos,p=f-a,h=0;f+1"u"?r:c}:s;return typeof t=="string"?t:JSON.stringify(jG(t,null,null,a),a," ")};Hv.equals=function(t,e){return Kp.prototype.equals.call(Hv,t.replace(/,([\r\n])/g,"$1"),e.replace(/,([\r\n])/g,"$1"))};GG=new Kp;GG.tokenize=function(t){return t.slice()};GG.join=GG.removeEmpty=function(t){return t}});var jR,Ede=Xe(()=>{Tc();jR=class{constructor(e){this.resolver=e}supportsDescriptor(e,r){return this.resolver.supportsDescriptor(e,r)}supportsLocator(e,r){return this.resolver.supportsLocator(e,r)}shouldPersistResolution(e,r){return this.resolver.shouldPersistResolution(e,r)}bindDescriptor(e,r,s){return this.resolver.bindDescriptor(e,r,s)}getResolutionDependencies(e,r){return this.resolver.getResolutionDependencies(e,r)}async getCandidates(e,r,s){throw new jt(20,`This package doesn't seem to be present in your lockfile; run "yarn install" to update the lockfile`)}async getSatisfying(e,r,s,a){throw new jt(20,`This package doesn't seem to be present in your lockfile; run "yarn install" to update the lockfile`)}async resolve(e,r){throw new jt(20,`This package doesn't seem to be present in your lockfile; run "yarn install" to update the lockfile`)}}});var ki,VG=Xe(()=>{Tc();ki=class extends Ao{reportCacheHit(e){}reportCacheMiss(e){}startSectionSync(e,r){return r()}async startSectionPromise(e,r){return await r()}startTimerSync(e,r,s){return(typeof r=="function"?r:s)()}async startTimerPromise(e,r,s){return await(typeof r=="function"?r:s)()}reportSeparator(){}reportInfo(e,r){}reportWarning(e,r){}reportError(e,r){}reportProgress(e){return{...Promise.resolve().then(async()=>{for await(let{}of e);}),stop:()=>{}}}reportJson(e){}reportFold(e,r){}async finalize(){}}});var Ide,XI,JG=Xe(()=>{Dt();Ide=ut(BQ());oI();tm();xc();I0();Rp();Wo();XI=class{constructor(e,{project:r}){this.workspacesCwds=new Set;this.project=r,this.cwd=e}async setup(){this.manifest=await Ut.tryFind(this.cwd)??new Ut,this.relativeCwd=J.relative(this.project.cwd,this.cwd)||vt.dot;let e=this.manifest.name?this.manifest.name:Da(null,`${this.computeCandidateName()}-${us(this.relativeCwd).substring(0,6)}`);this.anchoredDescriptor=On(e,`${Ei.protocol}${this.relativeCwd}`),this.anchoredLocator=Ws(e,`${Ei.protocol}${this.relativeCwd}`);let r=this.manifest.workspaceDefinitions.map(({pattern:a})=>a);if(r.length===0)return;let s=await(0,Ide.default)(r,{cwd:fe.fromPortablePath(this.cwd),onlyDirectories:!0,ignore:["**/node_modules","**/.git","**/.yarn"]});s.sort(),await s.reduce(async(a,n)=>{let c=J.resolve(this.cwd,fe.toPortablePath(n)),f=await ce.existsPromise(J.join(c,"package.json"));await a,f&&this.workspacesCwds.add(c)},Promise.resolve())}get anchoredPackage(){let e=this.project.storedPackages.get(this.anchoredLocator.locatorHash);if(!e)throw new Error(`Assertion failed: Expected workspace ${GB(this.project.configuration,this)} (${Ht(this.project.configuration,J.join(this.cwd,Er.manifest),ht.PATH)}) to have been resolved. Run "yarn install" to update the lockfile`);return e}accepts(e){let r=e.indexOf(":"),s=r!==-1?e.slice(0,r+1):null,a=r!==-1?e.slice(r+1):e;if(s===Ei.protocol&&J.normalize(a)===this.relativeCwd||s===Ei.protocol&&(a==="*"||a==="^"||a==="~"))return!0;let n=cl(a);return n?s===Ei.protocol?n.test(this.manifest.version??"0.0.0"):this.project.configuration.get("enableTransparentWorkspaces")&&this.manifest.version!==null?n.test(this.manifest.version):!1:!1}computeCandidateName(){return this.cwd===this.project.cwd?"root-workspace":`${J.basename(this.cwd)}`||"unnamed-workspace"}getRecursiveWorkspaceDependencies({dependencies:e=Ut.hardDependencies}={}){let r=new Set,s=a=>{for(let n of e)for(let c of a.manifest[n].values()){let f=this.project.tryWorkspaceByDescriptor(c);f===null||r.has(f)||(r.add(f),s(f))}};return s(this),r}getRecursiveWorkspaceDependents({dependencies:e=Ut.hardDependencies}={}){let r=new Set,s=a=>{for(let n of this.project.workspaces)e.some(f=>[...n.manifest[f].values()].some(p=>{let h=this.project.tryWorkspaceByDescriptor(p);return h!==null&&_B(h.anchoredLocator,a.anchoredLocator)}))&&!r.has(n)&&(r.add(n),s(n))};return s(this),r}getRecursiveWorkspaceChildren(){let e=new Set([this]);for(let r of e)for(let s of r.workspacesCwds){let a=this.project.workspacesByCwd.get(s);a&&e.add(a)}return e.delete(this),Array.from(e)}async persistManifest(){let e={};this.manifest.exportTo(e);let r=J.join(this.cwd,Ut.fileName),s=`${JSON.stringify(e,null,this.manifest.indent)} `;await ce.changeFilePromise(r,s,{automaticNewlines:!0}),this.manifest.raw=e}}});function hat({project:t,allDescriptors:e,allResolutions:r,allPackages:s,accessibleLocators:a=new Set,optionalBuilds:n=new Set,peerRequirements:c=new Map,peerWarnings:f=[],peerRequirementNodes:p=new Map,volatileDescriptors:h=new Set}){let E=new Map,C=[],S=new Map,P=new Map,I=new Map,R=new Map,N=new Map,U=new Map(t.workspaces.map(le=>{let me=le.anchoredLocator.locatorHash,pe=s.get(me);if(typeof pe>"u")throw new Error("Assertion failed: The workspace should have an associated package");return[me,LB(pe)]})),W=()=>{let le=ce.mktempSync(),me=J.join(le,"stacktrace.log"),pe=String(C.length+1).length,Be=C.map((Ce,g)=>`${`${g+1}.`.padStart(pe," ")} ${ll(Ce)} `).join("");throw ce.writeFileSync(me,Be),ce.detachTemp(le),new jt(45,`Encountered a stack overflow when resolving peer dependencies; cf ${fe.fromPortablePath(me)}`)},ee=le=>{let me=r.get(le.descriptorHash);if(typeof me>"u")throw new Error("Assertion failed: The resolution should have been registered");let pe=s.get(me);if(!pe)throw new Error("Assertion failed: The package could not be found");return pe},ie=(le,me,pe,{top:Be,optional:Ce})=>{C.length>1e3&&W(),C.push(me);let g=ue(le,me,pe,{top:Be,optional:Ce});return C.pop(),g},ue=(le,me,pe,{top:Be,optional:Ce})=>{if(Ce||n.delete(me.locatorHash),a.has(me.locatorHash))return;a.add(me.locatorHash);let g=s.get(me.locatorHash);if(!g)throw new Error(`Assertion failed: The package (${Yr(t.configuration,me)}) should have been registered`);let we=new Set,ye=new Map,Ae=[],se=[],Z=[],De=[];for(let Re of Array.from(g.dependencies.values())){if(g.peerDependencies.has(Re.identHash)&&g.locatorHash!==Be)continue;if(kp(Re))throw new Error("Assertion failed: Virtual packages shouldn't be encountered when virtualizing a branch");h.delete(Re.descriptorHash);let mt=Ce;if(!mt){let ke=g.dependenciesMeta.get(un(Re));if(typeof ke<"u"){let it=ke.get(null);typeof it<"u"&&it.optional&&(mt=!0)}}let j=r.get(Re.descriptorHash);if(!j)throw new Error(`Assertion failed: The resolution (${ni(t.configuration,Re)}) should have been registered`);let rt=U.get(j)||s.get(j);if(!rt)throw new Error(`Assertion failed: The package (${j}, resolved from ${ni(t.configuration,Re)}) should have been registered`);if(rt.peerDependencies.size===0){ie(Re,rt,new Map,{top:Be,optional:mt});continue}let Fe,Ne,Pe=new Set,Ve=new Map;Ae.push(()=>{Fe=b8(Re,me.locatorHash),Ne=P8(rt,me.locatorHash),g.dependencies.set(Re.identHash,Fe),r.set(Fe.descriptorHash,Ne.locatorHash),e.set(Fe.descriptorHash,Fe),s.set(Ne.locatorHash,Ne),bp(R,Ne.locatorHash).add(Fe.descriptorHash),we.add(Ne.locatorHash)}),se.push(()=>{N.set(Ne.locatorHash,Ve);for(let ke of Ne.peerDependencies.values()){let Ue=Yl(ye,ke.identHash,()=>{let x=pe.get(ke.identHash)??null,w=g.dependencies.get(ke.identHash);return!w&&UB(me,ke)&&(le.identHash===me.identHash?w=le:(w=On(me,le.range),e.set(w.descriptorHash,w),r.set(w.descriptorHash,me.locatorHash),h.delete(w.descriptorHash),x=null)),w||(w=On(ke,"missing:")),{subject:me,ident:ke,provided:w,root:!x,requests:new Map,hash:`p${us(me.locatorHash,ke.identHash).slice(0,6)}`}}).provided;if(Ue.range==="missing:"&&Ne.dependencies.has(ke.identHash)){Ne.peerDependencies.delete(ke.identHash);continue}if(Ve.set(ke.identHash,{requester:Ne,descriptor:ke,meta:Ne.peerDependenciesMeta.get(un(ke)),children:new Map}),Ne.dependencies.set(ke.identHash,Ue),kp(Ue)){let x=r.get(Ue.descriptorHash);bp(I,x).add(Ne.locatorHash)}S.set(Ue.identHash,Ue),Ue.range==="missing:"&&Pe.add(Ue.identHash)}Ne.dependencies=new Map(qs(Ne.dependencies,([ke,it])=>un(it)))}),Z.push(()=>{if(!s.has(Ne.locatorHash))return;let ke=E.get(rt.locatorHash);typeof ke=="number"&&ke>=2&&W();let it=E.get(rt.locatorHash),Ue=typeof it<"u"?it+1:1;E.set(rt.locatorHash,Ue),ie(Fe,Ne,Ve,{top:Be,optional:mt}),E.set(rt.locatorHash,Ue-1)}),De.push(()=>{let ke=r.get(Fe.descriptorHash);if(typeof ke>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");let it=N.get(ke);if(typeof it>"u")throw new Error("Assertion failed: Expected the peer requests to be registered");for(let Ue of ye.values()){let x=it.get(Ue.ident.identHash);x&&(Ue.requests.set(Fe.descriptorHash,x),p.set(Ue.hash,Ue),Ue.root||pe.get(Ue.ident.identHash)?.children.set(Fe.descriptorHash,x))}if(s.has(Ne.locatorHash))for(let Ue of Pe)Ne.dependencies.delete(Ue)})}for(let Re of[...Ae,...se])Re();for(let Re of we){we.delete(Re);let mt=s.get(Re),j=us(rI(mt).locatorHash,...Array.from(mt.dependencies.values(),Pe=>{let Ve=Pe.range!=="missing:"?r.get(Pe.descriptorHash):"missing:";if(typeof Ve>"u")throw new Error(`Assertion failed: Expected the resolution for ${ni(t.configuration,Pe)} to have been registered`);return Ve===Be?`${Ve} (top)`:Ve})),rt=P.get(j);if(typeof rt>"u"){P.set(j,mt);continue}let Fe=bp(R,rt.locatorHash);for(let Pe of R.get(mt.locatorHash)??[])r.set(Pe,rt.locatorHash),Fe.add(Pe);s.delete(mt.locatorHash),a.delete(mt.locatorHash),we.delete(mt.locatorHash);let Ne=I.get(mt.locatorHash);if(Ne!==void 0){let Pe=bp(I,rt.locatorHash);for(let Ve of Ne)Pe.add(Ve),we.add(Ve)}}for(let Re of[...Z,...De])Re()};for(let le of t.workspaces){let me=le.anchoredLocator;h.delete(le.anchoredDescriptor.descriptorHash),ie(le.anchoredDescriptor,me,new Map,{top:me.locatorHash,optional:!1})}for(let le of p.values()){if(!le.root)continue;let me=s.get(le.subject.locatorHash);if(typeof me>"u")continue;for(let Be of le.requests.values()){let Ce=`p${us(le.subject.locatorHash,un(le.ident),Be.requester.locatorHash).slice(0,6)}`;c.set(Ce,{subject:le.subject.locatorHash,requested:le.ident,rootRequester:Be.requester.locatorHash,allRequesters:Array.from(qB(Be),g=>g.requester.locatorHash)})}let pe=[...qB(le)];if(le.provided.range!=="missing:"){let Be=ee(le.provided),Ce=Be.version??"0.0.0",g=ye=>{if(ye.startsWith(Ei.protocol)){if(!t.tryWorkspaceByLocator(Be))return null;ye=ye.slice(Ei.protocol.length),(ye==="^"||ye==="~")&&(ye="*")}return ye},we=!0;for(let ye of pe){let Ae=g(ye.descriptor.range);if(Ae===null){we=!1;continue}if(!Zf(Ce,Ae)){we=!1;let se=`p${us(le.subject.locatorHash,un(le.ident),ye.requester.locatorHash).slice(0,6)}`;f.push({type:1,subject:me,requested:le.ident,requester:ye.requester,version:Ce,hash:se,requirementCount:pe.length})}}if(!we){let ye=pe.map(Ae=>g(Ae.descriptor.range));f.push({type:3,node:le,range:ye.includes(null)?null:Q8(ye),hash:le.hash})}}else{let Be=!0;for(let Ce of pe)if(!Ce.meta?.optional){Be=!1;let g=`p${us(le.subject.locatorHash,un(le.ident),Ce.requester.locatorHash).slice(0,6)}`;f.push({type:0,subject:me,requested:le.ident,requester:Ce.requester,hash:g})}Be||f.push({type:2,node:le,hash:le.hash})}}}function*gat(t){let e=new Map;if("children"in t)e.set(t,t);else for(let r of t.requests.values())e.set(r,r);for(let[r,s]of e){yield{request:r,root:s};for(let a of r.children.values())e.has(a)||e.set(a,s)}}function dat(t,e){let r=[],s=[],a=!1;for(let n of t.peerWarnings)if(!(n.type===1||n.type===0)){if(!t.tryWorkspaceByLocator(n.node.subject)){a=!0;continue}if(n.type===3){let c=t.storedResolutions.get(n.node.provided.descriptorHash);if(typeof c>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");let f=t.storedPackages.get(c);if(typeof f>"u")throw new Error("Assertion failed: Expected the package to be registered");let p=p0(gat(n.node),({request:C,root:S})=>Zf(f.version??"0.0.0",C.descriptor.range)?p0.skip:C===S?$i(t.configuration,C.requester):`${$i(t.configuration,C.requester)} (via ${$i(t.configuration,S.requester)})`),h=[...qB(n.node)].length>1?"and other dependencies request":"requests",E=n.range?iI(t.configuration,n.range):Ht(t.configuration,"but they have non-overlapping ranges!","redBright");r.push(`${$i(t.configuration,n.node.ident)} is listed by your project with version ${jB(t.configuration,f.version??"0.0.0")} (${Ht(t.configuration,n.hash,ht.CODE)}), which doesn't satisfy what ${p} ${h} (${E}).`)}if(n.type===2){let c=n.node.requests.size>1?" and other dependencies":"";s.push(`${Yr(t.configuration,n.node.subject)} doesn't provide ${$i(t.configuration,n.node.ident)} (${Ht(t.configuration,n.hash,ht.CODE)}), requested by ${$i(t.configuration,n.node.requests.values().next().value.requester)}${c}.`)}}e.startSectionSync({reportFooter:()=>{e.reportWarning(86,`Some peer dependencies are incorrectly met by your project; run ${Ht(t.configuration,"yarn explain peer-requirements ",ht.CODE)} for details, where ${Ht(t.configuration,"",ht.CODE)} is the six-letter p-prefixed code.`)},skipIfEmpty:!0},()=>{for(let n of qs(r,c=>JE.default(c)))e.reportWarning(60,n);for(let n of qs(s,c=>JE.default(c)))e.reportWarning(2,n)}),a&&e.reportWarning(86,`Some peer dependencies are incorrectly met by dependencies; run ${Ht(t.configuration,"yarn explain peer-requirements",ht.CODE)} for details.`)}var GR,qR,Bde,XG,zG,ZG,WR,cat,uat,Cde,fat,Aat,pat,$l,KG,YR,wde,Tt,vde=Xe(()=>{Dt();Dt();wc();Yt();GR=Ie("crypto");YG();ql();qR=ut(Ld()),Bde=ut(Ai()),XG=Ie("util"),zG=ut(Ie("v8")),ZG=ut(Ie("zlib"));LG();av();MG();UG();oI();F8();Tc();Ede();Ev();VG();tm();JG();LQ();xc();I0();Pc();gT();zj();Rp();Wo();WR=YE(process.env.YARN_LOCKFILE_VERSION_OVERRIDE??8),cat=3,uat=/ *, */g,Cde=/\/$/,fat=32,Aat=(0,XG.promisify)(ZG.default.gzip),pat=(0,XG.promisify)(ZG.default.gunzip),$l=(r=>(r.UpdateLockfile="update-lockfile",r.SkipBuild="skip-build",r))($l||{}),KG={restoreLinkersCustomData:["linkersCustomData"],restoreResolutions:["accessibleLocators","conditionalLocators","disabledLocators","optionalBuilds","storedDescriptors","storedResolutions","storedPackages","lockFileChecksum"],restoreBuildState:["skippedBuilds","storedBuildState"]},YR=(a=>(a[a.NotProvided=0]="NotProvided",a[a.NotCompatible=1]="NotCompatible",a[a.NodeNotProvided=2]="NodeNotProvided",a[a.NodeNotCompatible=3]="NodeNotCompatible",a))(YR||{}),wde=t=>us(`${cat}`,t),Tt=class t{constructor(e,{configuration:r}){this.resolutionAliases=new Map;this.workspaces=[];this.workspacesByCwd=new Map;this.workspacesByIdent=new Map;this.storedResolutions=new Map;this.storedDescriptors=new Map;this.storedPackages=new Map;this.storedChecksums=new Map;this.storedBuildState=new Map;this.accessibleLocators=new Set;this.conditionalLocators=new Set;this.disabledLocators=new Set;this.originalPackages=new Map;this.optionalBuilds=new Set;this.skippedBuilds=new Set;this.lockfileLastVersion=null;this.lockfileNeedsRefresh=!1;this.peerRequirements=new Map;this.peerWarnings=[];this.peerRequirementNodes=new Map;this.linkersCustomData=new Map;this.lockFileChecksum=null;this.installStateChecksum=null;this.configuration=r,this.cwd=e}static async find(e,r){if(!e.projectCwd)throw new nt(`No project found in ${r}`);let s=e.projectCwd,a=r,n=null;for(;n!==e.projectCwd;){if(n=a,ce.existsSync(J.join(n,Er.manifest))){s=n;break}a=J.dirname(n)}let c=new t(e.projectCwd,{configuration:e});ze.telemetry?.reportProject(c.cwd),await c.setupResolutions(),await c.setupWorkspaces(),ze.telemetry?.reportWorkspaceCount(c.workspaces.length),ze.telemetry?.reportDependencyCount(c.workspaces.reduce((I,R)=>I+R.manifest.dependencies.size+R.manifest.devDependencies.size,0));let f=c.tryWorkspaceByCwd(s);if(f)return{project:c,workspace:f,locator:f.anchoredLocator};let p=await c.findLocatorForLocation(`${s}/`,{strict:!0});if(p)return{project:c,locator:p,workspace:null};let h=Ht(e,c.cwd,ht.PATH),E=Ht(e,J.relative(c.cwd,s),ht.PATH),C=`- If ${h} isn't intended to be a project, remove any yarn.lock and/or package.json file there.`,S=`- If ${h} is intended to be a project, it might be that you forgot to list ${E} in its workspace configuration.`,P=`- Finally, if ${h} is fine and you intend ${E} to be treated as a completely separate project (not even a workspace), create an empty yarn.lock file in it.`;throw new nt(`The nearest package directory (${Ht(e,s,ht.PATH)}) doesn't seem to be part of the project declared in ${Ht(e,c.cwd,ht.PATH)}. ${[C,S,P].join(` `)}`)}async setupResolutions(){this.storedResolutions=new Map,this.storedDescriptors=new Map,this.storedPackages=new Map,this.lockFileChecksum=null;let e=J.join(this.cwd,Er.lockfile),r=this.configuration.get("defaultLanguageName");if(ce.existsSync(e)){let s=await ce.readFilePromise(e,"utf8");this.lockFileChecksum=wde(s);let a=ls(s);if(a.__metadata){let n=a.__metadata.version,c=a.__metadata.cacheKey;this.lockfileLastVersion=n,this.lockfileNeedsRefresh=n"u")throw new Error(`Assertion failed: Expected the lockfile entry to have a resolution field (${f})`);let h=Qp(p.resolution,!0),E=new Ut;E.load(p,{yamlCompatibilityMode:!0});let C=E.version,S=E.languageName||r,P=p.linkType.toUpperCase(),I=p.conditions??null,R=E.dependencies,N=E.peerDependencies,U=E.dependenciesMeta,W=E.peerDependenciesMeta,ee=E.bin;if(p.checksum!=null){let ue=typeof c<"u"&&!p.checksum.includes("/")?`${c}/${p.checksum}`:p.checksum;this.storedChecksums.set(h.locatorHash,ue)}let ie={...h,version:C,languageName:S,linkType:P,conditions:I,dependencies:R,peerDependencies:N,dependenciesMeta:U,peerDependenciesMeta:W,bin:ee};this.originalPackages.set(ie.locatorHash,ie);for(let ue of f.split(uat)){let le=C0(ue);n<=6&&(le=this.configuration.normalizeDependency(le),le=On(le,le.range.replace(/^patch:[^@]+@(?!npm(:|%3A))/,"$1npm%3A"))),this.storedDescriptors.set(le.descriptorHash,le),this.storedResolutions.set(le.descriptorHash,h.locatorHash)}}}else s.includes("yarn lockfile v1")&&(this.lockfileLastVersion=-1)}}async setupWorkspaces(){this.workspaces=[],this.workspacesByCwd=new Map,this.workspacesByIdent=new Map;let e=new Set,r=(0,qR.default)(4),s=async(a,n)=>{if(e.has(n))return a;e.add(n);let c=new XI(n,{project:this});await r(()=>c.setup());let f=a.then(()=>{this.addWorkspace(c)});return Array.from(c.workspacesCwds).reduce(s,f)};await s(Promise.resolve(),this.cwd)}addWorkspace(e){let r=this.workspacesByIdent.get(e.anchoredLocator.identHash);if(typeof r<"u")throw new Error(`Duplicate workspace name ${$i(this.configuration,e.anchoredLocator)}: ${fe.fromPortablePath(e.cwd)} conflicts with ${fe.fromPortablePath(r.cwd)}`);this.workspaces.push(e),this.workspacesByCwd.set(e.cwd,e),this.workspacesByIdent.set(e.anchoredLocator.identHash,e)}get topLevelWorkspace(){return this.getWorkspaceByCwd(this.cwd)}tryWorkspaceByCwd(e){J.isAbsolute(e)||(e=J.resolve(this.cwd,e)),e=J.normalize(e).replace(/\/+$/,"");let r=this.workspacesByCwd.get(e);return r||null}getWorkspaceByCwd(e){let r=this.tryWorkspaceByCwd(e);if(!r)throw new Error(`Workspace not found (${e})`);return r}tryWorkspaceByFilePath(e){let r=null;for(let s of this.workspaces)J.relative(s.cwd,e).startsWith("../")||r&&r.cwd.length>=s.cwd.length||(r=s);return r||null}getWorkspaceByFilePath(e){let r=this.tryWorkspaceByFilePath(e);if(!r)throw new Error(`Workspace not found (${e})`);return r}tryWorkspaceByIdent(e){let r=this.workspacesByIdent.get(e.identHash);return typeof r>"u"?null:r}getWorkspaceByIdent(e){let r=this.tryWorkspaceByIdent(e);if(!r)throw new Error(`Workspace not found (${$i(this.configuration,e)})`);return r}tryWorkspaceByDescriptor(e){if(e.range.startsWith(Ei.protocol)){let s=e.range.slice(Ei.protocol.length);if(s!=="^"&&s!=="~"&&s!=="*"&&!cl(s))return this.tryWorkspaceByCwd(s)}let r=this.tryWorkspaceByIdent(e);return r===null||(kp(e)&&(e=MB(e)),!r.accepts(e.range))?null:r}getWorkspaceByDescriptor(e){let r=this.tryWorkspaceByDescriptor(e);if(r===null)throw new Error(`Workspace not found (${ni(this.configuration,e)})`);return r}tryWorkspaceByLocator(e){let r=this.tryWorkspaceByIdent(e);return r===null||(Gu(e)&&(e=rI(e)),r.anchoredLocator.locatorHash!==e.locatorHash)?null:r}getWorkspaceByLocator(e){let r=this.tryWorkspaceByLocator(e);if(!r)throw new Error(`Workspace not found (${Yr(this.configuration,e)})`);return r}deleteDescriptor(e){this.storedResolutions.delete(e),this.storedDescriptors.delete(e)}deleteLocator(e){this.originalPackages.delete(e),this.storedPackages.delete(e),this.accessibleLocators.delete(e)}forgetResolution(e){if("descriptorHash"in e){let r=this.storedResolutions.get(e.descriptorHash);this.deleteDescriptor(e.descriptorHash);let s=new Set(this.storedResolutions.values());typeof r<"u"&&!s.has(r)&&this.deleteLocator(r)}if("locatorHash"in e){this.deleteLocator(e.locatorHash);for(let[r,s]of this.storedResolutions)s===e.locatorHash&&this.deleteDescriptor(r)}}forgetTransientResolutions(){let e=this.configuration.makeResolver(),r=new Map;for(let[s,a]of this.storedResolutions.entries()){let n=r.get(a);n||r.set(a,n=new Set),n.add(s)}for(let s of this.originalPackages.values()){let a;try{a=e.shouldPersistResolution(s,{project:this,resolver:e})}catch{a=!1}if(!a){this.deleteLocator(s.locatorHash);let n=r.get(s.locatorHash);if(n){r.delete(s.locatorHash);for(let c of n)this.deleteDescriptor(c)}}}}forgetVirtualResolutions(){for(let e of this.storedPackages.values())for(let[r,s]of e.dependencies)kp(s)&&e.dependencies.set(r,MB(s))}getDependencyMeta(e,r){let s={},n=this.topLevelWorkspace.manifest.dependenciesMeta.get(un(e));if(!n)return s;let c=n.get(null);if(c&&Object.assign(s,c),r===null||!Bde.default.valid(r))return s;for(let[f,p]of n)f!==null&&f===r&&Object.assign(s,p);return s}async findLocatorForLocation(e,{strict:r=!1}={}){let s=new ki,a=this.configuration.getLinkers(),n={project:this,report:s};for(let c of a){let f=await c.findPackageLocator(e,n);if(f){if(r&&(await c.findPackageLocation(f,n)).replace(Cde,"")!==e.replace(Cde,""))continue;return f}}return null}async loadUserConfig(){let e=J.join(this.cwd,".pnp.cjs");await ce.existsPromise(e)&&Pp(e).setup();let r=J.join(this.cwd,"yarn.config.cjs");return await ce.existsPromise(r)?Pp(r):null}async preparePackage(e,{resolver:r,resolveOptions:s}){let a=await this.configuration.getPackageExtensions(),n=this.configuration.normalizePackage(e,{packageExtensions:a});for(let[c,f]of n.dependencies){let p=await this.configuration.reduceHook(E=>E.reduceDependency,f,this,n,f,{resolver:r,resolveOptions:s});if(!UB(f,p))throw new Error("Assertion failed: The descriptor ident cannot be changed through aliases");let h=r.bindDescriptor(p,n,s);n.dependencies.set(c,h)}return n}async resolveEverything(e){if(!this.workspacesByCwd||!this.workspacesByIdent)throw new Error("Workspaces must have been setup before calling this function");this.forgetVirtualResolutions();let r=new Map(this.originalPackages),s=[];e.lockfileOnly||this.forgetTransientResolutions();let a=e.resolver||this.configuration.makeResolver(),n=new KI(a);await n.setup(this,{report:e.report});let c=e.lockfileOnly?[new jR(a)]:[n,a],f=new rm([new zI(a),...c]),p=new rm([...c]),h=this.configuration.makeFetcher(),E=e.lockfileOnly?{project:this,report:e.report,resolver:f}:{project:this,report:e.report,resolver:f,fetchOptions:{project:this,cache:e.cache,checksums:this.storedChecksums,report:e.report,fetcher:h,cacheOptions:{mirrorWriteOnly:!0}}},C=new Map,S=new Map,P=new Map,I=new Map,R=new Map,N=new Map,U=this.topLevelWorkspace.anchoredLocator,W=new Set,ee=[],ie=uj(),ue=this.configuration.getSupportedArchitectures();await e.report.startProgressPromise(Ao.progressViaTitle(),async se=>{let Z=async rt=>{let Fe=await qE(async()=>await f.resolve(rt,E),ke=>`${Yr(this.configuration,rt)}: ${ke}`);if(!_B(rt,Fe))throw new Error(`Assertion failed: The locator cannot be changed by the resolver (went from ${Yr(this.configuration,rt)} to ${Yr(this.configuration,Fe)})`);I.set(Fe.locatorHash,Fe),!r.delete(Fe.locatorHash)&&!this.tryWorkspaceByLocator(Fe)&&s.push(Fe);let Pe=await this.preparePackage(Fe,{resolver:f,resolveOptions:E}),Ve=Uu([...Pe.dependencies.values()].map(ke=>j(ke)));return ee.push(Ve),Ve.catch(()=>{}),S.set(Pe.locatorHash,Pe),Pe},De=async rt=>{let Fe=R.get(rt.locatorHash);if(typeof Fe<"u")return Fe;let Ne=Promise.resolve().then(()=>Z(rt));return R.set(rt.locatorHash,Ne),Ne},Re=async(rt,Fe)=>{let Ne=await j(Fe);return C.set(rt.descriptorHash,rt),P.set(rt.descriptorHash,Ne.locatorHash),Ne},mt=async rt=>{se.setTitle(ni(this.configuration,rt));let Fe=this.resolutionAliases.get(rt.descriptorHash);if(typeof Fe<"u")return Re(rt,this.storedDescriptors.get(Fe));let Ne=f.getResolutionDependencies(rt,E),Pe=Object.fromEntries(await Uu(Object.entries(Ne).map(async([it,Ue])=>{let x=f.bindDescriptor(Ue,U,E),w=await j(x);return W.add(w.locatorHash),[it,w]}))),ke=(await qE(async()=>await f.getCandidates(rt,Pe,E),it=>`${ni(this.configuration,rt)}: ${it}`))[0];if(typeof ke>"u")throw new jt(82,`${ni(this.configuration,rt)}: No candidates found`);if(e.checkResolutions){let{locators:it}=await p.getSatisfying(rt,Pe,[ke],{...E,resolver:p});if(!it.find(Ue=>Ue.locatorHash===ke.locatorHash))throw new jt(78,`Invalid resolution ${FB(this.configuration,rt,ke)}`)}return C.set(rt.descriptorHash,rt),P.set(rt.descriptorHash,ke.locatorHash),De(ke)},j=rt=>{let Fe=N.get(rt.descriptorHash);if(typeof Fe<"u")return Fe;C.set(rt.descriptorHash,rt);let Ne=Promise.resolve().then(()=>mt(rt));return N.set(rt.descriptorHash,Ne),Ne};for(let rt of this.workspaces){let Fe=rt.anchoredDescriptor;ee.push(j(Fe))}for(;ee.length>0;){let rt=[...ee];ee.length=0,await Uu(rt)}});let le=Wl(r.values(),se=>this.tryWorkspaceByLocator(se)?Wl.skip:se);if(s.length>0||le.length>0){let se=new Set(this.workspaces.flatMap(rt=>{let Fe=S.get(rt.anchoredLocator.locatorHash);if(!Fe)throw new Error("Assertion failed: The workspace should have been resolved");return Array.from(Fe.dependencies.values(),Ne=>{let Pe=P.get(Ne.descriptorHash);if(!Pe)throw new Error("Assertion failed: The resolution should have been registered");return Pe})})),Z=rt=>se.has(rt.locatorHash)?"0":"1",De=rt=>ll(rt),Re=qs(s,[Z,De]),mt=qs(le,[Z,De]),j=e.report.getRecommendedLength();Re.length>0&&e.report.reportInfo(85,`${Ht(this.configuration,"+",ht.ADDED)} ${$k(this.configuration,Re,j)}`),mt.length>0&&e.report.reportInfo(85,`${Ht(this.configuration,"-",ht.REMOVED)} ${$k(this.configuration,mt,j)}`)}let me=new Set(this.resolutionAliases.values()),pe=new Set(S.keys()),Be=new Set,Ce=new Map,g=[],we=new Map;hat({project:this,accessibleLocators:Be,volatileDescriptors:me,optionalBuilds:pe,peerRequirements:Ce,peerWarnings:g,peerRequirementNodes:we,allDescriptors:C,allResolutions:P,allPackages:S});for(let se of W)pe.delete(se);for(let se of me)C.delete(se),P.delete(se);let ye=new Set,Ae=new Set;for(let se of S.values())se.conditions!=null&&pe.has(se.locatorHash)&&(TQ(se,ue)||(TQ(se,ie)&&e.report.reportWarningOnce(77,`${Yr(this.configuration,se)}: Your current architecture (${process.platform}-${process.arch}) is supported by this package, but is missing from the ${Ht(this.configuration,"supportedArchitectures",ht.SETTING)} setting`),Ae.add(se.locatorHash)),ye.add(se.locatorHash));this.storedResolutions=P,this.storedDescriptors=C,this.storedPackages=S,this.accessibleLocators=Be,this.conditionalLocators=ye,this.disabledLocators=Ae,this.originalPackages=I,this.optionalBuilds=pe,this.peerRequirements=Ce,this.peerWarnings=g,this.peerRequirementNodes=we}async fetchEverything({cache:e,report:r,fetcher:s,mode:a,persistProject:n=!0}){let c={mockedPackages:this.disabledLocators,unstablePackages:this.conditionalLocators},f=s||this.configuration.makeFetcher(),p={checksums:this.storedChecksums,project:this,cache:e,fetcher:f,report:r,cacheOptions:c},h=Array.from(new Set(qs(this.storedResolutions.values(),[I=>{let R=this.storedPackages.get(I);if(!R)throw new Error("Assertion failed: The locator should have been registered");return ll(R)}])));a==="update-lockfile"&&(h=h.filter(I=>!this.storedChecksums.has(I)));let E=!1,C=Ao.progressViaCounter(h.length);await r.reportProgress(C);let S=(0,qR.default)(fat);if(await Uu(h.map(I=>S(async()=>{let R=this.storedPackages.get(I);if(!R)throw new Error("Assertion failed: The locator should have been registered");if(Gu(R))return;let N;try{N=await f.fetch(R,p)}catch(U){U.message=`${Yr(this.configuration,R)}: ${U.message}`,r.reportExceptionOnce(U),E=U;return}N.checksum!=null?this.storedChecksums.set(R.locatorHash,N.checksum):this.storedChecksums.delete(R.locatorHash),N.releaseFs&&N.releaseFs()}).finally(()=>{C.tick()}))),E)throw E;let P=n&&a!=="update-lockfile"?await this.cacheCleanup({cache:e,report:r}):null;if(r.cacheMisses.size>0||P){let R=(await Promise.all([...r.cacheMisses].map(async le=>{let me=this.storedPackages.get(le),pe=this.storedChecksums.get(le)??null,Be=e.getLocatorPath(me,pe);return(await ce.statPromise(Be)).size}))).reduce((le,me)=>le+me,0)-(P?.size??0),N=r.cacheMisses.size,U=P?.count??0,W=`${Wk(N,{zero:"No new packages",one:"A package was",more:`${Ht(this.configuration,N,ht.NUMBER)} packages were`})} added to the project`,ee=`${Wk(U,{zero:"none were",one:"one was",more:`${Ht(this.configuration,U,ht.NUMBER)} were`})} removed`,ie=R!==0?` (${Ht(this.configuration,R,ht.SIZE_DIFF)})`:"",ue=U>0?N>0?`${W}, and ${ee}${ie}.`:`${W}, but ${ee}${ie}.`:`${W}${ie}.`;r.reportInfo(13,ue)}}async linkEverything({cache:e,report:r,fetcher:s,mode:a}){let n={mockedPackages:this.disabledLocators,unstablePackages:this.conditionalLocators,skipIntegrityCheck:!0},c=s||this.configuration.makeFetcher(),f={checksums:this.storedChecksums,project:this,cache:e,fetcher:c,report:r,cacheOptions:n},p=this.configuration.getLinkers(),h={project:this,report:r},E=new Map(p.map(ye=>{let Ae=ye.makeInstaller(h),se=ye.getCustomDataKey(),Z=this.linkersCustomData.get(se);return typeof Z<"u"&&Ae.attachCustomData(Z),[ye,Ae]})),C=new Map,S=new Map,P=new Map,I=new Map(await Uu([...this.accessibleLocators].map(async ye=>{let Ae=this.storedPackages.get(ye);if(!Ae)throw new Error("Assertion failed: The locator should have been registered");return[ye,await c.fetch(Ae,f)]}))),R=[],N=new Set,U=[];for(let ye of this.accessibleLocators){let Ae=this.storedPackages.get(ye);if(typeof Ae>"u")throw new Error("Assertion failed: The locator should have been registered");let se=I.get(Ae.locatorHash);if(typeof se>"u")throw new Error("Assertion failed: The fetch result should have been registered");let Z=[],De=mt=>{Z.push(mt)},Re=this.tryWorkspaceByLocator(Ae);if(Re!==null){let mt=[],{scripts:j}=Re.manifest;for(let Fe of["preinstall","install","postinstall"])j.has(Fe)&&mt.push({type:0,script:Fe});try{for(let[Fe,Ne]of E)if(Fe.supportsPackage(Ae,h)&&(await Ne.installPackage(Ae,se,{holdFetchResult:De})).buildRequest!==null)throw new Error("Assertion failed: Linkers can't return build directives for workspaces; this responsibility befalls to the Yarn core")}finally{Z.length===0?se.releaseFs?.():R.push(Uu(Z).catch(()=>{}).then(()=>{se.releaseFs?.()}))}let rt=J.join(se.packageFs.getRealPath(),se.prefixPath);S.set(Ae.locatorHash,rt),!Gu(Ae)&&mt.length>0&&P.set(Ae.locatorHash,{buildDirectives:mt,buildLocations:[rt]})}else{let mt=p.find(Fe=>Fe.supportsPackage(Ae,h));if(!mt)throw new jt(12,`${Yr(this.configuration,Ae)} isn't supported by any available linker`);let j=E.get(mt);if(!j)throw new Error("Assertion failed: The installer should have been registered");let rt;try{rt=await j.installPackage(Ae,se,{holdFetchResult:De})}finally{Z.length===0?se.releaseFs?.():R.push(Uu(Z).then(()=>{}).then(()=>{se.releaseFs?.()}))}C.set(Ae.locatorHash,mt),S.set(Ae.locatorHash,rt.packageLocation),rt.buildRequest&&rt.packageLocation&&(rt.buildRequest.skipped?(N.add(Ae.locatorHash),this.skippedBuilds.has(Ae.locatorHash)||U.push([Ae,rt.buildRequest.explain])):P.set(Ae.locatorHash,{buildDirectives:rt.buildRequest.directives,buildLocations:[rt.packageLocation]}))}}let W=new Map;for(let ye of this.accessibleLocators){let Ae=this.storedPackages.get(ye);if(!Ae)throw new Error("Assertion failed: The locator should have been registered");let se=this.tryWorkspaceByLocator(Ae)!==null,Z=async(De,Re)=>{let mt=S.get(Ae.locatorHash);if(typeof mt>"u")throw new Error(`Assertion failed: The package (${Yr(this.configuration,Ae)}) should have been registered`);let j=[];for(let rt of Ae.dependencies.values()){let Fe=this.storedResolutions.get(rt.descriptorHash);if(typeof Fe>"u")throw new Error(`Assertion failed: The resolution (${ni(this.configuration,rt)}, from ${Yr(this.configuration,Ae)})should have been registered`);let Ne=this.storedPackages.get(Fe);if(typeof Ne>"u")throw new Error(`Assertion failed: The package (${Fe}, resolved from ${ni(this.configuration,rt)}) should have been registered`);let Pe=this.tryWorkspaceByLocator(Ne)===null?C.get(Fe):null;if(typeof Pe>"u")throw new Error(`Assertion failed: The package (${Fe}, resolved from ${ni(this.configuration,rt)}) should have been registered`);Pe===De||Pe===null?S.get(Ne.locatorHash)!==null&&j.push([rt,Ne]):!se&&mt!==null&&xB(W,Fe).push(mt)}mt!==null&&await Re.attachInternalDependencies(Ae,j)};if(se)for(let[De,Re]of E)De.supportsPackage(Ae,h)&&await Z(De,Re);else{let De=C.get(Ae.locatorHash);if(!De)throw new Error("Assertion failed: The linker should have been found");let Re=E.get(De);if(!Re)throw new Error("Assertion failed: The installer should have been registered");await Z(De,Re)}}for(let[ye,Ae]of W){let se=this.storedPackages.get(ye);if(!se)throw new Error("Assertion failed: The package should have been registered");let Z=C.get(se.locatorHash);if(!Z)throw new Error("Assertion failed: The linker should have been found");let De=E.get(Z);if(!De)throw new Error("Assertion failed: The installer should have been registered");await De.attachExternalDependents(se,Ae)}let ee=new Map;for(let[ye,Ae]of E){let se=await Ae.finalizeInstall();for(let Z of se?.records??[])Z.buildRequest.skipped?(N.add(Z.locator.locatorHash),this.skippedBuilds.has(Z.locator.locatorHash)||U.push([Z.locator,Z.buildRequest.explain])):P.set(Z.locator.locatorHash,{buildDirectives:Z.buildRequest.directives,buildLocations:Z.buildLocations});typeof se?.customData<"u"&&ee.set(ye.getCustomDataKey(),se.customData)}if(this.linkersCustomData=ee,await Uu(R),a==="skip-build")return;for(let[,ye]of qs(U,([Ae])=>ll(Ae)))ye(r);let ie=new Set(P.keys()),ue=(0,GR.createHash)("sha512");ue.update(process.versions.node),await this.configuration.triggerHook(ye=>ye.globalHashGeneration,this,ye=>{ue.update("\0"),ue.update(ye)});let le=ue.digest("hex"),me=new Map,pe=ye=>{let Ae=me.get(ye.locatorHash);if(typeof Ae<"u")return Ae;let se=this.storedPackages.get(ye.locatorHash);if(typeof se>"u")throw new Error("Assertion failed: The package should have been registered");let Z=(0,GR.createHash)("sha512");Z.update(ye.locatorHash),me.set(ye.locatorHash,"");for(let De of se.dependencies.values()){let Re=this.storedResolutions.get(De.descriptorHash);if(typeof Re>"u")throw new Error(`Assertion failed: The resolution (${ni(this.configuration,De)}) should have been registered`);let mt=this.storedPackages.get(Re);if(typeof mt>"u")throw new Error("Assertion failed: The package should have been registered");Z.update(pe(mt))}return Ae=Z.digest("hex"),me.set(ye.locatorHash,Ae),Ae},Be=(ye,Ae)=>{let se=(0,GR.createHash)("sha512");se.update(le),se.update(pe(ye));for(let Z of Ae)se.update(Z);return se.digest("hex")},Ce=new Map,g=!1,we=ye=>{let Ae=new Set([ye.locatorHash]);for(let se of Ae){let Z=this.storedPackages.get(se);if(!Z)throw new Error("Assertion failed: The package should have been registered");for(let De of Z.dependencies.values()){let Re=this.storedResolutions.get(De.descriptorHash);if(!Re)throw new Error(`Assertion failed: The resolution (${ni(this.configuration,De)}) should have been registered`);if(Re!==ye.locatorHash&&ie.has(Re))return!1;let mt=this.storedPackages.get(Re);if(!mt)throw new Error("Assertion failed: The package should have been registered");let j=this.tryWorkspaceByLocator(mt);if(j){if(j.anchoredLocator.locatorHash!==ye.locatorHash&&ie.has(j.anchoredLocator.locatorHash))return!1;Ae.add(j.anchoredLocator.locatorHash)}Ae.add(Re)}}return!0};for(;ie.size>0;){let ye=ie.size,Ae=[];for(let se of ie){let Z=this.storedPackages.get(se);if(!Z)throw new Error("Assertion failed: The package should have been registered");if(!we(Z))continue;let De=P.get(Z.locatorHash);if(!De)throw new Error("Assertion failed: The build directive should have been registered");let Re=Be(Z,De.buildLocations);if(this.storedBuildState.get(Z.locatorHash)===Re){Ce.set(Z.locatorHash,Re),ie.delete(se);continue}g||(await this.persistInstallStateFile(),g=!0),this.storedBuildState.has(Z.locatorHash)?r.reportInfo(8,`${Yr(this.configuration,Z)} must be rebuilt because its dependency tree changed`):r.reportInfo(7,`${Yr(this.configuration,Z)} must be built because it never has been before or the last one failed`);let mt=De.buildLocations.map(async j=>{if(!J.isAbsolute(j))throw new Error(`Assertion failed: Expected the build location to be absolute (not ${j})`);for(let rt of De.buildDirectives){let Fe=`# This file contains the result of Yarn building a package (${ll(Z)}) `;switch(rt.type){case 0:Fe+=`# Script name: ${rt.script} `;break;case 1:Fe+=`# Script code: ${rt.script} `;break}let Ne=null;if(!await ce.mktempPromise(async Ve=>{let ke=J.join(Ve,"build.log"),{stdout:it,stderr:Ue}=this.configuration.getSubprocessStreams(ke,{header:Fe,prefix:Yr(this.configuration,Z),report:r}),x;try{switch(rt.type){case 0:x=await LT(Z,rt.script,[],{cwd:j,project:this,stdin:Ne,stdout:it,stderr:Ue});break;case 1:x=await Yj(Z,rt.script,[],{cwd:j,project:this,stdin:Ne,stdout:it,stderr:Ue});break}}catch(y){Ue.write(y.stack),x=1}if(it.end(),Ue.end(),x===0)return!0;ce.detachTemp(Ve);let w=`${Yr(this.configuration,Z)} couldn't be built successfully (exit code ${Ht(this.configuration,x,ht.NUMBER)}, logs can be found here: ${Ht(this.configuration,ke,ht.PATH)})`,b=this.optionalBuilds.has(Z.locatorHash);return b?r.reportInfo(9,w):r.reportError(9,w),ehe&&r.reportFold(fe.fromPortablePath(ke),ce.readFileSync(ke,"utf8")),b}))return!1}return!0});Ae.push(...mt,Promise.allSettled(mt).then(j=>{ie.delete(se),j.every(rt=>rt.status==="fulfilled"&&rt.value===!0)&&Ce.set(Z.locatorHash,Re)}))}if(await Uu(Ae),ye===ie.size){let se=Array.from(ie).map(Z=>{let De=this.storedPackages.get(Z);if(!De)throw new Error("Assertion failed: The package should have been registered");return Yr(this.configuration,De)}).join(", ");r.reportError(3,`Some packages have circular dependencies that make their build order unsatisfiable - as a result they won't be built (affected packages are: ${se})`);break}}this.storedBuildState=Ce,this.skippedBuilds=N}async installWithNewReport(e,r){return(await Ot.start({configuration:this.configuration,json:e.json,stdout:e.stdout,forceSectionAlignment:!0,includeLogs:!e.json&&!e.quiet,includeVersion:!0},async a=>{await this.install({...r,report:a})})).exitCode()}async install(e){let r=this.configuration.get("nodeLinker");ze.telemetry?.reportInstall(r);let s=!1;if(await e.report.startTimerPromise("Project validation",{skipIfEmpty:!0},async()=>{this.configuration.get("enableOfflineMode")&&e.report.reportWarning(90,"Offline work is enabled; Yarn won't fetch packages from the remote registry if it can avoid it"),await this.configuration.triggerHook(E=>E.validateProject,this,{reportWarning:(E,C)=>{e.report.reportWarning(E,C)},reportError:(E,C)=>{e.report.reportError(E,C),s=!0}})}),s)return;let a=await this.configuration.getPackageExtensions();for(let E of a.values())for(let[,C]of E)for(let S of C)S.status="inactive";let n=J.join(this.cwd,Er.lockfile),c=null;if(e.immutable)try{c=await ce.readFilePromise(n,"utf8")}catch(E){throw E.code==="ENOENT"?new jt(28,"The lockfile would have been created by this install, which is explicitly forbidden."):E}await e.report.startTimerPromise("Resolution step",async()=>{await this.resolveEverything(e)}),await e.report.startTimerPromise("Post-resolution validation",{skipIfEmpty:!0},async()=>{dat(this,e.report);for(let[,E]of a)for(let[,C]of E)for(let S of C)if(S.userProvided){let P=Ht(this.configuration,S,ht.PACKAGE_EXTENSION);switch(S.status){case"inactive":e.report.reportWarning(68,`${P}: No matching package in the dependency tree; you may not need this rule anymore.`);break;case"redundant":e.report.reportWarning(69,`${P}: This rule seems redundant when applied on the original package; the extension may have been applied upstream.`);break}}if(c!==null){let E=Ed(c,this.generateLockfile());if(E!==c){let C=yde(n,n,c,E,void 0,void 0,{maxEditLength:100});if(C){e.report.reportSeparator();for(let S of C.hunks){e.report.reportInfo(null,`@@ -${S.oldStart},${S.oldLines} +${S.newStart},${S.newLines} @@`);for(let P of S.lines)P.startsWith("+")?e.report.reportError(28,Ht(this.configuration,P,ht.ADDED)):P.startsWith("-")?e.report.reportError(28,Ht(this.configuration,P,ht.REMOVED)):e.report.reportInfo(null,Ht(this.configuration,P,"grey"))}e.report.reportSeparator()}throw new jt(28,"The lockfile would have been modified by this install, which is explicitly forbidden.")}}});for(let E of a.values())for(let[,C]of E)for(let S of C)S.userProvided&&S.status==="active"&&ze.telemetry?.reportPackageExtension(Xd(S,ht.PACKAGE_EXTENSION));await e.report.startTimerPromise("Fetch step",async()=>{await this.fetchEverything(e)});let f=e.immutable?[...new Set(this.configuration.get("immutablePatterns"))].sort():[],p=await Promise.all(f.map(async E=>DQ(E,{cwd:this.cwd})));(typeof e.persistProject>"u"||e.persistProject)&&await this.persist(),await e.report.startTimerPromise("Link step",async()=>{if(e.mode==="update-lockfile"){e.report.reportWarning(73,`Skipped due to ${Ht(this.configuration,"mode=update-lockfile",ht.CODE)}`);return}await this.linkEverything(e);let E=await Promise.all(f.map(async C=>DQ(C,{cwd:this.cwd})));for(let C=0;C{await this.configuration.triggerHook(E=>E.validateProjectAfterInstall,this,{reportWarning:(E,C)=>{e.report.reportWarning(E,C)},reportError:(E,C)=>{e.report.reportError(E,C),h=!0}})}),!h&&await this.configuration.triggerHook(E=>E.afterAllInstalled,this,e)}generateLockfile(){let e=new Map;for(let[n,c]of this.storedResolutions.entries()){let f=e.get(c);f||e.set(c,f=new Set),f.add(n)}let r={},{cacheKey:s}=Kr.getCacheKey(this.configuration);r.__metadata={version:WR,cacheKey:s};for(let[n,c]of e.entries()){let f=this.originalPackages.get(n);if(!f)continue;let p=[];for(let C of c){let S=this.storedDescriptors.get(C);if(!S)throw new Error("Assertion failed: The descriptor should have been registered");p.push(S)}let h=p.map(C=>al(C)).sort().join(", "),E=new Ut;E.version=f.linkType==="HARD"?f.version:"0.0.0-use.local",E.languageName=f.languageName,E.dependencies=new Map(f.dependencies),E.peerDependencies=new Map(f.peerDependencies),E.dependenciesMeta=new Map(f.dependenciesMeta),E.peerDependenciesMeta=new Map(f.peerDependenciesMeta),E.bin=new Map(f.bin),r[h]={...E.exportTo({},{compatibilityMode:!1}),linkType:f.linkType.toLowerCase(),resolution:ll(f),checksum:this.storedChecksums.get(f.locatorHash),conditions:f.conditions||void 0}}return`${[`# This file is generated by running "yarn install" inside your project. `,`# Manual changes might be lost - proceed with caution! `].join("")} `+nl(r)}async persistLockfile(){let e=J.join(this.cwd,Er.lockfile),r="";try{r=await ce.readFilePromise(e,"utf8")}catch{}let s=this.generateLockfile(),a=Ed(r,s);a!==r&&(await ce.writeFilePromise(e,a),this.lockFileChecksum=wde(a),this.lockfileNeedsRefresh=!1)}async persistInstallStateFile(){let e=[];for(let c of Object.values(KG))e.push(...c);let r=Kd(this,e),s=zG.default.serialize(r),a=us(s);if(this.installStateChecksum===a)return;let n=this.configuration.get("installStatePath");await ce.mkdirPromise(J.dirname(n),{recursive:!0}),await ce.writeFilePromise(n,await Aat(s)),this.installStateChecksum=a}async restoreInstallState({restoreLinkersCustomData:e=!0,restoreResolutions:r=!0,restoreBuildState:s=!0}={}){let a=this.configuration.get("installStatePath"),n;try{let c=await pat(await ce.readFilePromise(a));n=zG.default.deserialize(c),this.installStateChecksum=us(c)}catch{r&&await this.applyLightResolution();return}e&&typeof n.linkersCustomData<"u"&&(this.linkersCustomData=n.linkersCustomData),s&&Object.assign(this,Kd(n,KG.restoreBuildState)),r&&(n.lockFileChecksum===this.lockFileChecksum?Object.assign(this,Kd(n,KG.restoreResolutions)):await this.applyLightResolution())}async applyLightResolution(){await this.resolveEverything({lockfileOnly:!0,report:new ki}),await this.persistInstallStateFile()}async persist(){let e=(0,qR.default)(4);await Promise.all([this.persistLockfile(),...this.workspaces.map(r=>e(()=>r.persistManifest()))])}async cacheCleanup({cache:e,report:r}){if(this.configuration.get("enableGlobalCache"))return null;let s=new Set([".gitignore"]);if(!q8(e.cwd,this.cwd)||!await ce.existsPromise(e.cwd))return null;let a=[];for(let c of await ce.readdirPromise(e.cwd)){if(s.has(c))continue;let f=J.resolve(e.cwd,c);e.markedFiles.has(f)||(e.immutable?r.reportError(56,`${Ht(this.configuration,J.basename(f),"magenta")} appears to be unused and would be marked for deletion, but the cache is immutable`):a.push(ce.lstatPromise(f).then(async p=>(await ce.removePromise(f),p.size))))}if(a.length===0)return null;let n=await Promise.all(a);return{count:a.length,size:n.reduce((c,f)=>c+f,0)}}}});function mat(t){let s=Math.floor(t.timeNow/864e5),a=t.updateInterval*864e5,n=t.state.lastUpdate??t.timeNow+a+Math.floor(a*t.randomInitialInterval),c=n+a,f=t.state.lastTips??s*864e5,p=f+864e5+8*36e5-t.timeZone,h=c<=t.timeNow,E=p<=t.timeNow,C=null;return(h||E||!t.state.lastUpdate||!t.state.lastTips)&&(C={},C.lastUpdate=h?t.timeNow:n,C.lastTips=f,C.blocks=h?{}:t.state.blocks,C.displayedTips=t.state.displayedTips),{nextState:C,triggerUpdate:h,triggerTips:E,nextTips:E?s*864e5:f}}var ZI,Sde=Xe(()=>{Dt();yv();I0();pT();Pc();Rp();ZI=class{constructor(e,r){this.values=new Map;this.hits=new Map;this.enumerators=new Map;this.nextTips=0;this.displayedTips=[];this.shouldCommitTips=!1;this.configuration=e;let s=this.getRegistryPath();this.isNew=!ce.existsSync(s),this.shouldShowTips=!1,this.sendReport(r),this.startBuffer()}commitTips(){this.shouldShowTips&&(this.shouldCommitTips=!0)}selectTip(e){let r=new Set(this.displayedTips),s=f=>f&&fn?Zf(fn,f):!1,a=e.map((f,p)=>p).filter(f=>e[f]&&s(e[f]?.selector));if(a.length===0)return null;let n=a.filter(f=>!r.has(f));if(n.length===0){let f=Math.floor(a.length*.2);this.displayedTips=f>0?this.displayedTips.slice(-f):[],n=a.filter(p=>!r.has(p))}let c=n[Math.floor(Math.random()*n.length)];return this.displayedTips.push(c),this.commitTips(),e[c]}reportVersion(e){this.reportValue("version",e.replace(/-git\..*/,"-git"))}reportCommandName(e){this.reportValue("commandName",e||"")}reportPluginName(e){this.reportValue("pluginName",e)}reportProject(e){this.reportEnumerator("projectCount",e)}reportInstall(e){this.reportHit("installCount",e)}reportPackageExtension(e){this.reportValue("packageExtension",e)}reportWorkspaceCount(e){this.reportValue("workspaceCount",String(e))}reportDependencyCount(e){this.reportValue("dependencyCount",String(e))}reportValue(e,r){bp(this.values,e).add(r)}reportEnumerator(e,r){bp(this.enumerators,e).add(us(r))}reportHit(e,r="*"){let s=q4(this.hits,e),a=Yl(s,r,()=>0);s.set(r,a+1)}getRegistryPath(){let e=this.configuration.get("globalFolder");return J.join(e,"telemetry.json")}sendReport(e){let r=this.getRegistryPath(),s;try{s=ce.readJsonSync(r)}catch{s={}}let{nextState:a,triggerUpdate:n,triggerTips:c,nextTips:f}=mat({state:s,timeNow:Date.now(),timeZone:new Date().getTimezoneOffset()*60*1e3,randomInitialInterval:Math.random(),updateInterval:this.configuration.get("telemetryInterval")});if(this.nextTips=f,this.displayedTips=s.displayedTips??[],a!==null)try{ce.mkdirSync(J.dirname(r),{recursive:!0}),ce.writeJsonSync(r,a)}catch{return!1}if(c&&this.configuration.get("enableTips")&&(this.shouldShowTips=!0),n){let p=s.blocks??{};if(Object.keys(p).length===0){let h=`https://browser-http-intake.logs.datadoghq.eu/v1/input/${e}?ddsource=yarn`,E=C=>cj(h,C,{configuration:this.configuration}).catch(()=>{});for(let[C,S]of Object.entries(s.blocks??{})){if(Object.keys(S).length===0)continue;let P=S;P.userId=C,P.reportType="primary";for(let N of Object.keys(P.enumerators??{}))P.enumerators[N]=P.enumerators[N].length;E(P);let I=new Map,R=20;for(let[N,U]of Object.entries(P.values))U.length>0&&I.set(N,U.slice(0,R));for(;I.size>0;){let N={};N.userId=C,N.reportType="secondary",N.metrics={};for(let[U,W]of I)N.metrics[U]=W.shift(),W.length===0&&I.delete(U);E(N)}}}}return!0}applyChanges(){let e=this.getRegistryPath(),r;try{r=ce.readJsonSync(e)}catch{r={}}let s=this.configuration.get("telemetryUserId")??"*",a=r.blocks=r.blocks??{},n=a[s]=a[s]??{};for(let c of this.hits.keys()){let f=n.hits=n.hits??{},p=f[c]=f[c]??{};for(let[h,E]of this.hits.get(c))p[h]=(p[h]??0)+E}for(let c of["values","enumerators"])for(let f of this[c].keys()){let p=n[c]=n[c]??{};p[f]=[...new Set([...p[f]??[],...this[c].get(f)??[]])]}this.shouldCommitTips&&(r.lastTips=this.nextTips,r.displayedTips=this.displayedTips),ce.mkdirSync(J.dirname(e),{recursive:!0}),ce.writeJsonSync(e,r)}startBuffer(){process.on("exit",()=>{try{this.applyChanges()}catch{}})}}});var jv={};Vt(jv,{BuildDirectiveType:()=>_R,CACHE_CHECKPOINT:()=>OG,CACHE_VERSION:()=>UR,Cache:()=>Kr,Configuration:()=>ze,DEFAULT_RC_FILENAME:()=>dj,DurationUnit:()=>mj,FormatType:()=>upe,InstallMode:()=>$l,LEGACY_PLUGINS:()=>ov,LOCKFILE_VERSION:()=>WR,LegacyMigrationResolver:()=>KI,LightReport:()=>lA,LinkType:()=>VE,LockfileResolver:()=>zI,Manifest:()=>Ut,MessageName:()=>Br,MultiFetcher:()=>aI,PackageExtensionStatus:()=>J4,PackageExtensionType:()=>V4,PeerWarningType:()=>YR,Project:()=>Tt,Report:()=>Ao,ReportError:()=>jt,SettingsType:()=>wI,StreamReport:()=>Ot,TAG_REGEXP:()=>Mp,TelemetryManager:()=>ZI,ThrowReport:()=>ki,VirtualFetcher:()=>lI,WindowsLinkType:()=>IT,Workspace:()=>XI,WorkspaceFetcher:()=>cI,WorkspaceResolver:()=>Ei,YarnVersion:()=>fn,execUtils:()=>qr,folderUtils:()=>OQ,formatUtils:()=>he,hashUtils:()=>Nn,httpUtils:()=>nn,miscUtils:()=>je,nodeUtils:()=>Ui,parseMessageName:()=>jx,reportOptionDeprecations:()=>SI,scriptUtils:()=>In,semverUtils:()=>Fr,stringifyMessageName:()=>Yf,structUtils:()=>G,tgzUtils:()=>ps,treeUtils:()=>xs});var Ge=Xe(()=>{dT();LQ();xc();I0();pT();Pc();gT();zj();Rp();Wo();nde();ude();LG();av();av();pde();MG();hde();UG();oI();Gx();R8();vde();Tc();Ev();Sde();VG();N8();O8();tm();JG();yv();hle()});var Qde=_((WHt,qv)=>{"use strict";var Eat=process.env.TERM_PROGRAM==="Hyper",Iat=process.platform==="win32",Pde=process.platform==="linux",$G={ballotDisabled:"\u2612",ballotOff:"\u2610",ballotOn:"\u2611",bullet:"\u2022",bulletWhite:"\u25E6",fullBlock:"\u2588",heart:"\u2764",identicalTo:"\u2261",line:"\u2500",mark:"\u203B",middot:"\xB7",minus:"\uFF0D",multiplication:"\xD7",obelus:"\xF7",pencilDownRight:"\u270E",pencilRight:"\u270F",pencilUpRight:"\u2710",percent:"%",pilcrow2:"\u2761",pilcrow:"\xB6",plusMinus:"\xB1",section:"\xA7",starsOff:"\u2606",starsOn:"\u2605",upDownArrow:"\u2195"},xde=Object.assign({},$G,{check:"\u221A",cross:"\xD7",ellipsisLarge:"...",ellipsis:"...",info:"i",question:"?",questionSmall:"?",pointer:">",pointerSmall:"\xBB",radioOff:"( )",radioOn:"(*)",warning:"\u203C"}),kde=Object.assign({},$G,{ballotCross:"\u2718",check:"\u2714",cross:"\u2716",ellipsisLarge:"\u22EF",ellipsis:"\u2026",info:"\u2139",question:"?",questionFull:"\uFF1F",questionSmall:"\uFE56",pointer:Pde?"\u25B8":"\u276F",pointerSmall:Pde?"\u2023":"\u203A",radioOff:"\u25EF",radioOn:"\u25C9",warning:"\u26A0"});qv.exports=Iat&&!Eat?xde:kde;Reflect.defineProperty(qv.exports,"common",{enumerable:!1,value:$G});Reflect.defineProperty(qv.exports,"windows",{enumerable:!1,value:xde});Reflect.defineProperty(qv.exports,"other",{enumerable:!1,value:kde})});var Ju=_((YHt,e5)=>{"use strict";var Cat=t=>t!==null&&typeof t=="object"&&!Array.isArray(t),wat=/[\u001b\u009b][[\]#;?()]*(?:(?:(?:[^\W_]*;?[^\W_]*)\u0007)|(?:(?:[0-9]{1,4}(;[0-9]{0,4})*)?[~0-9=<>cf-nqrtyA-PRZ]))/g,Tde=()=>{let t={enabled:!0,visible:!0,styles:{},keys:{}};"FORCE_COLOR"in process.env&&(t.enabled=process.env.FORCE_COLOR!=="0");let e=n=>{let c=n.open=`\x1B[${n.codes[0]}m`,f=n.close=`\x1B[${n.codes[1]}m`,p=n.regex=new RegExp(`\\u001b\\[${n.codes[1]}m`,"g");return n.wrap=(h,E)=>{h.includes(f)&&(h=h.replace(p,f+c));let C=c+h+f;return E?C.replace(/\r*\n/g,`${f}$&${c}`):C},n},r=(n,c,f)=>typeof n=="function"?n(c):n.wrap(c,f),s=(n,c)=>{if(n===""||n==null)return"";if(t.enabled===!1)return n;if(t.visible===!1)return"";let f=""+n,p=f.includes(` `),h=c.length;for(h>0&&c.includes("unstyle")&&(c=[...new Set(["unstyle",...c])].reverse());h-- >0;)f=r(t.styles[c[h]],f,p);return f},a=(n,c,f)=>{t.styles[n]=e({name:n,codes:c}),(t.keys[f]||(t.keys[f]=[])).push(n),Reflect.defineProperty(t,n,{configurable:!0,enumerable:!0,set(h){t.alias(n,h)},get(){let h=E=>s(E,h.stack);return Reflect.setPrototypeOf(h,t),h.stack=this.stack?this.stack.concat(n):[n],h}})};return a("reset",[0,0],"modifier"),a("bold",[1,22],"modifier"),a("dim",[2,22],"modifier"),a("italic",[3,23],"modifier"),a("underline",[4,24],"modifier"),a("inverse",[7,27],"modifier"),a("hidden",[8,28],"modifier"),a("strikethrough",[9,29],"modifier"),a("black",[30,39],"color"),a("red",[31,39],"color"),a("green",[32,39],"color"),a("yellow",[33,39],"color"),a("blue",[34,39],"color"),a("magenta",[35,39],"color"),a("cyan",[36,39],"color"),a("white",[37,39],"color"),a("gray",[90,39],"color"),a("grey",[90,39],"color"),a("bgBlack",[40,49],"bg"),a("bgRed",[41,49],"bg"),a("bgGreen",[42,49],"bg"),a("bgYellow",[43,49],"bg"),a("bgBlue",[44,49],"bg"),a("bgMagenta",[45,49],"bg"),a("bgCyan",[46,49],"bg"),a("bgWhite",[47,49],"bg"),a("blackBright",[90,39],"bright"),a("redBright",[91,39],"bright"),a("greenBright",[92,39],"bright"),a("yellowBright",[93,39],"bright"),a("blueBright",[94,39],"bright"),a("magentaBright",[95,39],"bright"),a("cyanBright",[96,39],"bright"),a("whiteBright",[97,39],"bright"),a("bgBlackBright",[100,49],"bgBright"),a("bgRedBright",[101,49],"bgBright"),a("bgGreenBright",[102,49],"bgBright"),a("bgYellowBright",[103,49],"bgBright"),a("bgBlueBright",[104,49],"bgBright"),a("bgMagentaBright",[105,49],"bgBright"),a("bgCyanBright",[106,49],"bgBright"),a("bgWhiteBright",[107,49],"bgBright"),t.ansiRegex=wat,t.hasColor=t.hasAnsi=n=>(t.ansiRegex.lastIndex=0,typeof n=="string"&&n!==""&&t.ansiRegex.test(n)),t.alias=(n,c)=>{let f=typeof c=="string"?t[c]:c;if(typeof f!="function")throw new TypeError("Expected alias to be the name of an existing color (string) or a function");f.stack||(Reflect.defineProperty(f,"name",{value:n}),t.styles[n]=f,f.stack=[n]),Reflect.defineProperty(t,n,{configurable:!0,enumerable:!0,set(p){t.alias(n,p)},get(){let p=h=>s(h,p.stack);return Reflect.setPrototypeOf(p,t),p.stack=this.stack?this.stack.concat(f.stack):f.stack,p}})},t.theme=n=>{if(!Cat(n))throw new TypeError("Expected theme to be an object");for(let c of Object.keys(n))t.alias(c,n[c]);return t},t.alias("unstyle",n=>typeof n=="string"&&n!==""?(t.ansiRegex.lastIndex=0,n.replace(t.ansiRegex,"")):""),t.alias("noop",n=>n),t.none=t.clear=t.noop,t.stripColor=t.unstyle,t.symbols=Qde(),t.define=a,t};e5.exports=Tde();e5.exports.create=Tde});var Zo=_(pn=>{"use strict";var Bat=Object.prototype.toString,jc=Ju(),Rde=!1,t5=[],Fde={yellow:"blue",cyan:"red",green:"magenta",black:"white",blue:"yellow",red:"cyan",magenta:"green",white:"black"};pn.longest=(t,e)=>t.reduce((r,s)=>Math.max(r,e?s[e].length:s.length),0);pn.hasColor=t=>!!t&&jc.hasColor(t);var JR=pn.isObject=t=>t!==null&&typeof t=="object"&&!Array.isArray(t);pn.nativeType=t=>Bat.call(t).slice(8,-1).toLowerCase().replace(/\s/g,"");pn.isAsyncFn=t=>pn.nativeType(t)==="asyncfunction";pn.isPrimitive=t=>t!=null&&typeof t!="object"&&typeof t!="function";pn.resolve=(t,e,...r)=>typeof e=="function"?e.call(t,...r):e;pn.scrollDown=(t=[])=>[...t.slice(1),t[0]];pn.scrollUp=(t=[])=>[t.pop(),...t];pn.reorder=(t=[])=>{let e=t.slice();return e.sort((r,s)=>r.index>s.index?1:r.index{let s=t.length,a=r===s?0:r<0?s-1:r,n=t[e];t[e]=t[a],t[a]=n};pn.width=(t,e=80)=>{let r=t&&t.columns?t.columns:e;return t&&typeof t.getWindowSize=="function"&&(r=t.getWindowSize()[0]),process.platform==="win32"?r-1:r};pn.height=(t,e=20)=>{let r=t&&t.rows?t.rows:e;return t&&typeof t.getWindowSize=="function"&&(r=t.getWindowSize()[1]),r};pn.wordWrap=(t,e={})=>{if(!t)return t;typeof e=="number"&&(e={width:e});let{indent:r="",newline:s=` `+r,width:a=80}=e,n=(s+r).match(/[^\S\n]/g)||[];a-=n.length;let c=`.{1,${a}}([\\s\\u200B]+|$)|[^\\s\\u200B]+?([\\s\\u200B]+|$)`,f=t.trim(),p=new RegExp(c,"g"),h=f.match(p)||[];return h=h.map(E=>E.replace(/\n$/,"")),e.padEnd&&(h=h.map(E=>E.padEnd(a," "))),e.padStart&&(h=h.map(E=>E.padStart(a," "))),r+h.join(s)};pn.unmute=t=>{let e=t.stack.find(s=>jc.keys.color.includes(s));return e?jc[e]:t.stack.find(s=>s.slice(2)==="bg")?jc[e.slice(2)]:s=>s};pn.pascal=t=>t?t[0].toUpperCase()+t.slice(1):"";pn.inverse=t=>{if(!t||!t.stack)return t;let e=t.stack.find(s=>jc.keys.color.includes(s));if(e){let s=jc["bg"+pn.pascal(e)];return s?s.black:t}let r=t.stack.find(s=>s.slice(0,2)==="bg");return r?jc[r.slice(2).toLowerCase()]||t:jc.none};pn.complement=t=>{if(!t||!t.stack)return t;let e=t.stack.find(s=>jc.keys.color.includes(s)),r=t.stack.find(s=>s.slice(0,2)==="bg");if(e&&!r)return jc[Fde[e]||e];if(r){let s=r.slice(2).toLowerCase(),a=Fde[s];return a&&jc["bg"+pn.pascal(a)]||t}return jc.none};pn.meridiem=t=>{let e=t.getHours(),r=t.getMinutes(),s=e>=12?"pm":"am";e=e%12;let a=e===0?12:e,n=r<10?"0"+r:r;return a+":"+n+" "+s};pn.set=(t={},e="",r)=>e.split(".").reduce((s,a,n,c)=>{let f=c.length-1>n?s[a]||{}:r;return!pn.isObject(f)&&n{let s=t[e]==null?e.split(".").reduce((a,n)=>a&&a[n],t):t[e];return s??r};pn.mixin=(t,e)=>{if(!JR(t))return e;if(!JR(e))return t;for(let r of Object.keys(e)){let s=Object.getOwnPropertyDescriptor(e,r);if(s.hasOwnProperty("value"))if(t.hasOwnProperty(r)&&JR(s.value)){let a=Object.getOwnPropertyDescriptor(t,r);JR(a.value)?t[r]=pn.merge({},t[r],e[r]):Reflect.defineProperty(t,r,s)}else Reflect.defineProperty(t,r,s);else Reflect.defineProperty(t,r,s)}return t};pn.merge=(...t)=>{let e={};for(let r of t)pn.mixin(e,r);return e};pn.mixinEmitter=(t,e)=>{let r=e.constructor.prototype;for(let s of Object.keys(r)){let a=r[s];typeof a=="function"?pn.define(t,s,a.bind(e)):pn.define(t,s,a)}};pn.onExit=t=>{let e=(r,s)=>{Rde||(Rde=!0,t5.forEach(a=>a()),r===!0&&process.exit(128+s))};t5.length===0&&(process.once("SIGTERM",e.bind(null,!0,15)),process.once("SIGINT",e.bind(null,!0,2)),process.once("exit",e)),t5.push(t)};pn.define=(t,e,r)=>{Reflect.defineProperty(t,e,{value:r})};pn.defineExport=(t,e,r)=>{let s;Reflect.defineProperty(t,e,{enumerable:!0,configurable:!0,set(a){s=a},get(){return s?s():r()}})}});var Nde=_(rC=>{"use strict";rC.ctrl={a:"first",b:"backward",c:"cancel",d:"deleteForward",e:"last",f:"forward",g:"reset",i:"tab",k:"cutForward",l:"reset",n:"newItem",m:"cancel",j:"submit",p:"search",r:"remove",s:"save",u:"undo",w:"cutLeft",x:"toggleCursor",v:"paste"};rC.shift={up:"shiftUp",down:"shiftDown",left:"shiftLeft",right:"shiftRight",tab:"prev"};rC.fn={up:"pageUp",down:"pageDown",left:"pageLeft",right:"pageRight",delete:"deleteForward"};rC.option={b:"backward",f:"forward",d:"cutRight",left:"cutLeft",up:"altUp",down:"altDown"};rC.keys={pageup:"pageUp",pagedown:"pageDown",home:"home",end:"end",cancel:"cancel",delete:"deleteForward",backspace:"delete",down:"down",enter:"submit",escape:"cancel",left:"left",space:"space",number:"number",return:"submit",right:"right",tab:"next",up:"up"}});var Mde=_((KHt,Lde)=>{"use strict";var Ode=Ie("readline"),vat=Nde(),Sat=/^(?:\x1b)([a-zA-Z0-9])$/,Dat=/^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/,bat={OP:"f1",OQ:"f2",OR:"f3",OS:"f4","[11~":"f1","[12~":"f2","[13~":"f3","[14~":"f4","[[A":"f1","[[B":"f2","[[C":"f3","[[D":"f4","[[E":"f5","[15~":"f5","[17~":"f6","[18~":"f7","[19~":"f8","[20~":"f9","[21~":"f10","[23~":"f11","[24~":"f12","[A":"up","[B":"down","[C":"right","[D":"left","[E":"clear","[F":"end","[H":"home",OA:"up",OB:"down",OC:"right",OD:"left",OE:"clear",OF:"end",OH:"home","[1~":"home","[2~":"insert","[3~":"delete","[4~":"end","[5~":"pageup","[6~":"pagedown","[[5~":"pageup","[[6~":"pagedown","[7~":"home","[8~":"end","[a":"up","[b":"down","[c":"right","[d":"left","[e":"clear","[2$":"insert","[3$":"delete","[5$":"pageup","[6$":"pagedown","[7$":"home","[8$":"end",Oa:"up",Ob:"down",Oc:"right",Od:"left",Oe:"clear","[2^":"insert","[3^":"delete","[5^":"pageup","[6^":"pagedown","[7^":"home","[8^":"end","[Z":"tab"};function Pat(t){return["[a","[b","[c","[d","[e","[2$","[3$","[5$","[6$","[7$","[8$","[Z"].includes(t)}function xat(t){return["Oa","Ob","Oc","Od","Oe","[2^","[3^","[5^","[6^","[7^","[8^"].includes(t)}var KR=(t="",e={})=>{let r,s={name:e.name,ctrl:!1,meta:!1,shift:!1,option:!1,sequence:t,raw:t,...e};if(Buffer.isBuffer(t)?t[0]>127&&t[1]===void 0?(t[0]-=128,t="\x1B"+String(t)):t=String(t):t!==void 0&&typeof t!="string"?t=String(t):t||(t=s.sequence||""),s.sequence=s.sequence||t||s.name,t==="\r")s.raw=void 0,s.name="return";else if(t===` `)s.name="enter";else if(t===" ")s.name="tab";else if(t==="\b"||t==="\x7F"||t==="\x1B\x7F"||t==="\x1B\b")s.name="backspace",s.meta=t.charAt(0)==="\x1B";else if(t==="\x1B"||t==="\x1B\x1B")s.name="escape",s.meta=t.length===2;else if(t===" "||t==="\x1B ")s.name="space",s.meta=t.length===2;else if(t<="")s.name=String.fromCharCode(t.charCodeAt(0)+97-1),s.ctrl=!0;else if(t.length===1&&t>="0"&&t<="9")s.name="number";else if(t.length===1&&t>="a"&&t<="z")s.name=t;else if(t.length===1&&t>="A"&&t<="Z")s.name=t.toLowerCase(),s.shift=!0;else if(r=Sat.exec(t))s.meta=!0,s.shift=/^[A-Z]$/.test(r[1]);else if(r=Dat.exec(t)){let a=[...t];a[0]==="\x1B"&&a[1]==="\x1B"&&(s.option=!0);let n=[r[1],r[2],r[4],r[6]].filter(Boolean).join(""),c=(r[3]||r[5]||1)-1;s.ctrl=!!(c&4),s.meta=!!(c&10),s.shift=!!(c&1),s.code=n,s.name=bat[n],s.shift=Pat(n)||s.shift,s.ctrl=xat(n)||s.ctrl}return s};KR.listen=(t={},e)=>{let{stdin:r}=t;if(!r||r!==process.stdin&&!r.isTTY)throw new Error("Invalid stream passed");let s=Ode.createInterface({terminal:!0,input:r});Ode.emitKeypressEvents(r,s);let a=(f,p)=>e(f,KR(f,p),s),n=r.isRaw;return r.isTTY&&r.setRawMode(!0),r.on("keypress",a),s.resume(),()=>{r.isTTY&&r.setRawMode(n),r.removeListener("keypress",a),s.pause(),s.close()}};KR.action=(t,e,r)=>{let s={...vat,...r};return e.ctrl?(e.action=s.ctrl[e.name],e):e.option&&s.option?(e.action=s.option[e.name],e):e.shift?(e.action=s.shift[e.name],e):(e.action=s.keys[e.name],e)};Lde.exports=KR});var _de=_((zHt,Ude)=>{"use strict";Ude.exports=t=>{t.timers=t.timers||{};let e=t.options.timers;if(e)for(let r of Object.keys(e)){let s=e[r];typeof s=="number"&&(s={interval:s}),kat(t,r,s)}};function kat(t,e,r={}){let s=t.timers[e]={name:e,start:Date.now(),ms:0,tick:0},a=r.interval||120;s.frames=r.frames||[],s.loading=!0;let n=setInterval(()=>{s.ms=Date.now()-s.start,s.tick++,t.render()},a);return s.stop=()=>{s.loading=!1,clearInterval(n)},Reflect.defineProperty(s,"interval",{value:n}),t.once("close",()=>s.stop()),s.stop}});var jde=_((XHt,Hde)=>{"use strict";var{define:Qat,width:Tat}=Zo(),r5=class{constructor(e){let r=e.options;Qat(this,"_prompt",e),this.type=e.type,this.name=e.name,this.message="",this.header="",this.footer="",this.error="",this.hint="",this.input="",this.cursor=0,this.index=0,this.lines=0,this.tick=0,this.prompt="",this.buffer="",this.width=Tat(r.stdout||process.stdout),Object.assign(this,r),this.name=this.name||this.message,this.message=this.message||this.name,this.symbols=e.symbols,this.styles=e.styles,this.required=new Set,this.cancelled=!1,this.submitted=!1}clone(){let e={...this};return e.status=this.status,e.buffer=Buffer.from(e.buffer),delete e.clone,e}set color(e){this._color=e}get color(){let e=this.prompt.styles;if(this.cancelled)return e.cancelled;if(this.submitted)return e.submitted;let r=this._color||e[this.status];return typeof r=="function"?r:e.pending}set loading(e){this._loading=e}get loading(){return typeof this._loading=="boolean"?this._loading:this.loadingChoices?"choices":!1}get status(){return this.cancelled?"cancelled":this.submitted?"submitted":"pending"}};Hde.exports=r5});var qde=_((ZHt,Gde)=>{"use strict";var n5=Zo(),ho=Ju(),i5={default:ho.noop,noop:ho.noop,set inverse(t){this._inverse=t},get inverse(){return this._inverse||n5.inverse(this.primary)},set complement(t){this._complement=t},get complement(){return this._complement||n5.complement(this.primary)},primary:ho.cyan,success:ho.green,danger:ho.magenta,strong:ho.bold,warning:ho.yellow,muted:ho.dim,disabled:ho.gray,dark:ho.dim.gray,underline:ho.underline,set info(t){this._info=t},get info(){return this._info||this.primary},set em(t){this._em=t},get em(){return this._em||this.primary.underline},set heading(t){this._heading=t},get heading(){return this._heading||this.muted.underline},set pending(t){this._pending=t},get pending(){return this._pending||this.primary},set submitted(t){this._submitted=t},get submitted(){return this._submitted||this.success},set cancelled(t){this._cancelled=t},get cancelled(){return this._cancelled||this.danger},set typing(t){this._typing=t},get typing(){return this._typing||this.dim},set placeholder(t){this._placeholder=t},get placeholder(){return this._placeholder||this.primary.dim},set highlight(t){this._highlight=t},get highlight(){return this._highlight||this.inverse}};i5.merge=(t={})=>{t.styles&&typeof t.styles.enabled=="boolean"&&(ho.enabled=t.styles.enabled),t.styles&&typeof t.styles.visible=="boolean"&&(ho.visible=t.styles.visible);let e=n5.merge({},i5,t.styles);delete e.merge;for(let r of Object.keys(ho))e.hasOwnProperty(r)||Reflect.defineProperty(e,r,{get:()=>ho[r]});for(let r of Object.keys(ho.styles))e.hasOwnProperty(r)||Reflect.defineProperty(e,r,{get:()=>ho[r]});return e};Gde.exports=i5});var Yde=_(($Ht,Wde)=>{"use strict";var s5=process.platform==="win32",zp=Ju(),Rat=Zo(),o5={...zp.symbols,upDownDoubleArrow:"\u21D5",upDownDoubleArrow2:"\u2B0D",upDownArrow:"\u2195",asterisk:"*",asterism:"\u2042",bulletWhite:"\u25E6",electricArrow:"\u2301",ellipsisLarge:"\u22EF",ellipsisSmall:"\u2026",fullBlock:"\u2588",identicalTo:"\u2261",indicator:zp.symbols.check,leftAngle:"\u2039",mark:"\u203B",minus:"\u2212",multiplication:"\xD7",obelus:"\xF7",percent:"%",pilcrow:"\xB6",pilcrow2:"\u2761",pencilUpRight:"\u2710",pencilDownRight:"\u270E",pencilRight:"\u270F",plus:"+",plusMinus:"\xB1",pointRight:"\u261E",rightAngle:"\u203A",section:"\xA7",hexagon:{off:"\u2B21",on:"\u2B22",disabled:"\u2B22"},ballot:{on:"\u2611",off:"\u2610",disabled:"\u2612"},stars:{on:"\u2605",off:"\u2606",disabled:"\u2606"},folder:{on:"\u25BC",off:"\u25B6",disabled:"\u25B6"},prefix:{pending:zp.symbols.question,submitted:zp.symbols.check,cancelled:zp.symbols.cross},separator:{pending:zp.symbols.pointerSmall,submitted:zp.symbols.middot,cancelled:zp.symbols.middot},radio:{off:s5?"( )":"\u25EF",on:s5?"(*)":"\u25C9",disabled:s5?"(|)":"\u24BE"},numbers:["\u24EA","\u2460","\u2461","\u2462","\u2463","\u2464","\u2465","\u2466","\u2467","\u2468","\u2469","\u246A","\u246B","\u246C","\u246D","\u246E","\u246F","\u2470","\u2471","\u2472","\u2473","\u3251","\u3252","\u3253","\u3254","\u3255","\u3256","\u3257","\u3258","\u3259","\u325A","\u325B","\u325C","\u325D","\u325E","\u325F","\u32B1","\u32B2","\u32B3","\u32B4","\u32B5","\u32B6","\u32B7","\u32B8","\u32B9","\u32BA","\u32BB","\u32BC","\u32BD","\u32BE","\u32BF"]};o5.merge=t=>{let e=Rat.merge({},zp.symbols,o5,t.symbols);return delete e.merge,e};Wde.exports=o5});var Jde=_((ejt,Vde)=>{"use strict";var Fat=qde(),Nat=Yde(),Oat=Zo();Vde.exports=t=>{t.options=Oat.merge({},t.options.theme,t.options),t.symbols=Nat.merge(t.options),t.styles=Fat.merge(t.options)}});var $de=_((Xde,Zde)=>{"use strict";var Kde=process.env.TERM_PROGRAM==="Apple_Terminal",Lat=Ju(),a5=Zo(),Ku=Zde.exports=Xde,_i="\x1B[",zde="\x07",l5=!1,j0=Ku.code={bell:zde,beep:zde,beginning:`${_i}G`,down:`${_i}J`,esc:_i,getPosition:`${_i}6n`,hide:`${_i}?25l`,line:`${_i}2K`,lineEnd:`${_i}K`,lineStart:`${_i}1K`,restorePosition:_i+(Kde?"8":"u"),savePosition:_i+(Kde?"7":"s"),screen:`${_i}2J`,show:`${_i}?25h`,up:`${_i}1J`},wm=Ku.cursor={get hidden(){return l5},hide(){return l5=!0,j0.hide},show(){return l5=!1,j0.show},forward:(t=1)=>`${_i}${t}C`,backward:(t=1)=>`${_i}${t}D`,nextLine:(t=1)=>`${_i}E`.repeat(t),prevLine:(t=1)=>`${_i}F`.repeat(t),up:(t=1)=>t?`${_i}${t}A`:"",down:(t=1)=>t?`${_i}${t}B`:"",right:(t=1)=>t?`${_i}${t}C`:"",left:(t=1)=>t?`${_i}${t}D`:"",to(t,e){return e?`${_i}${e+1};${t+1}H`:`${_i}${t+1}G`},move(t=0,e=0){let r="";return r+=t<0?wm.left(-t):t>0?wm.right(t):"",r+=e<0?wm.up(-e):e>0?wm.down(e):"",r},restore(t={}){let{after:e,cursor:r,initial:s,input:a,prompt:n,size:c,value:f}=t;if(s=a5.isPrimitive(s)?String(s):"",a=a5.isPrimitive(a)?String(a):"",f=a5.isPrimitive(f)?String(f):"",c){let p=Ku.cursor.up(c)+Ku.cursor.to(n.length),h=a.length-r;return h>0&&(p+=Ku.cursor.left(h)),p}if(f||e){let p=!a&&s?-s.length:-a.length+r;return e&&(p-=e.length),a===""&&s&&!n.includes(s)&&(p+=s.length),Ku.cursor.move(p)}}},c5=Ku.erase={screen:j0.screen,up:j0.up,down:j0.down,line:j0.line,lineEnd:j0.lineEnd,lineStart:j0.lineStart,lines(t){let e="";for(let r=0;r{if(!e)return c5.line+wm.to(0);let r=n=>[...Lat.unstyle(n)].length,s=t.split(/\r?\n/),a=0;for(let n of s)a+=1+Math.floor(Math.max(r(n)-1,0)/e);return(c5.line+wm.prevLine()).repeat(a-1)+c5.line+wm.to(0)}});var nC=_((tjt,tme)=>{"use strict";var Mat=Ie("events"),eme=Ju(),u5=Mde(),Uat=_de(),_at=jde(),Hat=Jde(),pl=Zo(),Bm=$de(),f5=class t extends Mat{constructor(e={}){super(),this.name=e.name,this.type=e.type,this.options=e,Hat(this),Uat(this),this.state=new _at(this),this.initial=[e.initial,e.default].find(r=>r!=null),this.stdout=e.stdout||process.stdout,this.stdin=e.stdin||process.stdin,this.scale=e.scale||1,this.term=this.options.term||process.env.TERM_PROGRAM,this.margin=Gat(this.options.margin),this.setMaxListeners(0),jat(this)}async keypress(e,r={}){this.keypressed=!0;let s=u5.action(e,u5(e,r),this.options.actions);this.state.keypress=s,this.emit("keypress",e,s),this.emit("state",this.state.clone());let a=this.options[s.action]||this[s.action]||this.dispatch;if(typeof a=="function")return await a.call(this,e,s);this.alert()}alert(){delete this.state.alert,this.options.show===!1?this.emit("alert"):this.stdout.write(Bm.code.beep)}cursorHide(){this.stdout.write(Bm.cursor.hide()),pl.onExit(()=>this.cursorShow())}cursorShow(){this.stdout.write(Bm.cursor.show())}write(e){e&&(this.stdout&&this.state.show!==!1&&this.stdout.write(e),this.state.buffer+=e)}clear(e=0){let r=this.state.buffer;this.state.buffer="",!(!r&&!e||this.options.show===!1)&&this.stdout.write(Bm.cursor.down(e)+Bm.clear(r,this.width))}restore(){if(this.state.closed||this.options.show===!1)return;let{prompt:e,after:r,rest:s}=this.sections(),{cursor:a,initial:n="",input:c="",value:f=""}=this,p=this.state.size=s.length,h={after:r,cursor:a,initial:n,input:c,prompt:e,size:p,value:f},E=Bm.cursor.restore(h);E&&this.stdout.write(E)}sections(){let{buffer:e,input:r,prompt:s}=this.state;s=eme.unstyle(s);let a=eme.unstyle(e),n=a.indexOf(s),c=a.slice(0,n),p=a.slice(n).split(` `),h=p[0],E=p[p.length-1],S=(s+(r?" "+r:"")).length,P=Se.call(this,this.value),this.result=()=>s.call(this,this.value),typeof r.initial=="function"&&(this.initial=await r.initial.call(this,this)),typeof r.onRun=="function"&&await r.onRun.call(this,this),typeof r.onSubmit=="function"){let a=r.onSubmit.bind(this),n=this.submit.bind(this);delete this.options.onSubmit,this.submit=async()=>(await a(this.name,this.value,this),n())}await this.start(),await this.render()}render(){throw new Error("expected prompt to have a custom render method")}run(){return new Promise(async(e,r)=>{if(this.once("submit",e),this.once("cancel",r),await this.skip())return this.render=()=>{},this.submit();await this.initialize(),this.emit("run")})}async element(e,r,s){let{options:a,state:n,symbols:c,timers:f}=this,p=f&&f[e];n.timer=p;let h=a[e]||n[e]||c[e],E=r&&r[e]!=null?r[e]:await h;if(E==="")return E;let C=await this.resolve(E,n,r,s);return!C&&r&&r[e]?this.resolve(h,n,r,s):C}async prefix(){let e=await this.element("prefix")||this.symbols,r=this.timers&&this.timers.prefix,s=this.state;return s.timer=r,pl.isObject(e)&&(e=e[s.status]||e.pending),pl.hasColor(e)?e:(this.styles[s.status]||this.styles.pending)(e)}async message(){let e=await this.element("message");return pl.hasColor(e)?e:this.styles.strong(e)}async separator(){let e=await this.element("separator")||this.symbols,r=this.timers&&this.timers.separator,s=this.state;s.timer=r;let a=e[s.status]||e.pending||s.separator,n=await this.resolve(a,s);return pl.isObject(n)&&(n=n[s.status]||n.pending),pl.hasColor(n)?n:this.styles.muted(n)}async pointer(e,r){let s=await this.element("pointer",e,r);if(typeof s=="string"&&pl.hasColor(s))return s;if(s){let a=this.styles,n=this.index===r,c=n?a.primary:h=>h,f=await this.resolve(s[n?"on":"off"]||s,this.state),p=pl.hasColor(f)?f:c(f);return n?p:" ".repeat(f.length)}}async indicator(e,r){let s=await this.element("indicator",e,r);if(typeof s=="string"&&pl.hasColor(s))return s;if(s){let a=this.styles,n=e.enabled===!0,c=n?a.success:a.dark,f=s[n?"on":"off"]||s;return pl.hasColor(f)?f:c(f)}return""}body(){return null}footer(){if(this.state.status==="pending")return this.element("footer")}header(){if(this.state.status==="pending")return this.element("header")}async hint(){if(this.state.status==="pending"&&!this.isValue(this.state.input)){let e=await this.element("hint");return pl.hasColor(e)?e:this.styles.muted(e)}}error(e){return this.state.submitted?"":e||this.state.error}format(e){return e}result(e){return e}validate(e){return this.options.required===!0?this.isValue(e):!0}isValue(e){return e!=null&&e!==""}resolve(e,...r){return pl.resolve(this,e,...r)}get base(){return t.prototype}get style(){return this.styles[this.state.status]}get height(){return this.options.rows||pl.height(this.stdout,25)}get width(){return this.options.columns||pl.width(this.stdout,80)}get size(){return{width:this.width,height:this.height}}set cursor(e){this.state.cursor=e}get cursor(){return this.state.cursor}set input(e){this.state.input=e}get input(){return this.state.input}set value(e){this.state.value=e}get value(){let{input:e,value:r}=this.state,s=[r,e].find(this.isValue.bind(this));return this.isValue(s)?s:this.initial}static get prompt(){return e=>new this(e).run()}};function jat(t){let e=a=>t[a]===void 0||typeof t[a]=="function",r=["actions","choices","initial","margin","roles","styles","symbols","theme","timers","value"],s=["body","footer","error","header","hint","indicator","message","prefix","separator","skip"];for(let a of Object.keys(t.options)){if(r.includes(a)||/^on[A-Z]/.test(a))continue;let n=t.options[a];typeof n=="function"&&e(a)?s.includes(a)||(t[a]=n.bind(t)):typeof t[a]!="function"&&(t[a]=n)}}function Gat(t){typeof t=="number"&&(t=[t,t,t,t]);let e=[].concat(t||[]),r=a=>a%2===0?` `:" ",s=[];for(let a=0;a<4;a++){let n=r(a);e[a]?s.push(n.repeat(e[a])):s.push("")}return s}tme.exports=f5});var ime=_((rjt,nme)=>{"use strict";var qat=Zo(),rme={default(t,e){return e},checkbox(t,e){throw new Error("checkbox role is not implemented yet")},editable(t,e){throw new Error("editable role is not implemented yet")},expandable(t,e){throw new Error("expandable role is not implemented yet")},heading(t,e){return e.disabled="",e.indicator=[e.indicator," "].find(r=>r!=null),e.message=e.message||"",e},input(t,e){throw new Error("input role is not implemented yet")},option(t,e){return rme.default(t,e)},radio(t,e){throw new Error("radio role is not implemented yet")},separator(t,e){return e.disabled="",e.indicator=[e.indicator," "].find(r=>r!=null),e.message=e.message||t.symbols.line.repeat(5),e},spacer(t,e){return e}};nme.exports=(t,e={})=>{let r=qat.merge({},rme,e.roles);return r[t]||r.default}});var Wv=_((njt,ame)=>{"use strict";var Wat=Ju(),Yat=nC(),Vat=ime(),zR=Zo(),{reorder:A5,scrollUp:Jat,scrollDown:Kat,isObject:sme,swap:zat}=zR,p5=class extends Yat{constructor(e){super(e),this.cursorHide(),this.maxSelected=e.maxSelected||1/0,this.multiple=e.multiple||!1,this.initial=e.initial||0,this.delay=e.delay||0,this.longest=0,this.num=""}async initialize(){typeof this.options.initial=="function"&&(this.initial=await this.options.initial.call(this)),await this.reset(!0),await super.initialize()}async reset(){let{choices:e,initial:r,autofocus:s,suggest:a}=this.options;if(this.state._choices=[],this.state.choices=[],this.choices=await Promise.all(await this.toChoices(e)),this.choices.forEach(n=>n.enabled=!1),typeof a!="function"&&this.selectable.length===0)throw new Error("At least one choice must be selectable");sme(r)&&(r=Object.keys(r)),Array.isArray(r)?(s!=null&&(this.index=this.findIndex(s)),r.forEach(n=>this.enable(this.find(n))),await this.render()):(s!=null&&(r=s),typeof r=="string"&&(r=this.findIndex(r)),typeof r=="number"&&r>-1&&(this.index=Math.max(0,Math.min(r,this.choices.length)),this.enable(this.find(this.index)))),this.isDisabled(this.focused)&&await this.down()}async toChoices(e,r){this.state.loadingChoices=!0;let s=[],a=0,n=async(c,f)=>{typeof c=="function"&&(c=await c.call(this)),c instanceof Promise&&(c=await c);for(let p=0;p(this.state.loadingChoices=!1,c))}async toChoice(e,r,s){if(typeof e=="function"&&(e=await e.call(this,this)),e instanceof Promise&&(e=await e),typeof e=="string"&&(e={name:e}),e.normalized)return e;e.normalized=!0;let a=e.value;if(e=Vat(e.role,this.options)(this,e),typeof e.disabled=="string"&&!e.hint&&(e.hint=e.disabled,e.disabled=!0),e.disabled===!0&&e.hint==null&&(e.hint="(disabled)"),e.index!=null)return e;e.name=e.name||e.key||e.title||e.value||e.message,e.message=e.message||e.name||"",e.value=[e.value,e.name].find(this.isValue.bind(this)),e.input="",e.index=r,e.cursor=0,zR.define(e,"parent",s),e.level=s?s.level+1:1,e.indent==null&&(e.indent=s?s.indent+" ":e.indent||""),e.path=s?s.path+"."+e.name:e.name,e.enabled=!!(this.multiple&&!this.isDisabled(e)&&(e.enabled||this.isSelected(e))),this.isDisabled(e)||(this.longest=Math.max(this.longest,Wat.unstyle(e.message).length));let c={...e};return e.reset=(f=c.input,p=c.value)=>{for(let h of Object.keys(c))e[h]=c[h];e.input=f,e.value=p},a==null&&typeof e.initial=="function"&&(e.input=await e.initial.call(this,this.state,e,r)),e}async onChoice(e,r){this.emit("choice",e,r,this),typeof e.onChoice=="function"&&await e.onChoice.call(this,this.state,e,r)}async addChoice(e,r,s){let a=await this.toChoice(e,r,s);return this.choices.push(a),this.index=this.choices.length-1,this.limit=this.choices.length,a}async newItem(e,r,s){let a={name:"New choice name?",editable:!0,newChoice:!0,...e},n=await this.addChoice(a,r,s);return n.updateChoice=()=>{delete n.newChoice,n.name=n.message=n.input,n.input="",n.cursor=0},this.render()}indent(e){return e.indent==null?e.level>1?" ".repeat(e.level-1):"":e.indent}dispatch(e,r){if(this.multiple&&this[r.name])return this[r.name]();this.alert()}focus(e,r){return typeof r!="boolean"&&(r=e.enabled),r&&!e.enabled&&this.selected.length>=this.maxSelected?this.alert():(this.index=e.index,e.enabled=r&&!this.isDisabled(e),e)}space(){return this.multiple?(this.toggle(this.focused),this.render()):this.alert()}a(){if(this.maxSelectedr.enabled);return this.choices.forEach(r=>r.enabled=!e),this.render()}i(){return this.choices.length-this.selected.length>this.maxSelected?this.alert():(this.choices.forEach(e=>e.enabled=!e.enabled),this.render())}g(e=this.focused){return this.choices.some(r=>!!r.parent)?(this.toggle(e.parent&&!e.choices?e.parent:e),this.render()):this.a()}toggle(e,r){if(!e.enabled&&this.selected.length>=this.maxSelected)return this.alert();typeof r!="boolean"&&(r=!e.enabled),e.enabled=r,e.choices&&e.choices.forEach(a=>this.toggle(a,r));let s=e.parent;for(;s;){let a=s.choices.filter(n=>this.isDisabled(n));s.enabled=a.every(n=>n.enabled===!0),s=s.parent}return ome(this,this.choices),this.emit("toggle",e,this),e}enable(e){return this.selected.length>=this.maxSelected?this.alert():(e.enabled=!this.isDisabled(e),e.choices&&e.choices.forEach(this.enable.bind(this)),e)}disable(e){return e.enabled=!1,e.choices&&e.choices.forEach(this.disable.bind(this)),e}number(e){this.num+=e;let r=s=>{let a=Number(s);if(a>this.choices.length-1)return this.alert();let n=this.focused,c=this.choices.find(f=>a===f.index);if(!c.enabled&&this.selected.length>=this.maxSelected)return this.alert();if(this.visible.indexOf(c)===-1){let f=A5(this.choices),p=f.indexOf(c);if(n.index>p){let h=f.slice(p,p+this.limit),E=f.filter(C=>!h.includes(C));this.choices=h.concat(E)}else{let h=p-this.limit+1;this.choices=f.slice(h).concat(f.slice(0,h))}}return this.index=this.choices.indexOf(c),this.toggle(this.focused),this.render()};return clearTimeout(this.numberTimeout),new Promise(s=>{let a=this.choices.length,n=this.num,c=(f=!1,p)=>{clearTimeout(this.numberTimeout),f&&(p=r(n)),this.num="",s(p)};if(n==="0"||n.length===1&&+(n+"0")>a)return c(!0);if(Number(n)>a)return c(!1,this.alert());this.numberTimeout=setTimeout(()=>c(!0),this.delay)})}home(){return this.choices=A5(this.choices),this.index=0,this.render()}end(){let e=this.choices.length-this.limit,r=A5(this.choices);return this.choices=r.slice(e).concat(r.slice(0,e)),this.index=this.limit-1,this.render()}first(){return this.index=0,this.render()}last(){return this.index=this.visible.length-1,this.render()}prev(){return this.visible.length<=1?this.alert():this.up()}next(){return this.visible.length<=1?this.alert():this.down()}right(){return this.cursor>=this.input.length?this.alert():(this.cursor++,this.render())}left(){return this.cursor<=0?this.alert():(this.cursor--,this.render())}up(){let e=this.choices.length,r=this.visible.length,s=this.index;return this.options.scroll===!1&&s===0?this.alert():e>r&&s===0?this.scrollUp():(this.index=(s-1%e+e)%e,this.isDisabled()?this.up():this.render())}down(){let e=this.choices.length,r=this.visible.length,s=this.index;return this.options.scroll===!1&&s===r-1?this.alert():e>r&&s===r-1?this.scrollDown():(this.index=(s+1)%e,this.isDisabled()?this.down():this.render())}scrollUp(e=0){return this.choices=Jat(this.choices),this.index=e,this.isDisabled()?this.up():this.render()}scrollDown(e=this.visible.length-1){return this.choices=Kat(this.choices),this.index=e,this.isDisabled()?this.down():this.render()}async shiftUp(){if(this.options.sort===!0){this.sorting=!0,this.swap(this.index-1),await this.up(),this.sorting=!1;return}return this.scrollUp(this.index)}async shiftDown(){if(this.options.sort===!0){this.sorting=!0,this.swap(this.index+1),await this.down(),this.sorting=!1;return}return this.scrollDown(this.index)}pageUp(){return this.visible.length<=1?this.alert():(this.limit=Math.max(this.limit-1,0),this.index=Math.min(this.limit-1,this.index),this._limit=this.limit,this.isDisabled()?this.up():this.render())}pageDown(){return this.visible.length>=this.choices.length?this.alert():(this.index=Math.max(0,this.index),this.limit=Math.min(this.limit+1,this.choices.length),this._limit=this.limit,this.isDisabled()?this.down():this.render())}swap(e){zat(this.choices,this.index,e)}isDisabled(e=this.focused){return e&&["disabled","collapsed","hidden","completing","readonly"].some(s=>e[s]===!0)?!0:e&&e.role==="heading"}isEnabled(e=this.focused){if(Array.isArray(e))return e.every(r=>this.isEnabled(r));if(e.choices){let r=e.choices.filter(s=>!this.isDisabled(s));return e.enabled&&r.every(s=>this.isEnabled(s))}return e.enabled&&!this.isDisabled(e)}isChoice(e,r){return e.name===r||e.index===Number(r)}isSelected(e){return Array.isArray(this.initial)?this.initial.some(r=>this.isChoice(e,r)):this.isChoice(e,this.initial)}map(e=[],r="value"){return[].concat(e||[]).reduce((s,a)=>(s[a]=this.find(a,r),s),{})}filter(e,r){let a=typeof e=="function"?e:(f,p)=>[f.name,p].includes(e),c=(this.options.multiple?this.state._choices:this.choices).filter(a);return r?c.map(f=>f[r]):c}find(e,r){if(sme(e))return r?e[r]:e;let a=typeof e=="function"?e:(c,f)=>[c.name,f].includes(e),n=this.choices.find(a);if(n)return r?n[r]:n}findIndex(e){return this.choices.indexOf(this.find(e))}async submit(){let e=this.focused;if(!e)return this.alert();if(e.newChoice)return e.input?(e.updateChoice(),this.render()):this.alert();if(this.choices.some(c=>c.newChoice))return this.alert();let{reorder:r,sort:s}=this.options,a=this.multiple===!0,n=this.selected;return n===void 0?this.alert():(Array.isArray(n)&&r!==!1&&s!==!0&&(n=zR.reorder(n)),this.value=a?n.map(c=>c.name):n.name,super.submit())}set choices(e=[]){this.state._choices=this.state._choices||[],this.state.choices=e;for(let r of e)this.state._choices.some(s=>s.name===r.name)||this.state._choices.push(r);if(!this._initial&&this.options.initial){this._initial=!0;let r=this.initial;if(typeof r=="string"||typeof r=="number"){let s=this.find(r);s&&(this.initial=s.index,this.focus(s,!0))}}}get choices(){return ome(this,this.state.choices||[])}set visible(e){this.state.visible=e}get visible(){return(this.state.visible||this.choices).slice(0,this.limit)}set limit(e){this.state.limit=e}get limit(){let{state:e,options:r,choices:s}=this,a=e.limit||this._limit||r.limit||s.length;return Math.min(a,this.height)}set value(e){super.value=e}get value(){return typeof super.value!="string"&&super.value===this.initial?this.input:super.value}set index(e){this.state.index=e}get index(){return Math.max(0,this.state?this.state.index:0)}get enabled(){return this.filter(this.isEnabled.bind(this))}get focused(){let e=this.choices[this.index];return e&&this.state.submitted&&this.multiple!==!0&&(e.enabled=!0),e}get selectable(){return this.choices.filter(e=>!this.isDisabled(e))}get selected(){return this.multiple?this.enabled:this.focused}};function ome(t,e){if(e instanceof Promise)return e;if(typeof e=="function"){if(zR.isAsyncFn(e))return e;e=e.call(t,t)}for(let r of e){if(Array.isArray(r.choices)){let s=r.choices.filter(a=>!t.isDisabled(a));r.enabled=s.every(a=>a.enabled===!0)}t.isDisabled(r)===!0&&delete r.enabled}return e}ame.exports=p5});var G0=_((ijt,lme)=>{"use strict";var Xat=Wv(),h5=Zo(),g5=class extends Xat{constructor(e){super(e),this.emptyError=this.options.emptyError||"No items were selected"}async dispatch(e,r){if(this.multiple)return this[r.name]?await this[r.name](e,r):await super.dispatch(e,r);this.alert()}separator(){if(this.options.separator)return super.separator();let e=this.styles.muted(this.symbols.ellipsis);return this.state.submitted?super.separator():e}pointer(e,r){return!this.multiple||this.options.pointer?super.pointer(e,r):""}indicator(e,r){return this.multiple?super.indicator(e,r):""}choiceMessage(e,r){let s=this.resolve(e.message,this.state,e,r);return e.role==="heading"&&!h5.hasColor(s)&&(s=this.styles.strong(s)),this.resolve(s,this.state,e,r)}choiceSeparator(){return":"}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=await this.pointer(e,r),n=await this.indicator(e,r)+(e.pad||""),c=await this.resolve(e.hint,this.state,e,r);c&&!h5.hasColor(c)&&(c=this.styles.muted(c));let f=this.indent(e),p=await this.choiceMessage(e,r),h=()=>[this.margin[3],f+a+n,p,this.margin[1],c].filter(Boolean).join(" ");return e.role==="heading"?h():e.disabled?(h5.hasColor(p)||(p=this.styles.disabled(p)),h()):(s&&(p=this.styles.em(p)),h())}async renderChoices(){if(this.state.loading==="choices")return this.styles.warning("Loading choices");if(this.state.submitted)return"";let e=this.visible.map(async(n,c)=>await this.renderChoice(n,c)),r=await Promise.all(e);r.length||r.push(this.styles.danger("No matching choices"));let s=this.margin[0]+r.join(` `),a;return this.options.choicesHeader&&(a=await this.resolve(this.options.choicesHeader,this.state)),[a,s].filter(Boolean).join(` `)}format(){return!this.state.submitted||this.state.cancelled?"":Array.isArray(this.selected)?this.selected.map(e=>this.styles.primary(e.name)).join(", "):this.styles.primary(this.selected.name)}async render(){let{submitted:e,size:r}=this.state,s="",a=await this.header(),n=await this.prefix(),c=await this.separator(),f=await this.message();this.options.promptLine!==!1&&(s=[n,f,c,""].join(" "),this.state.prompt=s);let p=await this.format(),h=await this.error()||await this.hint(),E=await this.renderChoices(),C=await this.footer();p&&(s+=p),h&&!s.includes(h)&&(s+=" "+h),e&&!p&&!E.trim()&&this.multiple&&this.emptyError!=null&&(s+=this.styles.danger(this.emptyError)),this.clear(r),this.write([a,s,E,C].filter(Boolean).join(` `)),this.write(this.margin[2]),this.restore()}};lme.exports=g5});var ume=_((sjt,cme)=>{"use strict";var Zat=G0(),$at=(t,e)=>{let r=t.toLowerCase();return s=>{let n=s.toLowerCase().indexOf(r),c=e(s.slice(n,n+r.length));return n>=0?s.slice(0,n)+c+s.slice(n+r.length):s}},d5=class extends Zat{constructor(e){super(e),this.cursorShow()}moveCursor(e){this.state.cursor+=e}dispatch(e){return this.append(e)}space(e){return this.options.multiple?super.space(e):this.append(e)}append(e){let{cursor:r,input:s}=this.state;return this.input=s.slice(0,r)+e+s.slice(r),this.moveCursor(1),this.complete()}delete(){let{cursor:e,input:r}=this.state;return r?(this.input=r.slice(0,e-1)+r.slice(e),this.moveCursor(-1),this.complete()):this.alert()}deleteForward(){let{cursor:e,input:r}=this.state;return r[e]===void 0?this.alert():(this.input=`${r}`.slice(0,e)+`${r}`.slice(e+1),this.complete())}number(e){return this.append(e)}async complete(){this.completing=!0,this.choices=await this.suggest(this.input,this.state._choices),this.state.limit=void 0,this.index=Math.min(Math.max(this.visible.length-1,0),this.index),await this.render(),this.completing=!1}suggest(e=this.input,r=this.state._choices){if(typeof this.options.suggest=="function")return this.options.suggest.call(this,e,r);let s=e.toLowerCase();return r.filter(a=>a.message.toLowerCase().includes(s))}pointer(){return""}format(){if(!this.focused)return this.input;if(this.options.multiple&&this.state.submitted)return this.selected.map(e=>this.styles.primary(e.message)).join(", ");if(this.state.submitted){let e=this.value=this.input=this.focused.value;return this.styles.primary(e)}return this.input}async render(){if(this.state.status!=="pending")return super.render();let e=this.options.highlight?this.options.highlight.bind(this):this.styles.placeholder,r=$at(this.input,e),s=this.choices;this.choices=s.map(a=>({...a,message:r(a.message)})),await super.render(),this.choices=s}submit(){return this.options.multiple&&(this.value=this.selected.map(e=>e.name)),super.submit()}};cme.exports=d5});var y5=_((ojt,fme)=>{"use strict";var m5=Zo();fme.exports=(t,e={})=>{t.cursorHide();let{input:r="",initial:s="",pos:a,showCursor:n=!0,color:c}=e,f=c||t.styles.placeholder,p=m5.inverse(t.styles.primary),h=R=>p(t.styles.black(R)),E=r,C=" ",S=h(C);if(t.blink&&t.blink.off===!0&&(h=R=>R,S=""),n&&a===0&&s===""&&r==="")return h(C);if(n&&a===0&&(r===s||r===""))return h(s[0])+f(s.slice(1));s=m5.isPrimitive(s)?`${s}`:"",r=m5.isPrimitive(r)?`${r}`:"";let P=s&&s.startsWith(r)&&s!==r,I=P?h(s[r.length]):S;if(a!==r.length&&n===!0&&(E=r.slice(0,a)+h(r[a])+r.slice(a+1),I=""),n===!1&&(I=""),P){let R=t.styles.unstyle(E+I);return E+I+f(s.slice(R.length))}return E+I}});var XR=_((ajt,Ame)=>{"use strict";var elt=Ju(),tlt=G0(),rlt=y5(),E5=class extends tlt{constructor(e){super({...e,multiple:!0}),this.type="form",this.initial=this.options.initial,this.align=[this.options.align,"right"].find(r=>r!=null),this.emptyError="",this.values={}}async reset(e){return await super.reset(),e===!0&&(this._index=this.index),this.index=this._index,this.values={},this.choices.forEach(r=>r.reset&&r.reset()),this.render()}dispatch(e){return!!e&&this.append(e)}append(e){let r=this.focused;if(!r)return this.alert();let{cursor:s,input:a}=r;return r.value=r.input=a.slice(0,s)+e+a.slice(s),r.cursor++,this.render()}delete(){let e=this.focused;if(!e||e.cursor<=0)return this.alert();let{cursor:r,input:s}=e;return e.value=e.input=s.slice(0,r-1)+s.slice(r),e.cursor--,this.render()}deleteForward(){let e=this.focused;if(!e)return this.alert();let{cursor:r,input:s}=e;if(s[r]===void 0)return this.alert();let a=`${s}`.slice(0,r)+`${s}`.slice(r+1);return e.value=e.input=a,this.render()}right(){let e=this.focused;return e?e.cursor>=e.input.length?this.alert():(e.cursor++,this.render()):this.alert()}left(){let e=this.focused;return e?e.cursor<=0?this.alert():(e.cursor--,this.render()):this.alert()}space(e,r){return this.dispatch(e,r)}number(e,r){return this.dispatch(e,r)}next(){let e=this.focused;if(!e)return this.alert();let{initial:r,input:s}=e;return r&&r.startsWith(s)&&s!==r?(e.value=e.input=r,e.cursor=e.value.length,this.render()):super.next()}prev(){let e=this.focused;return e?e.cursor===0?super.prev():(e.value=e.input="",e.cursor=0,this.render()):this.alert()}separator(){return""}format(e){return this.state.submitted?"":super.format(e)}pointer(){return""}indicator(e){return e.input?"\u29BF":"\u2299"}async choiceSeparator(e,r){let s=await this.resolve(e.separator,this.state,e,r)||":";return s?" "+this.styles.disabled(s):""}async renderChoice(e,r){await this.onChoice(e,r);let{state:s,styles:a}=this,{cursor:n,initial:c="",name:f,hint:p,input:h=""}=e,{muted:E,submitted:C,primary:S,danger:P}=a,I=p,R=this.index===r,N=e.validate||(()=>!0),U=await this.choiceSeparator(e,r),W=e.message;this.align==="right"&&(W=W.padStart(this.longest+1," ")),this.align==="left"&&(W=W.padEnd(this.longest+1," "));let ee=this.values[f]=h||c,ie=h?"success":"dark";await N.call(e,ee,this.state)!==!0&&(ie="danger");let ue=a[ie],le=ue(await this.indicator(e,r))+(e.pad||""),me=this.indent(e),pe=()=>[me,le,W+U,h,I].filter(Boolean).join(" ");if(s.submitted)return W=elt.unstyle(W),h=C(h),I="",pe();if(e.format)h=await e.format.call(this,h,e,r);else{let Be=this.styles.muted;h=rlt(this,{input:h,initial:c,pos:n,showCursor:R,color:Be})}return this.isValue(h)||(h=this.styles.muted(this.symbols.ellipsis)),e.result&&(this.values[f]=await e.result.call(this,ee,e,r)),R&&(W=S(W)),e.error?h+=(h?" ":"")+P(e.error.trim()):e.hint&&(h+=(h?" ":"")+E(e.hint.trim())),pe()}async submit(){return this.value=this.values,super.base.submit.call(this)}};Ame.exports=E5});var I5=_((ljt,hme)=>{"use strict";var nlt=XR(),ilt=()=>{throw new Error("expected prompt to have a custom authenticate method")},pme=(t=ilt)=>{class e extends nlt{constructor(s){super(s)}async submit(){this.value=await t.call(this,this.values,this.state),super.base.submit.call(this)}static create(s){return pme(s)}}return e};hme.exports=pme()});var mme=_((cjt,dme)=>{"use strict";var slt=I5();function olt(t,e){return t.username===this.options.username&&t.password===this.options.password}var gme=(t=olt)=>{let e=[{name:"username",message:"username"},{name:"password",message:"password",format(s){return this.options.showPassword?s:(this.state.submitted?this.styles.primary:this.styles.muted)(this.symbols.asterisk.repeat(s.length))}}];class r extends slt.create(t){constructor(a){super({...a,choices:e})}static create(a){return gme(a)}}return r};dme.exports=gme()});var ZR=_((ujt,yme)=>{"use strict";var alt=nC(),{isPrimitive:llt,hasColor:clt}=Zo(),C5=class extends alt{constructor(e){super(e),this.cursorHide()}async initialize(){let e=await this.resolve(this.initial,this.state);this.input=await this.cast(e),await super.initialize()}dispatch(e){return this.isValue(e)?(this.input=e,this.submit()):this.alert()}format(e){let{styles:r,state:s}=this;return s.submitted?r.success(e):r.primary(e)}cast(e){return this.isTrue(e)}isTrue(e){return/^[ty1]/i.test(e)}isFalse(e){return/^[fn0]/i.test(e)}isValue(e){return llt(e)&&(this.isTrue(e)||this.isFalse(e))}async hint(){if(this.state.status==="pending"){let e=await this.element("hint");return clt(e)?e:this.styles.muted(e)}}async render(){let{input:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c=this.styles.muted(this.default),f=[s,n,c,a].filter(Boolean).join(" ");this.state.prompt=f;let p=await this.header(),h=this.value=this.cast(e),E=await this.format(h),C=await this.error()||await this.hint(),S=await this.footer();C&&!f.includes(C)&&(E+=" "+C),f+=" "+E,this.clear(r),this.write([p,f,S].filter(Boolean).join(` `)),this.restore()}set value(e){super.value=e}get value(){return this.cast(super.value)}};yme.exports=C5});var Ime=_((fjt,Eme)=>{"use strict";var ult=ZR(),w5=class extends ult{constructor(e){super(e),this.default=this.options.default||(this.initial?"(Y/n)":"(y/N)")}};Eme.exports=w5});var wme=_((Ajt,Cme)=>{"use strict";var flt=G0(),Alt=XR(),iC=Alt.prototype,B5=class extends flt{constructor(e){super({...e,multiple:!0}),this.align=[this.options.align,"left"].find(r=>r!=null),this.emptyError="",this.values={}}dispatch(e,r){let s=this.focused,a=s.parent||{};return!s.editable&&!a.editable&&(e==="a"||e==="i")?super[e]():iC.dispatch.call(this,e,r)}append(e,r){return iC.append.call(this,e,r)}delete(e,r){return iC.delete.call(this,e,r)}space(e){return this.focused.editable?this.append(e):super.space()}number(e){return this.focused.editable?this.append(e):super.number(e)}next(){return this.focused.editable?iC.next.call(this):super.next()}prev(){return this.focused.editable?iC.prev.call(this):super.prev()}async indicator(e,r){let s=e.indicator||"",a=e.editable?s:super.indicator(e,r);return await this.resolve(a,this.state,e,r)||""}indent(e){return e.role==="heading"?"":e.editable?" ":" "}async renderChoice(e,r){return e.indent="",e.editable?iC.renderChoice.call(this,e,r):super.renderChoice(e,r)}error(){return""}footer(){return this.state.error}async validate(){let e=!0;for(let r of this.choices){if(typeof r.validate!="function"||r.role==="heading")continue;let s=r.parent?this.value[r.parent.name]:this.value;if(r.editable?s=r.value===r.name?r.initial||"":r.value:this.isDisabled(r)||(s=r.enabled===!0),e=await r.validate(s,this.state),e!==!0)break}return e!==!0&&(this.state.error=typeof e=="string"?e:"Invalid Input"),e}submit(){if(this.focused.newChoice===!0)return super.submit();if(this.choices.some(e=>e.newChoice))return this.alert();this.value={};for(let e of this.choices){let r=e.parent?this.value[e.parent.name]:this.value;if(e.role==="heading"){this.value[e.name]={};continue}e.editable?r[e.name]=e.value===e.name?e.initial||"":e.value:this.isDisabled(e)||(r[e.name]=e.enabled===!0)}return this.base.submit.call(this)}};Cme.exports=B5});var vm=_((pjt,Bme)=>{"use strict";var plt=nC(),hlt=y5(),{isPrimitive:glt}=Zo(),v5=class extends plt{constructor(e){super(e),this.initial=glt(this.initial)?String(this.initial):"",this.initial&&this.cursorHide(),this.state.prevCursor=0,this.state.clipboard=[]}async keypress(e,r={}){let s=this.state.prevKeypress;return this.state.prevKeypress=r,this.options.multiline===!0&&r.name==="return"&&(!s||s.name!=="return")?this.append(` `,r):super.keypress(e,r)}moveCursor(e){this.cursor+=e}reset(){return this.input=this.value="",this.cursor=0,this.render()}dispatch(e,r){if(!e||r.ctrl||r.code)return this.alert();this.append(e)}append(e){let{cursor:r,input:s}=this.state;this.input=`${s}`.slice(0,r)+e+`${s}`.slice(r),this.moveCursor(String(e).length),this.render()}insert(e){this.append(e)}delete(){let{cursor:e,input:r}=this.state;if(e<=0)return this.alert();this.input=`${r}`.slice(0,e-1)+`${r}`.slice(e),this.moveCursor(-1),this.render()}deleteForward(){let{cursor:e,input:r}=this.state;if(r[e]===void 0)return this.alert();this.input=`${r}`.slice(0,e)+`${r}`.slice(e+1),this.render()}cutForward(){let e=this.cursor;if(this.input.length<=e)return this.alert();this.state.clipboard.push(this.input.slice(e)),this.input=this.input.slice(0,e),this.render()}cutLeft(){let e=this.cursor;if(e===0)return this.alert();let r=this.input.slice(0,e),s=this.input.slice(e),a=r.split(" ");this.state.clipboard.push(a.pop()),this.input=a.join(" "),this.cursor=this.input.length,this.input+=s,this.render()}paste(){if(!this.state.clipboard.length)return this.alert();this.insert(this.state.clipboard.pop()),this.render()}toggleCursor(){this.state.prevCursor?(this.cursor=this.state.prevCursor,this.state.prevCursor=0):(this.state.prevCursor=this.cursor,this.cursor=0),this.render()}first(){this.cursor=0,this.render()}last(){this.cursor=this.input.length-1,this.render()}next(){let e=this.initial!=null?String(this.initial):"";if(!e||!e.startsWith(this.input))return this.alert();this.input=this.initial,this.cursor=this.initial.length,this.render()}prev(){if(!this.input)return this.alert();this.reset()}backward(){return this.left()}forward(){return this.right()}right(){return this.cursor>=this.input.length?this.alert():(this.moveCursor(1),this.render())}left(){return this.cursor<=0?this.alert():(this.moveCursor(-1),this.render())}isValue(e){return!!e}async format(e=this.value){let r=await this.resolve(this.initial,this.state);return this.state.submitted?this.styles.submitted(e||r):hlt(this,{input:e,initial:r,pos:this.cursor})}async render(){let e=this.state.size,r=await this.prefix(),s=await this.separator(),a=await this.message(),n=[r,a,s].filter(Boolean).join(" ");this.state.prompt=n;let c=await this.header(),f=await this.format(),p=await this.error()||await this.hint(),h=await this.footer();p&&!f.includes(p)&&(f+=" "+p),n+=" "+f,this.clear(e),this.write([c,n,h].filter(Boolean).join(` `)),this.restore()}};Bme.exports=v5});var Sme=_((hjt,vme)=>{"use strict";var dlt=t=>t.filter((e,r)=>t.lastIndexOf(e)===r),$R=t=>dlt(t).filter(Boolean);vme.exports=(t,e={},r="")=>{let{past:s=[],present:a=""}=e,n,c;switch(t){case"prev":case"undo":return n=s.slice(0,s.length-1),c=s[s.length-1]||"",{past:$R([r,...n]),present:c};case"next":case"redo":return n=s.slice(1),c=s[0]||"",{past:$R([...n,r]),present:c};case"save":return{past:$R([...s,r]),present:""};case"remove":return c=$R(s.filter(f=>f!==r)),a="",c.length&&(a=c.pop()),{past:c,present:a};default:throw new Error(`Invalid action: "${t}"`)}}});var D5=_((gjt,bme)=>{"use strict";var mlt=vm(),Dme=Sme(),S5=class extends mlt{constructor(e){super(e);let r=this.options.history;if(r&&r.store){let s=r.values||this.initial;this.autosave=!!r.autosave,this.store=r.store,this.data=this.store.get("values")||{past:[],present:s},this.initial=this.data.present||this.data.past[this.data.past.length-1]}}completion(e){return this.store?(this.data=Dme(e,this.data,this.input),this.data.present?(this.input=this.data.present,this.cursor=this.input.length,this.render()):this.alert()):this.alert()}altUp(){return this.completion("prev")}altDown(){return this.completion("next")}prev(){return this.save(),super.prev()}save(){this.store&&(this.data=Dme("save",this.data,this.input),this.store.set("values",this.data))}submit(){return this.store&&this.autosave===!0&&this.save(),super.submit()}};bme.exports=S5});var xme=_((djt,Pme)=>{"use strict";var ylt=vm(),b5=class extends ylt{format(){return""}};Pme.exports=b5});var Qme=_((mjt,kme)=>{"use strict";var Elt=vm(),P5=class extends Elt{constructor(e={}){super(e),this.sep=this.options.separator||/, */,this.initial=e.initial||""}split(e=this.value){return e?String(e).split(this.sep):[]}format(){let e=this.state.submitted?this.styles.primary:r=>r;return this.list.map(e).join(", ")}async submit(e){let r=this.state.error||await this.validate(this.list,this.state);return r!==!0?(this.state.error=r,super.submit()):(this.value=this.list,super.submit())}get list(){return this.split()}};kme.exports=P5});var Rme=_((yjt,Tme)=>{"use strict";var Ilt=G0(),x5=class extends Ilt{constructor(e){super({...e,multiple:!0})}};Tme.exports=x5});var Q5=_((Ejt,Fme)=>{"use strict";var Clt=vm(),k5=class extends Clt{constructor(e={}){super({style:"number",...e}),this.min=this.isValue(e.min)?this.toNumber(e.min):-1/0,this.max=this.isValue(e.max)?this.toNumber(e.max):1/0,this.delay=e.delay!=null?e.delay:1e3,this.float=e.float!==!1,this.round=e.round===!0||e.float===!1,this.major=e.major||10,this.minor=e.minor||1,this.initial=e.initial!=null?e.initial:"",this.input=String(this.initial),this.cursor=this.input.length,this.cursorShow()}append(e){return!/[-+.]/.test(e)||e==="."&&this.input.includes(".")?this.alert("invalid number"):super.append(e)}number(e){return super.append(e)}next(){return this.input&&this.input!==this.initial?this.alert():this.isValue(this.initial)?(this.input=this.initial,this.cursor=String(this.initial).length,this.render()):this.alert()}up(e){let r=e||this.minor,s=this.toNumber(this.input);return s>this.max+r?this.alert():(this.input=`${s+r}`,this.render())}down(e){let r=e||this.minor,s=this.toNumber(this.input);return sthis.isValue(r));return this.value=this.toNumber(e||0),super.submit()}};Fme.exports=k5});var Ome=_((Ijt,Nme)=>{Nme.exports=Q5()});var Mme=_((Cjt,Lme)=>{"use strict";var wlt=vm(),T5=class extends wlt{constructor(e){super(e),this.cursorShow()}format(e=this.input){return this.keypressed?(this.state.submitted?this.styles.primary:this.styles.muted)(this.symbols.asterisk.repeat(e.length)):""}};Lme.exports=T5});var Hme=_((wjt,_me)=>{"use strict";var Blt=Ju(),vlt=Wv(),Ume=Zo(),R5=class extends vlt{constructor(e={}){super(e),this.widths=[].concat(e.messageWidth||50),this.align=[].concat(e.align||"left"),this.linebreak=e.linebreak||!1,this.edgeLength=e.edgeLength||3,this.newline=e.newline||` `;let r=e.startNumber||1;typeof this.scale=="number"&&(this.scaleKey=!1,this.scale=Array(this.scale).fill(0).map((s,a)=>({name:a+r})))}async reset(){return this.tableized=!1,await super.reset(),this.render()}tableize(){if(this.tableized===!0)return;this.tableized=!0;let e=0;for(let r of this.choices){e=Math.max(e,r.message.length),r.scaleIndex=r.initial||2,r.scale=[];for(let s=0;s=this.scale.length-1?this.alert():(e.scaleIndex++,this.render())}left(){let e=this.focused;return e.scaleIndex<=0?this.alert():(e.scaleIndex--,this.render())}indent(){return""}format(){return this.state.submitted?this.choices.map(r=>this.styles.info(r.index)).join(", "):""}pointer(){return""}renderScaleKey(){return this.scaleKey===!1||this.state.submitted?"":["",...this.scale.map(s=>` ${s.name} - ${s.message}`)].map(s=>this.styles.muted(s)).join(` `)}renderScaleHeading(e){let r=this.scale.map(p=>p.name);typeof this.options.renderScaleHeading=="function"&&(r=this.options.renderScaleHeading.call(this,e));let s=this.scaleLength-r.join("").length,a=Math.round(s/(r.length-1)),c=r.map(p=>this.styles.strong(p)).join(" ".repeat(a)),f=" ".repeat(this.widths[0]);return this.margin[3]+f+this.margin[1]+c}scaleIndicator(e,r,s){if(typeof this.options.scaleIndicator=="function")return this.options.scaleIndicator.call(this,e,r,s);let a=e.scaleIndex===r.index;return r.disabled?this.styles.hint(this.symbols.radio.disabled):a?this.styles.success(this.symbols.radio.on):this.symbols.radio.off}renderScale(e,r){let s=e.scale.map(n=>this.scaleIndicator(e,n,r)),a=this.term==="Hyper"?"":" ";return s.join(a+this.symbols.line.repeat(this.edgeLength))}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=await this.pointer(e,r),n=await e.hint;n&&!Ume.hasColor(n)&&(n=this.styles.muted(n));let c=I=>this.margin[3]+I.replace(/\s+$/,"").padEnd(this.widths[0]," "),f=this.newline,p=this.indent(e),h=await this.resolve(e.message,this.state,e,r),E=await this.renderScale(e,r),C=this.margin[1]+this.margin[3];this.scaleLength=Blt.unstyle(E).length,this.widths[0]=Math.min(this.widths[0],this.width-this.scaleLength-C.length);let P=Ume.wordWrap(h,{width:this.widths[0],newline:f}).split(` `).map(I=>c(I)+this.margin[1]);return s&&(E=this.styles.info(E),P=P.map(I=>this.styles.info(I))),P[0]+=E,this.linebreak&&P.push(""),[p+a,P.join(` `)].filter(Boolean)}async renderChoices(){if(this.state.submitted)return"";this.tableize();let e=this.visible.map(async(a,n)=>await this.renderChoice(a,n)),r=await Promise.all(e),s=await this.renderScaleHeading();return this.margin[0]+[s,...r.map(a=>a.join(" "))].join(` `)}async render(){let{submitted:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c="";this.options.promptLine!==!1&&(c=[s,n,a,""].join(" "),this.state.prompt=c);let f=await this.header(),p=await this.format(),h=await this.renderScaleKey(),E=await this.error()||await this.hint(),C=await this.renderChoices(),S=await this.footer(),P=this.emptyError;p&&(c+=p),E&&!c.includes(E)&&(c+=" "+E),e&&!p&&!C.trim()&&this.multiple&&P!=null&&(c+=this.styles.danger(P)),this.clear(r),this.write([f,c,h,C,S].filter(Boolean).join(` `)),this.state.submitted||this.write(this.margin[2]),this.restore()}submit(){this.value={};for(let e of this.choices)this.value[e.name]=e.scaleIndex;return this.base.submit.call(this)}};_me.exports=R5});var qme=_((Bjt,Gme)=>{"use strict";var jme=Ju(),Slt=(t="")=>typeof t=="string"?t.replace(/^['"]|['"]$/g,""):"",N5=class{constructor(e){this.name=e.key,this.field=e.field||{},this.value=Slt(e.initial||this.field.initial||""),this.message=e.message||this.name,this.cursor=0,this.input="",this.lines=[]}},Dlt=async(t={},e={},r=s=>s)=>{let s=new Set,a=t.fields||[],n=t.template,c=[],f=[],p=[],h=1;typeof n=="function"&&(n=await n());let E=-1,C=()=>n[++E],S=()=>n[E+1],P=I=>{I.line=h,c.push(I)};for(P({type:"bos",value:""});Eie.name===U.key);U.field=a.find(ie=>ie.name===U.key),ee||(ee=new N5(U),f.push(ee)),ee.lines.push(U.line-1);continue}let R=c[c.length-1];R.type==="text"&&R.line===h?R.value+=I:P({type:"text",value:I})}return P({type:"eos",value:""}),{input:n,tabstops:c,unique:s,keys:p,items:f}};Gme.exports=async t=>{let e=t.options,r=new Set(e.required===!0?[]:e.required||[]),s={...e.values,...e.initial},{tabstops:a,items:n,keys:c}=await Dlt(e,s),f=F5("result",t,e),p=F5("format",t,e),h=F5("validate",t,e,!0),E=t.isValue.bind(t);return async(C={},S=!1)=>{let P=0;C.required=r,C.items=n,C.keys=c,C.output="";let I=async(W,ee,ie,ue)=>{let le=await h(W,ee,ie,ue);return le===!1?"Invalid field "+ie.name:le};for(let W of a){let ee=W.value,ie=W.key;if(W.type!=="template"){ee&&(C.output+=ee);continue}if(W.type==="template"){let ue=n.find(Ce=>Ce.name===ie);e.required===!0&&C.required.add(ue.name);let le=[ue.input,C.values[ue.value],ue.value,ee].find(E),pe=(ue.field||{}).message||W.inner;if(S){let Ce=await I(C.values[ie],C,ue,P);if(Ce&&typeof Ce=="string"||Ce===!1){C.invalid.set(ie,Ce);continue}C.invalid.delete(ie);let g=await f(C.values[ie],C,ue,P);C.output+=jme.unstyle(g);continue}ue.placeholder=!1;let Be=ee;ee=await p(ee,C,ue,P),le!==ee?(C.values[ie]=le,ee=t.styles.typing(le),C.missing.delete(pe)):(C.values[ie]=void 0,le=`<${pe}>`,ee=t.styles.primary(le),ue.placeholder=!0,C.required.has(ie)&&C.missing.add(pe)),C.missing.has(pe)&&C.validating&&(ee=t.styles.warning(le)),C.invalid.has(ie)&&C.validating&&(ee=t.styles.danger(le)),P===C.index&&(Be!==ee?ee=t.styles.underline(ee):ee=t.styles.heading(jme.unstyle(ee))),P++}ee&&(C.output+=ee)}let R=C.output.split(` `).map(W=>" "+W),N=n.length,U=0;for(let W of n)C.invalid.has(W.name)&&W.lines.forEach(ee=>{R[ee][0]===" "&&(R[ee]=C.styles.danger(C.symbols.bullet)+R[ee].slice(1))}),t.isValue(C.values[W.name])&&U++;return C.completed=(U/N*100).toFixed(0),C.output=R.join(` `),C.output}};function F5(t,e,r,s){return(a,n,c,f)=>typeof c.field[t]=="function"?c.field[t].call(e,a,n,c,f):[s,a].find(p=>e.isValue(p))}});var Yme=_((vjt,Wme)=>{"use strict";var blt=Ju(),Plt=qme(),xlt=nC(),O5=class extends xlt{constructor(e){super(e),this.cursorHide(),this.reset(!0)}async initialize(){this.interpolate=await Plt(this),await super.initialize()}async reset(e){this.state.keys=[],this.state.invalid=new Map,this.state.missing=new Set,this.state.completed=0,this.state.values={},e!==!0&&(await this.initialize(),await this.render())}moveCursor(e){let r=this.getItem();this.cursor+=e,r.cursor+=e}dispatch(e,r){if(!r.code&&!r.ctrl&&e!=null&&this.getItem()){this.append(e,r);return}this.alert()}append(e,r){let s=this.getItem(),a=s.input.slice(0,this.cursor),n=s.input.slice(this.cursor);this.input=s.input=`${a}${e}${n}`,this.moveCursor(1),this.render()}delete(){let e=this.getItem();if(this.cursor<=0||!e.input)return this.alert();let r=e.input.slice(this.cursor),s=e.input.slice(0,this.cursor-1);this.input=e.input=`${s}${r}`,this.moveCursor(-1),this.render()}increment(e){return e>=this.state.keys.length-1?0:e+1}decrement(e){return e<=0?this.state.keys.length-1:e-1}first(){this.state.index=0,this.render()}last(){this.state.index=this.state.keys.length-1,this.render()}right(){if(this.cursor>=this.input.length)return this.alert();this.moveCursor(1),this.render()}left(){if(this.cursor<=0)return this.alert();this.moveCursor(-1),this.render()}prev(){this.state.index=this.decrement(this.state.index),this.getItem(),this.render()}next(){this.state.index=this.increment(this.state.index),this.getItem(),this.render()}up(){this.prev()}down(){this.next()}format(e){let r=this.state.completed<100?this.styles.warning:this.styles.success;return this.state.submitted===!0&&this.state.completed!==100&&(r=this.styles.danger),r(`${this.state.completed}% completed`)}async render(){let{index:e,keys:r=[],submitted:s,size:a}=this.state,n=[this.options.newline,` `].find(W=>W!=null),c=await this.prefix(),f=await this.separator(),p=await this.message(),h=[c,p,f].filter(Boolean).join(" ");this.state.prompt=h;let E=await this.header(),C=await this.error()||"",S=await this.hint()||"",P=s?"":await this.interpolate(this.state),I=this.state.key=r[e]||"",R=await this.format(I),N=await this.footer();R&&(h+=" "+R),S&&!R&&this.state.completed===0&&(h+=" "+S),this.clear(a);let U=[E,h,P,N,C.trim()];this.write(U.filter(Boolean).join(n)),this.restore()}getItem(e){let{items:r,keys:s,index:a}=this.state,n=r.find(c=>c.name===s[a]);return n&&n.input!=null&&(this.input=n.input,this.cursor=n.cursor),n}async submit(){typeof this.interpolate!="function"&&await this.initialize(),await this.interpolate(this.state,!0);let{invalid:e,missing:r,output:s,values:a}=this.state;if(e.size){let f="";for(let[p,h]of e)f+=`Invalid ${p}: ${h} `;return this.state.error=f,super.submit()}if(r.size)return this.state.error="Required: "+[...r.keys()].join(", "),super.submit();let c=blt.unstyle(s).split(` `).map(f=>f.slice(1)).join(` `);return this.value={values:a,result:c},super.submit()}};Wme.exports=O5});var Jme=_((Sjt,Vme)=>{"use strict";var klt="(Use + to sort)",Qlt=G0(),L5=class extends Qlt{constructor(e){super({...e,reorder:!1,sort:!0,multiple:!0}),this.state.hint=[this.options.hint,klt].find(this.isValue.bind(this))}indicator(){return""}async renderChoice(e,r){let s=await super.renderChoice(e,r),a=this.symbols.identicalTo+" ",n=this.index===r&&this.sorting?this.styles.muted(a):" ";return this.options.drag===!1&&(n=""),this.options.numbered===!0?n+`${r+1} - `+s:n+s}get selected(){return this.choices}submit(){return this.value=this.choices.map(e=>e.value),super.submit()}};Vme.exports=L5});var zme=_((Djt,Kme)=>{"use strict";var Tlt=Wv(),M5=class extends Tlt{constructor(e={}){if(super(e),this.emptyError=e.emptyError||"No items were selected",this.term=process.env.TERM_PROGRAM,!this.options.header){let r=["","4 - Strongly Agree","3 - Agree","2 - Neutral","1 - Disagree","0 - Strongly Disagree",""];r=r.map(s=>this.styles.muted(s)),this.state.header=r.join(` `)}}async toChoices(...e){if(this.createdScales)return!1;this.createdScales=!0;let r=await super.toChoices(...e);for(let s of r)s.scale=Rlt(5,this.options),s.scaleIdx=2;return r}dispatch(){this.alert()}space(){let e=this.focused,r=e.scale[e.scaleIdx],s=r.selected;return e.scale.forEach(a=>a.selected=!1),r.selected=!s,this.render()}indicator(){return""}pointer(){return""}separator(){return this.styles.muted(this.symbols.ellipsis)}right(){let e=this.focused;return e.scaleIdx>=e.scale.length-1?this.alert():(e.scaleIdx++,this.render())}left(){let e=this.focused;return e.scaleIdx<=0?this.alert():(e.scaleIdx--,this.render())}indent(){return" "}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=this.term==="Hyper",n=a?9:8,c=a?"":" ",f=this.symbols.line.repeat(n),p=" ".repeat(n+(a?0:1)),h=ee=>(ee?this.styles.success("\u25C9"):"\u25EF")+c,E=r+1+".",C=s?this.styles.heading:this.styles.noop,S=await this.resolve(e.message,this.state,e,r),P=this.indent(e),I=P+e.scale.map((ee,ie)=>h(ie===e.scaleIdx)).join(f),R=ee=>ee===e.scaleIdx?C(ee):ee,N=P+e.scale.map((ee,ie)=>R(ie)).join(p),U=()=>[E,S].filter(Boolean).join(" "),W=()=>[U(),I,N," "].filter(Boolean).join(` `);return s&&(I=this.styles.cyan(I),N=this.styles.cyan(N)),W()}async renderChoices(){if(this.state.submitted)return"";let e=this.visible.map(async(s,a)=>await this.renderChoice(s,a)),r=await Promise.all(e);return r.length||r.push(this.styles.danger("No matching choices")),r.join(` `)}format(){return this.state.submitted?this.choices.map(r=>this.styles.info(r.scaleIdx)).join(", "):""}async render(){let{submitted:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c=[s,n,a].filter(Boolean).join(" ");this.state.prompt=c;let f=await this.header(),p=await this.format(),h=await this.error()||await this.hint(),E=await this.renderChoices(),C=await this.footer();(p||!h)&&(c+=" "+p),h&&!c.includes(h)&&(c+=" "+h),e&&!p&&!E&&this.multiple&&this.type!=="form"&&(c+=this.styles.danger(this.emptyError)),this.clear(r),this.write([c,f,E,C].filter(Boolean).join(` `)),this.restore()}submit(){this.value={};for(let e of this.choices)this.value[e.name]=e.scaleIdx;return this.base.submit.call(this)}};function Rlt(t,e={}){if(Array.isArray(e.scale))return e.scale.map(s=>({...s}));let r=[];for(let s=1;s{Xme.exports=D5()});var eye=_((Pjt,$me)=>{"use strict";var Flt=ZR(),U5=class extends Flt{async initialize(){await super.initialize(),this.value=this.initial=!!this.options.initial,this.disabled=this.options.disabled||"no",this.enabled=this.options.enabled||"yes",await this.render()}reset(){this.value=this.initial,this.render()}delete(){this.alert()}toggle(){this.value=!this.value,this.render()}enable(){if(this.value===!0)return this.alert();this.value=!0,this.render()}disable(){if(this.value===!1)return this.alert();this.value=!1,this.render()}up(){this.toggle()}down(){this.toggle()}right(){this.toggle()}left(){this.toggle()}next(){this.toggle()}prev(){this.toggle()}dispatch(e="",r){switch(e.toLowerCase()){case" ":return this.toggle();case"1":case"y":case"t":return this.enable();case"0":case"n":case"f":return this.disable();default:return this.alert()}}format(){let e=s=>this.styles.primary.underline(s);return[this.value?this.disabled:e(this.disabled),this.value?e(this.enabled):this.enabled].join(this.styles.muted(" / "))}async render(){let{size:e}=this.state,r=await this.header(),s=await this.prefix(),a=await this.separator(),n=await this.message(),c=await this.format(),f=await this.error()||await this.hint(),p=await this.footer(),h=[s,n,a,c].join(" ");this.state.prompt=h,f&&!h.includes(f)&&(h+=" "+f),this.clear(e),this.write([r,h,p].filter(Boolean).join(` `)),this.write(this.margin[2]),this.restore()}};$me.exports=U5});var rye=_((xjt,tye)=>{"use strict";var Nlt=G0(),_5=class extends Nlt{constructor(e){if(super(e),typeof this.options.correctChoice!="number"||this.options.correctChoice<0)throw new Error("Please specify the index of the correct answer from the list of choices")}async toChoices(e,r){let s=await super.toChoices(e,r);if(s.length<2)throw new Error("Please give at least two choices to the user");if(this.options.correctChoice>s.length)throw new Error("Please specify the index of the correct answer from the list of choices");return s}check(e){return e.index===this.options.correctChoice}async result(e){return{selectedAnswer:e,correctAnswer:this.options.choices[this.options.correctChoice].value,correct:await this.check(this.state)}}};tye.exports=_5});var iye=_(H5=>{"use strict";var nye=Zo(),ks=(t,e)=>{nye.defineExport(H5,t,e),nye.defineExport(H5,t.toLowerCase(),e)};ks("AutoComplete",()=>ume());ks("BasicAuth",()=>mme());ks("Confirm",()=>Ime());ks("Editable",()=>wme());ks("Form",()=>XR());ks("Input",()=>D5());ks("Invisible",()=>xme());ks("List",()=>Qme());ks("MultiSelect",()=>Rme());ks("Numeral",()=>Ome());ks("Password",()=>Mme());ks("Scale",()=>Hme());ks("Select",()=>G0());ks("Snippet",()=>Yme());ks("Sort",()=>Jme());ks("Survey",()=>zme());ks("Text",()=>Zme());ks("Toggle",()=>eye());ks("Quiz",()=>rye())});var oye=_((Qjt,sye)=>{sye.exports={ArrayPrompt:Wv(),AuthPrompt:I5(),BooleanPrompt:ZR(),NumberPrompt:Q5(),StringPrompt:vm()}});var Vv=_((Tjt,lye)=>{"use strict";var aye=Ie("assert"),G5=Ie("events"),q0=Zo(),zu=class extends G5{constructor(e,r){super(),this.options=q0.merge({},e),this.answers={...r}}register(e,r){if(q0.isObject(e)){for(let a of Object.keys(e))this.register(a,e[a]);return this}aye.equal(typeof r,"function","expected a function");let s=e.toLowerCase();return r.prototype instanceof this.Prompt?this.prompts[s]=r:this.prompts[s]=r(this.Prompt,this),this}async prompt(e=[]){for(let r of[].concat(e))try{typeof r=="function"&&(r=await r.call(this)),await this.ask(q0.merge({},this.options,r))}catch(s){return Promise.reject(s)}return this.answers}async ask(e){typeof e=="function"&&(e=await e.call(this));let r=q0.merge({},this.options,e),{type:s,name:a}=e,{set:n,get:c}=q0;if(typeof s=="function"&&(s=await s.call(this,e,this.answers)),!s)return this.answers[a];aye(this.prompts[s],`Prompt "${s}" is not registered`);let f=new this.prompts[s](r),p=c(this.answers,a);f.state.answers=this.answers,f.enquirer=this,a&&f.on("submit",E=>{this.emit("answer",a,E,f),n(this.answers,a,E)});let h=f.emit.bind(f);return f.emit=(...E)=>(this.emit.call(this,...E),h(...E)),this.emit("prompt",f,this),r.autofill&&p!=null?(f.value=f.input=p,r.autofill==="show"&&await f.submit()):p=f.value=await f.run(),p}use(e){return e.call(this,this),this}set Prompt(e){this._Prompt=e}get Prompt(){return this._Prompt||this.constructor.Prompt}get prompts(){return this.constructor.prompts}static set Prompt(e){this._Prompt=e}static get Prompt(){return this._Prompt||nC()}static get prompts(){return iye()}static get types(){return oye()}static get prompt(){let e=(r,...s)=>{let a=new this(...s),n=a.emit.bind(a);return a.emit=(...c)=>(e.emit(...c),n(...c)),a.prompt(r)};return q0.mixinEmitter(e,new G5),e}};q0.mixinEmitter(zu,new G5);var j5=zu.prompts;for(let t of Object.keys(j5)){let e=t.toLowerCase(),r=s=>new j5[t](s).run();zu.prompt[e]=r,zu[e]=r,zu[t]||Reflect.defineProperty(zu,t,{get:()=>j5[t]})}var Yv=t=>{q0.defineExport(zu,t,()=>zu.types[t])};Yv("ArrayPrompt");Yv("AuthPrompt");Yv("BooleanPrompt");Yv("NumberPrompt");Yv("StringPrompt");lye.exports=zu});var dye=_((tGt,qlt)=>{qlt.exports={name:"@yarnpkg/cli",version:"4.12.0",license:"BSD-2-Clause",main:"./sources/index.ts",exports:{".":"./sources/index.ts","./polyfills":"./sources/polyfills.ts","./package.json":"./package.json"},dependencies:{"@yarnpkg/core":"workspace:^","@yarnpkg/fslib":"workspace:^","@yarnpkg/libzip":"workspace:^","@yarnpkg/parsers":"workspace:^","@yarnpkg/plugin-catalog":"workspace:^","@yarnpkg/plugin-compat":"workspace:^","@yarnpkg/plugin-constraints":"workspace:^","@yarnpkg/plugin-dlx":"workspace:^","@yarnpkg/plugin-essentials":"workspace:^","@yarnpkg/plugin-exec":"workspace:^","@yarnpkg/plugin-file":"workspace:^","@yarnpkg/plugin-git":"workspace:^","@yarnpkg/plugin-github":"workspace:^","@yarnpkg/plugin-http":"workspace:^","@yarnpkg/plugin-init":"workspace:^","@yarnpkg/plugin-interactive-tools":"workspace:^","@yarnpkg/plugin-jsr":"workspace:^","@yarnpkg/plugin-link":"workspace:^","@yarnpkg/plugin-nm":"workspace:^","@yarnpkg/plugin-npm":"workspace:^","@yarnpkg/plugin-npm-cli":"workspace:^","@yarnpkg/plugin-pack":"workspace:^","@yarnpkg/plugin-patch":"workspace:^","@yarnpkg/plugin-pnp":"workspace:^","@yarnpkg/plugin-pnpm":"workspace:^","@yarnpkg/plugin-stage":"workspace:^","@yarnpkg/plugin-typescript":"workspace:^","@yarnpkg/plugin-version":"workspace:^","@yarnpkg/plugin-workspace-tools":"workspace:^","@yarnpkg/shell":"workspace:^","ci-info":"^4.0.0",clipanion:"^4.0.0-rc.2",semver:"^7.1.2",tslib:"^2.4.0",typanion:"^3.14.0"},devDependencies:{"@types/semver":"^7.1.0","@yarnpkg/builder":"workspace:^","@yarnpkg/monorepo":"workspace:^","@yarnpkg/pnpify":"workspace:^"},peerDependencies:{"@yarnpkg/core":"workspace:^"},scripts:{postpack:"rm -rf lib",prepack:'run build:compile "$(pwd)"',"build:cli+hook":"run build:pnp:hook && builder build bundle","build:cli":"builder build bundle","run:cli":"builder run","update-local":"run build:cli --no-git-hash && rsync -a --delete bundles/ bin/"},publishConfig:{main:"./lib/index.js",bin:null,exports:{".":"./lib/index.js","./package.json":"./package.json"}},files:["/lib/**/*","!/lib/pluginConfiguration.*","!/lib/cli.*"],"@yarnpkg/builder":{bundles:{standard:["@yarnpkg/plugin-essentials","@yarnpkg/plugin-catalog","@yarnpkg/plugin-compat","@yarnpkg/plugin-constraints","@yarnpkg/plugin-dlx","@yarnpkg/plugin-exec","@yarnpkg/plugin-file","@yarnpkg/plugin-git","@yarnpkg/plugin-github","@yarnpkg/plugin-http","@yarnpkg/plugin-init","@yarnpkg/plugin-interactive-tools","@yarnpkg/plugin-jsr","@yarnpkg/plugin-link","@yarnpkg/plugin-nm","@yarnpkg/plugin-npm","@yarnpkg/plugin-npm-cli","@yarnpkg/plugin-pack","@yarnpkg/plugin-patch","@yarnpkg/plugin-pnp","@yarnpkg/plugin-pnpm","@yarnpkg/plugin-stage","@yarnpkg/plugin-typescript","@yarnpkg/plugin-version","@yarnpkg/plugin-workspace-tools"]}},repository:{type:"git",url:"git+https://github.com/yarnpkg/berry.git",directory:"packages/yarnpkg-cli"},engines:{node:">=18.12.0"}}});var iq=_((R9t,Pye)=>{"use strict";Pye.exports=function(e,r){r===!0&&(r=0);var s="";if(typeof e=="string")try{s=new URL(e).protocol}catch{}else e&&e.constructor===URL&&(s=e.protocol);var a=s.split(/\:|\+/).filter(Boolean);return typeof r=="number"?a[r]:a}});var kye=_((F9t,xye)=>{"use strict";var uct=iq();function fct(t){var e={protocols:[],protocol:null,port:null,resource:"",host:"",user:"",password:"",pathname:"",hash:"",search:"",href:t,query:{},parse_failed:!1};try{var r=new URL(t);e.protocols=uct(r),e.protocol=e.protocols[0],e.port=r.port,e.resource=r.hostname,e.host=r.host,e.user=r.username||"",e.password=r.password||"",e.pathname=r.pathname,e.hash=r.hash.slice(1),e.search=r.search.slice(1),e.href=r.href,e.query=Object.fromEntries(r.searchParams)}catch{e.protocols=["file"],e.protocol=e.protocols[0],e.port="",e.resource="",e.user="",e.pathname="",e.hash="",e.search="",e.href=t,e.query={},e.parse_failed=!0}return e}xye.exports=fct});var Rye=_((N9t,Tye)=>{"use strict";var Act=kye();function pct(t){return t&&typeof t=="object"&&"default"in t?t:{default:t}}var hct=pct(Act),gct="text/plain",dct="us-ascii",Qye=(t,e)=>e.some(r=>r instanceof RegExp?r.test(t):r===t),mct=(t,{stripHash:e})=>{let r=/^data:(?[^,]*?),(?[^#]*?)(?:#(?.*))?$/.exec(t);if(!r)throw new Error(`Invalid URL: ${t}`);let{type:s,data:a,hash:n}=r.groups,c=s.split(";");n=e?"":n;let f=!1;c[c.length-1]==="base64"&&(c.pop(),f=!0);let p=(c.shift()||"").toLowerCase(),E=[...c.map(C=>{let[S,P=""]=C.split("=").map(I=>I.trim());return S==="charset"&&(P=P.toLowerCase(),P===dct)?"":`${S}${P?`=${P}`:""}`}).filter(Boolean)];return f&&E.push("base64"),(E.length>0||p&&p!==gct)&&E.unshift(p),`data:${E.join(";")},${f?a.trim():a}${n?`#${n}`:""}`};function yct(t,e){if(e={defaultProtocol:"http:",normalizeProtocol:!0,forceHttp:!1,forceHttps:!1,stripAuthentication:!0,stripHash:!1,stripTextFragment:!0,stripWWW:!0,removeQueryParameters:[/^utm_\w+/i],removeTrailingSlash:!0,removeSingleSlash:!0,removeDirectoryIndex:!1,sortQueryParameters:!0,...e},t=t.trim(),/^data:/i.test(t))return mct(t,e);if(/^view-source:/i.test(t))throw new Error("`view-source:` is not supported as it is a non-standard protocol");let r=t.startsWith("//");!r&&/^\.*\//.test(t)||(t=t.replace(/^(?!(?:\w+:)?\/\/)|^\/\//,e.defaultProtocol));let a=new URL(t);if(e.forceHttp&&e.forceHttps)throw new Error("The `forceHttp` and `forceHttps` options cannot be used together");if(e.forceHttp&&a.protocol==="https:"&&(a.protocol="http:"),e.forceHttps&&a.protocol==="http:"&&(a.protocol="https:"),e.stripAuthentication&&(a.username="",a.password=""),e.stripHash?a.hash="":e.stripTextFragment&&(a.hash=a.hash.replace(/#?:~:text.*?$/i,"")),a.pathname){let c=/\b[a-z][a-z\d+\-.]{1,50}:\/\//g,f=0,p="";for(;;){let E=c.exec(a.pathname);if(!E)break;let C=E[0],S=E.index,P=a.pathname.slice(f,S);p+=P.replace(/\/{2,}/g,"/"),p+=C,f=S+C.length}let h=a.pathname.slice(f,a.pathname.length);p+=h.replace(/\/{2,}/g,"/"),a.pathname=p}if(a.pathname)try{a.pathname=decodeURI(a.pathname)}catch{}if(e.removeDirectoryIndex===!0&&(e.removeDirectoryIndex=[/^index\.[a-z]+$/]),Array.isArray(e.removeDirectoryIndex)&&e.removeDirectoryIndex.length>0){let c=a.pathname.split("/"),f=c[c.length-1];Qye(f,e.removeDirectoryIndex)&&(c=c.slice(0,-1),a.pathname=c.slice(1).join("/")+"/")}if(a.hostname&&(a.hostname=a.hostname.replace(/\.$/,""),e.stripWWW&&/^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(a.hostname)&&(a.hostname=a.hostname.replace(/^www\./,""))),Array.isArray(e.removeQueryParameters))for(let c of[...a.searchParams.keys()])Qye(c,e.removeQueryParameters)&&a.searchParams.delete(c);if(e.removeQueryParameters===!0&&(a.search=""),e.sortQueryParameters){a.searchParams.sort();try{a.search=decodeURIComponent(a.search)}catch{}}e.removeTrailingSlash&&(a.pathname=a.pathname.replace(/\/$/,""));let n=t;return t=a.toString(),!e.removeSingleSlash&&a.pathname==="/"&&!n.endsWith("/")&&a.hash===""&&(t=t.replace(/\/$/,"")),(e.removeTrailingSlash||a.pathname==="/")&&a.hash===""&&e.removeSingleSlash&&(t=t.replace(/\/$/,"")),r&&!e.normalizeProtocol&&(t=t.replace(/^http:\/\//,"//")),e.stripProtocol&&(t=t.replace(/^(?:https?:)?\/\//,"")),t}var sq=(t,e=!1)=>{let r=/^(?:([a-z_][a-z0-9_-]{0,31})@|https?:\/\/)([\w\.\-@]+)[\/:]([\~,\.\w,\-,\_,\/]+?(?:\.git|\/)?)$/,s=n=>{let c=new Error(n);throw c.subject_url=t,c};(typeof t!="string"||!t.trim())&&s("Invalid url."),t.length>sq.MAX_INPUT_LENGTH&&s("Input exceeds maximum length. If needed, change the value of parseUrl.MAX_INPUT_LENGTH."),e&&(typeof e!="object"&&(e={stripHash:!1}),t=yct(t,e));let a=hct.default(t);if(a.parse_failed){let n=a.href.match(r);n?(a.protocols=["ssh"],a.protocol="ssh",a.resource=n[2],a.host=n[2],a.user=n[1],a.pathname=`/${n[3]}`,a.parse_failed=!1):s("URL parsing failed.")}return a};sq.MAX_INPUT_LENGTH=2048;Tye.exports=sq});var Oye=_((O9t,Nye)=>{"use strict";var Ect=iq();function Fye(t){if(Array.isArray(t))return t.indexOf("ssh")!==-1||t.indexOf("rsync")!==-1;if(typeof t!="string")return!1;var e=Ect(t);if(t=t.substring(t.indexOf("://")+3),Fye(e))return!0;var r=new RegExp(".([a-zA-Z\\d]+):(\\d+)/");return!t.match(r)&&t.indexOf("@"){"use strict";var Ict=Rye(),Lye=Oye();function Cct(t){var e=Ict(t);return e.token="",e.password==="x-oauth-basic"?e.token=e.user:e.user==="x-token-auth"&&(e.token=e.password),Lye(e.protocols)||e.protocols.length===0&&Lye(t)?e.protocol="ssh":e.protocols.length?e.protocol=e.protocols[0]:(e.protocol="file",e.protocols=["file"]),e.href=e.href.replace(/\/$/,""),e}Mye.exports=Cct});var Hye=_((M9t,_ye)=>{"use strict";var wct=Uye();function oq(t){if(typeof t!="string")throw new Error("The url must be a string.");var e=/^([a-z\d-]{1,39})\/([-\.\w]{1,100})$/i;e.test(t)&&(t="https://github.com/"+t);var r=wct(t),s=r.resource.split("."),a=null;switch(r.toString=function(N){return oq.stringify(this,N)},r.source=s.length>2?s.slice(1-s.length).join("."):r.source=r.resource,r.git_suffix=/\.git$/.test(r.pathname),r.name=decodeURIComponent((r.pathname||r.href).replace(/(^\/)|(\/$)/g,"").replace(/\.git$/,"")),r.owner=decodeURIComponent(r.user),r.source){case"git.cloudforge.com":r.owner=r.user,r.organization=s[0],r.source="cloudforge.com";break;case"visualstudio.com":if(r.resource==="vs-ssh.visualstudio.com"){a=r.name.split("/"),a.length===4&&(r.organization=a[1],r.owner=a[2],r.name=a[3],r.full_name=a[2]+"/"+a[3]);break}else{a=r.name.split("/"),a.length===2?(r.owner=a[1],r.name=a[1],r.full_name="_git/"+r.name):a.length===3?(r.name=a[2],a[0]==="DefaultCollection"?(r.owner=a[2],r.organization=a[0],r.full_name=r.organization+"/_git/"+r.name):(r.owner=a[0],r.full_name=r.owner+"/_git/"+r.name)):a.length===4&&(r.organization=a[0],r.owner=a[1],r.name=a[3],r.full_name=r.organization+"/"+r.owner+"/_git/"+r.name);break}case"dev.azure.com":case"azure.com":if(r.resource==="ssh.dev.azure.com"){a=r.name.split("/"),a.length===4&&(r.organization=a[1],r.owner=a[2],r.name=a[3]);break}else{a=r.name.split("/"),a.length===5?(r.organization=a[0],r.owner=a[1],r.name=a[4],r.full_name="_git/"+r.name):a.length===3?(r.name=a[2],a[0]==="DefaultCollection"?(r.owner=a[2],r.organization=a[0],r.full_name=r.organization+"/_git/"+r.name):(r.owner=a[0],r.full_name=r.owner+"/_git/"+r.name)):a.length===4&&(r.organization=a[0],r.owner=a[1],r.name=a[3],r.full_name=r.organization+"/"+r.owner+"/_git/"+r.name),r.query&&r.query.path&&(r.filepath=r.query.path.replace(/^\/+/g,"")),r.query&&r.query.version&&(r.ref=r.query.version.replace(/^GB/,""));break}default:a=r.name.split("/");var n=a.length-1;if(a.length>=2){var c=a.indexOf("-",2),f=a.indexOf("blob",2),p=a.indexOf("tree",2),h=a.indexOf("commit",2),E=a.indexOf("src",2),C=a.indexOf("raw",2),S=a.indexOf("edit",2);n=c>0?c-1:f>0?f-1:p>0?p-1:h>0?h-1:E>0?E-1:C>0?C-1:S>0?S-1:n,r.owner=a.slice(0,n).join("/"),r.name=a[n],h&&(r.commit=a[n+2])}r.ref="",r.filepathtype="",r.filepath="";var P=a.length>n&&a[n+1]==="-"?n+1:n;a.length>P+2&&["raw","src","blob","tree","edit"].indexOf(a[P+1])>=0&&(r.filepathtype=a[P+1],r.ref=a[P+2],a.length>P+3&&(r.filepath=a.slice(P+3).join("/"))),r.organization=r.owner;break}r.full_name||(r.full_name=r.owner,r.name&&(r.full_name&&(r.full_name+="/"),r.full_name+=r.name)),r.owner.startsWith("scm/")&&(r.source="bitbucket-server",r.owner=r.owner.replace("scm/",""),r.organization=r.owner,r.full_name=r.owner+"/"+r.name);var I=/(projects|users)\/(.*?)\/repos\/(.*?)((\/.*$)|$)/,R=I.exec(r.pathname);return R!=null&&(r.source="bitbucket-server",R[1]==="users"?r.owner="~"+R[2]:r.owner=R[2],r.organization=r.owner,r.name=R[3],a=R[4].split("/"),a.length>1&&(["raw","browse"].indexOf(a[1])>=0?(r.filepathtype=a[1],a.length>2&&(r.filepath=a.slice(2).join("/"))):a[1]==="commits"&&a.length>2&&(r.commit=a[2])),r.full_name=r.owner+"/"+r.name,r.query.at?r.ref=r.query.at:r.ref=""),r}oq.stringify=function(t,e){e=e||(t.protocols&&t.protocols.length?t.protocols.join("+"):t.protocol);var r=t.port?":"+t.port:"",s=t.user||"git",a=t.git_suffix?".git":"";switch(e){case"ssh":return r?"ssh://"+s+"@"+t.resource+r+"/"+t.full_name+a:s+"@"+t.resource+":"+t.full_name+a;case"git+ssh":case"ssh+git":case"ftp":case"ftps":return e+"://"+s+"@"+t.resource+r+"/"+t.full_name+a;case"http":case"https":var n=t.token?Bct(t):t.user&&(t.protocols.includes("http")||t.protocols.includes("https"))?t.user+"@":"";return e+"://"+n+t.resource+r+"/"+vct(t)+a;default:return t.href}};function Bct(t){switch(t.source){case"bitbucket.org":return"x-token-auth:"+t.token+"@";default:return t.token+"@"}}function vct(t){switch(t.source){case"bitbucket-server":return"scm/"+t.full_name;default:return""+t.full_name}}_ye.exports=oq});function jct(t,e){return e===1&&Hct.has(t[0])}function nS(t){let e=Array.isArray(t)?t:Mu(t);return e.map((s,a)=>Uct.test(s)?`[${s}]`:_ct.test(s)&&!jct(e,a)?`.${s}`:`[${JSON.stringify(s)}]`).join("").replace(/^\./,"")}function Gct(t,e){let r=[];if(e.methodName!==null&&r.push(he.pretty(t,e.methodName,he.Type.CODE)),e.file!==null){let s=[];s.push(he.pretty(t,e.file,he.Type.PATH)),e.line!==null&&(s.push(he.pretty(t,e.line,he.Type.NUMBER)),e.column!==null&&s.push(he.pretty(t,e.column,he.Type.NUMBER))),r.push(`(${s.join(he.pretty(t,":","grey"))})`)}return r.join(" ")}function iF(t,{manifestUpdates:e,reportedErrors:r},{fix:s}={}){let a=new Map,n=new Map,c=[...r.keys()].map(f=>[f,new Map]);for(let[f,p]of[...c,...e]){let h=r.get(f)?.map(P=>({text:P,fixable:!1}))??[],E=!1,C=t.getWorkspaceByCwd(f),S=C.manifest.exportTo({});for(let[P,I]of p){if(I.size>1){let R=[...I].map(([N,U])=>{let W=he.pretty(t.configuration,N,he.Type.INSPECT),ee=U.size>0?Gct(t.configuration,U.values().next().value):null;return ee!==null?` ${W} at ${ee}`:` ${W}`}).join("");h.push({text:`Conflict detected in constraint targeting ${he.pretty(t.configuration,P,he.Type.CODE)}; conflicting values are:${R}`,fixable:!1})}else{let[[R]]=I,N=va(S,P);if(JSON.stringify(N)===JSON.stringify(R))continue;if(!s){let U=typeof N>"u"?`Missing field ${he.pretty(t.configuration,P,he.Type.CODE)}; expected ${he.pretty(t.configuration,R,he.Type.INSPECT)}`:typeof R>"u"?`Extraneous field ${he.pretty(t.configuration,P,he.Type.CODE)} currently set to ${he.pretty(t.configuration,N,he.Type.INSPECT)}`:`Invalid field ${he.pretty(t.configuration,P,he.Type.CODE)}; expected ${he.pretty(t.configuration,R,he.Type.INSPECT)}, found ${he.pretty(t.configuration,N,he.Type.INSPECT)}`;h.push({text:U,fixable:!0});continue}typeof R>"u"?A0(S,P):Jd(S,P,R),E=!0}E&&a.set(C,S)}h.length>0&&n.set(C,h)}return{changedWorkspaces:a,remainingErrors:n}}function rEe(t,{configuration:e}){let r={children:[]};for(let[s,a]of t){let n=[];for(let f of a){let p=f.text.split(/\n/);f.fixable&&(p[0]=`${he.pretty(e,"\u2699","gray")} ${p[0]}`),n.push({value:he.tuple(he.Type.NO_HINT,p[0]),children:p.slice(1).map(h=>({value:he.tuple(he.Type.NO_HINT,h)}))})}let c={value:he.tuple(he.Type.LOCATOR,s.anchoredLocator),children:je.sortMap(n,f=>f.value[1])};r.children.push(c)}return r.children=je.sortMap(r.children,s=>s.value[1]),r}var WC,Uct,_ct,Hct,iS=Xe(()=>{Ge();ql();WC=class{constructor(e){this.indexedFields=e;this.items=[];this.indexes={};this.clear()}clear(){this.items=[];for(let e of this.indexedFields)this.indexes[e]=new Map}insert(e){this.items.push(e);for(let r of this.indexedFields){let s=Object.hasOwn(e,r)?e[r]:void 0;if(typeof s>"u")continue;je.getArrayWithDefault(this.indexes[r],s).push(e)}return e}find(e){if(typeof e>"u")return this.items;let r=Object.entries(e);if(r.length===0)return this.items;let s=[],a;for(let[c,f]of r){let p=c,h=Object.hasOwn(this.indexes,p)?this.indexes[p]:void 0;if(typeof h>"u"){s.push([p,f]);continue}let E=new Set(h.get(f)??[]);if(E.size===0)return[];if(typeof a>"u")a=E;else for(let C of a)E.has(C)||a.delete(C);if(a.size===0)break}let n=[...a??[]];return s.length>0&&(n=n.filter(c=>{for(let[f,p]of s)if(!(typeof p<"u"?Object.hasOwn(c,f)&&c[f]===p:Object.hasOwn(c,f)===!1))return!1;return!0})),n}},Uct=/^[0-9]+$/,_ct=/^[a-zA-Z0-9_]+$/,Hct=new Set(["scripts",...Ut.allDependencies])});var nEe=_((_Yt,vq)=>{var qct;(function(t){var e=function(){return{"append/2":[new t.type.Rule(new t.type.Term("append",[new t.type.Var("X"),new t.type.Var("L")]),new t.type.Term("foldl",[new t.type.Term("append",[]),new t.type.Var("X"),new t.type.Term("[]",[]),new t.type.Var("L")]))],"append/3":[new t.type.Rule(new t.type.Term("append",[new t.type.Term("[]",[]),new t.type.Var("X"),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("append",[new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("S")])]),new t.type.Term("append",[new t.type.Var("T"),new t.type.Var("X"),new t.type.Var("S")]))],"member/2":[new t.type.Rule(new t.type.Term("member",[new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("_")])]),null),new t.type.Rule(new t.type.Term("member",[new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("_"),new t.type.Var("Xs")])]),new t.type.Term("member",[new t.type.Var("X"),new t.type.Var("Xs")]))],"permutation/2":[new t.type.Rule(new t.type.Term("permutation",[new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("permutation",[new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("permutation",[new t.type.Var("T"),new t.type.Var("P")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("P")]),new t.type.Term("append",[new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("Y")]),new t.type.Var("S")])])]))],"maplist/2":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("X")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("Xs")])]))],"maplist/3":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs")])]))],"maplist/4":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs")])]))],"maplist/5":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds")])]))],"maplist/6":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")]),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Es")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D"),new t.type.Var("E")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds"),new t.type.Var("Es")])]))],"maplist/7":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")]),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Es")]),new t.type.Term(".",[new t.type.Var("F"),new t.type.Var("Fs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D"),new t.type.Var("E"),new t.type.Var("F")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds"),new t.type.Var("Es"),new t.type.Var("Fs")])]))],"maplist/8":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")]),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Es")]),new t.type.Term(".",[new t.type.Var("F"),new t.type.Var("Fs")]),new t.type.Term(".",[new t.type.Var("G"),new t.type.Var("Gs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D"),new t.type.Var("E"),new t.type.Var("F"),new t.type.Var("G")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds"),new t.type.Var("Es"),new t.type.Var("Fs"),new t.type.Var("Gs")])]))],"include/3":[new t.type.Rule(new t.type.Term("include",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("include",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("L")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P"),new t.type.Var("A")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("A"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Term("[]",[])]),new t.type.Var("B")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("F"),new t.type.Var("B")]),new t.type.Term(",",[new t.type.Term(";",[new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("F")]),new t.type.Term(",",[new t.type.Term("=",[new t.type.Var("L"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("S")])]),new t.type.Term("!",[])])]),new t.type.Term("=",[new t.type.Var("L"),new t.type.Var("S")])]),new t.type.Term("include",[new t.type.Var("P"),new t.type.Var("T"),new t.type.Var("S")])])])])]))],"exclude/3":[new t.type.Rule(new t.type.Term("exclude",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("exclude",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("exclude",[new t.type.Var("P"),new t.type.Var("T"),new t.type.Var("E")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P"),new t.type.Var("L")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("L"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Term("[]",[])]),new t.type.Var("Q")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("R"),new t.type.Var("Q")]),new t.type.Term(";",[new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("R")]),new t.type.Term(",",[new t.type.Term("!",[]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("E")])])]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("E")])])])])])])]))],"foldl/4":[new t.type.Rule(new t.type.Term("foldl",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Var("I"),new t.type.Var("I")]),null),new t.type.Rule(new t.type.Term("foldl",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("I"),new t.type.Var("R")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P"),new t.type.Var("L")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("L"),new t.type.Term(".",[new t.type.Var("I"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])])])]),new t.type.Var("L2")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P2"),new t.type.Var("L2")]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P2")]),new t.type.Term("foldl",[new t.type.Var("P"),new t.type.Var("T"),new t.type.Var("X"),new t.type.Var("R")])])])])]))],"select/3":[new t.type.Rule(new t.type.Term("select",[new t.type.Var("E"),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Xs")]),new t.type.Var("Xs")]),null),new t.type.Rule(new t.type.Term("select",[new t.type.Var("E"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Ys")])]),new t.type.Term("select",[new t.type.Var("E"),new t.type.Var("Xs"),new t.type.Var("Ys")]))],"sum_list/2":[new t.type.Rule(new t.type.Term("sum_list",[new t.type.Term("[]",[]),new t.type.Num(0,!1)]),null),new t.type.Rule(new t.type.Term("sum_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("sum_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term("is",[new t.type.Var("S"),new t.type.Term("+",[new t.type.Var("X"),new t.type.Var("Y")])])]))],"max_list/2":[new t.type.Rule(new t.type.Term("max_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])]),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("max_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("max_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term(";",[new t.type.Term(",",[new t.type.Term(">=",[new t.type.Var("X"),new t.type.Var("Y")]),new t.type.Term(",",[new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("X")]),new t.type.Term("!",[])])]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("Y")])])]))],"min_list/2":[new t.type.Rule(new t.type.Term("min_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])]),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("min_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("min_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term(";",[new t.type.Term(",",[new t.type.Term("=<",[new t.type.Var("X"),new t.type.Var("Y")]),new t.type.Term(",",[new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("X")]),new t.type.Term("!",[])])]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("Y")])])]))],"prod_list/2":[new t.type.Rule(new t.type.Term("prod_list",[new t.type.Term("[]",[]),new t.type.Num(1,!1)]),null),new t.type.Rule(new t.type.Term("prod_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("prod_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term("is",[new t.type.Var("S"),new t.type.Term("*",[new t.type.Var("X"),new t.type.Var("Y")])])]))],"last/2":[new t.type.Rule(new t.type.Term("last",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])]),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("last",[new t.type.Term(".",[new t.type.Var("_"),new t.type.Var("Xs")]),new t.type.Var("X")]),new t.type.Term("last",[new t.type.Var("Xs"),new t.type.Var("X")]))],"prefix/2":[new t.type.Rule(new t.type.Term("prefix",[new t.type.Var("Part"),new t.type.Var("Whole")]),new t.type.Term("append",[new t.type.Var("Part"),new t.type.Var("_"),new t.type.Var("Whole")]))],"nth0/3":[new t.type.Rule(new t.type.Term("nth0",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")])]),new t.type.Term(",",[new t.type.Term(">=",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")]),new t.type.Term("!",[])])])]))],"nth1/3":[new t.type.Rule(new t.type.Term("nth1",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")])]),new t.type.Term(",",[new t.type.Term(">",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")]),new t.type.Term("!",[])])])]))],"nth0/4":[new t.type.Rule(new t.type.Term("nth0",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")])]),new t.type.Term(",",[new t.type.Term(">=",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term("!",[])])])]))],"nth1/4":[new t.type.Rule(new t.type.Term("nth1",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")])]),new t.type.Term(",",[new t.type.Term(">",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term("!",[])])])]))],"nth/5":[new t.type.Rule(new t.type.Term("nth",[new t.type.Var("N"),new t.type.Var("N"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("X"),new t.type.Var("Xs")]),null),new t.type.Rule(new t.type.Term("nth",[new t.type.Var("N"),new t.type.Var("O"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("Y"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Ys")])]),new t.type.Term(",",[new t.type.Term("is",[new t.type.Var("M"),new t.type.Term("+",[new t.type.Var("N"),new t.type.Num(1,!1)])]),new t.type.Term("nth",[new t.type.Var("M"),new t.type.Var("O"),new t.type.Var("Xs"),new t.type.Var("Y"),new t.type.Var("Ys")])]))],"length/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(!t.type.is_variable(f)&&!t.type.is_integer(f))s.throw_error(t.error.type("integer",f,n.indicator));else if(t.type.is_integer(f)&&f.value<0)s.throw_error(t.error.domain("not_less_than_zero",f,n.indicator));else{var p=new t.type.Term("length",[c,new t.type.Num(0,!1),f]);t.type.is_integer(f)&&(p=new t.type.Term(",",[p,new t.type.Term("!",[])])),s.prepend([new t.type.State(a.goal.replace(p),a.substitution,a)])}},"length/3":[new t.type.Rule(new t.type.Term("length",[new t.type.Term("[]",[]),new t.type.Var("N"),new t.type.Var("N")]),null),new t.type.Rule(new t.type.Term("length",[new t.type.Term(".",[new t.type.Var("_"),new t.type.Var("X")]),new t.type.Var("A"),new t.type.Var("N")]),new t.type.Term(",",[new t.type.Term("succ",[new t.type.Var("A"),new t.type.Var("B")]),new t.type.Term("length",[new t.type.Var("X"),new t.type.Var("B"),new t.type.Var("N")])]))],"replicate/3":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_integer(f))s.throw_error(t.error.type("integer",f,n.indicator));else if(f.value<0)s.throw_error(t.error.domain("not_less_than_zero",f,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type("list",p,n.indicator));else{for(var h=new t.type.Term("[]"),E=0;E0;C--)E[C].equals(E[C-1])&&E.splice(C,1);for(var S=new t.type.Term("[]"),C=E.length-1;C>=0;C--)S=new t.type.Term(".",[E[C],S]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[S,f])),a.substitution,a)])}}},"msort/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type("list",f,n.indicator));else{for(var p=[],h=c;h.indicator==="./2";)p.push(h.args[0]),h=h.args[1];if(t.type.is_variable(h))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_empty_list(h))s.throw_error(t.error.type("list",c,n.indicator));else{for(var E=p.sort(t.compare),C=new t.type.Term("[]"),S=E.length-1;S>=0;S--)C=new t.type.Term(".",[E[S],C]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[C,f])),a.substitution,a)])}}},"keysort/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type("list",f,n.indicator));else{for(var p=[],h,E=c;E.indicator==="./2";){if(h=E.args[0],t.type.is_variable(h)){s.throw_error(t.error.instantiation(n.indicator));return}else if(!t.type.is_term(h)||h.indicator!=="-/2"){s.throw_error(t.error.type("pair",h,n.indicator));return}h.args[0].pair=h.args[1],p.push(h.args[0]),E=E.args[1]}if(t.type.is_variable(E))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_empty_list(E))s.throw_error(t.error.type("list",c,n.indicator));else{for(var C=p.sort(t.compare),S=new t.type.Term("[]"),P=C.length-1;P>=0;P--)S=new t.type.Term(".",[new t.type.Term("-",[C[P],C[P].pair]),S]),delete C[P].pair;s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[S,f])),a.substitution,a)])}}},"take/3":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f)||t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_list(f))s.throw_error(t.error.type("list",f,n.indicator));else if(!t.type.is_integer(c))s.throw_error(t.error.type("integer",c,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type("list",p,n.indicator));else{for(var h=c.value,E=[],C=f;h>0&&C.indicator==="./2";)E.push(C.args[0]),C=C.args[1],h--;if(h===0){for(var S=new t.type.Term("[]"),h=E.length-1;h>=0;h--)S=new t.type.Term(".",[E[h],S]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[S,p])),a.substitution,a)])}}},"drop/3":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f)||t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_list(f))s.throw_error(t.error.type("list",f,n.indicator));else if(!t.type.is_integer(c))s.throw_error(t.error.type("integer",c,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type("list",p,n.indicator));else{for(var h=c.value,E=[],C=f;h>0&&C.indicator==="./2";)E.push(C.args[0]),C=C.args[1],h--;h===0&&s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[C,p])),a.substitution,a)])}},"reverse/2":function(s,a,n){var c=n.args[0],f=n.args[1],p=t.type.is_instantiated_list(c),h=t.type.is_instantiated_list(f);if(t.type.is_variable(c)&&t.type.is_variable(f))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(c)&&!t.type.is_fully_list(c))s.throw_error(t.error.type("list",c,n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type("list",f,n.indicator));else if(!p&&!h)s.throw_error(t.error.instantiation(n.indicator));else{for(var E=p?c:f,C=new t.type.Term("[]",[]);E.indicator==="./2";)C=new t.type.Term(".",[E.args[0],C]),E=E.args[1];s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[C,p?f:c])),a.substitution,a)])}},"list_to_set/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else{for(var p=c,h=[];p.indicator==="./2";)h.push(p.args[0]),p=p.args[1];if(t.type.is_variable(p))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_term(p)||p.indicator!=="[]/0")s.throw_error(t.error.type("list",c,n.indicator));else{for(var E=[],C=new t.type.Term("[]",[]),S,P=0;P=0;P--)C=new t.type.Term(".",[E[P],C]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[f,C])),a.substitution,a)])}}}}},r=["append/2","append/3","member/2","permutation/2","maplist/2","maplist/3","maplist/4","maplist/5","maplist/6","maplist/7","maplist/8","include/3","exclude/3","foldl/4","sum_list/2","max_list/2","min_list/2","prod_list/2","last/2","prefix/2","nth0/3","nth1/3","nth0/4","nth1/4","length/2","replicate/3","select/3","sort/2","msort/2","keysort/2","take/3","drop/3","reverse/2","list_to_set/2"];typeof vq<"u"?vq.exports=function(s){t=s,new t.type.Module("lists",e(),r)}:new t.type.Module("lists",e(),r)})(qct)});var yEe=_($r=>{"use strict";var bm=process.platform==="win32",Sq="aes-256-cbc",Wct="sha256",oEe="The current environment doesn't support interactive reading from TTY.",si=Ie("fs"),iEe=process.binding("tty_wrap").TTY,bq=Ie("child_process"),V0=Ie("path"),Pq={prompt:"> ",hideEchoBack:!1,mask:"*",limit:[],limitMessage:"Input another, please.$<( [)limit(])>",defaultInput:"",trueValue:[],falseValue:[],caseSensitive:!1,keepWhitespace:!1,encoding:"utf8",bufferSize:1024,print:void 0,history:!0,cd:!1,phContent:void 0,preCheck:void 0},Xp="none",Zu,VC,sEe=!1,Y0,oF,Dq,Yct=0,Rq="",Dm=[],aF,aEe=!1,xq=!1,sS=!1;function lEe(t){function e(r){return r.replace(/[^\w\u0080-\uFFFF]/g,function(s){return"#"+s.charCodeAt(0)+";"})}return oF.concat(function(r){var s=[];return Object.keys(r).forEach(function(a){r[a]==="boolean"?t[a]&&s.push("--"+a):r[a]==="string"&&t[a]&&s.push("--"+a,e(t[a]))}),s}({display:"string",displayOnly:"boolean",keyIn:"boolean",hideEchoBack:"boolean",mask:"string",limit:"string",caseSensitive:"boolean"}))}function Vct(t,e){function r(U){var W,ee="",ie;for(Dq=Dq||Ie("os").tmpdir();;){W=V0.join(Dq,U+ee);try{ie=si.openSync(W,"wx")}catch(ue){if(ue.code==="EEXIST"){ee++;continue}else throw ue}si.closeSync(ie);break}return W}var s,a,n,c={},f,p,h=r("readline-sync.stdout"),E=r("readline-sync.stderr"),C=r("readline-sync.exit"),S=r("readline-sync.done"),P=Ie("crypto"),I,R,N;I=P.createHash(Wct),I.update(""+process.pid+Yct+++Math.random()),N=I.digest("hex"),R=P.createDecipher(Sq,N),s=lEe(t),bm?(a=process.env.ComSpec||"cmd.exe",process.env.Q='"',n=["/V:ON","/S","/C","(%Q%"+a+"%Q% /V:ON /S /C %Q%%Q%"+Y0+"%Q%"+s.map(function(U){return" %Q%"+U+"%Q%"}).join("")+" & (echo !ERRORLEVEL!)>%Q%"+C+"%Q%%Q%) 2>%Q%"+E+"%Q% |%Q%"+process.execPath+"%Q% %Q%"+__dirname+"\\encrypt.js%Q% %Q%"+Sq+"%Q% %Q%"+N+"%Q% >%Q%"+h+"%Q% & (echo 1)>%Q%"+S+"%Q%"]):(a="/bin/sh",n=["-c",'("'+Y0+'"'+s.map(function(U){return" '"+U.replace(/'/g,"'\\''")+"'"}).join("")+'; echo $?>"'+C+'") 2>"'+E+'" |"'+process.execPath+'" "'+__dirname+'/encrypt.js" "'+Sq+'" "'+N+'" >"'+h+'"; echo 1 >"'+S+'"']),sS&&sS("_execFileSync",s);try{bq.spawn(a,n,e)}catch(U){c.error=new Error(U.message),c.error.method="_execFileSync - spawn",c.error.program=a,c.error.args=n}for(;si.readFileSync(S,{encoding:t.encoding}).trim()!=="1";);return(f=si.readFileSync(C,{encoding:t.encoding}).trim())==="0"?c.input=R.update(si.readFileSync(h,{encoding:"binary"}),"hex",t.encoding)+R.final(t.encoding):(p=si.readFileSync(E,{encoding:t.encoding}).trim(),c.error=new Error(oEe+(p?` `+p:"")),c.error.method="_execFileSync",c.error.program=a,c.error.args=n,c.error.extMessage=p,c.error.exitCode=+f),si.unlinkSync(h),si.unlinkSync(E),si.unlinkSync(C),si.unlinkSync(S),c}function Jct(t){var e,r={},s,a={env:process.env,encoding:t.encoding};if(Y0||(bm?process.env.PSModulePath?(Y0="powershell.exe",oF=["-ExecutionPolicy","Bypass","-File",__dirname+"\\read.ps1"]):(Y0="cscript.exe",oF=["//nologo",__dirname+"\\read.cs.js"]):(Y0="/bin/sh",oF=[__dirname+"/read.sh"])),bm&&!process.env.PSModulePath&&(a.stdio=[process.stdin]),bq.execFileSync){e=lEe(t),sS&&sS("execFileSync",e);try{r.input=bq.execFileSync(Y0,e,a)}catch(n){s=n.stderr?(n.stderr+"").trim():"",r.error=new Error(oEe+(s?` `+s:"")),r.error.method="execFileSync",r.error.program=Y0,r.error.args=e,r.error.extMessage=s,r.error.exitCode=n.status,r.error.code=n.code,r.error.signal=n.signal}}else r=Vct(t,a);return r.error||(r.input=r.input.replace(/^\s*'|'\s*$/g,""),t.display=""),r}function kq(t){var e="",r=t.display,s=!t.display&&t.keyIn&&t.hideEchoBack&&!t.mask;function a(){var n=Jct(t);if(n.error)throw n.error;return n.input}return xq&&xq(t),function(){var n,c,f;function p(){return n||(n=process.binding("fs"),c=process.binding("constants")),n}if(typeof Xp=="string")if(Xp=null,bm){if(f=function(h){var E=h.replace(/^\D+/,"").split("."),C=0;return(E[0]=+E[0])&&(C+=E[0]*1e4),(E[1]=+E[1])&&(C+=E[1]*100),(E[2]=+E[2])&&(C+=E[2]),C}(process.version),!(f>=20302&&f<40204||f>=5e4&&f<50100||f>=50600&&f<60200)&&process.stdin.isTTY)process.stdin.pause(),Xp=process.stdin.fd,VC=process.stdin._handle;else try{Xp=p().open("CONIN$",c.O_RDWR,parseInt("0666",8)),VC=new iEe(Xp,!0)}catch{}if(process.stdout.isTTY)Zu=process.stdout.fd;else{try{Zu=si.openSync("\\\\.\\CON","w")}catch{}if(typeof Zu!="number")try{Zu=p().open("CONOUT$",c.O_RDWR,parseInt("0666",8))}catch{}}}else{if(process.stdin.isTTY){process.stdin.pause();try{Xp=si.openSync("/dev/tty","r"),VC=process.stdin._handle}catch{}}else try{Xp=si.openSync("/dev/tty","r"),VC=new iEe(Xp,!1)}catch{}if(process.stdout.isTTY)Zu=process.stdout.fd;else try{Zu=si.openSync("/dev/tty","w")}catch{}}}(),function(){var n,c,f=!t.hideEchoBack&&!t.keyIn,p,h,E,C,S;aF="";function P(I){return I===sEe?!0:VC.setRawMode(I)!==0?!1:(sEe=I,!0)}if(aEe||!VC||typeof Zu!="number"&&(t.display||!f)){e=a();return}if(t.display&&(si.writeSync(Zu,t.display),t.display=""),!t.displayOnly){if(!P(!f)){e=a();return}for(h=t.keyIn?1:t.bufferSize,p=Buffer.allocUnsafe&&Buffer.alloc?Buffer.alloc(h):new Buffer(h),t.keyIn&&t.limit&&(c=new RegExp("[^"+t.limit+"]","g"+(t.caseSensitive?"":"i")));;){E=0;try{E=si.readSync(Xp,p,0,h)}catch(I){if(I.code!=="EOF"){P(!1),e+=a();return}}if(E>0?(C=p.toString(t.encoding,0,E),aF+=C):(C=` `,aF+="\0"),C&&typeof(S=(C.match(/^(.*?)[\r\n]/)||[])[1])=="string"&&(C=S,n=!0),C&&(C=C.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"")),C&&c&&(C=C.replace(c,"")),C&&(f||(t.hideEchoBack?t.mask&&si.writeSync(Zu,new Array(C.length+1).join(t.mask)):si.writeSync(Zu,C)),e+=C),!t.keyIn&&n||t.keyIn&&e.length>=h)break}!f&&!s&&si.writeSync(Zu,` `),P(!1)}}(),t.print&&!s&&t.print(r+(t.displayOnly?"":(t.hideEchoBack?new Array(e.length+1).join(t.mask):e)+` `),t.encoding),t.displayOnly?"":Rq=t.keepWhitespace||t.keyIn?e:e.trim()}function Kct(t,e){var r=[];function s(a){a!=null&&(Array.isArray(a)?a.forEach(s):(!e||e(a))&&r.push(a))}return s(t),r}function Fq(t){return t.replace(/[\x00-\x7f]/g,function(e){return"\\x"+("00"+e.charCodeAt().toString(16)).substr(-2)})}function Vs(){var t=Array.prototype.slice.call(arguments),e,r;return t.length&&typeof t[0]=="boolean"&&(r=t.shift(),r&&(e=Object.keys(Pq),t.unshift(Pq))),t.reduce(function(s,a){return a==null||(a.hasOwnProperty("noEchoBack")&&!a.hasOwnProperty("hideEchoBack")&&(a.hideEchoBack=a.noEchoBack,delete a.noEchoBack),a.hasOwnProperty("noTrim")&&!a.hasOwnProperty("keepWhitespace")&&(a.keepWhitespace=a.noTrim,delete a.noTrim),r||(e=Object.keys(a)),e.forEach(function(n){var c;if(a.hasOwnProperty(n))switch(c=a[n],n){case"mask":case"limitMessage":case"defaultInput":case"encoding":c=c!=null?c+"":"",c&&n!=="limitMessage"&&(c=c.replace(/[\r\n]/g,"")),s[n]=c;break;case"bufferSize":!isNaN(c=parseInt(c,10))&&typeof c=="number"&&(s[n]=c);break;case"displayOnly":case"keyIn":case"hideEchoBack":case"caseSensitive":case"keepWhitespace":case"history":case"cd":s[n]=!!c;break;case"limit":case"trueValue":case"falseValue":s[n]=Kct(c,function(f){var p=typeof f;return p==="string"||p==="number"||p==="function"||f instanceof RegExp}).map(function(f){return typeof f=="string"?f.replace(/[\r\n]/g,""):f});break;case"print":case"phContent":case"preCheck":s[n]=typeof c=="function"?c:void 0;break;case"prompt":case"display":s[n]=c??"";break}})),s},{})}function Qq(t,e,r){return e.some(function(s){var a=typeof s;return a==="string"?r?t===s:t.toLowerCase()===s.toLowerCase():a==="number"?parseFloat(t)===s:a==="function"?s(t):s instanceof RegExp?s.test(t):!1})}function Nq(t,e){var r=V0.normalize(bm?(process.env.HOMEDRIVE||"")+(process.env.HOMEPATH||""):process.env.HOME||"").replace(/[\/\\]+$/,"");return t=V0.normalize(t),e?t.replace(/^~(?=\/|\\|$)/,r):t.replace(new RegExp("^"+Fq(r)+"(?=\\/|\\\\|$)",bm?"i":""),"~")}function JC(t,e){var r="(?:\\(([\\s\\S]*?)\\))?(\\w+|.-.)(?:\\(([\\s\\S]*?)\\))?",s=new RegExp("(\\$)?(\\$<"+r+">)","g"),a=new RegExp("(\\$)?(\\$\\{"+r+"\\})","g");function n(c,f,p,h,E,C){var S;return f||typeof(S=e(E))!="string"?p:S?(h||"")+S+(C||""):""}return t.replace(s,n).replace(a,n)}function cEe(t,e,r){var s,a=[],n=-1,c=0,f="",p;function h(E,C){return C.length>3?(E.push(C[0]+"..."+C[C.length-1]),p=!0):C.length&&(E=E.concat(C)),E}return s=t.reduce(function(E,C){return E.concat((C+"").split(""))},[]).reduce(function(E,C){var S,P;return e||(C=C.toLowerCase()),S=/^\d$/.test(C)?1:/^[A-Z]$/.test(C)?2:/^[a-z]$/.test(C)?3:0,r&&S===0?f+=C:(P=C.charCodeAt(0),S&&S===n&&P===c+1?a.push(C):(E=h(E,a),a=[C],n=S),c=P),E},[]),s=h(s,a),f&&(s.push(f),p=!0),{values:s,suppressed:p}}function uEe(t,e){return t.join(t.length>2?", ":e?" / ":"/")}function fEe(t,e){var r,s,a={},n;if(e.phContent&&(r=e.phContent(t,e)),typeof r!="string")switch(t){case"hideEchoBack":case"mask":case"defaultInput":case"caseSensitive":case"keepWhitespace":case"encoding":case"bufferSize":case"history":case"cd":r=e.hasOwnProperty(t)?typeof e[t]=="boolean"?e[t]?"on":"off":e[t]+"":"";break;case"limit":case"trueValue":case"falseValue":s=e[e.hasOwnProperty(t+"Src")?t+"Src":t],e.keyIn?(a=cEe(s,e.caseSensitive),s=a.values):s=s.filter(function(c){var f=typeof c;return f==="string"||f==="number"}),r=uEe(s,a.suppressed);break;case"limitCount":case"limitCountNotZero":r=e[e.hasOwnProperty("limitSrc")?"limitSrc":"limit"].length,r=r||t!=="limitCountNotZero"?r+"":"";break;case"lastInput":r=Rq;break;case"cwd":case"CWD":case"cwdHome":r=process.cwd(),t==="CWD"?r=V0.basename(r):t==="cwdHome"&&(r=Nq(r));break;case"date":case"time":case"localeDate":case"localeTime":r=new Date()["to"+t.replace(/^./,function(c){return c.toUpperCase()})+"String"]();break;default:typeof(n=(t.match(/^history_m(\d+)$/)||[])[1])=="string"&&(r=Dm[Dm.length-n]||"")}return r}function AEe(t){var e=/^(.)-(.)$/.exec(t),r="",s,a,n,c;if(!e)return null;for(s=e[1].charCodeAt(0),a=e[2].charCodeAt(0),c=s And the length must be: $`,trueValue:null,falseValue:null,caseSensitive:!0},e,{history:!1,cd:!1,phContent:function(P){return P==="charlist"?r.text:P==="length"?s+"..."+a:null}}),c,f,p,h,E,C,S;for(e=e||{},c=JC(e.charlist?e.charlist+"":"$",AEe),(isNaN(s=parseInt(e.min,10))||typeof s!="number")&&(s=12),(isNaN(a=parseInt(e.max,10))||typeof a!="number")&&(a=24),h=new RegExp("^["+Fq(c)+"]{"+s+","+a+"}$"),r=cEe([c],n.caseSensitive,!0),r.text=uEe(r.values,r.suppressed),f=e.confirmMessage!=null?e.confirmMessage:"Reinput a same one to confirm it: ",p=e.unmatchMessage!=null?e.unmatchMessage:"It differs from first one. Hit only the Enter key if you want to retry from first one.",t==null&&(t="Input new password: "),E=n.limitMessage;!S;)n.limit=h,n.limitMessage=E,C=$r.question(t,n),n.limit=[C,""],n.limitMessage=p,S=$r.question(f,n);return C};function gEe(t,e,r){var s;function a(n){return s=r(n),!isNaN(s)&&typeof s=="number"}return $r.question(t,Vs({limitMessage:"Input valid number, please."},e,{limit:a,cd:!1})),s}$r.questionInt=function(t,e){return gEe(t,e,function(r){return parseInt(r,10)})};$r.questionFloat=function(t,e){return gEe(t,e,parseFloat)};$r.questionPath=function(t,e){var r,s="",a=Vs({hideEchoBack:!1,limitMessage:`$Input valid path, please.$<( Min:)min>$<( Max:)max>`,history:!0,cd:!0},e,{keepWhitespace:!1,limit:function(n){var c,f,p;n=Nq(n,!0),s="";function h(E){E.split(/\/|\\/).reduce(function(C,S){var P=V0.resolve(C+=S+V0.sep);if(!si.existsSync(P))si.mkdirSync(P);else if(!si.statSync(P).isDirectory())throw new Error("Non directory already exists: "+P);return C},"")}try{if(c=si.existsSync(n),r=c?si.realpathSync(n):V0.resolve(n),!e.hasOwnProperty("exists")&&!c||typeof e.exists=="boolean"&&e.exists!==c)return s=(c?"Already exists":"No such file or directory")+": "+r,!1;if(!c&&e.create&&(e.isDirectory?h(r):(h(V0.dirname(r)),si.closeSync(si.openSync(r,"w"))),r=si.realpathSync(r)),c&&(e.min||e.max||e.isFile||e.isDirectory)){if(f=si.statSync(r),e.isFile&&!f.isFile())return s="Not file: "+r,!1;if(e.isDirectory&&!f.isDirectory())return s="Not directory: "+r,!1;if(e.min&&f.size<+e.min||e.max&&f.size>+e.max)return s="Size "+f.size+" is out of range: "+r,!1}if(typeof e.validate=="function"&&(p=e.validate(r))!==!0)return typeof p=="string"&&(s=p),!1}catch(E){return s=E+"",!1}return!0},phContent:function(n){return n==="error"?s:n!=="min"&&n!=="max"?null:e.hasOwnProperty(n)?e[n]+"":""}});return e=e||{},t==null&&(t='Input path (you can "cd" and "pwd"): '),$r.question(t,a),r};function dEe(t,e){var r={},s={};return typeof t=="object"?(Object.keys(t).forEach(function(a){typeof t[a]=="function"&&(s[e.caseSensitive?a:a.toLowerCase()]=t[a])}),r.preCheck=function(a){var n;return r.args=Tq(a),n=r.args[0]||"",e.caseSensitive||(n=n.toLowerCase()),r.hRes=n!=="_"&&s.hasOwnProperty(n)?s[n].apply(a,r.args.slice(1)):s.hasOwnProperty("_")?s._.apply(a,r.args):null,{res:a,forceNext:!1}},s.hasOwnProperty("_")||(r.limit=function(){var a=r.args[0]||"";return e.caseSensitive||(a=a.toLowerCase()),s.hasOwnProperty(a)})):r.preCheck=function(a){return r.args=Tq(a),r.hRes=typeof t=="function"?t.apply(a,r.args):!0,{res:a,forceNext:!1}},r}$r.promptCL=function(t,e){var r=Vs({hideEchoBack:!1,limitMessage:"Requested command is not available.",caseSensitive:!1,history:!0},e),s=dEe(t,r);return r.limit=s.limit,r.preCheck=s.preCheck,$r.prompt(r),s.args};$r.promptLoop=function(t,e){for(var r=Vs({hideEchoBack:!1,trueValue:null,falseValue:null,caseSensitive:!1,history:!0},e);!t($r.prompt(r)););};$r.promptCLLoop=function(t,e){var r=Vs({hideEchoBack:!1,limitMessage:"Requested command is not available.",caseSensitive:!1,history:!0},e),s=dEe(t,r);for(r.limit=s.limit,r.preCheck=s.preCheck;$r.prompt(r),!s.hRes;);};$r.promptSimShell=function(t){return $r.prompt(Vs({hideEchoBack:!1,history:!0},t,{prompt:function(){return bm?"$>":(process.env.USER||"")+(process.env.HOSTNAME?"@"+process.env.HOSTNAME.replace(/\..*$/,""):"")+":$$ "}()}))};function mEe(t,e,r){var s;return t==null&&(t="Are you sure? "),(!e||e.guide!==!1)&&(t+="")&&(t=t.replace(/\s*:?\s*$/,"")+" [y/n]: "),s=$r.keyIn(t,Vs(e,{hideEchoBack:!1,limit:r,trueValue:"y",falseValue:"n",caseSensitive:!1})),typeof s=="boolean"?s:""}$r.keyInYN=function(t,e){return mEe(t,e)};$r.keyInYNStrict=function(t,e){return mEe(t,e,"yn")};$r.keyInPause=function(t,e){t==null&&(t="Continue..."),(!e||e.guide!==!1)&&(t+="")&&(t=t.replace(/\s+$/,"")+" (Hit any key)"),$r.keyIn(t,Vs({limit:null},e,{hideEchoBack:!0,mask:""}))};$r.keyInSelect=function(t,e,r){var s=Vs({hideEchoBack:!1},r,{trueValue:null,falseValue:null,caseSensitive:!1,phContent:function(p){return p==="itemsCount"?t.length+"":p==="firstItem"?(t[0]+"").trim():p==="lastItem"?(t[t.length-1]+"").trim():null}}),a="",n={},c=49,f=` `;if(!Array.isArray(t)||!t.length||t.length>35)throw"`items` must be Array (max length: 35).";return t.forEach(function(p,h){var E=String.fromCharCode(c);a+=E,n[E]=h,f+="["+E+"] "+(p+"").trim()+` `,c=c===57?97:c+1}),(!r||r.cancel!==!1)&&(a+="0",n[0]=-1,f+="[0] "+(r&&r.cancel!=null&&typeof r.cancel!="boolean"?(r.cancel+"").trim():"CANCEL")+` `),s.limit=a,f+=` `,e==null&&(e="Choose one from list: "),(e+="")&&((!r||r.guide!==!1)&&(e=e.replace(/\s*:?\s*$/,"")+" [$]: "),f+=e),n[$r.keyIn(f,s).toLowerCase()]};$r.getRawInput=function(){return aF};function oS(t,e){var r;return e.length&&(r={},r[t]=e[0]),$r.setDefaultOptions(r)[t]}$r.setPrint=function(){return oS("print",arguments)};$r.setPrompt=function(){return oS("prompt",arguments)};$r.setEncoding=function(){return oS("encoding",arguments)};$r.setMask=function(){return oS("mask",arguments)};$r.setBufferSize=function(){return oS("bufferSize",arguments)}});var Oq=_((jYt,ec)=>{(function(){var t={major:0,minor:2,patch:66,status:"beta"};tau_file_system={files:{},open:function(w,b,y){var F=tau_file_system.files[w];if(!F){if(y==="read")return null;F={path:w,text:"",type:b,get:function(z,X){return X===this.text.length||X>this.text.length?"end_of_file":this.text.substring(X,X+z)},put:function(z,X){return X==="end_of_file"?(this.text+=z,!0):X==="past_end_of_file"?null:(this.text=this.text.substring(0,X)+z+this.text.substring(X+z.length),!0)},get_byte:function(z){if(z==="end_of_stream")return-1;var X=Math.floor(z/2);if(this.text.length<=X)return-1;var $=n(this.text[Math.floor(z/2)],0);return z%2===0?$&255:$/256>>>0},put_byte:function(z,X){var $=X==="end_of_stream"?this.text.length:Math.floor(X/2);if(this.text.length<$)return null;var oe=this.text.length===$?-1:n(this.text[Math.floor(X/2)],0);return X%2===0?(oe=oe/256>>>0,oe=(oe&255)<<8|z&255):(oe=oe&255,oe=(z&255)<<8|oe&255),this.text.length===$?this.text+=c(oe):this.text=this.text.substring(0,$)+c(oe)+this.text.substring($+1),!0},flush:function(){return!0},close:function(){var z=tau_file_system.files[this.path];return z?!0:null}},tau_file_system.files[w]=F}return y==="write"&&(F.text=""),F}},tau_user_input={buffer:"",get:function(w,b){for(var y;tau_user_input.buffer.length\?\@\^\~\\]+|'(?:[^']*?(?:\\(?:x?\d+)?\\)*(?:'')*(?:\\')*)*')/,number:/^(?:0o[0-7]+|0x[0-9a-fA-F]+|0b[01]+|0'(?:''|\\[abfnrtv\\'"`]|\\x?\d+\\|[^\\])|\d+(?:\.\d+(?:[eE][+-]?\d+)?)?)/,string:/^(?:"([^"]|""|\\")*"|`([^`]|``|\\`)*`)/,l_brace:/^(?:\[)/,r_brace:/^(?:\])/,l_bracket:/^(?:\{)/,r_bracket:/^(?:\})/,bar:/^(?:\|)/,l_paren:/^(?:\()/,r_paren:/^(?:\))/};function N(w,b){return w.get_flag("char_conversion").id==="on"?b.replace(/./g,function(y){return w.get_char_conversion(y)}):b}function U(w){this.thread=w,this.text="",this.tokens=[]}U.prototype.set_last_tokens=function(w){return this.tokens=w},U.prototype.new_text=function(w){this.text=w,this.tokens=[]},U.prototype.get_tokens=function(w){var b,y=0,F=0,z=0,X=[],$=!1;if(w){var oe=this.tokens[w-1];y=oe.len,b=N(this.thread,this.text.substr(oe.len)),F=oe.line,z=oe.start}else b=this.text;if(/^\s*$/.test(b))return null;for(;b!=="";){var xe=[],Te=!1;if(/^\n/.exec(b)!==null){F++,z=0,y++,b=b.replace(/\n/,""),$=!0;continue}for(var lt in R)if(R.hasOwnProperty(lt)){var Ct=R[lt].exec(b);Ct&&xe.push({value:Ct[0],name:lt,matches:Ct})}if(!xe.length)return this.set_last_tokens([{value:b,matches:[],name:"lexical",line:F,start:z}]);var oe=r(xe,function(Pr,Ir){return Pr.value.length>=Ir.value.length?Pr:Ir});switch(oe.start=z,oe.line=F,b=b.replace(oe.value,""),z+=oe.value.length,y+=oe.value.length,oe.name){case"atom":oe.raw=oe.value,oe.value.charAt(0)==="'"&&(oe.value=S(oe.value.substr(1,oe.value.length-2),"'"),oe.value===null&&(oe.name="lexical",oe.value="unknown escape sequence"));break;case"number":oe.float=oe.value.substring(0,2)!=="0x"&&oe.value.match(/[.eE]/)!==null&&oe.value!=="0'.",oe.value=I(oe.value),oe.blank=Te;break;case"string":var qt=oe.value.charAt(0);oe.value=S(oe.value.substr(1,oe.value.length-2),qt),oe.value===null&&(oe.name="lexical",oe.value="unknown escape sequence");break;case"whitespace":var ir=X[X.length-1];ir&&(ir.space=!0),Te=!0;continue;case"r_bracket":X.length>0&&X[X.length-1].name==="l_bracket"&&(oe=X.pop(),oe.name="atom",oe.value="{}",oe.raw="{}",oe.space=!1);break;case"r_brace":X.length>0&&X[X.length-1].name==="l_brace"&&(oe=X.pop(),oe.name="atom",oe.value="[]",oe.raw="[]",oe.space=!1);break}oe.len=y,X.push(oe),Te=!1}var Pt=this.set_last_tokens(X);return Pt.length===0?null:Pt};function W(w,b,y,F,z){if(!b[y])return{type:f,value:x.error.syntax(b[y-1],"expression expected",!0)};var X;if(F==="0"){var $=b[y];switch($.name){case"number":return{type:p,len:y+1,value:new x.type.Num($.value,$.float)};case"variable":return{type:p,len:y+1,value:new x.type.Var($.value)};case"string":var oe;switch(w.get_flag("double_quotes").id){case"atom":oe=new j($.value,[]);break;case"codes":oe=new j("[]",[]);for(var xe=$.value.length-1;xe>=0;xe--)oe=new j(".",[new x.type.Num(n($.value,xe),!1),oe]);break;case"chars":oe=new j("[]",[]);for(var xe=$.value.length-1;xe>=0;xe--)oe=new j(".",[new x.type.Term($.value.charAt(xe),[]),oe]);break}return{type:p,len:y+1,value:oe};case"l_paren":var Pt=W(w,b,y+1,w.__get_max_priority(),!0);return Pt.type!==p?Pt:b[Pt.len]&&b[Pt.len].name==="r_paren"?(Pt.len++,Pt):{type:f,derived:!0,value:x.error.syntax(b[Pt.len]?b[Pt.len]:b[Pt.len-1],") or operator expected",!b[Pt.len])};case"l_bracket":var Pt=W(w,b,y+1,w.__get_max_priority(),!0);return Pt.type!==p?Pt:b[Pt.len]&&b[Pt.len].name==="r_bracket"?(Pt.len++,Pt.value=new j("{}",[Pt.value]),Pt):{type:f,derived:!0,value:x.error.syntax(b[Pt.len]?b[Pt.len]:b[Pt.len-1],"} or operator expected",!b[Pt.len])}}var Te=ee(w,b,y,z);return Te.type===p||Te.derived||(Te=ie(w,b,y),Te.type===p||Te.derived)?Te:{type:f,derived:!1,value:x.error.syntax(b[y],"unexpected token")}}var lt=w.__get_max_priority(),Ct=w.__get_next_priority(F),qt=y;if(b[y].name==="atom"&&b[y+1]&&(b[y].space||b[y+1].name!=="l_paren")){var $=b[y++],ir=w.__lookup_operator_classes(F,$.value);if(ir&&ir.indexOf("fy")>-1){var Pt=W(w,b,y,F,z);if(Pt.type!==f)return $.value==="-"&&!$.space&&x.type.is_number(Pt.value)?{value:new x.type.Num(-Pt.value.value,Pt.value.is_float),len:Pt.len,type:p}:{value:new x.type.Term($.value,[Pt.value]),len:Pt.len,type:p};X=Pt}else if(ir&&ir.indexOf("fx")>-1){var Pt=W(w,b,y,Ct,z);if(Pt.type!==f)return{value:new x.type.Term($.value,[Pt.value]),len:Pt.len,type:p};X=Pt}}y=qt;var Pt=W(w,b,y,Ct,z);if(Pt.type===p){y=Pt.len;var $=b[y];if(b[y]&&(b[y].name==="atom"&&w.__lookup_operator_classes(F,$.value)||b[y].name==="bar"&&w.__lookup_operator_classes(F,"|"))){var gn=Ct,Pr=F,ir=w.__lookup_operator_classes(F,$.value);if(ir.indexOf("xf")>-1)return{value:new x.type.Term($.value,[Pt.value]),len:++Pt.len,type:p};if(ir.indexOf("xfx")>-1){var Ir=W(w,b,y+1,gn,z);return Ir.type===p?{value:new x.type.Term($.value,[Pt.value,Ir.value]),len:Ir.len,type:p}:(Ir.derived=!0,Ir)}else if(ir.indexOf("xfy")>-1){var Ir=W(w,b,y+1,Pr,z);return Ir.type===p?{value:new x.type.Term($.value,[Pt.value,Ir.value]),len:Ir.len,type:p}:(Ir.derived=!0,Ir)}else if(Pt.type!==f)for(;;){y=Pt.len;var $=b[y];if($&&$.name==="atom"&&w.__lookup_operator_classes(F,$.value)){var ir=w.__lookup_operator_classes(F,$.value);if(ir.indexOf("yf")>-1)Pt={value:new x.type.Term($.value,[Pt.value]),len:++y,type:p};else if(ir.indexOf("yfx")>-1){var Ir=W(w,b,++y,gn,z);if(Ir.type===f)return Ir.derived=!0,Ir;y=Ir.len,Pt={value:new x.type.Term($.value,[Pt.value,Ir.value]),len:y,type:p}}else break}else break}}else X={type:f,value:x.error.syntax(b[Pt.len-1],"operator expected")};return Pt}return Pt}function ee(w,b,y,F){if(!b[y]||b[y].name==="atom"&&b[y].raw==="."&&!F&&(b[y].space||!b[y+1]||b[y+1].name!=="l_paren"))return{type:f,derived:!1,value:x.error.syntax(b[y-1],"unfounded token")};var z=b[y],X=[];if(b[y].name==="atom"&&b[y].raw!==","){if(y++,b[y-1].space)return{type:p,len:y,value:new x.type.Term(z.value,X)};if(b[y]&&b[y].name==="l_paren"){if(b[y+1]&&b[y+1].name==="r_paren")return{type:f,derived:!0,value:x.error.syntax(b[y+1],"argument expected")};var $=W(w,b,++y,"999",!0);if($.type===f)return $.derived?$:{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],"argument expected",!b[y])};for(X.push($.value),y=$.len;b[y]&&b[y].name==="atom"&&b[y].value===",";){if($=W(w,b,y+1,"999",!0),$.type===f)return $.derived?$:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],"argument expected",!b[y+1])};X.push($.value),y=$.len}if(b[y]&&b[y].name==="r_paren")y++;else return{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],", or ) expected",!b[y])}}return{type:p,len:y,value:new x.type.Term(z.value,X)}}return{type:f,derived:!1,value:x.error.syntax(b[y],"term expected")}}function ie(w,b,y){if(!b[y])return{type:f,derived:!1,value:x.error.syntax(b[y-1],"[ expected")};if(b[y]&&b[y].name==="l_brace"){var F=W(w,b,++y,"999",!0),z=[F.value],X=void 0;if(F.type===f)return b[y]&&b[y].name==="r_brace"?{type:p,len:y+1,value:new x.type.Term("[]",[])}:{type:f,derived:!0,value:x.error.syntax(b[y],"] expected")};for(y=F.len;b[y]&&b[y].name==="atom"&&b[y].value===",";){if(F=W(w,b,y+1,"999",!0),F.type===f)return F.derived?F:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],"argument expected",!b[y+1])};z.push(F.value),y=F.len}var $=!1;if(b[y]&&b[y].name==="bar"){if($=!0,F=W(w,b,y+1,"999",!0),F.type===f)return F.derived?F:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],"argument expected",!b[y+1])};X=F.value,y=F.len}return b[y]&&b[y].name==="r_brace"?{type:p,len:y+1,value:g(z,X)}:{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],$?"] expected":", or | or ] expected",!b[y])}}return{type:f,derived:!1,value:x.error.syntax(b[y],"list expected")}}function ue(w,b,y){var F=b[y].line,z=W(w,b,y,w.__get_max_priority(),!1),X=null,$;if(z.type!==f)if(y=z.len,b[y]&&b[y].name==="atom"&&b[y].raw===".")if(y++,x.type.is_term(z.value)){if(z.value.indicator===":-/2"?(X=new x.type.Rule(z.value.args[0],Ce(z.value.args[1])),$={value:X,len:y,type:p}):z.value.indicator==="-->/2"?(X=pe(new x.type.Rule(z.value.args[0],z.value.args[1]),w),X.body=Ce(X.body),$={value:X,len:y,type:x.type.is_rule(X)?p:f}):(X=new x.type.Rule(z.value,null),$={value:X,len:y,type:p}),X){var oe=X.singleton_variables();oe.length>0&&w.throw_warning(x.warning.singleton(oe,X.head.indicator,F))}return $}else return{type:f,value:x.error.syntax(b[y],"callable expected")};else return{type:f,value:x.error.syntax(b[y]?b[y]:b[y-1],". or operator expected")};return z}function le(w,b,y){y=y||{},y.from=y.from?y.from:"$tau-js",y.reconsult=y.reconsult!==void 0?y.reconsult:!0;var F=new U(w),z={},X;F.new_text(b);var $=0,oe=F.get_tokens($);do{if(oe===null||!oe[$])break;var xe=ue(w,oe,$);if(xe.type===f)return new j("throw",[xe.value]);if(xe.value.body===null&&xe.value.head.indicator==="?-/1"){var Te=new it(w.session);Te.add_goal(xe.value.head.args[0]),Te.answer(function(Ct){x.type.is_error(Ct)?w.throw_warning(Ct.args[0]):(Ct===!1||Ct===null)&&w.throw_warning(x.warning.failed_goal(xe.value.head.args[0],xe.len))}),$=xe.len;var lt=!0}else if(xe.value.body===null&&xe.value.head.indicator===":-/1"){var lt=w.run_directive(xe.value.head.args[0]);$=xe.len,xe.value.head.args[0].indicator==="char_conversion/2"&&(oe=F.get_tokens($),$=0)}else{X=xe.value.head.indicator,y.reconsult!==!1&&z[X]!==!0&&!w.is_multifile_predicate(X)&&(w.session.rules[X]=a(w.session.rules[X]||[],function(qt){return qt.dynamic}),z[X]=!0);var lt=w.add_rule(xe.value,y);$=xe.len}if(!lt)return lt}while(!0);return!0}function me(w,b){var y=new U(w);y.new_text(b);var F=0;do{var z=y.get_tokens(F);if(z===null)break;var X=W(w,z,0,w.__get_max_priority(),!1);if(X.type!==f){var $=X.len,oe=$;if(z[$]&&z[$].name==="atom"&&z[$].raw===".")w.add_goal(Ce(X.value));else{var xe=z[$];return new j("throw",[x.error.syntax(xe||z[$-1],". or operator expected",!xe)])}F=X.len+1}else return new j("throw",[X.value])}while(!0);return!0}function pe(w,b){w=w.rename(b);var y=b.next_free_variable(),F=Be(w.body,y,b);return F.error?F.value:(w.body=F.value,w.head.args=w.head.args.concat([y,F.variable]),w.head=new j(w.head.id,w.head.args),w)}function Be(w,b,y){var F;if(x.type.is_term(w)&&w.indicator==="!/0")return{value:w,variable:b,error:!1};if(x.type.is_term(w)&&w.indicator===",/2"){var z=Be(w.args[0],b,y);if(z.error)return z;var X=Be(w.args[1],z.variable,y);return X.error?X:{value:new j(",",[z.value,X.value]),variable:X.variable,error:!1}}else{if(x.type.is_term(w)&&w.indicator==="{}/1")return{value:w.args[0],variable:b,error:!1};if(x.type.is_empty_list(w))return{value:new j("true",[]),variable:b,error:!1};if(x.type.is_list(w)){F=y.next_free_variable();for(var $=w,oe;$.indicator==="./2";)oe=$,$=$.args[1];return x.type.is_variable($)?{value:x.error.instantiation("DCG"),variable:b,error:!0}:x.type.is_empty_list($)?(oe.args[1]=F,{value:new j("=",[b,w]),variable:F,error:!1}):{value:x.error.type("list",w,"DCG"),variable:b,error:!0}}else return x.type.is_callable(w)?(F=y.next_free_variable(),w.args=w.args.concat([b,F]),w=new j(w.id,w.args),{value:w,variable:F,error:!1}):{value:x.error.type("callable",w,"DCG"),variable:b,error:!0}}}function Ce(w){return x.type.is_variable(w)?new j("call",[w]):x.type.is_term(w)&&[",/2",";/2","->/2"].indexOf(w.indicator)!==-1?new j(w.id,[Ce(w.args[0]),Ce(w.args[1])]):w}function g(w,b){for(var y=b||new x.type.Term("[]",[]),F=w.length-1;F>=0;F--)y=new x.type.Term(".",[w[F],y]);return y}function we(w,b){for(var y=w.length-1;y>=0;y--)w[y]===b&&w.splice(y,1)}function ye(w){for(var b={},y=[],F=0;F=0;b--)if(w.charAt(b)==="/")return new j("/",[new j(w.substring(0,b)),new Re(parseInt(w.substring(b+1)),!1)])}function De(w){this.id=w}function Re(w,b){this.is_float=b!==void 0?b:parseInt(w)!==w,this.value=this.is_float?w:parseInt(w)}var mt=0;function j(w,b,y){this.ref=y||++mt,this.id=w,this.args=b||[],this.indicator=w+"/"+this.args.length}var rt=0;function Fe(w,b,y,F,z,X){this.id=rt++,this.stream=w,this.mode=b,this.alias=y,this.type=F!==void 0?F:"text",this.reposition=z!==void 0?z:!0,this.eof_action=X!==void 0?X:"eof_code",this.position=this.mode==="append"?"end_of_stream":0,this.output=this.mode==="write"||this.mode==="append",this.input=this.mode==="read"}function Ne(w){w=w||{},this.links=w}function Pe(w,b,y){b=b||new Ne,y=y||null,this.goal=w,this.substitution=b,this.parent=y}function Ve(w,b,y){this.head=w,this.body=b,this.dynamic=y||!1}function ke(w){w=w===void 0||w<=0?1e3:w,this.rules={},this.src_predicates={},this.rename=0,this.modules=[],this.thread=new it(this),this.total_threads=1,this.renamed_variables={},this.public_predicates={},this.multifile_predicates={},this.limit=w,this.streams={user_input:new Fe(typeof ec<"u"&&ec.exports?nodejs_user_input:tau_user_input,"read","user_input","text",!1,"reset"),user_output:new Fe(typeof ec<"u"&&ec.exports?nodejs_user_output:tau_user_output,"write","user_output","text",!1,"eof_code")},this.file_system=typeof ec<"u"&&ec.exports?nodejs_file_system:tau_file_system,this.standard_input=this.streams.user_input,this.standard_output=this.streams.user_output,this.current_input=this.streams.user_input,this.current_output=this.streams.user_output,this.format_success=function(b){return b.substitution},this.format_error=function(b){return b.goal},this.flag={bounded:x.flag.bounded.value,max_integer:x.flag.max_integer.value,min_integer:x.flag.min_integer.value,integer_rounding_function:x.flag.integer_rounding_function.value,char_conversion:x.flag.char_conversion.value,debug:x.flag.debug.value,max_arity:x.flag.max_arity.value,unknown:x.flag.unknown.value,double_quotes:x.flag.double_quotes.value,occurs_check:x.flag.occurs_check.value,dialect:x.flag.dialect.value,version_data:x.flag.version_data.value,nodejs:x.flag.nodejs.value},this.__loaded_modules=[],this.__char_conversion={},this.__operators={1200:{":-":["fx","xfx"],"-->":["xfx"],"?-":["fx"]},1100:{";":["xfy"]},1050:{"->":["xfy"]},1e3:{",":["xfy"]},900:{"\\+":["fy"]},700:{"=":["xfx"],"\\=":["xfx"],"==":["xfx"],"\\==":["xfx"],"@<":["xfx"],"@=<":["xfx"],"@>":["xfx"],"@>=":["xfx"],"=..":["xfx"],is:["xfx"],"=:=":["xfx"],"=\\=":["xfx"],"<":["xfx"],"=<":["xfx"],">":["xfx"],">=":["xfx"]},600:{":":["xfy"]},500:{"+":["yfx"],"-":["yfx"],"/\\":["yfx"],"\\/":["yfx"]},400:{"*":["yfx"],"/":["yfx"],"//":["yfx"],rem:["yfx"],mod:["yfx"],"<<":["yfx"],">>":["yfx"]},200:{"**":["xfx"],"^":["xfy"],"-":["fy"],"+":["fy"],"\\":["fy"]}}}function it(w){this.epoch=Date.now(),this.session=w,this.session.total_threads++,this.total_steps=0,this.cpu_time=0,this.cpu_time_last=0,this.points=[],this.debugger=!1,this.debugger_states=[],this.level="top_level/0",this.__calls=[],this.current_limit=this.session.limit,this.warnings=[]}function Ue(w,b,y){this.id=w,this.rules=b,this.exports=y,x.module[w]=this}Ue.prototype.exports_predicate=function(w){return this.exports.indexOf(w)!==-1},De.prototype.unify=function(w,b){if(b&&e(w.variables(),this.id)!==-1&&!x.type.is_variable(w))return null;var y={};return y[this.id]=w,new Ne(y)},Re.prototype.unify=function(w,b){return x.type.is_number(w)&&this.value===w.value&&this.is_float===w.is_float?new Ne:null},j.prototype.unify=function(w,b){if(x.type.is_term(w)&&this.indicator===w.indicator){for(var y=new Ne,F=0;F=0){var F=this.args[0].value,z=Math.floor(F/26),X=F%26;return"ABCDEFGHIJKLMNOPQRSTUVWXYZ"[X]+(z!==0?z:"")}switch(this.indicator){case"[]/0":case"{}/0":case"!/0":return this.id;case"{}/1":return"{"+this.args[0].toString(w)+"}";case"./2":for(var $="["+this.args[0].toString(w),oe=this.args[1];oe.indicator==="./2";)$+=", "+oe.args[0].toString(w),oe=oe.args[1];return oe.indicator!=="[]/0"&&($+="|"+oe.toString(w)),$+="]",$;case",/2":return"("+this.args[0].toString(w)+", "+this.args[1].toString(w)+")";default:var xe=this.id,Te=w.session?w.session.lookup_operator(this.id,this.args.length):null;if(w.session===void 0||w.ignore_ops||Te===null)return w.quoted&&!/^(!|,|;|[a-z][0-9a-zA-Z_]*)$/.test(xe)&&xe!=="{}"&&xe!=="[]"&&(xe="'"+P(xe)+"'"),xe+(this.args.length?"("+s(this.args,function(ir){return ir.toString(w)}).join(", ")+")":"");var lt=Te.priority>b.priority||Te.priority===b.priority&&(Te.class==="xfy"&&this.indicator!==b.indicator||Te.class==="yfx"&&this.indicator!==b.indicator||this.indicator===b.indicator&&Te.class==="yfx"&&y==="right"||this.indicator===b.indicator&&Te.class==="xfy"&&y==="left");Te.indicator=this.indicator;var Ct=lt?"(":"",qt=lt?")":"";return this.args.length===0?"("+this.id+")":["fy","fx"].indexOf(Te.class)!==-1?Ct+xe+" "+this.args[0].toString(w,Te)+qt:["yf","xf"].indexOf(Te.class)!==-1?Ct+this.args[0].toString(w,Te)+" "+xe+qt:Ct+this.args[0].toString(w,Te,"left")+" "+this.id+" "+this.args[1].toString(w,Te,"right")+qt}},Fe.prototype.toString=function(w){return"("+this.id+")"},Ne.prototype.toString=function(w){var b="{";for(var y in this.links)this.links.hasOwnProperty(y)&&(b!=="{"&&(b+=", "),b+=y+"/"+this.links[y].toString(w));return b+="}",b},Pe.prototype.toString=function(w){return this.goal===null?"<"+this.substitution.toString(w)+">":"<"+this.goal.toString(w)+", "+this.substitution.toString(w)+">"},Ve.prototype.toString=function(w){return this.body?this.head.toString(w)+" :- "+this.body.toString(w)+".":this.head.toString(w)+"."},ke.prototype.toString=function(w){for(var b="",y=0;y=0;z--)F=new j(".",[b[z],F]);return F}return new j(this.id,s(this.args,function(X){return X.apply(w)}),this.ref)},Fe.prototype.apply=function(w){return this},Ve.prototype.apply=function(w){return new Ve(this.head.apply(w),this.body!==null?this.body.apply(w):null)},Ne.prototype.apply=function(w){var b,y={};for(b in this.links)this.links.hasOwnProperty(b)&&(y[b]=this.links[b].apply(w));return new Ne(y)},j.prototype.select=function(){for(var w=this;w.indicator===",/2";)w=w.args[0];return w},j.prototype.replace=function(w){return this.indicator===",/2"?this.args[0].indicator===",/2"?new j(",",[this.args[0].replace(w),this.args[1]]):w===null?this.args[1]:new j(",",[w,this.args[1]]):w},j.prototype.search=function(w){if(x.type.is_term(w)&&w.ref!==void 0&&this.ref===w.ref)return!0;for(var b=0;bb&&F0&&(b=this.head_point().substitution.domain());e(b,x.format_variable(this.session.rename))!==-1;)this.session.rename++;if(w.id==="_")return new De(x.format_variable(this.session.rename));this.session.renamed_variables[w.id]=x.format_variable(this.session.rename)}return new De(this.session.renamed_variables[w.id])},ke.prototype.next_free_variable=function(){return this.thread.next_free_variable()},it.prototype.next_free_variable=function(){this.session.rename++;var w=[];for(this.points.length>0&&(w=this.head_point().substitution.domain());e(w,x.format_variable(this.session.rename))!==-1;)this.session.rename++;return new De(x.format_variable(this.session.rename))},ke.prototype.is_public_predicate=function(w){return!this.public_predicates.hasOwnProperty(w)||this.public_predicates[w]===!0},it.prototype.is_public_predicate=function(w){return this.session.is_public_predicate(w)},ke.prototype.is_multifile_predicate=function(w){return this.multifile_predicates.hasOwnProperty(w)&&this.multifile_predicates[w]===!0},it.prototype.is_multifile_predicate=function(w){return this.session.is_multifile_predicate(w)},ke.prototype.prepend=function(w){return this.thread.prepend(w)},it.prototype.prepend=function(w){for(var b=w.length-1;b>=0;b--)this.points.push(w[b])},ke.prototype.success=function(w,b){return this.thread.success(w,b)},it.prototype.success=function(w,y){var y=typeof y>"u"?w:y;this.prepend([new Pe(w.goal.replace(null),w.substitution,y)])},ke.prototype.throw_error=function(w){return this.thread.throw_error(w)},it.prototype.throw_error=function(w){this.prepend([new Pe(new j("throw",[w]),new Ne,null,null)])},ke.prototype.step_rule=function(w,b){return this.thread.step_rule(w,b)},it.prototype.step_rule=function(w,b){var y=b.indicator;if(w==="user"&&(w=null),w===null&&this.session.rules.hasOwnProperty(y))return this.session.rules[y];for(var F=w===null?this.session.modules:e(this.session.modules,w)===-1?[]:[w],z=0;z1)&&this.again()},ke.prototype.answers=function(w,b,y){return this.thread.answers(w,b,y)},it.prototype.answers=function(w,b,y){var F=b||1e3,z=this;if(b<=0){y&&y();return}this.answer(function(X){w(X),X!==!1?setTimeout(function(){z.answers(w,b-1,y)},1):y&&y()})},ke.prototype.again=function(w){return this.thread.again(w)},it.prototype.again=function(w){for(var b,y=Date.now();this.__calls.length>0;){for(this.warnings=[],w!==!1&&(this.current_limit=this.session.limit);this.current_limit>0&&this.points.length>0&&this.head_point().goal!==null&&!x.type.is_error(this.head_point().goal);)if(this.current_limit--,this.step()===!0)return;var F=Date.now();this.cpu_time_last=F-y,this.cpu_time+=this.cpu_time_last;var z=this.__calls.shift();this.current_limit<=0?z(null):this.points.length===0?z(!1):x.type.is_error(this.head_point().goal)?(b=this.session.format_error(this.points.pop()),this.points=[],z(b)):(this.debugger&&this.debugger_states.push(this.head_point()),b=this.session.format_success(this.points.pop()),z(b))}},ke.prototype.unfold=function(w){if(w.body===null)return!1;var b=w.head,y=w.body,F=y.select(),z=new it(this),X=[];z.add_goal(F),z.step();for(var $=z.points.length-1;$>=0;$--){var oe=z.points[$],xe=b.apply(oe.substitution),Te=y.replace(oe.goal);Te!==null&&(Te=Te.apply(oe.substitution)),X.push(new Ve(xe,Te))}var lt=this.rules[b.indicator],Ct=e(lt,w);return X.length>0&&Ct!==-1?(lt.splice.apply(lt,[Ct,1].concat(X)),!0):!1},it.prototype.unfold=function(w){return this.session.unfold(w)},De.prototype.interpret=function(w){return x.error.instantiation(w.level)},Re.prototype.interpret=function(w){return this},j.prototype.interpret=function(w){return x.type.is_unitary_list(this)?this.args[0].interpret(w):x.operate(w,this)},De.prototype.compare=function(w){return this.idw.id?1:0},Re.prototype.compare=function(w){if(this.value===w.value&&this.is_float===w.is_float)return 0;if(this.valuew.value)return 1},j.prototype.compare=function(w){if(this.args.lengthw.args.length||this.args.length===w.args.length&&this.id>w.id)return 1;for(var b=0;bF)return 1;if(w.constructor===Re){if(w.is_float&&b.is_float)return 0;if(w.is_float)return-1;if(b.is_float)return 1}return 0},is_substitution:function(w){return w instanceof Ne},is_state:function(w){return w instanceof Pe},is_rule:function(w){return w instanceof Ve},is_variable:function(w){return w instanceof De},is_stream:function(w){return w instanceof Fe},is_anonymous_var:function(w){return w instanceof De&&w.id==="_"},is_callable:function(w){return w instanceof j},is_number:function(w){return w instanceof Re},is_integer:function(w){return w instanceof Re&&!w.is_float},is_float:function(w){return w instanceof Re&&w.is_float},is_term:function(w){return w instanceof j},is_atom:function(w){return w instanceof j&&w.args.length===0},is_ground:function(w){if(w instanceof De)return!1;if(w instanceof j){for(var b=0;b0},is_list:function(w){return w instanceof j&&(w.indicator==="[]/0"||w.indicator==="./2")},is_empty_list:function(w){return w instanceof j&&w.indicator==="[]/0"},is_non_empty_list:function(w){return w instanceof j&&w.indicator==="./2"},is_fully_list:function(w){for(;w instanceof j&&w.indicator==="./2";)w=w.args[1];return w instanceof De||w instanceof j&&w.indicator==="[]/0"},is_instantiated_list:function(w){for(;w instanceof j&&w.indicator==="./2";)w=w.args[1];return w instanceof j&&w.indicator==="[]/0"},is_unitary_list:function(w){return w instanceof j&&w.indicator==="./2"&&w.args[1]instanceof j&&w.args[1].indicator==="[]/0"},is_character:function(w){return w instanceof j&&(w.id.length===1||w.id.length>0&&w.id.length<=2&&n(w.id,0)>=65536)},is_character_code:function(w){return w instanceof Re&&!w.is_float&&w.value>=0&&w.value<=1114111},is_byte:function(w){return w instanceof Re&&!w.is_float&&w.value>=0&&w.value<=255},is_operator:function(w){return w instanceof j&&x.arithmetic.evaluation[w.indicator]},is_directive:function(w){return w instanceof j&&x.directive[w.indicator]!==void 0},is_builtin:function(w){return w instanceof j&&x.predicate[w.indicator]!==void 0},is_error:function(w){return w instanceof j&&w.indicator==="throw/1"},is_predicate_indicator:function(w){return w instanceof j&&w.indicator==="//2"&&w.args[0]instanceof j&&w.args[0].args.length===0&&w.args[1]instanceof Re&&w.args[1].is_float===!1},is_flag:function(w){return w instanceof j&&w.args.length===0&&x.flag[w.id]!==void 0},is_value_flag:function(w,b){if(!x.type.is_flag(w))return!1;for(var y in x.flag[w.id].allowed)if(x.flag[w.id].allowed.hasOwnProperty(y)&&x.flag[w.id].allowed[y].equals(b))return!0;return!1},is_io_mode:function(w){return x.type.is_atom(w)&&["read","write","append"].indexOf(w.id)!==-1},is_stream_option:function(w){return x.type.is_term(w)&&(w.indicator==="alias/1"&&x.type.is_atom(w.args[0])||w.indicator==="reposition/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")||w.indicator==="type/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="text"||w.args[0].id==="binary")||w.indicator==="eof_action/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="error"||w.args[0].id==="eof_code"||w.args[0].id==="reset"))},is_stream_position:function(w){return x.type.is_integer(w)&&w.value>=0||x.type.is_atom(w)&&(w.id==="end_of_stream"||w.id==="past_end_of_stream")},is_stream_property:function(w){return x.type.is_term(w)&&(w.indicator==="input/0"||w.indicator==="output/0"||w.indicator==="alias/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0]))||w.indicator==="file_name/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0]))||w.indicator==="position/1"&&(x.type.is_variable(w.args[0])||x.type.is_stream_position(w.args[0]))||w.indicator==="reposition/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false"))||w.indicator==="type/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="text"||w.args[0].id==="binary"))||w.indicator==="mode/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="read"||w.args[0].id==="write"||w.args[0].id==="append"))||w.indicator==="eof_action/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="error"||w.args[0].id==="eof_code"||w.args[0].id==="reset"))||w.indicator==="end_of_stream/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="at"||w.args[0].id==="past"||w.args[0].id==="not")))},is_streamable:function(w){return w.__proto__.stream!==void 0},is_read_option:function(w){return x.type.is_term(w)&&["variables/1","variable_names/1","singletons/1"].indexOf(w.indicator)!==-1},is_write_option:function(w){return x.type.is_term(w)&&(w.indicator==="quoted/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")||w.indicator==="ignore_ops/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")||w.indicator==="numbervars/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false"))},is_close_option:function(w){return x.type.is_term(w)&&w.indicator==="force/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")},is_modifiable_flag:function(w){return x.type.is_flag(w)&&x.flag[w.id].changeable},is_module:function(w){return w instanceof j&&w.indicator==="library/1"&&w.args[0]instanceof j&&w.args[0].args.length===0&&x.module[w.args[0].id]!==void 0}},arithmetic:{evaluation:{"e/0":{type_args:null,type_result:!0,fn:function(w){return Math.E}},"pi/0":{type_args:null,type_result:!0,fn:function(w){return Math.PI}},"tau/0":{type_args:null,type_result:!0,fn:function(w){return 2*Math.PI}},"epsilon/0":{type_args:null,type_result:!0,fn:function(w){return Number.EPSILON}},"+/1":{type_args:null,type_result:null,fn:function(w,b){return w}},"-/1":{type_args:null,type_result:null,fn:function(w,b){return-w}},"\\/1":{type_args:!1,type_result:!1,fn:function(w,b){return~w}},"abs/1":{type_args:null,type_result:null,fn:function(w,b){return Math.abs(w)}},"sign/1":{type_args:null,type_result:null,fn:function(w,b){return Math.sign(w)}},"float_integer_part/1":{type_args:!0,type_result:!1,fn:function(w,b){return parseInt(w)}},"float_fractional_part/1":{type_args:!0,type_result:!0,fn:function(w,b){return w-parseInt(w)}},"float/1":{type_args:null,type_result:!0,fn:function(w,b){return parseFloat(w)}},"floor/1":{type_args:!0,type_result:!1,fn:function(w,b){return Math.floor(w)}},"truncate/1":{type_args:!0,type_result:!1,fn:function(w,b){return parseInt(w)}},"round/1":{type_args:!0,type_result:!1,fn:function(w,b){return Math.round(w)}},"ceiling/1":{type_args:!0,type_result:!1,fn:function(w,b){return Math.ceil(w)}},"sin/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.sin(w)}},"cos/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.cos(w)}},"tan/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.tan(w)}},"asin/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.asin(w)}},"acos/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.acos(w)}},"atan/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.atan(w)}},"atan2/2":{type_args:null,type_result:!0,fn:function(w,b,y){return Math.atan2(w,b)}},"exp/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.exp(w)}},"sqrt/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.sqrt(w)}},"log/1":{type_args:null,type_result:!0,fn:function(w,b){return w>0?Math.log(w):x.error.evaluation("undefined",b.__call_indicator)}},"+/2":{type_args:null,type_result:null,fn:function(w,b,y){return w+b}},"-/2":{type_args:null,type_result:null,fn:function(w,b,y){return w-b}},"*/2":{type_args:null,type_result:null,fn:function(w,b,y){return w*b}},"//2":{type_args:null,type_result:!0,fn:function(w,b,y){return b?w/b:x.error.evaluation("zero_division",y.__call_indicator)}},"///2":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?parseInt(w/b):x.error.evaluation("zero_division",y.__call_indicator)}},"**/2":{type_args:null,type_result:!0,fn:function(w,b,y){return Math.pow(w,b)}},"^/2":{type_args:null,type_result:null,fn:function(w,b,y){return Math.pow(w,b)}},"<>/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w>>b}},"/\\/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w&b}},"\\//2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w|b}},"xor/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w^b}},"rem/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?w%b:x.error.evaluation("zero_division",y.__call_indicator)}},"mod/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?w-parseInt(w/b)*b:x.error.evaluation("zero_division",y.__call_indicator)}},"max/2":{type_args:null,type_result:null,fn:function(w,b,y){return Math.max(w,b)}},"min/2":{type_args:null,type_result:null,fn:function(w,b,y){return Math.min(w,b)}}}},directive:{"dynamic/1":function(w,b){var y=b.args[0];if(x.type.is_variable(y))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_compound(y)||y.indicator!=="//2")w.throw_error(x.error.type("predicate_indicator",y,b.indicator));else if(x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1]))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_atom(y.args[0]))w.throw_error(x.error.type("atom",y.args[0],b.indicator));else if(!x.type.is_integer(y.args[1]))w.throw_error(x.error.type("integer",y.args[1],b.indicator));else{var F=b.args[0].args[0].id+"/"+b.args[0].args[1].value;w.session.public_predicates[F]=!0,w.session.rules[F]||(w.session.rules[F]=[])}},"multifile/1":function(w,b){var y=b.args[0];x.type.is_variable(y)?w.throw_error(x.error.instantiation(b.indicator)):!x.type.is_compound(y)||y.indicator!=="//2"?w.throw_error(x.error.type("predicate_indicator",y,b.indicator)):x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1])?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_atom(y.args[0])?x.type.is_integer(y.args[1])?w.session.multifile_predicates[b.args[0].args[0].id+"/"+b.args[0].args[1].value]=!0:w.throw_error(x.error.type("integer",y.args[1],b.indicator)):w.throw_error(x.error.type("atom",y.args[0],b.indicator))},"set_prolog_flag/2":function(w,b){var y=b.args[0],F=b.args[1];x.type.is_variable(y)||x.type.is_variable(F)?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_atom(y)?x.type.is_flag(y)?x.type.is_value_flag(y,F)?x.type.is_modifiable_flag(y)?w.session.flag[y.id]=F:w.throw_error(x.error.permission("modify","flag",y)):w.throw_error(x.error.domain("flag_value",new j("+",[y,F]),b.indicator)):w.throw_error(x.error.domain("prolog_flag",y,b.indicator)):w.throw_error(x.error.type("atom",y,b.indicator))},"use_module/1":function(w,b){var y=b.args[0];if(x.type.is_variable(y))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_term(y))w.throw_error(x.error.type("term",y,b.indicator));else if(x.type.is_module(y)){var F=y.args[0].id;e(w.session.modules,F)===-1&&w.session.modules.push(F)}},"char_conversion/2":function(w,b){var y=b.args[0],F=b.args[1];x.type.is_variable(y)||x.type.is_variable(F)?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_character(y)?x.type.is_character(F)?y.id===F.id?delete w.session.__char_conversion[y.id]:w.session.__char_conversion[y.id]=F.id:w.throw_error(x.error.type("character",F,b.indicator)):w.throw_error(x.error.type("character",y,b.indicator))},"op/3":function(w,b){var y=b.args[0],F=b.args[1],z=b.args[2];if(x.type.is_variable(y)||x.type.is_variable(F)||x.type.is_variable(z))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_integer(y))w.throw_error(x.error.type("integer",y,b.indicator));else if(!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,b.indicator));else if(!x.type.is_atom(z))w.throw_error(x.error.type("atom",z,b.indicator));else if(y.value<0||y.value>1200)w.throw_error(x.error.domain("operator_priority",y,b.indicator));else if(z.id===",")w.throw_error(x.error.permission("modify","operator",z,b.indicator));else if(z.id==="|"&&(y.value<1001||F.id.length!==3))w.throw_error(x.error.permission("modify","operator",z,b.indicator));else if(["fy","fx","yf","xf","xfx","yfx","xfy"].indexOf(F.id)===-1)w.throw_error(x.error.domain("operator_specifier",F,b.indicator));else{var X={prefix:null,infix:null,postfix:null};for(var $ in w.session.__operators)if(w.session.__operators.hasOwnProperty($)){var oe=w.session.__operators[$][z.id];oe&&(e(oe,"fx")!==-1&&(X.prefix={priority:$,type:"fx"}),e(oe,"fy")!==-1&&(X.prefix={priority:$,type:"fy"}),e(oe,"xf")!==-1&&(X.postfix={priority:$,type:"xf"}),e(oe,"yf")!==-1&&(X.postfix={priority:$,type:"yf"}),e(oe,"xfx")!==-1&&(X.infix={priority:$,type:"xfx"}),e(oe,"xfy")!==-1&&(X.infix={priority:$,type:"xfy"}),e(oe,"yfx")!==-1&&(X.infix={priority:$,type:"yfx"}))}var xe;switch(F.id){case"fy":case"fx":xe="prefix";break;case"yf":case"xf":xe="postfix";break;default:xe="infix";break}if(((X.prefix&&xe==="prefix"||X.postfix&&xe==="postfix"||X.infix&&xe==="infix")&&X[xe].type!==F.id||X.infix&&xe==="postfix"||X.postfix&&xe==="infix")&&y.value!==0)w.throw_error(x.error.permission("create","operator",z,b.indicator));else return X[xe]&&(we(w.session.__operators[X[xe].priority][z.id],F.id),w.session.__operators[X[xe].priority][z.id].length===0&&delete w.session.__operators[X[xe].priority][z.id]),y.value>0&&(w.session.__operators[y.value]||(w.session.__operators[y.value.toString()]={}),w.session.__operators[y.value][z.id]||(w.session.__operators[y.value][z.id]=[]),w.session.__operators[y.value][z.id].push(F.id)),!0}}},predicate:{"op/3":function(w,b,y){x.directive["op/3"](w,y)&&w.success(b)},"current_op/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2],$=[];for(var oe in w.session.__operators)for(var xe in w.session.__operators[oe])for(var Te=0;Te/2"){var F=w.points,z=w.session.format_success,X=w.session.format_error;w.session.format_success=function(Te){return Te.substitution},w.session.format_error=function(Te){return Te.goal},w.points=[new Pe(y.args[0].args[0],b.substitution,b)];var $=function(Te){w.points=F,w.session.format_success=z,w.session.format_error=X,Te===!1?w.prepend([new Pe(b.goal.replace(y.args[1]),b.substitution,b)]):x.type.is_error(Te)?w.throw_error(Te.args[0]):Te===null?(w.prepend([b]),w.__calls.shift()(null)):w.prepend([new Pe(b.goal.replace(y.args[0].args[1]).apply(Te),b.substitution.apply(Te),b)])};w.__calls.unshift($)}else{var oe=new Pe(b.goal.replace(y.args[0]),b.substitution,b),xe=new Pe(b.goal.replace(y.args[1]),b.substitution,b);w.prepend([oe,xe])}},"!/0":function(w,b,y){var F,z,X=[];for(F=b,z=null;F.parent!==null&&F.parent.goal.search(y);)if(z=F,F=F.parent,F.goal!==null){var $=F.goal.select();if($&&$.id==="call"&&$.search(y)){F=z;break}}for(var oe=w.points.length-1;oe>=0;oe--){for(var xe=w.points[oe],Te=xe.parent;Te!==null&&Te!==F.parent;)Te=Te.parent;Te===null&&Te!==F.parent&&X.push(xe)}w.points=X.reverse(),w.success(b)},"\\+/1":function(w,b,y){var F=y.args[0];x.type.is_variable(F)?w.throw_error(x.error.instantiation(w.level)):x.type.is_callable(F)?w.prepend([new Pe(b.goal.replace(new j(",",[new j(",",[new j("call",[F]),new j("!",[])]),new j("fail",[])])),b.substitution,b),new Pe(b.goal.replace(null),b.substitution,b)]):w.throw_error(x.error.type("callable",F,w.level))},"->/2":function(w,b,y){var F=b.goal.replace(new j(",",[y.args[0],new j(",",[new j("!"),y.args[1]])]));w.prepend([new Pe(F,b.substitution,b)])},"fail/0":function(w,b,y){},"false/0":function(w,b,y){},"true/0":function(w,b,y){w.success(b)},"call/1":se(1),"call/2":se(2),"call/3":se(3),"call/4":se(4),"call/5":se(5),"call/6":se(6),"call/7":se(7),"call/8":se(8),"once/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("call",[F]),new j("!",[])])),b.substitution,b)])},"forall/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("\\+",[new j(",",[new j("call",[F]),new j("\\+",[new j("call",[z])])])])),b.substitution,b)])},"repeat/0":function(w,b,y){w.prepend([new Pe(b.goal.replace(null),b.substitution,b),b])},"throw/1":function(w,b,y){x.type.is_variable(y.args[0])?w.throw_error(x.error.instantiation(w.level)):w.throw_error(y.args[0])},"catch/3":function(w,b,y){var F=w.points;w.points=[],w.prepend([new Pe(y.args[0],b.substitution,b)]);var z=w.session.format_success,X=w.session.format_error;w.session.format_success=function(oe){return oe.substitution},w.session.format_error=function(oe){return oe.goal};var $=function(oe){var xe=w.points;if(w.points=F,w.session.format_success=z,w.session.format_error=X,x.type.is_error(oe)){for(var Te=[],lt=w.points.length-1;lt>=0;lt--){for(var ir=w.points[lt],Ct=ir.parent;Ct!==null&&Ct!==b.parent;)Ct=Ct.parent;Ct===null&&Ct!==b.parent&&Te.push(ir)}w.points=Te;var qt=w.get_flag("occurs_check").indicator==="true/0",ir=new Pe,Pt=x.unify(oe.args[0],y.args[1],qt);Pt!==null?(ir.substitution=b.substitution.apply(Pt),ir.goal=b.goal.replace(y.args[2]).apply(Pt),ir.parent=b,w.prepend([ir])):w.throw_error(oe.args[0])}else if(oe!==!1){for(var gn=oe===null?[]:[new Pe(b.goal.apply(oe).replace(null),b.substitution.apply(oe),b)],Pr=[],lt=xe.length-1;lt>=0;lt--){Pr.push(xe[lt]);var Ir=xe[lt].goal!==null?xe[lt].goal.select():null;if(x.type.is_term(Ir)&&Ir.indicator==="!/0")break}var Or=s(Pr,function(on){return on.goal===null&&(on.goal=new j("true",[])),on=new Pe(b.goal.replace(new j("catch",[on.goal,y.args[1],y.args[2]])),b.substitution.apply(on.substitution),on.parent),on.exclude=y.args[0].variables(),on}).reverse();w.prepend(Or),w.prepend(gn),oe===null&&(this.current_limit=0,w.__calls.shift()(null))}};w.__calls.unshift($)},"=/2":function(w,b,y){var F=w.get_flag("occurs_check").indicator==="true/0",z=new Pe,X=x.unify(y.args[0],y.args[1],F);X!==null&&(z.goal=b.goal.apply(X).replace(null),z.substitution=b.substitution.apply(X),z.parent=b,w.prepend([z]))},"unify_with_occurs_check/2":function(w,b,y){var F=new Pe,z=x.unify(y.args[0],y.args[1],!0);z!==null&&(F.goal=b.goal.apply(z).replace(null),F.substitution=b.substitution.apply(z),F.parent=b,w.prepend([F]))},"\\=/2":function(w,b,y){var F=w.get_flag("occurs_check").indicator==="true/0",z=x.unify(y.args[0],y.args[1],F);z===null&&w.success(b)},"subsumes_term/2":function(w,b,y){var F=w.get_flag("occurs_check").indicator==="true/0",z=x.unify(y.args[1],y.args[0],F);z!==null&&y.args[1].apply(z).equals(y.args[1])&&w.success(b)},"findall/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2];if(x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(z))w.throw_error(x.error.type("callable",z,y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_list(X))w.throw_error(x.error.type("list",X,y.indicator));else{var $=w.next_free_variable(),oe=new j(",",[z,new j("=",[$,F])]),xe=w.points,Te=w.session.limit,lt=w.session.format_success;w.session.format_success=function(ir){return ir.substitution},w.add_goal(oe,!0,b);var Ct=[],qt=function(ir){if(ir!==!1&&ir!==null&&!x.type.is_error(ir))w.__calls.unshift(qt),Ct.push(ir.links[$.id]),w.session.limit=w.current_limit;else if(w.points=xe,w.session.limit=Te,w.session.format_success=lt,x.type.is_error(ir))w.throw_error(ir.args[0]);else if(w.current_limit>0){for(var Pt=new j("[]"),gn=Ct.length-1;gn>=0;gn--)Pt=new j(".",[Ct[gn],Pt]);w.prepend([new Pe(b.goal.replace(new j("=",[X,Pt])),b.substitution,b)])}};w.__calls.unshift(qt)}},"bagof/3":function(w,b,y){var F,z=y.args[0],X=y.args[1],$=y.args[2];if(x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(X))w.throw_error(x.error.type("callable",X,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_list($))w.throw_error(x.error.type("list",$,y.indicator));else{var oe=w.next_free_variable(),xe;X.indicator==="^/2"?(xe=X.args[0].variables(),X=X.args[1]):xe=[],xe=xe.concat(z.variables());for(var Te=X.variables().filter(function(Or){return e(xe,Or)===-1}),lt=new j("[]"),Ct=Te.length-1;Ct>=0;Ct--)lt=new j(".",[new De(Te[Ct]),lt]);var qt=new j(",",[X,new j("=",[oe,new j(",",[lt,z])])]),ir=w.points,Pt=w.session.limit,gn=w.session.format_success;w.session.format_success=function(Or){return Or.substitution},w.add_goal(qt,!0,b);var Pr=[],Ir=function(Or){if(Or!==!1&&Or!==null&&!x.type.is_error(Or)){w.__calls.unshift(Ir);var on=!1,ai=Or.links[oe.id].args[0],Io=Or.links[oe.id].args[1];for(var rs in Pr)if(Pr.hasOwnProperty(rs)){var $s=Pr[rs];if($s.variables.equals(ai)){$s.answers.push(Io),on=!0;break}}on||Pr.push({variables:ai,answers:[Io]}),w.session.limit=w.current_limit}else if(w.points=ir,w.session.limit=Pt,w.session.format_success=gn,x.type.is_error(Or))w.throw_error(Or.args[0]);else if(w.current_limit>0){for(var Co=[],ji=0;ji=0;wo--)eo=new j(".",[Or[wo],eo]);Co.push(new Pe(b.goal.replace(new j(",",[new j("=",[lt,Pr[ji].variables]),new j("=",[$,eo])])),b.substitution,b))}w.prepend(Co)}};w.__calls.unshift(Ir)}},"setof/3":function(w,b,y){var F,z=y.args[0],X=y.args[1],$=y.args[2];if(x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(X))w.throw_error(x.error.type("callable",X,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_list($))w.throw_error(x.error.type("list",$,y.indicator));else{var oe=w.next_free_variable(),xe;X.indicator==="^/2"?(xe=X.args[0].variables(),X=X.args[1]):xe=[],xe=xe.concat(z.variables());for(var Te=X.variables().filter(function(Or){return e(xe,Or)===-1}),lt=new j("[]"),Ct=Te.length-1;Ct>=0;Ct--)lt=new j(".",[new De(Te[Ct]),lt]);var qt=new j(",",[X,new j("=",[oe,new j(",",[lt,z])])]),ir=w.points,Pt=w.session.limit,gn=w.session.format_success;w.session.format_success=function(Or){return Or.substitution},w.add_goal(qt,!0,b);var Pr=[],Ir=function(Or){if(Or!==!1&&Or!==null&&!x.type.is_error(Or)){w.__calls.unshift(Ir);var on=!1,ai=Or.links[oe.id].args[0],Io=Or.links[oe.id].args[1];for(var rs in Pr)if(Pr.hasOwnProperty(rs)){var $s=Pr[rs];if($s.variables.equals(ai)){$s.answers.push(Io),on=!0;break}}on||Pr.push({variables:ai,answers:[Io]}),w.session.limit=w.current_limit}else if(w.points=ir,w.session.limit=Pt,w.session.format_success=gn,x.type.is_error(Or))w.throw_error(Or.args[0]);else if(w.current_limit>0){for(var Co=[],ji=0;ji=0;wo--)eo=new j(".",[Or[wo],eo]);Co.push(new Pe(b.goal.replace(new j(",",[new j("=",[lt,Pr[ji].variables]),new j("=",[$,eo])])),b.substitution,b))}w.prepend(Co)}};w.__calls.unshift(Ir)}},"functor/3":function(w,b,y){var F,z=y.args[0],X=y.args[1],$=y.args[2];if(x.type.is_variable(z)&&(x.type.is_variable(X)||x.type.is_variable($)))w.throw_error(x.error.instantiation("functor/3"));else if(!x.type.is_variable($)&&!x.type.is_integer($))w.throw_error(x.error.type("integer",y.args[2],"functor/3"));else if(!x.type.is_variable(X)&&!x.type.is_atomic(X))w.throw_error(x.error.type("atomic",y.args[1],"functor/3"));else if(x.type.is_integer(X)&&x.type.is_integer($)&&$.value!==0)w.throw_error(x.error.type("atom",y.args[1],"functor/3"));else if(x.type.is_variable(z)){if(y.args[2].value>=0){for(var oe=[],xe=0;xe<$.value;xe++)oe.push(w.next_free_variable());var Te=x.type.is_integer(X)?X:new j(X.id,oe);w.prepend([new Pe(b.goal.replace(new j("=",[z,Te])),b.substitution,b)])}}else{var lt=x.type.is_integer(z)?z:new j(z.id,[]),Ct=x.type.is_integer(z)?new Re(0,!1):new Re(z.args.length,!1),qt=new j(",",[new j("=",[lt,X]),new j("=",[Ct,$])]);w.prepend([new Pe(b.goal.replace(qt),b.substitution,b)])}},"arg/3":function(w,b,y){if(x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1]))w.throw_error(x.error.instantiation(y.indicator));else if(y.args[0].value<0)w.throw_error(x.error.domain("not_less_than_zero",y.args[0],y.indicator));else if(!x.type.is_compound(y.args[1]))w.throw_error(x.error.type("compound",y.args[1],y.indicator));else{var F=y.args[0].value;if(F>0&&F<=y.args[1].args.length){var z=new j("=",[y.args[1].args[F-1],y.args[2]]);w.prepend([new Pe(b.goal.replace(z),b.substitution,b)])}}},"=../2":function(w,b,y){var F;if(x.type.is_variable(y.args[0])&&(x.type.is_variable(y.args[1])||x.type.is_non_empty_list(y.args[1])&&x.type.is_variable(y.args[1].args[0])))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_fully_list(y.args[1]))w.throw_error(x.error.type("list",y.args[1],y.indicator));else if(x.type.is_variable(y.args[0])){if(!x.type.is_variable(y.args[1])){var X=[];for(F=y.args[1].args[1];F.indicator==="./2";)X.push(F.args[0]),F=F.args[1];x.type.is_variable(y.args[0])&&x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):X.length===0&&x.type.is_compound(y.args[1].args[0])?w.throw_error(x.error.type("atomic",y.args[1].args[0],y.indicator)):X.length>0&&(x.type.is_compound(y.args[1].args[0])||x.type.is_number(y.args[1].args[0]))?w.throw_error(x.error.type("atom",y.args[1].args[0],y.indicator)):X.length===0?w.prepend([new Pe(b.goal.replace(new j("=",[y.args[1].args[0],y.args[0]],b)),b.substitution,b)]):w.prepend([new Pe(b.goal.replace(new j("=",[new j(y.args[1].args[0].id,X),y.args[0]])),b.substitution,b)])}}else{if(x.type.is_atomic(y.args[0]))F=new j(".",[y.args[0],new j("[]")]);else{F=new j("[]");for(var z=y.args[0].args.length-1;z>=0;z--)F=new j(".",[y.args[0].args[z],F]);F=new j(".",[new j(y.args[0].id),F])}w.prepend([new Pe(b.goal.replace(new j("=",[F,y.args[1]])),b.substitution,b)])}},"copy_term/2":function(w,b,y){var F=y.args[0].rename(w);w.prepend([new Pe(b.goal.replace(new j("=",[F,y.args[1]])),b.substitution,b.parent)])},"term_variables/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(!x.type.is_fully_list(z))w.throw_error(x.error.type("list",z,y.indicator));else{var X=g(s(ye(F.variables()),function($){return new De($)}));w.prepend([new Pe(b.goal.replace(new j("=",[z,X])),b.substitution,b)])}},"clause/2":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else if(!x.type.is_variable(y.args[1])&&!x.type.is_callable(y.args[1]))w.throw_error(x.error.type("callable",y.args[1],y.indicator));else if(w.session.rules[y.args[0].indicator]!==void 0)if(w.is_public_predicate(y.args[0].indicator)){var F=[];for(var z in w.session.rules[y.args[0].indicator])if(w.session.rules[y.args[0].indicator].hasOwnProperty(z)){var X=w.session.rules[y.args[0].indicator][z];w.session.renamed_variables={},X=X.rename(w),X.body===null&&(X.body=new j("true"));var $=new j(",",[new j("=",[X.head,y.args[0]]),new j("=",[X.body,y.args[1]])]);F.push(new Pe(b.goal.replace($),b.substitution,b))}w.prepend(F)}else w.throw_error(x.error.permission("access","private_procedure",y.args[0].indicator,y.indicator))},"current_predicate/1":function(w,b,y){var F=y.args[0];if(!x.type.is_variable(F)&&(!x.type.is_compound(F)||F.indicator!=="//2"))w.throw_error(x.error.type("predicate_indicator",F,y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_variable(F.args[0])&&!x.type.is_atom(F.args[0]))w.throw_error(x.error.type("atom",F.args[0],y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_variable(F.args[1])&&!x.type.is_integer(F.args[1]))w.throw_error(x.error.type("integer",F.args[1],y.indicator));else{var z=[];for(var X in w.session.rules)if(w.session.rules.hasOwnProperty(X)){var $=X.lastIndexOf("/"),oe=X.substr(0,$),xe=parseInt(X.substr($+1,X.length-($+1))),Te=new j("/",[new j(oe),new Re(xe,!1)]),lt=new j("=",[Te,F]);z.push(new Pe(b.goal.replace(lt),b.substitution,b))}w.prepend(z)}},"asserta/1":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else{var F,z;y.args[0].indicator===":-/2"?(F=y.args[0].args[0],z=Ce(y.args[0].args[1])):(F=y.args[0],z=null),x.type.is_callable(F)?z!==null&&!x.type.is_callable(z)?w.throw_error(x.error.type("callable",z,y.indicator)):w.is_public_predicate(F.indicator)?(w.session.rules[F.indicator]===void 0&&(w.session.rules[F.indicator]=[]),w.session.public_predicates[F.indicator]=!0,w.session.rules[F.indicator]=[new Ve(F,z,!0)].concat(w.session.rules[F.indicator]),w.success(b)):w.throw_error(x.error.permission("modify","static_procedure",F.indicator,y.indicator)):w.throw_error(x.error.type("callable",F,y.indicator))}},"assertz/1":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else{var F,z;y.args[0].indicator===":-/2"?(F=y.args[0].args[0],z=Ce(y.args[0].args[1])):(F=y.args[0],z=null),x.type.is_callable(F)?z!==null&&!x.type.is_callable(z)?w.throw_error(x.error.type("callable",z,y.indicator)):w.is_public_predicate(F.indicator)?(w.session.rules[F.indicator]===void 0&&(w.session.rules[F.indicator]=[]),w.session.public_predicates[F.indicator]=!0,w.session.rules[F.indicator].push(new Ve(F,z,!0)),w.success(b)):w.throw_error(x.error.permission("modify","static_procedure",F.indicator,y.indicator)):w.throw_error(x.error.type("callable",F,y.indicator))}},"retract/1":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else{var F,z;if(y.args[0].indicator===":-/2"?(F=y.args[0].args[0],z=y.args[0].args[1]):(F=y.args[0],z=new j("true")),typeof b.retract>"u")if(w.is_public_predicate(F.indicator)){if(w.session.rules[F.indicator]!==void 0){for(var X=[],$=0;$w.get_flag("max_arity").value)w.throw_error(x.error.representation("max_arity",y.indicator));else{var F=y.args[0].args[0].id+"/"+y.args[0].args[1].value;w.is_public_predicate(F)?(delete w.session.rules[F],w.success(b)):w.throw_error(x.error.permission("modify","static_procedure",F,y.indicator))}},"atom_length/2":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_atom(y.args[0]))w.throw_error(x.error.type("atom",y.args[0],y.indicator));else if(!x.type.is_variable(y.args[1])&&!x.type.is_integer(y.args[1]))w.throw_error(x.error.type("integer",y.args[1],y.indicator));else if(x.type.is_integer(y.args[1])&&y.args[1].value<0)w.throw_error(x.error.domain("not_less_than_zero",y.args[1],y.indicator));else{var F=new Re(y.args[0].id.length,!1);w.prepend([new Pe(b.goal.replace(new j("=",[F,y.args[1]])),b.substitution,b)])}},"atom_concat/3":function(w,b,y){var F,z,X=y.args[0],$=y.args[1],oe=y.args[2];if(x.type.is_variable(oe)&&(x.type.is_variable(X)||x.type.is_variable($)))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_atom(X))w.throw_error(x.error.type("atom",X,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_atom($))w.throw_error(x.error.type("atom",$,y.indicator));else if(!x.type.is_variable(oe)&&!x.type.is_atom(oe))w.throw_error(x.error.type("atom",oe,y.indicator));else{var xe=x.type.is_variable(X),Te=x.type.is_variable($);if(!xe&&!Te)z=new j("=",[oe,new j(X.id+$.id)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]);else if(xe&&!Te)F=oe.id.substr(0,oe.id.length-$.id.length),F+$.id===oe.id&&(z=new j("=",[X,new j(F)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]));else if(Te&&!xe)F=oe.id.substr(X.id.length),X.id+F===oe.id&&(z=new j("=",[$,new j(F)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]));else{for(var lt=[],Ct=0;Ct<=oe.id.length;Ct++){var qt=new j(oe.id.substr(0,Ct)),ir=new j(oe.id.substr(Ct));z=new j(",",[new j("=",[qt,X]),new j("=",[ir,$])]),lt.push(new Pe(b.goal.replace(z),b.substitution,b))}w.prepend(lt)}}},"sub_atom/5":function(w,b,y){var F,z=y.args[0],X=y.args[1],$=y.args[2],oe=y.args[3],xe=y.args[4];if(x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_integer(X))w.throw_error(x.error.type("integer",X,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_integer($))w.throw_error(x.error.type("integer",$,y.indicator));else if(!x.type.is_variable(oe)&&!x.type.is_integer(oe))w.throw_error(x.error.type("integer",oe,y.indicator));else if(x.type.is_integer(X)&&X.value<0)w.throw_error(x.error.domain("not_less_than_zero",X,y.indicator));else if(x.type.is_integer($)&&$.value<0)w.throw_error(x.error.domain("not_less_than_zero",$,y.indicator));else if(x.type.is_integer(oe)&&oe.value<0)w.throw_error(x.error.domain("not_less_than_zero",oe,y.indicator));else{var Te=[],lt=[],Ct=[];if(x.type.is_variable(X))for(F=0;F<=z.id.length;F++)Te.push(F);else Te.push(X.value);if(x.type.is_variable($))for(F=0;F<=z.id.length;F++)lt.push(F);else lt.push($.value);if(x.type.is_variable(oe))for(F=0;F<=z.id.length;F++)Ct.push(F);else Ct.push(oe.value);var qt=[];for(var ir in Te)if(Te.hasOwnProperty(ir)){F=Te[ir];for(var Pt in lt)if(lt.hasOwnProperty(Pt)){var gn=lt[Pt],Pr=z.id.length-F-gn;if(e(Ct,Pr)!==-1&&F+gn+Pr===z.id.length){var Ir=z.id.substr(F,gn);if(z.id===z.id.substr(0,F)+Ir+z.id.substr(F+gn,Pr)){var Or=new j("=",[new j(Ir),xe]),on=new j("=",[X,new Re(F)]),ai=new j("=",[$,new Re(gn)]),Io=new j("=",[oe,new Re(Pr)]),rs=new j(",",[new j(",",[new j(",",[on,ai]),Io]),Or]);qt.push(new Pe(b.goal.replace(rs),b.substitution,b))}}}}w.prepend(qt)}},"atom_chars/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(x.type.is_variable(F)){for(var oe=z,xe=x.type.is_variable(F),Te="";oe.indicator==="./2";){if(x.type.is_character(oe.args[0]))Te+=oe.args[0].id;else if(x.type.is_variable(oe.args[0])&&xe){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type("character",oe.args[0],y.indicator));return}oe=oe.args[1]}x.type.is_variable(oe)&&xe?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)?w.throw_error(x.error.type("list",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[new j(Te),F])),b.substitution,b)])}else{for(var X=new j("[]"),$=F.id.length-1;$>=0;$--)X=new j(".",[new j(F.id.charAt($)),X]);w.prepend([new Pe(b.goal.replace(new j("=",[z,X])),b.substitution,b)])}},"atom_codes/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(x.type.is_variable(F)){for(var oe=z,xe=x.type.is_variable(F),Te="";oe.indicator==="./2";){if(x.type.is_character_code(oe.args[0]))Te+=c(oe.args[0].value);else if(x.type.is_variable(oe.args[0])&&xe){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.representation("character_code",y.indicator));return}oe=oe.args[1]}x.type.is_variable(oe)&&xe?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)?w.throw_error(x.error.type("list",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[new j(Te),F])),b.substitution,b)])}else{for(var X=new j("[]"),$=F.id.length-1;$>=0;$--)X=new j(".",[new Re(n(F.id,$),!1),X]);w.prepend([new Pe(b.goal.replace(new j("=",[z,X])),b.substitution,b)])}},"char_code/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_character(F))w.throw_error(x.error.type("character",F,y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_integer(z))w.throw_error(x.error.type("integer",z,y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_character_code(z))w.throw_error(x.error.representation("character_code",y.indicator));else if(x.type.is_variable(z)){var X=new Re(n(F.id,0),!1);w.prepend([new Pe(b.goal.replace(new j("=",[X,z])),b.substitution,b)])}else{var $=new j(c(z.value));w.prepend([new Pe(b.goal.replace(new j("=",[$,F])),b.substitution,b)])}},"number_chars/2":function(w,b,y){var F,z=y.args[0],X=y.args[1];if(x.type.is_variable(z)&&x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_number(z))w.throw_error(x.error.type("number",z,y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_list(X))w.throw_error(x.error.type("list",X,y.indicator));else{var $=x.type.is_variable(z);if(!x.type.is_variable(X)){var oe=X,xe=!0;for(F="";oe.indicator==="./2";){if(x.type.is_character(oe.args[0]))F+=oe.args[0].id;else if(x.type.is_variable(oe.args[0]))xe=!1;else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type("character",oe.args[0],y.indicator));return}oe=oe.args[1]}if(xe=xe&&x.type.is_empty_list(oe),!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)){w.throw_error(x.error.type("list",X,y.indicator));return}if(!xe&&$){w.throw_error(x.error.instantiation(y.indicator));return}else if(xe)if(x.type.is_variable(oe)&&$){w.throw_error(x.error.instantiation(y.indicator));return}else{var Te=w.parse(F),lt=Te.value;!x.type.is_number(lt)||Te.tokens[Te.tokens.length-1].space?w.throw_error(x.error.syntax_by_predicate("parseable_number",y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,lt])),b.substitution,b)]);return}}if(!$){F=z.toString();for(var Ct=new j("[]"),qt=F.length-1;qt>=0;qt--)Ct=new j(".",[new j(F.charAt(qt)),Ct]);w.prepend([new Pe(b.goal.replace(new j("=",[X,Ct])),b.substitution,b)])}}},"number_codes/2":function(w,b,y){var F,z=y.args[0],X=y.args[1];if(x.type.is_variable(z)&&x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_number(z))w.throw_error(x.error.type("number",z,y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_list(X))w.throw_error(x.error.type("list",X,y.indicator));else{var $=x.type.is_variable(z);if(!x.type.is_variable(X)){var oe=X,xe=!0;for(F="";oe.indicator==="./2";){if(x.type.is_character_code(oe.args[0]))F+=c(oe.args[0].value);else if(x.type.is_variable(oe.args[0]))xe=!1;else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type("character_code",oe.args[0],y.indicator));return}oe=oe.args[1]}if(xe=xe&&x.type.is_empty_list(oe),!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)){w.throw_error(x.error.type("list",X,y.indicator));return}if(!xe&&$){w.throw_error(x.error.instantiation(y.indicator));return}else if(xe)if(x.type.is_variable(oe)&&$){w.throw_error(x.error.instantiation(y.indicator));return}else{var Te=w.parse(F),lt=Te.value;!x.type.is_number(lt)||Te.tokens[Te.tokens.length-1].space?w.throw_error(x.error.syntax_by_predicate("parseable_number",y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,lt])),b.substitution,b)]);return}}if(!$){F=z.toString();for(var Ct=new j("[]"),qt=F.length-1;qt>=0;qt--)Ct=new j(".",[new Re(n(F,qt),!1),Ct]);w.prepend([new Pe(b.goal.replace(new j("=",[X,Ct])),b.substitution,b)])}}},"upcase_atom/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?!x.type.is_variable(z)&&!x.type.is_atom(z)?w.throw_error(x.error.type("atom",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,new j(F.id.toUpperCase(),[])])),b.substitution,b)]):w.throw_error(x.error.type("atom",F,y.indicator))},"downcase_atom/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?!x.type.is_variable(z)&&!x.type.is_atom(z)?w.throw_error(x.error.type("atom",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,new j(F.id.toLowerCase(),[])])),b.substitution,b)]):w.throw_error(x.error.type("atom",F,y.indicator))},"atomic_list_concat/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("atomic_list_concat",[F,new j("",[]),z])),b.substitution,b)])},"atomic_list_concat/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2];if(x.type.is_variable(z)||x.type.is_variable(F)&&x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_list(F))w.throw_error(x.error.type("list",F,y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_atom(X))w.throw_error(x.error.type("atom",X,y.indicator));else if(x.type.is_variable(X)){for(var oe="",xe=F;x.type.is_term(xe)&&xe.indicator==="./2";){if(!x.type.is_atom(xe.args[0])&&!x.type.is_number(xe.args[0])){w.throw_error(x.error.type("atomic",xe.args[0],y.indicator));return}oe!==""&&(oe+=z.id),x.type.is_atom(xe.args[0])?oe+=xe.args[0].id:oe+=""+xe.args[0].value,xe=xe.args[1]}oe=new j(oe,[]),x.type.is_variable(xe)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_term(xe)||xe.indicator!=="[]/0"?w.throw_error(x.error.type("list",F,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[oe,X])),b.substitution,b)])}else{var $=g(s(X.id.split(z.id),function(Te){return new j(Te,[])}));w.prepend([new Pe(b.goal.replace(new j("=",[$,F])),b.substitution,b)])}},"@=/2":function(w,b,y){x.compare(y.args[0],y.args[1])>0&&w.success(b)},"@>=/2":function(w,b,y){x.compare(y.args[0],y.args[1])>=0&&w.success(b)},"compare/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2];if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(x.type.is_atom(F)&&["<",">","="].indexOf(F.id)===-1)w.throw_error(x.type.domain("order",F,y.indicator));else{var $=x.compare(z,X);$=$===0?"=":$===-1?"<":">",w.prepend([new Pe(b.goal.replace(new j("=",[F,new j($,[])])),b.substitution,b)])}},"is/2":function(w,b,y){var F=y.args[1].interpret(w);x.type.is_number(F)?w.prepend([new Pe(b.goal.replace(new j("=",[y.args[0],F],w.level)),b.substitution,b)]):w.throw_error(F)},"between/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2];if(x.type.is_variable(F)||x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_integer(F))w.throw_error(x.error.type("integer",F,y.indicator));else if(!x.type.is_integer(z))w.throw_error(x.error.type("integer",z,y.indicator));else if(!x.type.is_variable(X)&&!x.type.is_integer(X))w.throw_error(x.error.type("integer",X,y.indicator));else if(x.type.is_variable(X)){var $=[new Pe(b.goal.replace(new j("=",[X,F])),b.substitution,b)];F.value=X.value&&w.success(b)},"succ/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)&&x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_variable(F)&&!x.type.is_integer(F)?w.throw_error(x.error.type("integer",F,y.indicator)):!x.type.is_variable(z)&&!x.type.is_integer(z)?w.throw_error(x.error.type("integer",z,y.indicator)):!x.type.is_variable(F)&&F.value<0?w.throw_error(x.error.domain("not_less_than_zero",F,y.indicator)):!x.type.is_variable(z)&&z.value<0?w.throw_error(x.error.domain("not_less_than_zero",z,y.indicator)):(x.type.is_variable(z)||z.value>0)&&(x.type.is_variable(F)?w.prepend([new Pe(b.goal.replace(new j("=",[F,new Re(z.value-1,!1)])),b.substitution,b)]):w.prepend([new Pe(b.goal.replace(new j("=",[z,new Re(F.value+1,!1)])),b.substitution,b)]))},"=:=/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F===0&&w.success(b)},"=\\=/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F!==0&&w.success(b)},"/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F>0&&w.success(b)},">=/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F>=0&&w.success(b)},"var/1":function(w,b,y){x.type.is_variable(y.args[0])&&w.success(b)},"atom/1":function(w,b,y){x.type.is_atom(y.args[0])&&w.success(b)},"atomic/1":function(w,b,y){x.type.is_atomic(y.args[0])&&w.success(b)},"compound/1":function(w,b,y){x.type.is_compound(y.args[0])&&w.success(b)},"integer/1":function(w,b,y){x.type.is_integer(y.args[0])&&w.success(b)},"float/1":function(w,b,y){x.type.is_float(y.args[0])&&w.success(b)},"number/1":function(w,b,y){x.type.is_number(y.args[0])&&w.success(b)},"nonvar/1":function(w,b,y){x.type.is_variable(y.args[0])||w.success(b)},"ground/1":function(w,b,y){y.variables().length===0&&w.success(b)},"acyclic_term/1":function(w,b,y){for(var F=b.substitution.apply(b.substitution),z=y.args[0].variables(),X=0;X0?Pt[Pt.length-1]:null,Pt!==null&&(qt=W(w,Pt,0,w.__get_max_priority(),!1))}if(qt.type===p&&qt.len===Pt.length-1&&gn.value==="."){qt=qt.value.rename(w);var Pr=new j("=",[z,qt]);if(oe.variables){var Ir=g(s(ye(qt.variables()),function(Or){return new De(Or)}));Pr=new j(",",[Pr,new j("=",[oe.variables,Ir])])}if(oe.variable_names){var Ir=g(s(ye(qt.variables()),function(on){var ai;for(ai in w.session.renamed_variables)if(w.session.renamed_variables.hasOwnProperty(ai)&&w.session.renamed_variables[ai]===on)break;return new j("=",[new j(ai,[]),new De(on)])}));Pr=new j(",",[Pr,new j("=",[oe.variable_names,Ir])])}if(oe.singletons){var Ir=g(s(new Ve(qt,null).singleton_variables(),function(on){var ai;for(ai in w.session.renamed_variables)if(w.session.renamed_variables.hasOwnProperty(ai)&&w.session.renamed_variables[ai]===on)break;return new j("=",[new j(ai,[]),new De(on)])}));Pr=new j(",",[Pr,new j("=",[oe.singletons,Ir])])}w.prepend([new Pe(b.goal.replace(Pr),b.substitution,b)])}else qt.type===p?w.throw_error(x.error.syntax(Pt[qt.len],"unexpected token",!1)):w.throw_error(qt.value)}}},"write/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("write",[new De("S"),F])])),b.substitution,b)])},"write/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("write_term",[F,z,new j(".",[new j("quoted",[new j("false",[])]),new j(".",[new j("ignore_ops",[new j("false")]),new j(".",[new j("numbervars",[new j("true")]),new j("[]",[])])])])])),b.substitution,b)])},"writeq/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("writeq",[new De("S"),F])])),b.substitution,b)])},"writeq/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("write_term",[F,z,new j(".",[new j("quoted",[new j("true",[])]),new j(".",[new j("ignore_ops",[new j("false")]),new j(".",[new j("numbervars",[new j("true")]),new j("[]",[])])])])])),b.substitution,b)])},"write_canonical/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("write_canonical",[new De("S"),F])])),b.substitution,b)])},"write_canonical/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("write_term",[F,z,new j(".",[new j("quoted",[new j("true",[])]),new j(".",[new j("ignore_ops",[new j("true")]),new j(".",[new j("numbervars",[new j("false")]),new j("[]",[])])])])])),b.substitution,b)])},"write_term/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("write_term",[new De("S"),F,z])])),b.substitution,b)])},"write_term/3":function(w,b,y){var F=y.args[0],z=y.args[1],X=y.args[2],$=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F)||x.type.is_variable(X))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_list(X))w.throw_error(x.error.type("list",X,y.indicator));else if(!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain("stream_or_alias",F,y.indicator));else if(!x.type.is_stream($)||$.stream===null)w.throw_error(x.error.existence("stream",F,y.indicator));else if($.input)w.throw_error(x.error.permission("output","stream",F,y.indicator));else if($.type==="binary")w.throw_error(x.error.permission("output","binary_stream",F,y.indicator));else if($.position==="past_end_of_stream"&&$.eof_action==="error")w.throw_error(x.error.permission("output","past_end_of_stream",F,y.indicator));else{for(var oe={},xe=X,Te;x.type.is_term(xe)&&xe.indicator==="./2";){if(Te=xe.args[0],x.type.is_variable(Te)){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_write_option(Te)){w.throw_error(x.error.domain("write_option",Te,y.indicator));return}oe[Te.id]=Te.args[0].id==="true",xe=xe.args[1]}if(xe.indicator!=="[]/0"){x.type.is_variable(xe)?w.throw_error(x.error.instantiation(y.indicator)):w.throw_error(x.error.type("list",X,y.indicator));return}else{oe.session=w.session;var lt=z.toString(oe);$.stream.put(lt,$.position),typeof $.position=="number"&&($.position+=lt.length),w.success(b)}}},"halt/0":function(w,b,y){w.points=[]},"halt/1":function(w,b,y){var F=y.args[0];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_integer(F)?w.points=[]:w.throw_error(x.error.type("integer",F,y.indicator))},"current_prolog_flag/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_flag(F))w.throw_error(x.error.domain("prolog_flag",F,y.indicator));else{var X=[];for(var $ in x.flag)if(x.flag.hasOwnProperty($)){var oe=new j(",",[new j("=",[new j($),F]),new j("=",[w.get_flag($),z])]);X.push(new Pe(b.goal.replace(oe),b.substitution,b))}w.prepend(X)}},"set_prolog_flag/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)||x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?x.type.is_flag(F)?x.type.is_value_flag(F,z)?x.type.is_modifiable_flag(F)?(w.session.flag[F.id]=z,w.success(b)):w.throw_error(x.error.permission("modify","flag",F)):w.throw_error(x.error.domain("flag_value",new j("+",[F,z]),y.indicator)):w.throw_error(x.error.domain("prolog_flag",F,y.indicator)):w.throw_error(x.error.type("atom",F,y.indicator))}},flag:{bounded:{allowed:[new j("true"),new j("false")],value:new j("true"),changeable:!1},max_integer:{allowed:[new Re(Number.MAX_SAFE_INTEGER)],value:new Re(Number.MAX_SAFE_INTEGER),changeable:!1},min_integer:{allowed:[new Re(Number.MIN_SAFE_INTEGER)],value:new Re(Number.MIN_SAFE_INTEGER),changeable:!1},integer_rounding_function:{allowed:[new j("down"),new j("toward_zero")],value:new j("toward_zero"),changeable:!1},char_conversion:{allowed:[new j("on"),new j("off")],value:new j("on"),changeable:!0},debug:{allowed:[new j("on"),new j("off")],value:new j("off"),changeable:!0},max_arity:{allowed:[new j("unbounded")],value:new j("unbounded"),changeable:!1},unknown:{allowed:[new j("error"),new j("fail"),new j("warning")],value:new j("error"),changeable:!0},double_quotes:{allowed:[new j("chars"),new j("codes"),new j("atom")],value:new j("codes"),changeable:!0},occurs_check:{allowed:[new j("false"),new j("true")],value:new j("false"),changeable:!0},dialect:{allowed:[new j("tau")],value:new j("tau"),changeable:!1},version_data:{allowed:[new j("tau",[new Re(t.major,!1),new Re(t.minor,!1),new Re(t.patch,!1),new j(t.status)])],value:new j("tau",[new Re(t.major,!1),new Re(t.minor,!1),new Re(t.patch,!1),new j(t.status)]),changeable:!1},nodejs:{allowed:[new j("yes"),new j("no")],value:new j(typeof ec<"u"&&ec.exports?"yes":"no"),changeable:!1}},unify:function(w,b,y){y=y===void 0?!1:y;for(var F=[{left:w,right:b}],z={};F.length!==0;){var X=F.pop();if(w=X.left,b=X.right,x.type.is_term(w)&&x.type.is_term(b)){if(w.indicator!==b.indicator)return null;for(var $=0;$z.value?1:0:z}else return F},operate:function(w,b){if(x.type.is_operator(b)){for(var y=x.type.is_operator(b),F=[],z,X=!1,$=0;$w.get_flag("max_integer").value||z0?w.start+w.matches[0].length:w.start,z=y?new j("token_not_found"):new j("found",[new j(w.value.toString())]),X=new j(".",[new j("line",[new Re(w.line+1)]),new j(".",[new j("column",[new Re(F+1)]),new j(".",[z,new j("[]",[])])])]);return new j("error",[new j("syntax_error",[new j(b)]),X])},syntax_by_predicate:function(w,b){return new j("error",[new j("syntax_error",[new j(w)]),Z(b)])}},warning:{singleton:function(w,b,y){for(var F=new j("[]"),z=w.length-1;z>=0;z--)F=new j(".",[new De(w[z]),F]);return new j("warning",[new j("singleton_variables",[F,Z(b)]),new j(".",[new j("line",[new Re(y,!1)]),new j("[]")])])},failed_goal:function(w,b){return new j("warning",[new j("failed_goal",[w]),new j(".",[new j("line",[new Re(b,!1)]),new j("[]")])])}},format_variable:function(w){return"_"+w},format_answer:function(w,b,F){b instanceof ke&&(b=b.thread);var F=F||{};if(F.session=b?b.session:void 0,x.type.is_error(w))return"uncaught exception: "+w.args[0].toString();if(w===!1)return"false.";if(w===null)return"limit exceeded ;";var z=0,X="";if(x.type.is_substitution(w)){var $=w.domain(!0);w=w.filter(function(Te,lt){return!x.type.is_variable(lt)||$.indexOf(lt.id)!==-1&&Te!==lt.id})}for(var oe in w.links)w.links.hasOwnProperty(oe)&&(z++,X!==""&&(X+=", "),X+=oe.toString(F)+" = "+w.links[oe].toString(F));var xe=typeof b>"u"||b.points.length>0?" ;":".";return z===0?"true"+xe:X+xe},flatten_error:function(w){if(!x.type.is_error(w))return null;w=w.args[0];var b={};return b.type=w.args[0].id,b.thrown=b.type==="syntax_error"?null:w.args[1].id,b.expected=null,b.found=null,b.representation=null,b.existence=null,b.existence_type=null,b.line=null,b.column=null,b.permission_operation=null,b.permission_type=null,b.evaluation_type=null,b.type==="type_error"||b.type==="domain_error"?(b.expected=w.args[0].args[0].id,b.found=w.args[0].args[1].toString()):b.type==="syntax_error"?w.args[1].indicator==="./2"?(b.expected=w.args[0].args[0].id,b.found=w.args[1].args[1].args[1].args[0],b.found=b.found.id==="token_not_found"?b.found.id:b.found.args[0].id,b.line=w.args[1].args[0].args[0].value,b.column=w.args[1].args[1].args[0].args[0].value):b.thrown=w.args[1].id:b.type==="permission_error"?(b.found=w.args[0].args[2].toString(),b.permission_operation=w.args[0].args[0].id,b.permission_type=w.args[0].args[1].id):b.type==="evaluation_error"?b.evaluation_type=w.args[0].args[0].id:b.type==="representation_error"?b.representation=w.args[0].args[0].id:b.type==="existence_error"&&(b.existence=w.args[0].args[1].toString(),b.existence_type=w.args[0].args[0].id),b},create:function(w){return new x.type.Session(w)}};typeof ec<"u"?ec.exports=x:window.pl=x})()});function EEe(t,e,r){t.prepend(r.map(s=>new hl.default.type.State(e.goal.replace(s),e.substitution,e)))}function Lq(t){let e=CEe.get(t.session);if(e==null)throw new Error("Assertion failed: A project should have been registered for the active session");return e}function wEe(t,e){CEe.set(t,e),t.consult(`:- use_module(library(${Zct.id})).`)}var hl,IEe,J0,zct,Xct,CEe,Zct,BEe=Xe(()=>{Ge();ql();hl=ut(Oq()),IEe=ut(Ie("vm")),{is_atom:J0,is_variable:zct,is_instantiated_list:Xct}=hl.default.type;CEe=new WeakMap;Zct=new hl.default.type.Module("constraints",{"project_workspaces_by_descriptor/3":(t,e,r)=>{let[s,a,n]=r.args;if(!J0(s)||!J0(a)){t.throw_error(hl.default.error.instantiation(r.indicator));return}let c=G.parseIdent(s.id),f=G.makeDescriptor(c,a.id),h=Lq(t).tryWorkspaceByDescriptor(f);zct(n)&&h!==null&&EEe(t,e,[new hl.default.type.Term("=",[n,new hl.default.type.Term(String(h.relativeCwd))])]),J0(n)&&h!==null&&h.relativeCwd===n.id&&t.success(e)},"workspace_field/3":(t,e,r)=>{let[s,a,n]=r.args;if(!J0(s)||!J0(a)){t.throw_error(hl.default.error.instantiation(r.indicator));return}let f=Lq(t).tryWorkspaceByCwd(s.id);if(f==null)return;let p=va(f.manifest.raw,a.id);typeof p>"u"||EEe(t,e,[new hl.default.type.Term("=",[n,new hl.default.type.Term(typeof p=="object"?JSON.stringify(p):p)])])},"workspace_field_test/3":(t,e,r)=>{let[s,a,n]=r.args;t.prepend([new hl.default.type.State(e.goal.replace(new hl.default.type.Term("workspace_field_test",[s,a,n,new hl.default.type.Term("[]",[])])),e.substitution,e)])},"workspace_field_test/4":(t,e,r)=>{let[s,a,n,c]=r.args;if(!J0(s)||!J0(a)||!J0(n)||!Xct(c)){t.throw_error(hl.default.error.instantiation(r.indicator));return}let p=Lq(t).tryWorkspaceByCwd(s.id);if(p==null)return;let h=va(p.manifest.raw,a.id);if(typeof h>"u")return;let E={$$:h};for(let[S,P]of c.toJavaScript().entries())E[`$${S}`]=P;IEe.default.runInNewContext(n.id,E)&&t.success(e)}},["project_workspaces_by_descriptor/3","workspace_field/3","workspace_field_test/3","workspace_field_test/4"])});var aS={};Vt(aS,{Constraints:()=>Uq,DependencyType:()=>bEe});function go(t){if(t instanceof KC.default.type.Num)return t.value;if(t instanceof KC.default.type.Term)switch(t.indicator){case"throw/1":return go(t.args[0]);case"error/1":return go(t.args[0]);case"error/2":if(t.args[0]instanceof KC.default.type.Term&&t.args[0].indicator==="syntax_error/1")return Object.assign(go(t.args[0]),...go(t.args[1]));{let e=go(t.args[0]);return e.message+=` (in ${go(t.args[1])})`,e}case"syntax_error/1":return new jt(43,`Syntax error: ${go(t.args[0])}`);case"existence_error/2":return new jt(44,`Existence error: ${go(t.args[0])} ${go(t.args[1])} not found`);case"instantiation_error/0":return new jt(75,"Instantiation error: an argument is variable when an instantiated argument was expected");case"line/1":return{line:go(t.args[0])};case"column/1":return{column:go(t.args[0])};case"found/1":return{found:go(t.args[0])};case"./2":return[go(t.args[0])].concat(go(t.args[1]));case"//2":return`${go(t.args[0])}/${go(t.args[1])}`;default:return t.id}throw`couldn't pretty print because of unsupported node ${t}`}function SEe(t){let e;try{e=go(t)}catch(r){throw typeof r=="string"?new jt(42,`Unknown error: ${t} (note: ${r})`):r}return typeof e.line<"u"&&typeof e.column<"u"&&(e.message+=` at line ${e.line}, column ${e.column}`),e}function Pm(t){return t.id==="null"?null:`${t.toJavaScript()}`}function $ct(t){if(t.id==="null")return null;{let e=t.toJavaScript();if(typeof e!="string")return JSON.stringify(e);try{return JSON.stringify(JSON.parse(e))}catch{return JSON.stringify(e)}}}function K0(t){return typeof t=="string"?`'${t}'`:"[]"}var DEe,KC,bEe,vEe,Mq,Uq,lS=Xe(()=>{Ge();Ge();Dt();DEe=ut(nEe()),KC=ut(Oq());iS();BEe();(0,DEe.default)(KC.default);bEe=(s=>(s.Dependencies="dependencies",s.DevDependencies="devDependencies",s.PeerDependencies="peerDependencies",s))(bEe||{}),vEe=["dependencies","devDependencies","peerDependencies"];Mq=class{constructor(e,r){let s=1e3*e.workspaces.length;this.session=KC.default.create(s),wEe(this.session,e),this.session.consult(":- use_module(library(lists))."),this.session.consult(r)}fetchNextAnswer(){return new Promise(e=>{this.session.answer(r=>{e(r)})})}async*makeQuery(e){let r=this.session.query(e);if(r!==!0)throw SEe(r);for(;;){let s=await this.fetchNextAnswer();if(s===null)throw new jt(79,"Resolution limit exceeded");if(!s)break;if(s.id==="throw")throw SEe(s);yield s}}};Uq=class t{constructor(e){this.source="";this.project=e;let r=e.configuration.get("constraintsPath");ce.existsSync(r)&&(this.source=ce.readFileSync(r,"utf8"))}static async find(e){return new t(e)}getProjectDatabase(){let e="";for(let r of vEe)e+=`dependency_type(${r}). `;for(let r of this.project.workspacesByCwd.values()){let s=r.relativeCwd;e+=`workspace(${K0(s)}). `,e+=`workspace_ident(${K0(s)}, ${K0(G.stringifyIdent(r.anchoredLocator))}). `,e+=`workspace_version(${K0(s)}, ${K0(r.manifest.version)}). `;for(let a of vEe)for(let n of r.manifest[a].values())e+=`workspace_has_dependency(${K0(s)}, ${K0(G.stringifyIdent(n))}, ${K0(n.range)}, ${a}). `}return e+=`workspace(_) :- false. `,e+=`workspace_ident(_, _) :- false. `,e+=`workspace_version(_, _) :- false. `,e+=`workspace_has_dependency(_, _, _, _) :- false. `,e}getDeclarations(){let e="";return e+=`gen_enforced_dependency(_, _, _, _) :- false. `,e+=`gen_enforced_field(_, _, _) :- false. `,e}get fullSource(){return`${this.getProjectDatabase()} ${this.source} ${this.getDeclarations()}`}createSession(){return new Mq(this.project,this.fullSource)}async processClassic(){let e=this.createSession();return{enforcedDependencies:await this.genEnforcedDependencies(e),enforcedFields:await this.genEnforcedFields(e)}}async process(){let{enforcedDependencies:e,enforcedFields:r}=await this.processClassic(),s=new Map;for(let{workspace:a,dependencyIdent:n,dependencyRange:c,dependencyType:f}of e){let p=nS([f,G.stringifyIdent(n)]),h=je.getMapWithDefault(s,a.cwd);je.getMapWithDefault(h,p).set(c??void 0,new Set)}for(let{workspace:a,fieldPath:n,fieldValue:c}of r){let f=nS(n),p=je.getMapWithDefault(s,a.cwd);je.getMapWithDefault(p,f).set(JSON.parse(c)??void 0,new Set)}return{manifestUpdates:s,reportedErrors:new Map}}async genEnforcedDependencies(e){let r=[];for await(let s of e.makeQuery("workspace(WorkspaceCwd), dependency_type(DependencyType), gen_enforced_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType).")){let a=J.resolve(this.project.cwd,Pm(s.links.WorkspaceCwd)),n=Pm(s.links.DependencyIdent),c=Pm(s.links.DependencyRange),f=Pm(s.links.DependencyType);if(a===null||n===null)throw new Error("Invalid rule");let p=this.project.getWorkspaceByCwd(a),h=G.parseIdent(n);r.push({workspace:p,dependencyIdent:h,dependencyRange:c,dependencyType:f})}return je.sortMap(r,[({dependencyRange:s})=>s!==null?"0":"1",({workspace:s})=>G.stringifyIdent(s.anchoredLocator),({dependencyIdent:s})=>G.stringifyIdent(s)])}async genEnforcedFields(e){let r=[];for await(let s of e.makeQuery("workspace(WorkspaceCwd), gen_enforced_field(WorkspaceCwd, FieldPath, FieldValue).")){let a=J.resolve(this.project.cwd,Pm(s.links.WorkspaceCwd)),n=Pm(s.links.FieldPath),c=$ct(s.links.FieldValue);if(a===null||n===null)throw new Error("Invalid rule");let f=this.project.getWorkspaceByCwd(a);r.push({workspace:f,fieldPath:n,fieldValue:c})}return je.sortMap(r,[({workspace:s})=>G.stringifyIdent(s.anchoredLocator),({fieldPath:s})=>s])}async*query(e){let r=this.createSession();for await(let s of r.makeQuery(e)){let a={};for(let[n,c]of Object.entries(s.links))n!=="_"&&(a[n]=Pm(c));yield a}}}});var OEe=_(fF=>{"use strict";Object.defineProperty(fF,"__esModule",{value:!0});function BS(t){let e=[...t.caches],r=e.shift();return r===void 0?NEe():{get(s,a,n={miss:()=>Promise.resolve()}){return r.get(s,a,n).catch(()=>BS({caches:e}).get(s,a,n))},set(s,a){return r.set(s,a).catch(()=>BS({caches:e}).set(s,a))},delete(s){return r.delete(s).catch(()=>BS({caches:e}).delete(s))},clear(){return r.clear().catch(()=>BS({caches:e}).clear())}}}function NEe(){return{get(t,e,r={miss:()=>Promise.resolve()}){return e().then(a=>Promise.all([a,r.miss(a)])).then(([a])=>a)},set(t,e){return Promise.resolve(e)},delete(t){return Promise.resolve()},clear(){return Promise.resolve()}}}fF.createFallbackableCache=BS;fF.createNullCache=NEe});var MEe=_((BJt,LEe)=>{LEe.exports=OEe()});var UEe=_($q=>{"use strict";Object.defineProperty($q,"__esModule",{value:!0});function yut(t={serializable:!0}){let e={};return{get(r,s,a={miss:()=>Promise.resolve()}){let n=JSON.stringify(r);if(n in e)return Promise.resolve(t.serializable?JSON.parse(e[n]):e[n]);let c=s(),f=a&&a.miss||(()=>Promise.resolve());return c.then(p=>f(p)).then(()=>c)},set(r,s){return e[JSON.stringify(r)]=t.serializable?JSON.stringify(s):s,Promise.resolve(s)},delete(r){return delete e[JSON.stringify(r)],Promise.resolve()},clear(){return e={},Promise.resolve()}}}$q.createInMemoryCache=yut});var HEe=_((SJt,_Ee)=>{_Ee.exports=UEe()});var GEe=_($u=>{"use strict";Object.defineProperty($u,"__esModule",{value:!0});function Eut(t,e,r){let s={"x-algolia-api-key":r,"x-algolia-application-id":e};return{headers(){return t===e9.WithinHeaders?s:{}},queryParameters(){return t===e9.WithinQueryParameters?s:{}}}}function Iut(t){let e=0,r=()=>(e++,new Promise(s=>{setTimeout(()=>{s(t(r))},Math.min(100*e,1e3))}));return t(r)}function jEe(t,e=(r,s)=>Promise.resolve()){return Object.assign(t,{wait(r){return jEe(t.then(s=>Promise.all([e(s,r),s])).then(s=>s[1]))}})}function Cut(t){let e=t.length-1;for(e;e>0;e--){let r=Math.floor(Math.random()*(e+1)),s=t[e];t[e]=t[r],t[r]=s}return t}function wut(t,e){return e&&Object.keys(e).forEach(r=>{t[r]=e[r](t)}),t}function But(t,...e){let r=0;return t.replace(/%s/g,()=>encodeURIComponent(e[r++]))}var vut="4.22.1",Sut=t=>()=>t.transporter.requester.destroy(),e9={WithinQueryParameters:0,WithinHeaders:1};$u.AuthMode=e9;$u.addMethods=wut;$u.createAuth=Eut;$u.createRetryablePromise=Iut;$u.createWaitablePromise=jEe;$u.destroy=Sut;$u.encode=But;$u.shuffle=Cut;$u.version=vut});var vS=_((bJt,qEe)=>{qEe.exports=GEe()});var WEe=_(t9=>{"use strict";Object.defineProperty(t9,"__esModule",{value:!0});var Dut={Delete:"DELETE",Get:"GET",Post:"POST",Put:"PUT"};t9.MethodEnum=Dut});var SS=_((xJt,YEe)=>{YEe.exports=WEe()});var aIe=_(Yi=>{"use strict";Object.defineProperty(Yi,"__esModule",{value:!0});var JEe=SS();function r9(t,e){let r=t||{},s=r.data||{};return Object.keys(r).forEach(a=>{["timeout","headers","queryParameters","data","cacheable"].indexOf(a)===-1&&(s[a]=r[a])}),{data:Object.entries(s).length>0?s:void 0,timeout:r.timeout||e,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var DS={Read:1,Write:2,Any:3},sw={Up:1,Down:2,Timeouted:3},KEe=2*60*1e3;function i9(t,e=sw.Up){return{...t,status:e,lastUpdate:Date.now()}}function zEe(t){return t.status===sw.Up||Date.now()-t.lastUpdate>KEe}function XEe(t){return t.status===sw.Timeouted&&Date.now()-t.lastUpdate<=KEe}function s9(t){return typeof t=="string"?{protocol:"https",url:t,accept:DS.Any}:{protocol:t.protocol||"https",url:t.url,accept:t.accept||DS.Any}}function but(t,e){return Promise.all(e.map(r=>t.get(r,()=>Promise.resolve(i9(r))))).then(r=>{let s=r.filter(f=>zEe(f)),a=r.filter(f=>XEe(f)),n=[...s,...a],c=n.length>0?n.map(f=>s9(f)):e;return{getTimeout(f,p){return(a.length===0&&f===0?1:a.length+3+f)*p},statelessHosts:c}})}var Put=({isTimedOut:t,status:e})=>!t&&~~e===0,xut=t=>{let e=t.status;return t.isTimedOut||Put(t)||~~(e/100)!==2&&~~(e/100)!==4},kut=({status:t})=>~~(t/100)===2,Qut=(t,e)=>xut(t)?e.onRetry(t):kut(t)?e.onSuccess(t):e.onFail(t);function VEe(t,e,r,s){let a=[],n=rIe(r,s),c=nIe(t,s),f=r.method,p=r.method!==JEe.MethodEnum.Get?{}:{...r.data,...s.data},h={"x-algolia-agent":t.userAgent.value,...t.queryParameters,...p,...s.queryParameters},E=0,C=(S,P)=>{let I=S.pop();if(I===void 0)throw oIe(n9(a));let R={data:n,headers:c,method:f,url:eIe(I,r.path,h),connectTimeout:P(E,t.timeouts.connect),responseTimeout:P(E,s.timeout)},N=W=>{let ee={request:R,response:W,host:I,triesLeft:S.length};return a.push(ee),ee},U={onSuccess:W=>ZEe(W),onRetry(W){let ee=N(W);return W.isTimedOut&&E++,Promise.all([t.logger.info("Retryable failure",o9(ee)),t.hostsCache.set(I,i9(I,W.isTimedOut?sw.Timeouted:sw.Down))]).then(()=>C(S,P))},onFail(W){throw N(W),$Ee(W,n9(a))}};return t.requester.send(R).then(W=>Qut(W,U))};return but(t.hostsCache,e).then(S=>C([...S.statelessHosts].reverse(),S.getTimeout))}function Tut(t){let{hostsCache:e,logger:r,requester:s,requestsCache:a,responsesCache:n,timeouts:c,userAgent:f,hosts:p,queryParameters:h,headers:E}=t,C={hostsCache:e,logger:r,requester:s,requestsCache:a,responsesCache:n,timeouts:c,userAgent:f,headers:E,queryParameters:h,hosts:p.map(S=>s9(S)),read(S,P){let I=r9(P,C.timeouts.read),R=()=>VEe(C,C.hosts.filter(W=>(W.accept&DS.Read)!==0),S,I);if((I.cacheable!==void 0?I.cacheable:S.cacheable)!==!0)return R();let U={request:S,mappedRequestOptions:I,transporter:{queryParameters:C.queryParameters,headers:C.headers}};return C.responsesCache.get(U,()=>C.requestsCache.get(U,()=>C.requestsCache.set(U,R()).then(W=>Promise.all([C.requestsCache.delete(U),W]),W=>Promise.all([C.requestsCache.delete(U),Promise.reject(W)])).then(([W,ee])=>ee)),{miss:W=>C.responsesCache.set(U,W)})},write(S,P){return VEe(C,C.hosts.filter(I=>(I.accept&DS.Write)!==0),S,r9(P,C.timeouts.write))}};return C}function Rut(t){let e={value:`Algolia for JavaScript (${t})`,add(r){let s=`; ${r.segment}${r.version!==void 0?` (${r.version})`:""}`;return e.value.indexOf(s)===-1&&(e.value=`${e.value}${s}`),e}};return e}function ZEe(t){try{return JSON.parse(t.content)}catch(e){throw sIe(e.message,t)}}function $Ee({content:t,status:e},r){let s=t;try{s=JSON.parse(t).message}catch{}return iIe(s,e,r)}function Fut(t,...e){let r=0;return t.replace(/%s/g,()=>encodeURIComponent(e[r++]))}function eIe(t,e,r){let s=tIe(r),a=`${t.protocol}://${t.url}/${e.charAt(0)==="/"?e.substr(1):e}`;return s.length&&(a+=`?${s}`),a}function tIe(t){let e=r=>Object.prototype.toString.call(r)==="[object Object]"||Object.prototype.toString.call(r)==="[object Array]";return Object.keys(t).map(r=>Fut("%s=%s",r,e(t[r])?JSON.stringify(t[r]):t[r])).join("&")}function rIe(t,e){if(t.method===JEe.MethodEnum.Get||t.data===void 0&&e.data===void 0)return;let r=Array.isArray(t.data)?t.data:{...t.data,...e.data};return JSON.stringify(r)}function nIe(t,e){let r={...t.headers,...e.headers},s={};return Object.keys(r).forEach(a=>{let n=r[a];s[a.toLowerCase()]=n}),s}function n9(t){return t.map(e=>o9(e))}function o9(t){let e=t.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return{...t,request:{...t.request,headers:{...t.request.headers,...e}}}}function iIe(t,e,r){return{name:"ApiError",message:t,status:e,transporterStackTrace:r}}function sIe(t,e){return{name:"DeserializationError",message:t,response:e}}function oIe(t){return{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:t}}Yi.CallEnum=DS;Yi.HostStatusEnum=sw;Yi.createApiError=iIe;Yi.createDeserializationError=sIe;Yi.createMappedRequestOptions=r9;Yi.createRetryError=oIe;Yi.createStatefulHost=i9;Yi.createStatelessHost=s9;Yi.createTransporter=Tut;Yi.createUserAgent=Rut;Yi.deserializeFailure=$Ee;Yi.deserializeSuccess=ZEe;Yi.isStatefulHostTimeouted=XEe;Yi.isStatefulHostUp=zEe;Yi.serializeData=rIe;Yi.serializeHeaders=nIe;Yi.serializeQueryParameters=tIe;Yi.serializeUrl=eIe;Yi.stackFrameWithoutCredentials=o9;Yi.stackTraceWithoutCredentials=n9});var bS=_((QJt,lIe)=>{lIe.exports=aIe()});var cIe=_(X0=>{"use strict";Object.defineProperty(X0,"__esModule",{value:!0});var ow=vS(),Nut=bS(),PS=SS(),Out=t=>{let e=t.region||"us",r=ow.createAuth(ow.AuthMode.WithinHeaders,t.appId,t.apiKey),s=Nut.createTransporter({hosts:[{url:`analytics.${e}.algolia.com`}],...t,headers:{...r.headers(),"content-type":"application/json",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}}),a=t.appId;return ow.addMethods({appId:a,transporter:s},t.methods)},Lut=t=>(e,r)=>t.transporter.write({method:PS.MethodEnum.Post,path:"2/abtests",data:e},r),Mut=t=>(e,r)=>t.transporter.write({method:PS.MethodEnum.Delete,path:ow.encode("2/abtests/%s",e)},r),Uut=t=>(e,r)=>t.transporter.read({method:PS.MethodEnum.Get,path:ow.encode("2/abtests/%s",e)},r),_ut=t=>e=>t.transporter.read({method:PS.MethodEnum.Get,path:"2/abtests"},e),Hut=t=>(e,r)=>t.transporter.write({method:PS.MethodEnum.Post,path:ow.encode("2/abtests/%s/stop",e)},r);X0.addABTest=Lut;X0.createAnalyticsClient=Out;X0.deleteABTest=Mut;X0.getABTest=Uut;X0.getABTests=_ut;X0.stopABTest=Hut});var fIe=_((RJt,uIe)=>{uIe.exports=cIe()});var pIe=_(xS=>{"use strict";Object.defineProperty(xS,"__esModule",{value:!0});var a9=vS(),jut=bS(),AIe=SS(),Gut=t=>{let e=t.region||"us",r=a9.createAuth(a9.AuthMode.WithinHeaders,t.appId,t.apiKey),s=jut.createTransporter({hosts:[{url:`personalization.${e}.algolia.com`}],...t,headers:{...r.headers(),"content-type":"application/json",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}});return a9.addMethods({appId:t.appId,transporter:s},t.methods)},qut=t=>e=>t.transporter.read({method:AIe.MethodEnum.Get,path:"1/strategies/personalization"},e),Wut=t=>(e,r)=>t.transporter.write({method:AIe.MethodEnum.Post,path:"1/strategies/personalization",data:e},r);xS.createPersonalizationClient=Gut;xS.getPersonalizationStrategy=qut;xS.setPersonalizationStrategy=Wut});var gIe=_((NJt,hIe)=>{hIe.exports=pIe()});var xIe=_(Ft=>{"use strict";Object.defineProperty(Ft,"__esModule",{value:!0});var Jt=vS(),gl=bS(),br=SS(),Yut=Ie("crypto");function AF(t){let e=r=>t.request(r).then(s=>{if(t.batch!==void 0&&t.batch(s.hits),!t.shouldStop(s))return s.cursor?e({cursor:s.cursor}):e({page:(r.page||0)+1})});return e({})}var Vut=t=>{let e=t.appId,r=Jt.createAuth(t.authMode!==void 0?t.authMode:Jt.AuthMode.WithinHeaders,e,t.apiKey),s=gl.createTransporter({hosts:[{url:`${e}-dsn.algolia.net`,accept:gl.CallEnum.Read},{url:`${e}.algolia.net`,accept:gl.CallEnum.Write}].concat(Jt.shuffle([{url:`${e}-1.algolianet.com`},{url:`${e}-2.algolianet.com`},{url:`${e}-3.algolianet.com`}])),...t,headers:{...r.headers(),"content-type":"application/x-www-form-urlencoded",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}}),a={transporter:s,appId:e,addAlgoliaAgent(n,c){s.userAgent.add({segment:n,version:c})},clearCache(){return Promise.all([s.requestsCache.clear(),s.responsesCache.clear()]).then(()=>{})}};return Jt.addMethods(a,t.methods)};function dIe(){return{name:"MissingObjectIDError",message:"All objects must have an unique objectID (like a primary key) to be valid. Algolia is also able to generate objectIDs automatically but *it's not recommended*. To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option."}}function mIe(){return{name:"ObjectNotFoundError",message:"Object not found."}}function yIe(){return{name:"ValidUntilNotFoundError",message:"ValidUntil not found in given secured api key."}}var Jut=t=>(e,r)=>{let{queryParameters:s,...a}=r||{},n={acl:e,...s!==void 0?{queryParameters:s}:{}},c=(f,p)=>Jt.createRetryablePromise(h=>kS(t)(f.key,p).catch(E=>{if(E.status!==404)throw E;return h()}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:"1/keys",data:n},a),c)},Kut=t=>(e,r,s)=>{let a=gl.createMappedRequestOptions(s);return a.queryParameters["X-Algolia-User-ID"]=e,t.transporter.write({method:br.MethodEnum.Post,path:"1/clusters/mapping",data:{cluster:r}},a)},zut=t=>(e,r,s)=>t.transporter.write({method:br.MethodEnum.Post,path:"1/clusters/mapping/batch",data:{users:e,cluster:r}},s),Xut=t=>(e,r)=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!0,requests:{action:"addEntry",body:[]}}},r),(s,a)=>aw(t)(s.taskID,a)),pF=t=>(e,r,s)=>{let a=(n,c)=>QS(t)(e,{methods:{waitTask:hs}}).waitTask(n.taskID,c);return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/operation",e),data:{operation:"copy",destination:r}},s),a)},Zut=t=>(e,r,s)=>pF(t)(e,r,{...s,scope:[gF.Rules]}),$ut=t=>(e,r,s)=>pF(t)(e,r,{...s,scope:[gF.Settings]}),eft=t=>(e,r,s)=>pF(t)(e,r,{...s,scope:[gF.Synonyms]}),tft=t=>(e,r)=>e.method===br.MethodEnum.Get?t.transporter.read(e,r):t.transporter.write(e,r),rft=t=>(e,r)=>{let s=(a,n)=>Jt.createRetryablePromise(c=>kS(t)(e,n).then(c).catch(f=>{if(f.status!==404)throw f}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Jt.encode("1/keys/%s",e)},r),s)},nft=t=>(e,r,s)=>{let a=r.map(n=>({action:"deleteEntry",body:{objectID:n}}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!1,requests:a}},s),(n,c)=>aw(t)(n.taskID,c))},ift=()=>(t,e)=>{let r=gl.serializeQueryParameters(e),s=Yut.createHmac("sha256",t).update(r).digest("hex");return Buffer.from(s+r).toString("base64")},kS=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/keys/%s",e)},r),EIe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/task/%s",e.toString())},r),sft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"/1/dictionaries/*/settings"},e),oft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/logs"},e),aft=()=>t=>{let e=Buffer.from(t,"base64").toString("ascii"),r=/validUntil=(\d+)/,s=e.match(r);if(s===null)throw yIe();return parseInt(s[1],10)-Math.round(new Date().getTime()/1e3)},lft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters/mapping/top"},e),cft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/clusters/mapping/%s",e)},r),uft=t=>e=>{let{retrieveMappings:r,...s}=e||{};return r===!0&&(s.getClusters=!0),t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters/mapping/pending"},s)},QS=t=>(e,r={})=>{let s={transporter:t.transporter,appId:t.appId,indexName:e};return Jt.addMethods(s,r.methods)},fft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/keys"},e),Aft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters"},e),pft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/indexes"},e),hft=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters/mapping"},e),gft=t=>(e,r,s)=>{let a=(n,c)=>QS(t)(e,{methods:{waitTask:hs}}).waitTask(n.taskID,c);return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/operation",e),data:{operation:"move",destination:r}},s),a)},dft=t=>(e,r)=>{let s=(a,n)=>Promise.all(Object.keys(a.taskID).map(c=>QS(t)(c,{methods:{waitTask:hs}}).waitTask(a.taskID[c],n)));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:"1/indexes/*/batch",data:{requests:e}},r),s)},mft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:e}},r),yft=t=>(e,r)=>{let s=e.map(a=>({...a,params:gl.serializeQueryParameters(a.params||{})}));return t.transporter.read({method:br.MethodEnum.Post,path:"1/indexes/*/queries",data:{requests:s},cacheable:!0},r)},Eft=t=>(e,r)=>Promise.all(e.map(s=>{let{facetName:a,facetQuery:n,...c}=s.params;return QS(t)(s.indexName,{methods:{searchForFacetValues:DIe}}).searchForFacetValues(a,n,{...r,...c})})),Ift=t=>(e,r)=>{let s=gl.createMappedRequestOptions(r);return s.queryParameters["X-Algolia-User-ID"]=e,t.transporter.write({method:br.MethodEnum.Delete,path:"1/clusters/mapping"},s)},Cft=t=>(e,r,s)=>{let a=r.map(n=>({action:"addEntry",body:n}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!0,requests:a}},s),(n,c)=>aw(t)(n.taskID,c))},wft=t=>(e,r)=>{let s=(a,n)=>Jt.createRetryablePromise(c=>kS(t)(e,n).catch(f=>{if(f.status!==404)throw f;return c()}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/keys/%s/restore",e)},r),s)},Bft=t=>(e,r,s)=>{let a=r.map(n=>({action:"addEntry",body:n}));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!1,requests:a}},s),(n,c)=>aw(t)(n.taskID,c))},vft=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("/1/dictionaries/%s/search",e),data:{query:r},cacheable:!0},s),Sft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:"1/clusters/mapping/search",data:{query:e}},r),Dft=t=>(e,r)=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:"/1/dictionaries/*/settings",data:e},r),(s,a)=>aw(t)(s.taskID,a)),bft=t=>(e,r)=>{let s=Object.assign({},r),{queryParameters:a,...n}=r||{},c=a?{queryParameters:a}:{},f=["acl","indexes","referers","restrictSources","queryParameters","description","maxQueriesPerIPPerHour","maxHitsPerQuery"],p=E=>Object.keys(s).filter(C=>f.indexOf(C)!==-1).every(C=>{if(Array.isArray(E[C])&&Array.isArray(s[C])){let S=E[C];return S.length===s[C].length&&S.every((P,I)=>P===s[C][I])}else return E[C]===s[C]}),h=(E,C)=>Jt.createRetryablePromise(S=>kS(t)(e,C).then(P=>p(P)?Promise.resolve():S()));return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:Jt.encode("1/keys/%s",e),data:c},n),h)},aw=t=>(e,r)=>Jt.createRetryablePromise(s=>EIe(t)(e,r).then(a=>a.status!=="published"?s():void 0)),IIe=t=>(e,r)=>{let s=(a,n)=>hs(t)(a.taskID,n);return Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/batch",t.indexName),data:{requests:e}},r),s)},Pft=t=>e=>AF({shouldStop:r=>r.cursor===void 0,...e,request:r=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/browse",t.indexName),data:r},e)}),xft=t=>e=>{let r={hitsPerPage:1e3,...e};return AF({shouldStop:s=>s.hits.length({...a,hits:a.hits.map(n=>(delete n._highlightResult,n))}))}})},kft=t=>e=>{let r={hitsPerPage:1e3,...e};return AF({shouldStop:s=>s.hits.length({...a,hits:a.hits.map(n=>(delete n._highlightResult,n))}))}})},hF=t=>(e,r,s)=>{let{batchSize:a,...n}=s||{},c={taskIDs:[],objectIDs:[]},f=(p=0)=>{let h=[],E;for(E=p;E({action:r,body:C})),n).then(C=>(c.objectIDs=c.objectIDs.concat(C.objectIDs),c.taskIDs.push(C.taskID),E++,f(E)))};return Jt.createWaitablePromise(f(),(p,h)=>Promise.all(p.taskIDs.map(E=>hs(t)(E,h))))},Qft=t=>e=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/clear",t.indexName)},e),(r,s)=>hs(t)(r.taskID,s)),Tft=t=>e=>{let{forwardToReplicas:r,...s}=e||{},a=gl.createMappedRequestOptions(s);return r&&(a.queryParameters.forwardToReplicas=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/rules/clear",t.indexName)},a),(n,c)=>hs(t)(n.taskID,c))},Rft=t=>e=>{let{forwardToReplicas:r,...s}=e||{},a=gl.createMappedRequestOptions(s);return r&&(a.queryParameters.forwardToReplicas=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/synonyms/clear",t.indexName)},a),(n,c)=>hs(t)(n.taskID,c))},Fft=t=>(e,r)=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/deleteByQuery",t.indexName),data:e},r),(s,a)=>hs(t)(s.taskID,a)),Nft=t=>e=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Jt.encode("1/indexes/%s",t.indexName)},e),(r,s)=>hs(t)(r.taskID,s)),Oft=t=>(e,r)=>Jt.createWaitablePromise(CIe(t)([e],r).then(s=>({taskID:s.taskIDs[0]})),(s,a)=>hs(t)(s.taskID,a)),CIe=t=>(e,r)=>{let s=e.map(a=>({objectID:a}));return hF(t)(s,km.DeleteObject,r)},Lft=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=gl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Jt.encode("1/indexes/%s/rules/%s",t.indexName,e)},n),(c,f)=>hs(t)(c.taskID,f))},Mft=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=gl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Jt.encode("1/indexes/%s/synonyms/%s",t.indexName,e)},n),(c,f)=>hs(t)(c.taskID,f))},Uft=t=>e=>wIe(t)(e).then(()=>!0).catch(r=>{if(r.status!==404)throw r;return!1}),_ft=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/answers/%s/prediction",t.indexName),data:{query:e,queryLanguages:r},cacheable:!0},s),Hft=t=>(e,r)=>{let{query:s,paginate:a,...n}=r||{},c=0,f=()=>SIe(t)(s||"",{...n,page:c}).then(p=>{for(let[h,E]of Object.entries(p.hits))if(e(E))return{object:E,position:parseInt(h,10),page:c};if(c++,a===!1||c>=p.nbPages)throw mIe();return f()});return f()},jft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/indexes/%s/%s",t.indexName,e)},r),Gft=()=>(t,e)=>{for(let[r,s]of Object.entries(t.hits))if(s.objectID===e)return parseInt(r,10);return-1},qft=t=>(e,r)=>{let{attributesToRetrieve:s,...a}=r||{},n=e.map(c=>({indexName:t.indexName,objectID:c,...s?{attributesToRetrieve:s}:{}}));return t.transporter.read({method:br.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:n}},a)},Wft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/indexes/%s/rules/%s",t.indexName,e)},r),wIe=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/indexes/%s/settings",t.indexName),data:{getVersion:2}},e),Yft=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/indexes/%s/synonyms/%s",t.indexName,e)},r),BIe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Jt.encode("1/indexes/%s/task/%s",t.indexName,e.toString())},r),Vft=t=>(e,r)=>Jt.createWaitablePromise(vIe(t)([e],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,a)=>hs(t)(s.taskID,a)),vIe=t=>(e,r)=>{let{createIfNotExists:s,...a}=r||{},n=s?km.PartialUpdateObject:km.PartialUpdateObjectNoCreate;return hF(t)(e,n,a)},Jft=t=>(e,r)=>{let{safe:s,autoGenerateObjectIDIfNotExist:a,batchSize:n,...c}=r||{},f=(I,R,N,U)=>Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/operation",I),data:{operation:N,destination:R}},U),(W,ee)=>hs(t)(W.taskID,ee)),p=Math.random().toString(36).substring(7),h=`${t.indexName}_tmp_${p}`,E=l9({appId:t.appId,transporter:t.transporter,indexName:h}),C=[],S=f(t.indexName,h,"copy",{...c,scope:["settings","synonyms","rules"]});C.push(S);let P=(s?S.wait(c):S).then(()=>{let I=E(e,{...c,autoGenerateObjectIDIfNotExist:a,batchSize:n});return C.push(I),s?I.wait(c):I}).then(()=>{let I=f(h,t.indexName,"move",c);return C.push(I),s?I.wait(c):I}).then(()=>Promise.all(C)).then(([I,R,N])=>({objectIDs:R.objectIDs,taskIDs:[I.taskID,...R.taskIDs,N.taskID]}));return Jt.createWaitablePromise(P,(I,R)=>Promise.all(C.map(N=>N.wait(R))))},Kft=t=>(e,r)=>c9(t)(e,{...r,clearExistingRules:!0}),zft=t=>(e,r)=>u9(t)(e,{...r,clearExistingSynonyms:!0}),Xft=t=>(e,r)=>Jt.createWaitablePromise(l9(t)([e],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,a)=>hs(t)(s.taskID,a)),l9=t=>(e,r)=>{let{autoGenerateObjectIDIfNotExist:s,...a}=r||{},n=s?km.AddObject:km.UpdateObject;if(n===km.UpdateObject){for(let c of e)if(c.objectID===void 0)return Jt.createWaitablePromise(Promise.reject(dIe()))}return hF(t)(e,n,a)},Zft=t=>(e,r)=>c9(t)([e],r),c9=t=>(e,r)=>{let{forwardToReplicas:s,clearExistingRules:a,...n}=r||{},c=gl.createMappedRequestOptions(n);return s&&(c.queryParameters.forwardToReplicas=1),a&&(c.queryParameters.clearExistingRules=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/rules/batch",t.indexName),data:e},c),(f,p)=>hs(t)(f.taskID,p))},$ft=t=>(e,r)=>u9(t)([e],r),u9=t=>(e,r)=>{let{forwardToReplicas:s,clearExistingSynonyms:a,replaceExistingSynonyms:n,...c}=r||{},f=gl.createMappedRequestOptions(c);return s&&(f.queryParameters.forwardToReplicas=1),(n||a)&&(f.queryParameters.replaceExistingSynonyms=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/synonyms/batch",t.indexName),data:e},f),(p,h)=>hs(t)(p.taskID,h))},SIe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/query",t.indexName),data:{query:e},cacheable:!0},r),DIe=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/facets/%s/query",t.indexName,e),data:{facetQuery:r},cacheable:!0},s),bIe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/rules/search",t.indexName),data:{query:e}},r),PIe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Jt.encode("1/indexes/%s/synonyms/search",t.indexName),data:{query:e}},r),eAt=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=gl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Jt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:Jt.encode("1/indexes/%s/settings",t.indexName),data:e},n),(c,f)=>hs(t)(c.taskID,f))},hs=t=>(e,r)=>Jt.createRetryablePromise(s=>BIe(t)(e,r).then(a=>a.status!=="published"?s():void 0)),tAt={AddObject:"addObject",Analytics:"analytics",Browser:"browse",DeleteIndex:"deleteIndex",DeleteObject:"deleteObject",EditSettings:"editSettings",Inference:"inference",ListIndexes:"listIndexes",Logs:"logs",Personalization:"personalization",Recommendation:"recommendation",Search:"search",SeeUnretrievableAttributes:"seeUnretrievableAttributes",Settings:"settings",Usage:"usage"},km={AddObject:"addObject",UpdateObject:"updateObject",PartialUpdateObject:"partialUpdateObject",PartialUpdateObjectNoCreate:"partialUpdateObjectNoCreate",DeleteObject:"deleteObject",DeleteIndex:"delete",ClearIndex:"clear"},gF={Settings:"settings",Synonyms:"synonyms",Rules:"rules"},rAt={None:"none",StopIfEnoughMatches:"stopIfEnoughMatches"},nAt={Synonym:"synonym",OneWaySynonym:"oneWaySynonym",AltCorrection1:"altCorrection1",AltCorrection2:"altCorrection2",Placeholder:"placeholder"};Ft.ApiKeyACLEnum=tAt;Ft.BatchActionEnum=km;Ft.ScopeEnum=gF;Ft.StrategyEnum=rAt;Ft.SynonymEnum=nAt;Ft.addApiKey=Jut;Ft.assignUserID=Kut;Ft.assignUserIDs=zut;Ft.batch=IIe;Ft.browseObjects=Pft;Ft.browseRules=xft;Ft.browseSynonyms=kft;Ft.chunkedBatch=hF;Ft.clearDictionaryEntries=Xut;Ft.clearObjects=Qft;Ft.clearRules=Tft;Ft.clearSynonyms=Rft;Ft.copyIndex=pF;Ft.copyRules=Zut;Ft.copySettings=$ut;Ft.copySynonyms=eft;Ft.createBrowsablePromise=AF;Ft.createMissingObjectIDError=dIe;Ft.createObjectNotFoundError=mIe;Ft.createSearchClient=Vut;Ft.createValidUntilNotFoundError=yIe;Ft.customRequest=tft;Ft.deleteApiKey=rft;Ft.deleteBy=Fft;Ft.deleteDictionaryEntries=nft;Ft.deleteIndex=Nft;Ft.deleteObject=Oft;Ft.deleteObjects=CIe;Ft.deleteRule=Lft;Ft.deleteSynonym=Mft;Ft.exists=Uft;Ft.findAnswers=_ft;Ft.findObject=Hft;Ft.generateSecuredApiKey=ift;Ft.getApiKey=kS;Ft.getAppTask=EIe;Ft.getDictionarySettings=sft;Ft.getLogs=oft;Ft.getObject=jft;Ft.getObjectPosition=Gft;Ft.getObjects=qft;Ft.getRule=Wft;Ft.getSecuredApiKeyRemainingValidity=aft;Ft.getSettings=wIe;Ft.getSynonym=Yft;Ft.getTask=BIe;Ft.getTopUserIDs=lft;Ft.getUserID=cft;Ft.hasPendingMappings=uft;Ft.initIndex=QS;Ft.listApiKeys=fft;Ft.listClusters=Aft;Ft.listIndices=pft;Ft.listUserIDs=hft;Ft.moveIndex=gft;Ft.multipleBatch=dft;Ft.multipleGetObjects=mft;Ft.multipleQueries=yft;Ft.multipleSearchForFacetValues=Eft;Ft.partialUpdateObject=Vft;Ft.partialUpdateObjects=vIe;Ft.removeUserID=Ift;Ft.replaceAllObjects=Jft;Ft.replaceAllRules=Kft;Ft.replaceAllSynonyms=zft;Ft.replaceDictionaryEntries=Cft;Ft.restoreApiKey=wft;Ft.saveDictionaryEntries=Bft;Ft.saveObject=Xft;Ft.saveObjects=l9;Ft.saveRule=Zft;Ft.saveRules=c9;Ft.saveSynonym=$ft;Ft.saveSynonyms=u9;Ft.search=SIe;Ft.searchDictionaryEntries=vft;Ft.searchForFacetValues=DIe;Ft.searchRules=bIe;Ft.searchSynonyms=PIe;Ft.searchUserIDs=Sft;Ft.setDictionarySettings=Dft;Ft.setSettings=eAt;Ft.updateApiKey=bft;Ft.waitAppTask=aw;Ft.waitTask=hs});var QIe=_((LJt,kIe)=>{kIe.exports=xIe()});var TIe=_(dF=>{"use strict";Object.defineProperty(dF,"__esModule",{value:!0});function iAt(){return{debug(t,e){return Promise.resolve()},info(t,e){return Promise.resolve()},error(t,e){return Promise.resolve()}}}var sAt={Debug:1,Info:2,Error:3};dF.LogLevelEnum=sAt;dF.createNullLogger=iAt});var FIe=_((UJt,RIe)=>{RIe.exports=TIe()});var MIe=_(f9=>{"use strict";Object.defineProperty(f9,"__esModule",{value:!0});var NIe=Ie("http"),OIe=Ie("https"),oAt=Ie("url"),LIe={keepAlive:!0},aAt=new NIe.Agent(LIe),lAt=new OIe.Agent(LIe);function cAt({agent:t,httpAgent:e,httpsAgent:r,requesterOptions:s={}}={}){let a=e||t||aAt,n=r||t||lAt;return{send(c){return new Promise(f=>{let p=oAt.parse(c.url),h=p.query===null?p.pathname:`${p.pathname}?${p.query}`,E={...s,agent:p.protocol==="https:"?n:a,hostname:p.hostname,path:h,method:c.method,headers:{...s&&s.headers?s.headers:{},...c.headers},...p.port!==void 0?{port:p.port||""}:{}},C=(p.protocol==="https:"?OIe:NIe).request(E,R=>{let N=[];R.on("data",U=>{N=N.concat(U)}),R.on("end",()=>{clearTimeout(P),clearTimeout(I),f({status:R.statusCode||0,content:Buffer.concat(N).toString(),isTimedOut:!1})})}),S=(R,N)=>setTimeout(()=>{C.abort(),f({status:0,content:N,isTimedOut:!0})},R*1e3),P=S(c.connectTimeout,"Connection timeout"),I;C.on("error",R=>{clearTimeout(P),clearTimeout(I),f({status:0,content:R.message,isTimedOut:!1})}),C.once("response",()=>{clearTimeout(P),I=S(c.responseTimeout,"Socket timeout")}),c.data!==void 0&&C.write(c.data),C.end()})},destroy(){return a.destroy(),n.destroy(),Promise.resolve()}}}f9.createNodeHttpRequester=cAt});var _Ie=_((HJt,UIe)=>{UIe.exports=MIe()});var qIe=_((jJt,GIe)=>{"use strict";var HIe=MEe(),uAt=HEe(),lw=fIe(),p9=vS(),A9=gIe(),Gt=QIe(),fAt=FIe(),AAt=_Ie(),pAt=bS();function jIe(t,e,r){let s={appId:t,apiKey:e,timeouts:{connect:2,read:5,write:30},requester:AAt.createNodeHttpRequester(),logger:fAt.createNullLogger(),responsesCache:HIe.createNullCache(),requestsCache:HIe.createNullCache(),hostsCache:uAt.createInMemoryCache(),userAgent:pAt.createUserAgent(p9.version).add({segment:"Node.js",version:process.versions.node})},a={...s,...r},n=()=>c=>A9.createPersonalizationClient({...s,...c,methods:{getPersonalizationStrategy:A9.getPersonalizationStrategy,setPersonalizationStrategy:A9.setPersonalizationStrategy}});return Gt.createSearchClient({...a,methods:{search:Gt.multipleQueries,searchForFacetValues:Gt.multipleSearchForFacetValues,multipleBatch:Gt.multipleBatch,multipleGetObjects:Gt.multipleGetObjects,multipleQueries:Gt.multipleQueries,copyIndex:Gt.copyIndex,copySettings:Gt.copySettings,copyRules:Gt.copyRules,copySynonyms:Gt.copySynonyms,moveIndex:Gt.moveIndex,listIndices:Gt.listIndices,getLogs:Gt.getLogs,listClusters:Gt.listClusters,multipleSearchForFacetValues:Gt.multipleSearchForFacetValues,getApiKey:Gt.getApiKey,addApiKey:Gt.addApiKey,listApiKeys:Gt.listApiKeys,updateApiKey:Gt.updateApiKey,deleteApiKey:Gt.deleteApiKey,restoreApiKey:Gt.restoreApiKey,assignUserID:Gt.assignUserID,assignUserIDs:Gt.assignUserIDs,getUserID:Gt.getUserID,searchUserIDs:Gt.searchUserIDs,listUserIDs:Gt.listUserIDs,getTopUserIDs:Gt.getTopUserIDs,removeUserID:Gt.removeUserID,hasPendingMappings:Gt.hasPendingMappings,generateSecuredApiKey:Gt.generateSecuredApiKey,getSecuredApiKeyRemainingValidity:Gt.getSecuredApiKeyRemainingValidity,destroy:p9.destroy,clearDictionaryEntries:Gt.clearDictionaryEntries,deleteDictionaryEntries:Gt.deleteDictionaryEntries,getDictionarySettings:Gt.getDictionarySettings,getAppTask:Gt.getAppTask,replaceDictionaryEntries:Gt.replaceDictionaryEntries,saveDictionaryEntries:Gt.saveDictionaryEntries,searchDictionaryEntries:Gt.searchDictionaryEntries,setDictionarySettings:Gt.setDictionarySettings,waitAppTask:Gt.waitAppTask,customRequest:Gt.customRequest,initIndex:c=>f=>Gt.initIndex(c)(f,{methods:{batch:Gt.batch,delete:Gt.deleteIndex,findAnswers:Gt.findAnswers,getObject:Gt.getObject,getObjects:Gt.getObjects,saveObject:Gt.saveObject,saveObjects:Gt.saveObjects,search:Gt.search,searchForFacetValues:Gt.searchForFacetValues,waitTask:Gt.waitTask,setSettings:Gt.setSettings,getSettings:Gt.getSettings,partialUpdateObject:Gt.partialUpdateObject,partialUpdateObjects:Gt.partialUpdateObjects,deleteObject:Gt.deleteObject,deleteObjects:Gt.deleteObjects,deleteBy:Gt.deleteBy,clearObjects:Gt.clearObjects,browseObjects:Gt.browseObjects,getObjectPosition:Gt.getObjectPosition,findObject:Gt.findObject,exists:Gt.exists,saveSynonym:Gt.saveSynonym,saveSynonyms:Gt.saveSynonyms,getSynonym:Gt.getSynonym,searchSynonyms:Gt.searchSynonyms,browseSynonyms:Gt.browseSynonyms,deleteSynonym:Gt.deleteSynonym,clearSynonyms:Gt.clearSynonyms,replaceAllObjects:Gt.replaceAllObjects,replaceAllSynonyms:Gt.replaceAllSynonyms,searchRules:Gt.searchRules,getRule:Gt.getRule,deleteRule:Gt.deleteRule,saveRule:Gt.saveRule,saveRules:Gt.saveRules,replaceAllRules:Gt.replaceAllRules,browseRules:Gt.browseRules,clearRules:Gt.clearRules}}),initAnalytics:()=>c=>lw.createAnalyticsClient({...s,...c,methods:{addABTest:lw.addABTest,getABTest:lw.getABTest,getABTests:lw.getABTests,stopABTest:lw.stopABTest,deleteABTest:lw.deleteABTest}}),initPersonalization:n,initRecommendation:()=>c=>(a.logger.info("The `initRecommendation` method is deprecated. Use `initPersonalization` instead."),n()(c))}})}jIe.version=p9.version;GIe.exports=jIe});var g9=_((GJt,h9)=>{var WIe=qIe();h9.exports=WIe;h9.exports.default=WIe});var y9=_((WJt,JIe)=>{"use strict";var VIe=Object.getOwnPropertySymbols,gAt=Object.prototype.hasOwnProperty,dAt=Object.prototype.propertyIsEnumerable;function mAt(t){if(t==null)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(t)}function yAt(){try{if(!Object.assign)return!1;var t=new String("abc");if(t[5]="de",Object.getOwnPropertyNames(t)[0]==="5")return!1;for(var e={},r=0;r<10;r++)e["_"+String.fromCharCode(r)]=r;var s=Object.getOwnPropertyNames(e).map(function(n){return e[n]});if(s.join("")!=="0123456789")return!1;var a={};return"abcdefghijklmnopqrst".split("").forEach(function(n){a[n]=n}),Object.keys(Object.assign({},a)).join("")==="abcdefghijklmnopqrst"}catch{return!1}}JIe.exports=yAt()?Object.assign:function(t,e){for(var r,s=mAt(t),a,n=1;n{"use strict";var I9=y9(),cw=60103,XIe=60106;Dn.Fragment=60107;Dn.StrictMode=60108;Dn.Profiler=60114;var ZIe=60109,$Ie=60110,eCe=60112;Dn.Suspense=60113;var tCe=60115,rCe=60116;typeof Symbol=="function"&&Symbol.for&&(Gc=Symbol.for,cw=Gc("react.element"),XIe=Gc("react.portal"),Dn.Fragment=Gc("react.fragment"),Dn.StrictMode=Gc("react.strict_mode"),Dn.Profiler=Gc("react.profiler"),ZIe=Gc("react.provider"),$Ie=Gc("react.context"),eCe=Gc("react.forward_ref"),Dn.Suspense=Gc("react.suspense"),tCe=Gc("react.memo"),rCe=Gc("react.lazy"));var Gc,KIe=typeof Symbol=="function"&&Symbol.iterator;function EAt(t){return t===null||typeof t!="object"?null:(t=KIe&&t[KIe]||t["@@iterator"],typeof t=="function"?t:null)}function TS(t){for(var e="https://reactjs.org/docs/error-decoder.html?invariant="+t,r=1;r{"use strict";fCe.exports=uCe()});var EF=_((JJt,ACe)=>{function vAt(t){var e=typeof t;return t!=null&&(e=="object"||e=="function")}ACe.exports=vAt});var hCe=_((KJt,pCe)=>{var SAt=typeof global=="object"&&global&&global.Object===Object&&global;pCe.exports=SAt});var S9=_((zJt,gCe)=>{var DAt=hCe(),bAt=typeof self=="object"&&self&&self.Object===Object&&self,PAt=DAt||bAt||Function("return this")();gCe.exports=PAt});var mCe=_((XJt,dCe)=>{var xAt=S9(),kAt=function(){return xAt.Date.now()};dCe.exports=kAt});var ECe=_((ZJt,yCe)=>{var QAt=/\s/;function TAt(t){for(var e=t.length;e--&&QAt.test(t.charAt(e)););return e}yCe.exports=TAt});var CCe=_(($Jt,ICe)=>{var RAt=ECe(),FAt=/^\s+/;function NAt(t){return t&&t.slice(0,RAt(t)+1).replace(FAt,"")}ICe.exports=NAt});var D9=_((eKt,wCe)=>{var OAt=S9(),LAt=OAt.Symbol;wCe.exports=LAt});var DCe=_((tKt,SCe)=>{var BCe=D9(),vCe=Object.prototype,MAt=vCe.hasOwnProperty,UAt=vCe.toString,RS=BCe?BCe.toStringTag:void 0;function _At(t){var e=MAt.call(t,RS),r=t[RS];try{t[RS]=void 0;var s=!0}catch{}var a=UAt.call(t);return s&&(e?t[RS]=r:delete t[RS]),a}SCe.exports=_At});var PCe=_((rKt,bCe)=>{var HAt=Object.prototype,jAt=HAt.toString;function GAt(t){return jAt.call(t)}bCe.exports=GAt});var TCe=_((nKt,QCe)=>{var xCe=D9(),qAt=DCe(),WAt=PCe(),YAt="[object Null]",VAt="[object Undefined]",kCe=xCe?xCe.toStringTag:void 0;function JAt(t){return t==null?t===void 0?VAt:YAt:kCe&&kCe in Object(t)?qAt(t):WAt(t)}QCe.exports=JAt});var FCe=_((iKt,RCe)=>{function KAt(t){return t!=null&&typeof t=="object"}RCe.exports=KAt});var OCe=_((sKt,NCe)=>{var zAt=TCe(),XAt=FCe(),ZAt="[object Symbol]";function $At(t){return typeof t=="symbol"||XAt(t)&&zAt(t)==ZAt}NCe.exports=$At});var _Ce=_((oKt,UCe)=>{var ept=CCe(),LCe=EF(),tpt=OCe(),MCe=NaN,rpt=/^[-+]0x[0-9a-f]+$/i,npt=/^0b[01]+$/i,ipt=/^0o[0-7]+$/i,spt=parseInt;function opt(t){if(typeof t=="number")return t;if(tpt(t))return MCe;if(LCe(t)){var e=typeof t.valueOf=="function"?t.valueOf():t;t=LCe(e)?e+"":e}if(typeof t!="string")return t===0?t:+t;t=ept(t);var r=npt.test(t);return r||ipt.test(t)?spt(t.slice(2),r?2:8):rpt.test(t)?MCe:+t}UCe.exports=opt});var GCe=_((aKt,jCe)=>{var apt=EF(),b9=mCe(),HCe=_Ce(),lpt="Expected a function",cpt=Math.max,upt=Math.min;function fpt(t,e,r){var s,a,n,c,f,p,h=0,E=!1,C=!1,S=!0;if(typeof t!="function")throw new TypeError(lpt);e=HCe(e)||0,apt(r)&&(E=!!r.leading,C="maxWait"in r,n=C?cpt(HCe(r.maxWait)||0,e):n,S="trailing"in r?!!r.trailing:S);function P(le){var me=s,pe=a;return s=a=void 0,h=le,c=t.apply(pe,me),c}function I(le){return h=le,f=setTimeout(U,e),E?P(le):c}function R(le){var me=le-p,pe=le-h,Be=e-me;return C?upt(Be,n-pe):Be}function N(le){var me=le-p,pe=le-h;return p===void 0||me>=e||me<0||C&&pe>=n}function U(){var le=b9();if(N(le))return W(le);f=setTimeout(U,R(le))}function W(le){return f=void 0,S&&s?P(le):(s=a=void 0,c)}function ee(){f!==void 0&&clearTimeout(f),h=0,s=p=a=f=void 0}function ie(){return f===void 0?c:W(b9())}function ue(){var le=b9(),me=N(le);if(s=arguments,a=this,p=le,me){if(f===void 0)return I(p);if(C)return clearTimeout(f),f=setTimeout(U,e),P(p)}return f===void 0&&(f=setTimeout(U,e)),c}return ue.cancel=ee,ue.flush=ie,ue}jCe.exports=fpt});var WCe=_((lKt,qCe)=>{var Apt=GCe(),ppt=EF(),hpt="Expected a function";function gpt(t,e,r){var s=!0,a=!0;if(typeof t!="function")throw new TypeError(hpt);return ppt(r)&&(s="leading"in r?!!r.leading:s,a="trailing"in r?!!r.trailing:a),Apt(t,e,{leading:s,maxWait:e,trailing:a})}qCe.exports=gpt});var x9=_((cKt,P9)=>{"use strict";var Cn=P9.exports;P9.exports.default=Cn;var Xn="\x1B[",NS="\x1B]",fw="\x07",IF=";",YCe=process.env.TERM_PROGRAM==="Apple_Terminal";Cn.cursorTo=(t,e)=>{if(typeof t!="number")throw new TypeError("The `x` argument is required");return typeof e!="number"?Xn+(t+1)+"G":Xn+(e+1)+";"+(t+1)+"H"};Cn.cursorMove=(t,e)=>{if(typeof t!="number")throw new TypeError("The `x` argument is required");let r="";return t<0?r+=Xn+-t+"D":t>0&&(r+=Xn+t+"C"),e<0?r+=Xn+-e+"A":e>0&&(r+=Xn+e+"B"),r};Cn.cursorUp=(t=1)=>Xn+t+"A";Cn.cursorDown=(t=1)=>Xn+t+"B";Cn.cursorForward=(t=1)=>Xn+t+"C";Cn.cursorBackward=(t=1)=>Xn+t+"D";Cn.cursorLeft=Xn+"G";Cn.cursorSavePosition=YCe?"\x1B7":Xn+"s";Cn.cursorRestorePosition=YCe?"\x1B8":Xn+"u";Cn.cursorGetPosition=Xn+"6n";Cn.cursorNextLine=Xn+"E";Cn.cursorPrevLine=Xn+"F";Cn.cursorHide=Xn+"?25l";Cn.cursorShow=Xn+"?25h";Cn.eraseLines=t=>{let e="";for(let r=0;r[NS,"8",IF,IF,e,fw,t,NS,"8",IF,IF,fw].join("");Cn.image=(t,e={})=>{let r=`${NS}1337;File=inline=1`;return e.width&&(r+=`;width=${e.width}`),e.height&&(r+=`;height=${e.height}`),e.preserveAspectRatio===!1&&(r+=";preserveAspectRatio=0"),r+":"+t.toString("base64")+fw};Cn.iTerm={setCwd:(t=process.cwd())=>`${NS}50;CurrentDir=${t}${fw}`,annotation:(t,e={})=>{let r=`${NS}1337;`,s=typeof e.x<"u",a=typeof e.y<"u";if((s||a)&&!(s&&a&&typeof e.length<"u"))throw new Error("`x`, `y` and `length` must be defined when `x` or `y` is defined");return t=t.replace(/\|/g,""),r+=e.isHidden?"AddHiddenAnnotation=":"AddAnnotation=",e.length>0?r+=(s?[t,e.length,e.x,e.y]:[e.length,t]).join("|"):r+=t,r+fw}}});var JCe=_((uKt,k9)=>{"use strict";var VCe=(t,e)=>{for(let r of Reflect.ownKeys(e))Object.defineProperty(t,r,Object.getOwnPropertyDescriptor(e,r));return t};k9.exports=VCe;k9.exports.default=VCe});var zCe=_((fKt,wF)=>{"use strict";var dpt=JCe(),CF=new WeakMap,KCe=(t,e={})=>{if(typeof t!="function")throw new TypeError("Expected a function");let r,s=0,a=t.displayName||t.name||"",n=function(...c){if(CF.set(n,++s),s===1)r=t.apply(this,c),t=null;else if(e.throw===!0)throw new Error(`Function \`${a}\` can only be called once`);return r};return dpt(n,t),CF.set(n,s),n};wF.exports=KCe;wF.exports.default=KCe;wF.exports.callCount=t=>{if(!CF.has(t))throw new Error(`The given function \`${t.name}\` is not wrapped by the \`onetime\` package`);return CF.get(t)}});var XCe=_((AKt,BF)=>{BF.exports=["SIGABRT","SIGALRM","SIGHUP","SIGINT","SIGTERM"];process.platform!=="win32"&&BF.exports.push("SIGVTALRM","SIGXCPU","SIGXFSZ","SIGUSR2","SIGTRAP","SIGSYS","SIGQUIT","SIGIOT");process.platform==="linux"&&BF.exports.push("SIGIO","SIGPOLL","SIGPWR","SIGSTKFLT","SIGUNUSED")});var R9=_((pKt,hw)=>{var Qi=global.process,Qm=function(t){return t&&typeof t=="object"&&typeof t.removeListener=="function"&&typeof t.emit=="function"&&typeof t.reallyExit=="function"&&typeof t.listeners=="function"&&typeof t.kill=="function"&&typeof t.pid=="number"&&typeof t.on=="function"};Qm(Qi)?(ZCe=Ie("assert"),Aw=XCe(),$Ce=/^win/i.test(Qi.platform),OS=Ie("events"),typeof OS!="function"&&(OS=OS.EventEmitter),Qi.__signal_exit_emitter__?Js=Qi.__signal_exit_emitter__:(Js=Qi.__signal_exit_emitter__=new OS,Js.count=0,Js.emitted={}),Js.infinite||(Js.setMaxListeners(1/0),Js.infinite=!0),hw.exports=function(t,e){if(!Qm(global.process))return function(){};ZCe.equal(typeof t,"function","a callback must be provided for exit handler"),pw===!1&&Q9();var r="exit";e&&e.alwaysLast&&(r="afterexit");var s=function(){Js.removeListener(r,t),Js.listeners("exit").length===0&&Js.listeners("afterexit").length===0&&vF()};return Js.on(r,t),s},vF=function(){!pw||!Qm(global.process)||(pw=!1,Aw.forEach(function(e){try{Qi.removeListener(e,SF[e])}catch{}}),Qi.emit=DF,Qi.reallyExit=T9,Js.count-=1)},hw.exports.unload=vF,Tm=function(e,r,s){Js.emitted[e]||(Js.emitted[e]=!0,Js.emit(e,r,s))},SF={},Aw.forEach(function(t){SF[t]=function(){if(Qm(global.process)){var r=Qi.listeners(t);r.length===Js.count&&(vF(),Tm("exit",null,t),Tm("afterexit",null,t),$Ce&&t==="SIGHUP"&&(t="SIGINT"),Qi.kill(Qi.pid,t))}}}),hw.exports.signals=function(){return Aw},pw=!1,Q9=function(){pw||!Qm(global.process)||(pw=!0,Js.count+=1,Aw=Aw.filter(function(e){try{return Qi.on(e,SF[e]),!0}catch{return!1}}),Qi.emit=twe,Qi.reallyExit=ewe)},hw.exports.load=Q9,T9=Qi.reallyExit,ewe=function(e){Qm(global.process)&&(Qi.exitCode=e||0,Tm("exit",Qi.exitCode,null),Tm("afterexit",Qi.exitCode,null),T9.call(Qi,Qi.exitCode))},DF=Qi.emit,twe=function(e,r){if(e==="exit"&&Qm(global.process)){r!==void 0&&(Qi.exitCode=r);var s=DF.apply(this,arguments);return Tm("exit",Qi.exitCode,null),Tm("afterexit",Qi.exitCode,null),s}else return DF.apply(this,arguments)}):hw.exports=function(){return function(){}};var ZCe,Aw,$Ce,OS,Js,vF,Tm,SF,pw,Q9,T9,ewe,DF,twe});var nwe=_((hKt,rwe)=>{"use strict";var mpt=zCe(),ypt=R9();rwe.exports=mpt(()=>{ypt(()=>{process.stderr.write("\x1B[?25h")},{alwaysLast:!0})})});var F9=_(gw=>{"use strict";var Ept=nwe(),bF=!1;gw.show=(t=process.stderr)=>{t.isTTY&&(bF=!1,t.write("\x1B[?25h"))};gw.hide=(t=process.stderr)=>{t.isTTY&&(Ept(),bF=!0,t.write("\x1B[?25l"))};gw.toggle=(t,e)=>{t!==void 0&&(bF=t),bF?gw.show(e):gw.hide(e)}});var awe=_(LS=>{"use strict";var owe=LS&&LS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(LS,"__esModule",{value:!0});var iwe=owe(x9()),swe=owe(F9()),Ipt=(t,{showCursor:e=!1}={})=>{let r=0,s="",a=!1,n=c=>{!e&&!a&&(swe.default.hide(),a=!0);let f=c+` `;f!==s&&(s=f,t.write(iwe.default.eraseLines(r)+f),r=f.split(` `).length)};return n.clear=()=>{t.write(iwe.default.eraseLines(r)),s="",r=0},n.done=()=>{s="",r=0,e||(swe.default.show(),a=!1)},n};LS.default={create:Ipt}});var lwe=_((mKt,Cpt)=>{Cpt.exports=[{name:"AppVeyor",constant:"APPVEYOR",env:"APPVEYOR",pr:"APPVEYOR_PULL_REQUEST_NUMBER"},{name:"Azure Pipelines",constant:"AZURE_PIPELINES",env:"SYSTEM_TEAMFOUNDATIONCOLLECTIONURI",pr:"SYSTEM_PULLREQUEST_PULLREQUESTID"},{name:"Bamboo",constant:"BAMBOO",env:"bamboo_planKey"},{name:"Bitbucket Pipelines",constant:"BITBUCKET",env:"BITBUCKET_COMMIT",pr:"BITBUCKET_PR_ID"},{name:"Bitrise",constant:"BITRISE",env:"BITRISE_IO",pr:"BITRISE_PULL_REQUEST"},{name:"Buddy",constant:"BUDDY",env:"BUDDY_WORKSPACE_ID",pr:"BUDDY_EXECUTION_PULL_REQUEST_ID"},{name:"Buildkite",constant:"BUILDKITE",env:"BUILDKITE",pr:{env:"BUILDKITE_PULL_REQUEST",ne:"false"}},{name:"CircleCI",constant:"CIRCLE",env:"CIRCLECI",pr:"CIRCLE_PULL_REQUEST"},{name:"Cirrus CI",constant:"CIRRUS",env:"CIRRUS_CI",pr:"CIRRUS_PR"},{name:"AWS CodeBuild",constant:"CODEBUILD",env:"CODEBUILD_BUILD_ARN"},{name:"Codeship",constant:"CODESHIP",env:{CI_NAME:"codeship"}},{name:"Drone",constant:"DRONE",env:"DRONE",pr:{DRONE_BUILD_EVENT:"pull_request"}},{name:"dsari",constant:"DSARI",env:"DSARI"},{name:"GitLab CI",constant:"GITLAB",env:"GITLAB_CI"},{name:"GoCD",constant:"GOCD",env:"GO_PIPELINE_LABEL"},{name:"Hudson",constant:"HUDSON",env:"HUDSON_URL"},{name:"Jenkins",constant:"JENKINS",env:["JENKINS_URL","BUILD_ID"],pr:{any:["ghprbPullId","CHANGE_ID"]}},{name:"Magnum CI",constant:"MAGNUM",env:"MAGNUM"},{name:"Netlify CI",constant:"NETLIFY",env:"NETLIFY_BUILD_BASE",pr:{env:"PULL_REQUEST",ne:"false"}},{name:"Sail CI",constant:"SAIL",env:"SAILCI",pr:"SAIL_PULL_REQUEST_NUMBER"},{name:"Semaphore",constant:"SEMAPHORE",env:"SEMAPHORE",pr:"PULL_REQUEST_NUMBER"},{name:"Shippable",constant:"SHIPPABLE",env:"SHIPPABLE",pr:{IS_PULL_REQUEST:"true"}},{name:"Solano CI",constant:"SOLANO",env:"TDDIUM",pr:"TDDIUM_PR_ID"},{name:"Strider CD",constant:"STRIDER",env:"STRIDER"},{name:"TaskCluster",constant:"TASKCLUSTER",env:["TASK_ID","RUN_ID"]},{name:"TeamCity",constant:"TEAMCITY",env:"TEAMCITY_VERSION"},{name:"Travis CI",constant:"TRAVIS",env:"TRAVIS",pr:{env:"TRAVIS_PULL_REQUEST",ne:"false"}}]});var fwe=_(tc=>{"use strict";var uwe=lwe(),uA=process.env;Object.defineProperty(tc,"_vendors",{value:uwe.map(function(t){return t.constant})});tc.name=null;tc.isPR=null;uwe.forEach(function(t){var e=Array.isArray(t.env)?t.env:[t.env],r=e.every(function(s){return cwe(s)});if(tc[t.constant]=r,r)switch(tc.name=t.name,typeof t.pr){case"string":tc.isPR=!!uA[t.pr];break;case"object":"env"in t.pr?tc.isPR=t.pr.env in uA&&uA[t.pr.env]!==t.pr.ne:"any"in t.pr?tc.isPR=t.pr.any.some(function(s){return!!uA[s]}):tc.isPR=cwe(t.pr);break;default:tc.isPR=null}});tc.isCI=!!(uA.CI||uA.CONTINUOUS_INTEGRATION||uA.BUILD_NUMBER||uA.RUN_ID||tc.name);function cwe(t){return typeof t=="string"?!!uA[t]:Object.keys(t).every(function(e){return uA[e]===t[e]})}});var pwe=_((EKt,Awe)=>{"use strict";Awe.exports=fwe().isCI});var gwe=_((IKt,hwe)=>{"use strict";var wpt=t=>{let e=new Set;do for(let r of Reflect.ownKeys(t))e.add([t,r]);while((t=Reflect.getPrototypeOf(t))&&t!==Object.prototype);return e};hwe.exports=(t,{include:e,exclude:r}={})=>{let s=a=>{let n=c=>typeof c=="string"?a===c:c.test(a);return e?e.some(n):r?!r.some(n):!0};for(let[a,n]of wpt(t.constructor.prototype)){if(n==="constructor"||!s(n))continue;let c=Reflect.getOwnPropertyDescriptor(a,n);c&&typeof c.value=="function"&&(t[n]=t[n].bind(t))}return t}});var Cwe=_(Vn=>{"use strict";var mw,_S,QF,H9;typeof performance=="object"&&typeof performance.now=="function"?(dwe=performance,Vn.unstable_now=function(){return dwe.now()}):(N9=Date,mwe=N9.now(),Vn.unstable_now=function(){return N9.now()-mwe});var dwe,N9,mwe;typeof window>"u"||typeof MessageChannel!="function"?(dw=null,O9=null,L9=function(){if(dw!==null)try{var t=Vn.unstable_now();dw(!0,t),dw=null}catch(e){throw setTimeout(L9,0),e}},mw=function(t){dw!==null?setTimeout(mw,0,t):(dw=t,setTimeout(L9,0))},_S=function(t,e){O9=setTimeout(t,e)},QF=function(){clearTimeout(O9)},Vn.unstable_shouldYield=function(){return!1},H9=Vn.unstable_forceFrameRate=function(){}):(ywe=window.setTimeout,Ewe=window.clearTimeout,typeof console<"u"&&(Iwe=window.cancelAnimationFrame,typeof window.requestAnimationFrame!="function"&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills"),typeof Iwe!="function"&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills")),MS=!1,US=null,PF=-1,M9=5,U9=0,Vn.unstable_shouldYield=function(){return Vn.unstable_now()>=U9},H9=function(){},Vn.unstable_forceFrameRate=function(t){0>t||125>>1,a=t[s];if(a!==void 0&&0kF(c,r))p!==void 0&&0>kF(p,c)?(t[s]=p,t[f]=r,s=f):(t[s]=c,t[n]=r,s=n);else if(p!==void 0&&0>kF(p,r))t[s]=p,t[f]=r,s=f;else break e}}return e}return null}function kF(t,e){var r=t.sortIndex-e.sortIndex;return r!==0?r:t.id-e.id}var fA=[],Z0=[],Bpt=1,qc=null,$o=3,RF=!1,Rm=!1,HS=!1;function G9(t){for(var e=ef(Z0);e!==null;){if(e.callback===null)TF(Z0);else if(e.startTime<=t)TF(Z0),e.sortIndex=e.expirationTime,j9(fA,e);else break;e=ef(Z0)}}function q9(t){if(HS=!1,G9(t),!Rm)if(ef(fA)!==null)Rm=!0,mw(W9);else{var e=ef(Z0);e!==null&&_S(q9,e.startTime-t)}}function W9(t,e){Rm=!1,HS&&(HS=!1,QF()),RF=!0;var r=$o;try{for(G9(e),qc=ef(fA);qc!==null&&(!(qc.expirationTime>e)||t&&!Vn.unstable_shouldYield());){var s=qc.callback;if(typeof s=="function"){qc.callback=null,$o=qc.priorityLevel;var a=s(qc.expirationTime<=e);e=Vn.unstable_now(),typeof a=="function"?qc.callback=a:qc===ef(fA)&&TF(fA),G9(e)}else TF(fA);qc=ef(fA)}if(qc!==null)var n=!0;else{var c=ef(Z0);c!==null&&_S(q9,c.startTime-e),n=!1}return n}finally{qc=null,$o=r,RF=!1}}var vpt=H9;Vn.unstable_IdlePriority=5;Vn.unstable_ImmediatePriority=1;Vn.unstable_LowPriority=4;Vn.unstable_NormalPriority=3;Vn.unstable_Profiling=null;Vn.unstable_UserBlockingPriority=2;Vn.unstable_cancelCallback=function(t){t.callback=null};Vn.unstable_continueExecution=function(){Rm||RF||(Rm=!0,mw(W9))};Vn.unstable_getCurrentPriorityLevel=function(){return $o};Vn.unstable_getFirstCallbackNode=function(){return ef(fA)};Vn.unstable_next=function(t){switch($o){case 1:case 2:case 3:var e=3;break;default:e=$o}var r=$o;$o=e;try{return t()}finally{$o=r}};Vn.unstable_pauseExecution=function(){};Vn.unstable_requestPaint=vpt;Vn.unstable_runWithPriority=function(t,e){switch(t){case 1:case 2:case 3:case 4:case 5:break;default:t=3}var r=$o;$o=t;try{return e()}finally{$o=r}};Vn.unstable_scheduleCallback=function(t,e,r){var s=Vn.unstable_now();switch(typeof r=="object"&&r!==null?(r=r.delay,r=typeof r=="number"&&0s?(t.sortIndex=r,j9(Z0,t),ef(fA)===null&&t===ef(Z0)&&(HS?QF():HS=!0,_S(q9,r-s))):(t.sortIndex=a,j9(fA,t),Rm||RF||(Rm=!0,mw(W9))),t};Vn.unstable_wrapCallback=function(t){var e=$o;return function(){var r=$o;$o=e;try{return t.apply(this,arguments)}finally{$o=r}}}});var Y9=_((wKt,wwe)=>{"use strict";wwe.exports=Cwe()});var Bwe=_((BKt,jS)=>{jS.exports=function(e){var r={},s=y9(),a=hn(),n=Y9();function c(v){for(var D="https://reactjs.org/docs/error-decoder.html?invariant="+v,Q=1;Q_e||V[Se]!==ne[_e])return` `+V[Se].replace(" at new "," at ");while(1<=Se&&0<=_e);break}}}finally{ve=!1,Error.prepareStackTrace=Q}return(v=v?v.displayName||v.name:"")?oc(v):""}var ac=[],Oi=-1;function no(v){return{current:v}}function Rt(v){0>Oi||(v.current=ac[Oi],ac[Oi]=null,Oi--)}function xn(v,D){Oi++,ac[Oi]=v.current,v.current=D}var la={},Gi=no(la),Li=no(!1),Na=la;function dn(v,D){var Q=v.type.contextTypes;if(!Q)return la;var H=v.stateNode;if(H&&H.__reactInternalMemoizedUnmaskedChildContext===D)return H.__reactInternalMemoizedMaskedChildContext;var V={},ne;for(ne in Q)V[ne]=D[ne];return H&&(v=v.stateNode,v.__reactInternalMemoizedUnmaskedChildContext=D,v.__reactInternalMemoizedMaskedChildContext=V),V}function Kn(v){return v=v.childContextTypes,v!=null}function Au(){Rt(Li),Rt(Gi)}function yh(v,D,Q){if(Gi.current!==la)throw Error(c(168));xn(Gi,D),xn(Li,Q)}function Oa(v,D,Q){var H=v.stateNode;if(v=D.childContextTypes,typeof H.getChildContext!="function")return Q;H=H.getChildContext();for(var V in H)if(!(V in v))throw Error(c(108,g(D)||"Unknown",V));return s({},Q,H)}function La(v){return v=(v=v.stateNode)&&v.__reactInternalMemoizedMergedChildContext||la,Na=Gi.current,xn(Gi,v),xn(Li,Li.current),!0}function Ma(v,D,Q){var H=v.stateNode;if(!H)throw Error(c(169));Q?(v=Oa(v,D,Na),H.__reactInternalMemoizedMergedChildContext=v,Rt(Li),Rt(Gi),xn(Gi,v)):Rt(Li),xn(Li,Q)}var $e=null,Ua=null,hf=n.unstable_now;hf();var lc=0,wn=8;function ca(v){if(1&v)return wn=15,1;if(2&v)return wn=14,2;if(4&v)return wn=13,4;var D=24&v;return D!==0?(wn=12,D):v&32?(wn=11,32):(D=192&v,D!==0?(wn=10,D):v&256?(wn=9,256):(D=3584&v,D!==0?(wn=8,D):v&4096?(wn=7,4096):(D=4186112&v,D!==0?(wn=6,D):(D=62914560&v,D!==0?(wn=5,D):v&67108864?(wn=4,67108864):v&134217728?(wn=3,134217728):(D=805306368&v,D!==0?(wn=2,D):1073741824&v?(wn=1,1073741824):(wn=8,v))))))}function LA(v){switch(v){case 99:return 15;case 98:return 10;case 97:case 96:return 8;case 95:return 2;default:return 0}}function MA(v){switch(v){case 15:case 14:return 99;case 13:case 12:case 11:case 10:return 98;case 9:case 8:case 7:case 6:case 4:case 5:return 97;case 3:case 2:case 1:return 95;case 0:return 90;default:throw Error(c(358,v))}}function ua(v,D){var Q=v.pendingLanes;if(Q===0)return wn=0;var H=0,V=0,ne=v.expiredLanes,Se=v.suspendedLanes,_e=v.pingedLanes;if(ne!==0)H=ne,V=wn=15;else if(ne=Q&134217727,ne!==0){var pt=ne&~Se;pt!==0?(H=ca(pt),V=wn):(_e&=ne,_e!==0&&(H=ca(_e),V=wn))}else ne=Q&~Se,ne!==0?(H=ca(ne),V=wn):_e!==0&&(H=ca(_e),V=wn);if(H===0)return 0;if(H=31-ns(H),H=Q&((0>H?0:1<Q;Q++)D.push(v);return D}function Ha(v,D,Q){v.pendingLanes|=D;var H=D-1;v.suspendedLanes&=H,v.pingedLanes&=H,v=v.eventTimes,D=31-ns(D),v[D]=Q}var ns=Math.clz32?Math.clz32:uc,cc=Math.log,pu=Math.LN2;function uc(v){return v===0?32:31-(cc(v)/pu|0)|0}var ja=n.unstable_runWithPriority,Mi=n.unstable_scheduleCallback,Is=n.unstable_cancelCallback,vl=n.unstable_shouldYield,gf=n.unstable_requestPaint,fc=n.unstable_now,wi=n.unstable_getCurrentPriorityLevel,Qn=n.unstable_ImmediatePriority,Ac=n.unstable_UserBlockingPriority,Ke=n.unstable_NormalPriority,st=n.unstable_LowPriority,St=n.unstable_IdlePriority,lr={},te=gf!==void 0?gf:function(){},Ee=null,Oe=null,dt=!1,Et=fc(),bt=1e4>Et?fc:function(){return fc()-Et};function tr(){switch(wi()){case Qn:return 99;case Ac:return 98;case Ke:return 97;case st:return 96;case St:return 95;default:throw Error(c(332))}}function An(v){switch(v){case 99:return Qn;case 98:return Ac;case 97:return Ke;case 96:return st;case 95:return St;default:throw Error(c(332))}}function li(v,D){return v=An(v),ja(v,D)}function qi(v,D,Q){return v=An(v),Mi(v,D,Q)}function Tn(){if(Oe!==null){var v=Oe;Oe=null,Is(v)}Ga()}function Ga(){if(!dt&&Ee!==null){dt=!0;var v=0;try{var D=Ee;li(99,function(){for(;vRn?(_n=kr,kr=null):_n=kr.sibling;var zr=Zt(et,kr,gt[Rn],Xt);if(zr===null){kr===null&&(kr=_n);break}v&&kr&&zr.alternate===null&&D(et,kr),qe=ne(zr,qe,Rn),Zn===null?Dr=zr:Zn.sibling=zr,Zn=zr,kr=_n}if(Rn===gt.length)return Q(et,kr),Dr;if(kr===null){for(;RnRn?(_n=kr,kr=null):_n=kr.sibling;var ci=Zt(et,kr,zr.value,Xt);if(ci===null){kr===null&&(kr=_n);break}v&&kr&&ci.alternate===null&&D(et,kr),qe=ne(ci,qe,Rn),Zn===null?Dr=ci:Zn.sibling=ci,Zn=ci,kr=_n}if(zr.done)return Q(et,kr),Dr;if(kr===null){for(;!zr.done;Rn++,zr=gt.next())zr=Lr(et,zr.value,Xt),zr!==null&&(qe=ne(zr,qe,Rn),Zn===null?Dr=zr:Zn.sibling=zr,Zn=zr);return Dr}for(kr=H(et,kr);!zr.done;Rn++,zr=gt.next())zr=zn(kr,et,Rn,zr.value,Xt),zr!==null&&(v&&zr.alternate!==null&&kr.delete(zr.key===null?Rn:zr.key),qe=ne(zr,qe,Rn),Zn===null?Dr=zr:Zn.sibling=zr,Zn=zr);return v&&kr.forEach(function(Du){return D(et,Du)}),Dr}return function(et,qe,gt,Xt){var Dr=typeof gt=="object"&>!==null&>.type===E&>.key===null;Dr&&(gt=gt.props.children);var Zn=typeof gt=="object"&>!==null;if(Zn)switch(gt.$$typeof){case p:e:{for(Zn=gt.key,Dr=qe;Dr!==null;){if(Dr.key===Zn){switch(Dr.tag){case 7:if(gt.type===E){Q(et,Dr.sibling),qe=V(Dr,gt.props.children),qe.return=et,et=qe;break e}break;default:if(Dr.elementType===gt.type){Q(et,Dr.sibling),qe=V(Dr,gt.props),qe.ref=yt(et,Dr,gt),qe.return=et,et=qe;break e}}Q(et,Dr);break}else D(et,Dr);Dr=Dr.sibling}gt.type===E?(qe=kf(gt.props.children,et.mode,Xt,gt.key),qe.return=et,et=qe):(Xt=sd(gt.type,gt.key,gt.props,null,et.mode,Xt),Xt.ref=yt(et,qe,gt),Xt.return=et,et=Xt)}return Se(et);case h:e:{for(Dr=gt.key;qe!==null;){if(qe.key===Dr)if(qe.tag===4&&qe.stateNode.containerInfo===gt.containerInfo&&qe.stateNode.implementation===gt.implementation){Q(et,qe.sibling),qe=V(qe,gt.children||[]),qe.return=et,et=qe;break e}else{Q(et,qe);break}else D(et,qe);qe=qe.sibling}qe=Qo(gt,et.mode,Xt),qe.return=et,et=qe}return Se(et)}if(typeof gt=="string"||typeof gt=="number")return gt=""+gt,qe!==null&&qe.tag===6?(Q(et,qe.sibling),qe=V(qe,gt),qe.return=et,et=qe):(Q(et,qe),qe=b2(gt,et.mode,Xt),qe.return=et,et=qe),Se(et);if(mf(gt))return yi(et,qe,gt,Xt);if(Ce(gt))return za(et,qe,gt,Xt);if(Zn&&gu(et,gt),typeof gt>"u"&&!Dr)switch(et.tag){case 1:case 22:case 0:case 11:case 15:throw Error(c(152,g(et.type)||"Component"))}return Q(et,qe)}}var Mg=By(!0),e2=By(!1),vh={},ur=no(vh),zi=no(vh),yf=no(vh);function qa(v){if(v===vh)throw Error(c(174));return v}function Ug(v,D){xn(yf,D),xn(zi,v),xn(ur,vh),v=mt(D),Rt(ur),xn(ur,v)}function du(){Rt(ur),Rt(zi),Rt(yf)}function Ef(v){var D=qa(yf.current),Q=qa(ur.current);D=j(Q,v.type,D),Q!==D&&(xn(zi,v),xn(ur,D))}function wt(v){zi.current===v&&(Rt(ur),Rt(zi))}var di=no(0);function GA(v){for(var D=v;D!==null;){if(D.tag===13){var Q=D.memoizedState;if(Q!==null&&(Q=Q.dehydrated,Q===null||gr(Q)||Bo(Q)))return D}else if(D.tag===19&&D.memoizedProps.revealOrder!==void 0){if(D.flags&64)return D}else if(D.child!==null){D.child.return=D,D=D.child;continue}if(D===v)break;for(;D.sibling===null;){if(D.return===null||D.return===v)return null;D=D.return}D.sibling.return=D.return,D=D.sibling}return null}var Wa=null,Aa=null,Ya=!1;function _g(v,D){var Q=Ka(5,null,null,0);Q.elementType="DELETED",Q.type="DELETED",Q.stateNode=D,Q.return=v,Q.flags=8,v.lastEffect!==null?(v.lastEffect.nextEffect=Q,v.lastEffect=Q):v.firstEffect=v.lastEffect=Q}function Sh(v,D){switch(v.tag){case 5:return D=aa(D,v.type,v.pendingProps),D!==null?(v.stateNode=D,!0):!1;case 6:return D=FA(D,v.pendingProps),D!==null?(v.stateNode=D,!0):!1;case 13:return!1;default:return!1}}function Hg(v){if(Ya){var D=Aa;if(D){var Q=D;if(!Sh(v,D)){if(D=Me(Q),!D||!Sh(v,D)){v.flags=v.flags&-1025|2,Ya=!1,Wa=v;return}_g(Wa,Q)}Wa=v,Aa=cu(D)}else v.flags=v.flags&-1025|2,Ya=!1,Wa=v}}function vy(v){for(v=v.return;v!==null&&v.tag!==5&&v.tag!==3&&v.tag!==13;)v=v.return;Wa=v}function qA(v){if(!X||v!==Wa)return!1;if(!Ya)return vy(v),Ya=!0,!1;var D=v.type;if(v.tag!==5||D!=="head"&&D!=="body"&&!it(D,v.memoizedProps))for(D=Aa;D;)_g(v,D),D=Me(D);if(vy(v),v.tag===13){if(!X)throw Error(c(316));if(v=v.memoizedState,v=v!==null?v.dehydrated:null,!v)throw Error(c(317));Aa=NA(v)}else Aa=Wa?Me(v.stateNode):null;return!0}function jg(){X&&(Aa=Wa=null,Ya=!1)}var mu=[];function yu(){for(var v=0;vne))throw Error(c(301));ne+=1,Pi=is=null,D.updateQueue=null,If.current=re,v=Q(H,V)}while(Cf)}if(If.current=kt,D=is!==null&&is.next!==null,Eu=0,Pi=is=Gn=null,WA=!1,D)throw Error(c(300));return v}function ss(){var v={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return Pi===null?Gn.memoizedState=Pi=v:Pi=Pi.next=v,Pi}function Pl(){if(is===null){var v=Gn.alternate;v=v!==null?v.memoizedState:null}else v=is.next;var D=Pi===null?Gn.memoizedState:Pi.next;if(D!==null)Pi=D,is=v;else{if(v===null)throw Error(c(310));is=v,v={memoizedState:is.memoizedState,baseState:is.baseState,baseQueue:is.baseQueue,queue:is.queue,next:null},Pi===null?Gn.memoizedState=Pi=v:Pi=Pi.next=v}return Pi}function Po(v,D){return typeof D=="function"?D(v):D}function wf(v){var D=Pl(),Q=D.queue;if(Q===null)throw Error(c(311));Q.lastRenderedReducer=v;var H=is,V=H.baseQueue,ne=Q.pending;if(ne!==null){if(V!==null){var Se=V.next;V.next=ne.next,ne.next=Se}H.baseQueue=V=ne,Q.pending=null}if(V!==null){V=V.next,H=H.baseState;var _e=Se=ne=null,pt=V;do{var Wt=pt.lane;if((Eu&Wt)===Wt)_e!==null&&(_e=_e.next={lane:0,action:pt.action,eagerReducer:pt.eagerReducer,eagerState:pt.eagerState,next:null}),H=pt.eagerReducer===v?pt.eagerState:v(H,pt.action);else{var Sr={lane:Wt,action:pt.action,eagerReducer:pt.eagerReducer,eagerState:pt.eagerState,next:null};_e===null?(Se=_e=Sr,ne=H):_e=_e.next=Sr,Gn.lanes|=Wt,Zg|=Wt}pt=pt.next}while(pt!==null&&pt!==V);_e===null?ne=H:_e.next=Se,vo(H,D.memoizedState)||(Je=!0),D.memoizedState=H,D.baseState=ne,D.baseQueue=_e,Q.lastRenderedState=H}return[D.memoizedState,Q.dispatch]}function Bf(v){var D=Pl(),Q=D.queue;if(Q===null)throw Error(c(311));Q.lastRenderedReducer=v;var H=Q.dispatch,V=Q.pending,ne=D.memoizedState;if(V!==null){Q.pending=null;var Se=V=V.next;do ne=v(ne,Se.action),Se=Se.next;while(Se!==V);vo(ne,D.memoizedState)||(Je=!0),D.memoizedState=ne,D.baseQueue===null&&(D.baseState=ne),Q.lastRenderedState=ne}return[ne,H]}function xl(v,D,Q){var H=D._getVersion;H=H(D._source);var V=y?D._workInProgressVersionPrimary:D._workInProgressVersionSecondary;if(V!==null?v=V===H:(v=v.mutableReadLanes,(v=(Eu&v)===v)&&(y?D._workInProgressVersionPrimary=H:D._workInProgressVersionSecondary=H,mu.push(D))),v)return Q(D._source);throw mu.push(D),Error(c(350))}function yn(v,D,Q,H){var V=so;if(V===null)throw Error(c(349));var ne=D._getVersion,Se=ne(D._source),_e=If.current,pt=_e.useState(function(){return xl(V,D,Q)}),Wt=pt[1],Sr=pt[0];pt=Pi;var Lr=v.memoizedState,Zt=Lr.refs,zn=Zt.getSnapshot,yi=Lr.source;Lr=Lr.subscribe;var za=Gn;return v.memoizedState={refs:Zt,source:D,subscribe:H},_e.useEffect(function(){Zt.getSnapshot=Q,Zt.setSnapshot=Wt;var et=ne(D._source);if(!vo(Se,et)){et=Q(D._source),vo(Sr,et)||(Wt(et),et=Bs(za),V.mutableReadLanes|=et&V.pendingLanes),et=V.mutableReadLanes,V.entangledLanes|=et;for(var qe=V.entanglements,gt=et;0Q?98:Q,function(){v(!0)}),li(97m2&&(D.flags|=64,V=!0,XA(H,!1),D.lanes=33554432)}else{if(!V)if(v=GA(ne),v!==null){if(D.flags|=64,V=!0,v=v.updateQueue,v!==null&&(D.updateQueue=v,D.flags|=4),XA(H,!0),H.tail===null&&H.tailMode==="hidden"&&!ne.alternate&&!Ya)return D=D.lastEffect=H.lastEffect,D!==null&&(D.nextEffect=null),null}else 2*bt()-H.renderingStartTime>m2&&Q!==1073741824&&(D.flags|=64,V=!0,XA(H,!1),D.lanes=33554432);H.isBackwards?(ne.sibling=D.child,D.child=ne):(v=H.last,v!==null?v.sibling=ne:D.child=ne,H.last=ne)}return H.tail!==null?(v=H.tail,H.rendering=v,H.tail=v.sibling,H.lastEffect=D.lastEffect,H.renderingStartTime=bt(),v.sibling=null,D=di.current,xn(di,V?D&1|2:D&1),v):null;case 23:case 24:return B2(),v!==null&&v.memoizedState!==null!=(D.memoizedState!==null)&&H.mode!=="unstable-defer-without-hiding"&&(D.flags|=4),null}throw Error(c(156,D.tag))}function qL(v){switch(v.tag){case 1:Kn(v.type)&&Au();var D=v.flags;return D&4096?(v.flags=D&-4097|64,v):null;case 3:if(du(),Rt(Li),Rt(Gi),yu(),D=v.flags,D&64)throw Error(c(285));return v.flags=D&-4097|64,v;case 5:return wt(v),null;case 13:return Rt(di),D=v.flags,D&4096?(v.flags=D&-4097|64,v):null;case 19:return Rt(di),null;case 4:return du(),null;case 10:return Og(v),null;case 23:case 24:return B2(),null;default:return null}}function Yg(v,D){try{var Q="",H=D;do Q+=$1(H),H=H.return;while(H);var V=Q}catch(ne){V=` Error generating stack: `+ne.message+` `+ne.stack}return{value:v,source:D,stack:V}}function Vg(v,D){try{console.error(D.value)}catch(Q){setTimeout(function(){throw Q})}}var WL=typeof WeakMap=="function"?WeakMap:Map;function i2(v,D,Q){Q=Dl(-1,Q),Q.tag=3,Q.payload={element:null};var H=D.value;return Q.callback=function(){_y||(_y=!0,y2=H),Vg(v,D)},Q}function Jg(v,D,Q){Q=Dl(-1,Q),Q.tag=3;var H=v.type.getDerivedStateFromError;if(typeof H=="function"){var V=D.value;Q.payload=function(){return Vg(v,D),H(V)}}var ne=v.stateNode;return ne!==null&&typeof ne.componentDidCatch=="function"&&(Q.callback=function(){typeof H!="function"&&(hc===null?hc=new Set([this]):hc.add(this),Vg(v,D));var Se=D.stack;this.componentDidCatch(D.value,{componentStack:Se!==null?Se:""})}),Q}var YL=typeof WeakSet=="function"?WeakSet:Set;function s2(v){var D=v.ref;if(D!==null)if(typeof D=="function")try{D(null)}catch(Q){xf(v,Q)}else D.current=null}function xy(v,D){switch(D.tag){case 0:case 11:case 15:case 22:return;case 1:if(D.flags&256&&v!==null){var Q=v.memoizedProps,H=v.memoizedState;v=D.stateNode,D=v.getSnapshotBeforeUpdate(D.elementType===D.type?Q:So(D.type,Q),H),v.__reactInternalSnapshotBeforeUpdate=D}return;case 3:F&&D.flags&256&&Ts(D.stateNode.containerInfo);return;case 5:case 6:case 4:case 17:return}throw Error(c(163))}function Th(v,D){if(D=D.updateQueue,D=D!==null?D.lastEffect:null,D!==null){var Q=D=D.next;do{if((Q.tag&v)===v){var H=Q.destroy;Q.destroy=void 0,H!==void 0&&H()}Q=Q.next}while(Q!==D)}}function uP(v,D,Q){switch(Q.tag){case 0:case 11:case 15:case 22:if(D=Q.updateQueue,D=D!==null?D.lastEffect:null,D!==null){v=D=D.next;do{if((v.tag&3)===3){var H=v.create;v.destroy=H()}v=v.next}while(v!==D)}if(D=Q.updateQueue,D=D!==null?D.lastEffect:null,D!==null){v=D=D.next;do{var V=v;H=V.next,V=V.tag,V&4&&V&1&&(vP(Q,v),tM(Q,v)),v=H}while(v!==D)}return;case 1:v=Q.stateNode,Q.flags&4&&(D===null?v.componentDidMount():(H=Q.elementType===Q.type?D.memoizedProps:So(Q.type,D.memoizedProps),v.componentDidUpdate(H,D.memoizedState,v.__reactInternalSnapshotBeforeUpdate))),D=Q.updateQueue,D!==null&&Cy(Q,D,v);return;case 3:if(D=Q.updateQueue,D!==null){if(v=null,Q.child!==null)switch(Q.child.tag){case 5:v=Re(Q.child.stateNode);break;case 1:v=Q.child.stateNode}Cy(Q,D,v)}return;case 5:v=Q.stateNode,D===null&&Q.flags&4&&$s(v,Q.type,Q.memoizedProps,Q);return;case 6:return;case 4:return;case 12:return;case 13:X&&Q.memoizedState===null&&(Q=Q.alternate,Q!==null&&(Q=Q.memoizedState,Q!==null&&(Q=Q.dehydrated,Q!==null&&uu(Q))));return;case 19:case 17:case 20:case 21:case 23:case 24:return}throw Error(c(163))}function fP(v,D){if(F)for(var Q=v;;){if(Q.tag===5){var H=Q.stateNode;D?dh(H):to(Q.stateNode,Q.memoizedProps)}else if(Q.tag===6)H=Q.stateNode,D?mh(H):jn(H,Q.memoizedProps);else if((Q.tag!==23&&Q.tag!==24||Q.memoizedState===null||Q===v)&&Q.child!==null){Q.child.return=Q,Q=Q.child;continue}if(Q===v)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===v)return;Q=Q.return}Q.sibling.return=Q.return,Q=Q.sibling}}function ky(v,D){if(Ua&&typeof Ua.onCommitFiberUnmount=="function")try{Ua.onCommitFiberUnmount($e,D)}catch{}switch(D.tag){case 0:case 11:case 14:case 15:case 22:if(v=D.updateQueue,v!==null&&(v=v.lastEffect,v!==null)){var Q=v=v.next;do{var H=Q,V=H.destroy;if(H=H.tag,V!==void 0)if(H&4)vP(D,Q);else{H=D;try{V()}catch(ne){xf(H,ne)}}Q=Q.next}while(Q!==v)}break;case 1:if(s2(D),v=D.stateNode,typeof v.componentWillUnmount=="function")try{v.props=D.memoizedProps,v.state=D.memoizedState,v.componentWillUnmount()}catch(ne){xf(D,ne)}break;case 5:s2(D);break;case 4:F?gP(v,D):z&&z&&(D=D.stateNode.containerInfo,v=ou(D),TA(D,v))}}function AP(v,D){for(var Q=D;;)if(ky(v,Q),Q.child===null||F&&Q.tag===4){if(Q===D)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===D)return;Q=Q.return}Q.sibling.return=Q.return,Q=Q.sibling}else Q.child.return=Q,Q=Q.child}function Qy(v){v.alternate=null,v.child=null,v.dependencies=null,v.firstEffect=null,v.lastEffect=null,v.memoizedProps=null,v.memoizedState=null,v.pendingProps=null,v.return=null,v.updateQueue=null}function pP(v){return v.tag===5||v.tag===3||v.tag===4}function hP(v){if(F){e:{for(var D=v.return;D!==null;){if(pP(D))break e;D=D.return}throw Error(c(160))}var Q=D;switch(D=Q.stateNode,Q.tag){case 5:var H=!1;break;case 3:D=D.containerInfo,H=!0;break;case 4:D=D.containerInfo,H=!0;break;default:throw Error(c(161))}Q.flags&16&&(Af(D),Q.flags&=-17);e:t:for(Q=v;;){for(;Q.sibling===null;){if(Q.return===null||pP(Q.return)){Q=null;break e}Q=Q.return}for(Q.sibling.return=Q.return,Q=Q.sibling;Q.tag!==5&&Q.tag!==6&&Q.tag!==18;){if(Q.flags&2||Q.child===null||Q.tag===4)continue t;Q.child.return=Q,Q=Q.child}if(!(Q.flags&2)){Q=Q.stateNode;break e}}H?o2(v,Q,D):a2(v,Q,D)}}function o2(v,D,Q){var H=v.tag,V=H===5||H===6;if(V)v=V?v.stateNode:v.stateNode.instance,D?eo(Q,v,D):Io(Q,v);else if(H!==4&&(v=v.child,v!==null))for(o2(v,D,Q),v=v.sibling;v!==null;)o2(v,D,Q),v=v.sibling}function a2(v,D,Q){var H=v.tag,V=H===5||H===6;if(V)v=V?v.stateNode:v.stateNode.instance,D?ji(Q,v,D):ai(Q,v);else if(H!==4&&(v=v.child,v!==null))for(a2(v,D,Q),v=v.sibling;v!==null;)a2(v,D,Q),v=v.sibling}function gP(v,D){for(var Q=D,H=!1,V,ne;;){if(!H){H=Q.return;e:for(;;){if(H===null)throw Error(c(160));switch(V=H.stateNode,H.tag){case 5:ne=!1;break e;case 3:V=V.containerInfo,ne=!0;break e;case 4:V=V.containerInfo,ne=!0;break e}H=H.return}H=!0}if(Q.tag===5||Q.tag===6)AP(v,Q),ne?QA(V,Q.stateNode):wo(V,Q.stateNode);else if(Q.tag===4){if(Q.child!==null){V=Q.stateNode.containerInfo,ne=!0,Q.child.return=Q,Q=Q.child;continue}}else if(ky(v,Q),Q.child!==null){Q.child.return=Q,Q=Q.child;continue}if(Q===D)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===D)return;Q=Q.return,Q.tag===4&&(H=!1)}Q.sibling.return=Q.return,Q=Q.sibling}}function l2(v,D){if(F){switch(D.tag){case 0:case 11:case 14:case 15:case 22:Th(3,D);return;case 1:return;case 5:var Q=D.stateNode;if(Q!=null){var H=D.memoizedProps;v=v!==null?v.memoizedProps:H;var V=D.type,ne=D.updateQueue;D.updateQueue=null,ne!==null&&Co(Q,ne,V,v,H,D)}return;case 6:if(D.stateNode===null)throw Error(c(162));Q=D.memoizedProps,rs(D.stateNode,v!==null?v.memoizedProps:Q,Q);return;case 3:X&&(D=D.stateNode,D.hydrate&&(D.hydrate=!1,OA(D.containerInfo)));return;case 12:return;case 13:dP(D),Kg(D);return;case 19:Kg(D);return;case 17:return;case 23:case 24:fP(D,D.memoizedState!==null);return}throw Error(c(163))}switch(D.tag){case 0:case 11:case 14:case 15:case 22:Th(3,D);return;case 12:return;case 13:dP(D),Kg(D);return;case 19:Kg(D);return;case 3:X&&(Q=D.stateNode,Q.hydrate&&(Q.hydrate=!1,OA(Q.containerInfo)));break;case 23:case 24:return}e:if(z){switch(D.tag){case 1:case 5:case 6:case 20:break e;case 3:case 4:D=D.stateNode,TA(D.containerInfo,D.pendingChildren);break e}throw Error(c(163))}}function dP(v){v.memoizedState!==null&&(d2=bt(),F&&fP(v.child,!0))}function Kg(v){var D=v.updateQueue;if(D!==null){v.updateQueue=null;var Q=v.stateNode;Q===null&&(Q=v.stateNode=new YL),D.forEach(function(H){var V=nM.bind(null,v,H);Q.has(H)||(Q.add(H),H.then(V,V))})}}function VL(v,D){return v!==null&&(v=v.memoizedState,v===null||v.dehydrated!==null)?(D=D.memoizedState,D!==null&&D.dehydrated===null):!1}var Ty=0,Ry=1,Fy=2,zg=3,Ny=4;if(typeof Symbol=="function"&&Symbol.for){var Xg=Symbol.for;Ty=Xg("selector.component"),Ry=Xg("selector.has_pseudo_class"),Fy=Xg("selector.role"),zg=Xg("selector.test_id"),Ny=Xg("selector.text")}function Oy(v){var D=$(v);if(D!=null){if(typeof D.memoizedProps["data-testname"]!="string")throw Error(c(364));return D}if(v=ir(v),v===null)throw Error(c(362));return v.stateNode.current}function Sf(v,D){switch(D.$$typeof){case Ty:if(v.type===D.value)return!0;break;case Ry:e:{D=D.value,v=[v,0];for(var Q=0;Q";case Ry:return":has("+(Df(v)||"")+")";case Fy:return'[role="'+v.value+'"]';case Ny:return'"'+v.value+'"';case zg:return'[data-testname="'+v.value+'"]';default:throw Error(c(365,v))}}function c2(v,D){var Q=[];v=[v,0];for(var H=0;HV&&(V=Se),Q&=~ne}if(Q=V,Q=bt()-Q,Q=(120>Q?120:480>Q?480:1080>Q?1080:1920>Q?1920:3e3>Q?3e3:4320>Q?4320:1960*KL(Q/1960))-Q,10 component higher in the tree to provide a loading indicator or placeholder to display.`)}ws!==5&&(ws=2),pt=Yg(pt,_e),Zt=Se;do{switch(Zt.tag){case 3:ne=pt,Zt.flags|=4096,D&=-D,Zt.lanes|=D;var Zn=i2(Zt,ne,D);Iy(Zt,Zn);break e;case 1:ne=pt;var kr=Zt.type,Rn=Zt.stateNode;if(!(Zt.flags&64)&&(typeof kr.getDerivedStateFromError=="function"||Rn!==null&&typeof Rn.componentDidCatch=="function"&&(hc===null||!hc.has(Rn)))){Zt.flags|=4096,D&=-D,Zt.lanes|=D;var _n=Jg(Zt,ne,D);Iy(Zt,_n);break e}}Zt=Zt.return}while(Zt!==null)}BP(Q)}catch(zr){D=zr,Xi===Q&&Q!==null&&(Xi=Q=Q.return);continue}break}while(!0)}function CP(){var v=My.current;return My.current=kt,v===null?kt:v}function id(v,D){var Q=xr;xr|=16;var H=CP();so===v&&Ns===D||Oh(v,D);do try{XL();break}catch(V){IP(v,V)}while(!0);if(Fg(),xr=Q,My.current=H,Xi!==null)throw Error(c(261));return so=null,Ns=0,ws}function XL(){for(;Xi!==null;)wP(Xi)}function ZL(){for(;Xi!==null&&!vl();)wP(Xi)}function wP(v){var D=bP(v.alternate,v,ZA);v.memoizedProps=v.pendingProps,D===null?BP(v):Xi=D,f2.current=null}function BP(v){var D=v;do{var Q=D.alternate;if(v=D.return,D.flags&2048){if(Q=qL(D),Q!==null){Q.flags&=2047,Xi=Q;return}v!==null&&(v.firstEffect=v.lastEffect=null,v.flags|=2048)}else{if(Q=jL(Q,D,ZA),Q!==null){Xi=Q;return}if(Q=D,Q.tag!==24&&Q.tag!==23||Q.memoizedState===null||ZA&1073741824||!(Q.mode&4)){for(var H=0,V=Q.child;V!==null;)H|=V.lanes|V.childLanes,V=V.sibling;Q.childLanes=H}v!==null&&!(v.flags&2048)&&(v.firstEffect===null&&(v.firstEffect=D.firstEffect),D.lastEffect!==null&&(v.lastEffect!==null&&(v.lastEffect.nextEffect=D.firstEffect),v.lastEffect=D.lastEffect),1bt()-d2?Oh(v,0):h2|=Q),ga(v,D)}function nM(v,D){var Q=v.stateNode;Q!==null&&Q.delete(D),D=0,D===0&&(D=v.mode,D&2?D&4?(Bu===0&&(Bu=Rh),D=kn(62914560&~Bu),D===0&&(D=4194304)):D=tr()===99?1:2:D=1),Q=ko(),v=Gy(v,D),v!==null&&(Ha(v,D,Q),ga(v,Q))}var bP;bP=function(v,D,Q){var H=D.lanes;if(v!==null)if(v.memoizedProps!==D.pendingProps||Li.current)Je=!0;else if(Q&H)Je=!!(v.flags&16384);else{switch(Je=!1,D.tag){case 3:by(D),jg();break;case 5:Ef(D);break;case 1:Kn(D.type)&&La(D);break;case 4:Ug(D,D.stateNode.containerInfo);break;case 10:Ng(D,D.memoizedProps.value);break;case 13:if(D.memoizedState!==null)return Q&D.child.childLanes?r2(v,D,Q):(xn(di,di.current&1),D=qn(v,D,Q),D!==null?D.sibling:null);xn(di,di.current&1);break;case 19:if(H=(Q&D.childLanes)!==0,v.flags&64){if(H)return cP(v,D,Q);D.flags|=64}var V=D.memoizedState;if(V!==null&&(V.rendering=null,V.tail=null,V.lastEffect=null),xn(di,di.current),H)break;return null;case 23:case 24:return D.lanes=0,mi(v,D,Q)}return qn(v,D,Q)}else Je=!1;switch(D.lanes=0,D.tag){case 2:if(H=D.type,v!==null&&(v.alternate=null,D.alternate=null,D.flags|=2),v=D.pendingProps,V=dn(D,Gi.current),df(D,Q),V=qg(null,D,H,v,V,Q),D.flags|=1,typeof V=="object"&&V!==null&&typeof V.render=="function"&&V.$$typeof===void 0){if(D.tag=1,D.memoizedState=null,D.updateQueue=null,Kn(H)){var ne=!0;La(D)}else ne=!1;D.memoizedState=V.state!==null&&V.state!==void 0?V.state:null,Bh(D);var Se=H.getDerivedStateFromProps;typeof Se=="function"&&_A(D,H,Se,v),V.updater=HA,D.stateNode=V,V._reactInternals=D,bo(D,H,v,Q),D=t2(null,D,H,!0,ne,Q)}else D.tag=0,At(null,D,V,Q),D=D.child;return D;case 16:V=D.elementType;e:{switch(v!==null&&(v.alternate=null,D.alternate=null,D.flags|=2),v=D.pendingProps,ne=V._init,V=ne(V._payload),D.type=V,ne=D.tag=sM(V),v=So(V,v),ne){case 0:D=JA(null,D,V,v,Q);break e;case 1:D=lP(null,D,V,v,Q);break e;case 11:D=dr(null,D,V,v,Q);break e;case 14:D=vr(null,D,V,So(V.type,v),H,Q);break e}throw Error(c(306,V,""))}return D;case 0:return H=D.type,V=D.pendingProps,V=D.elementType===H?V:So(H,V),JA(v,D,H,V,Q);case 1:return H=D.type,V=D.pendingProps,V=D.elementType===H?V:So(H,V),lP(v,D,H,V,Q);case 3:if(by(D),H=D.updateQueue,v===null||H===null)throw Error(c(282));if(H=D.pendingProps,V=D.memoizedState,V=V!==null?V.element:null,Lg(v,D),UA(D,H,null,Q),H=D.memoizedState.element,H===V)jg(),D=qn(v,D,Q);else{if(V=D.stateNode,(ne=V.hydrate)&&(X?(Aa=cu(D.stateNode.containerInfo),Wa=D,ne=Ya=!0):ne=!1),ne){if(X&&(v=V.mutableSourceEagerHydrationData,v!=null))for(V=0;V=Wt&&ne>=Lr&&V<=Sr&&Se<=Zt){v.splice(D,1);break}else if(H!==Wt||Q.width!==pt.width||ZtSe){if(!(ne!==Lr||Q.height!==pt.height||SrV)){Wt>H&&(pt.width+=Wt-H,pt.x=H),Srne&&(pt.height+=Lr-ne,pt.y=ne),ZtQ&&(Q=Se)),Se ")+` No matching component was found for: `)+v.join(" > ")}return null},r.getPublicRootInstance=function(v){if(v=v.current,!v.child)return null;switch(v.child.tag){case 5:return Re(v.child.stateNode);default:return v.child.stateNode}},r.injectIntoDevTools=function(v){if(v={bundleType:v.bundleType,version:v.version,rendererPackageName:v.rendererPackageName,rendererConfig:v.rendererConfig,overrideHookState:null,overrideHookStateDeletePath:null,overrideHookStateRenamePath:null,overrideProps:null,overridePropsDeletePath:null,overridePropsRenamePath:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:f.ReactCurrentDispatcher,findHostInstanceByFiber:aM,findFiberByHostInstance:v.findFiberByHostInstance||lM,findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null},typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u")v=!1;else{var D=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!D.isDisabled&&D.supportsFiber)try{$e=D.inject(v),Ua=D}catch{}v=!0}return v},r.observeVisibleRects=function(v,D,Q,H){if(!qt)throw Error(c(363));v=u2(v,D);var V=on(v,Q,H).disconnect;return{disconnect:function(){V()}}},r.registerMutableSourceForHydration=function(v,D){var Q=D._getVersion;Q=Q(D._source),v.mutableSourceEagerHydrationData==null?v.mutableSourceEagerHydrationData=[D,Q]:v.mutableSourceEagerHydrationData.push(D,Q)},r.runWithPriority=function(v,D){var Q=lc;try{return lc=v,D()}finally{lc=Q}},r.shouldSuspend=function(){return!1},r.unbatchedUpdates=function(v,D){var Q=xr;xr&=-2,xr|=8;try{return v(D)}finally{xr=Q,xr===0&&(bf(),Tn())}},r.updateContainer=function(v,D,Q,H){var V=D.current,ne=ko(),Se=Bs(V);e:if(Q){Q=Q._reactInternals;t:{if(we(Q)!==Q||Q.tag!==1)throw Error(c(170));var _e=Q;do{switch(_e.tag){case 3:_e=_e.stateNode.context;break t;case 1:if(Kn(_e.type)){_e=_e.stateNode.__reactInternalMemoizedMergedChildContext;break t}}_e=_e.return}while(_e!==null);throw Error(c(171))}if(Q.tag===1){var pt=Q.type;if(Kn(pt)){Q=Oa(Q,pt,_e);break e}}Q=_e}else Q=la;return D.context===null?D.context=Q:D.pendingContext=Q,D=Dl(ne,Se),D.payload={element:v},H=H===void 0?null:H,H!==null&&(D.callback=H),bl(V,D),Tl(V,Se,ne),Se},r}});var Swe=_((vKt,vwe)=>{"use strict";vwe.exports=Bwe()});var bwe=_((SKt,Dwe)=>{"use strict";var Spt={ALIGN_COUNT:8,ALIGN_AUTO:0,ALIGN_FLEX_START:1,ALIGN_CENTER:2,ALIGN_FLEX_END:3,ALIGN_STRETCH:4,ALIGN_BASELINE:5,ALIGN_SPACE_BETWEEN:6,ALIGN_SPACE_AROUND:7,DIMENSION_COUNT:2,DIMENSION_WIDTH:0,DIMENSION_HEIGHT:1,DIRECTION_COUNT:3,DIRECTION_INHERIT:0,DIRECTION_LTR:1,DIRECTION_RTL:2,DISPLAY_COUNT:2,DISPLAY_FLEX:0,DISPLAY_NONE:1,EDGE_COUNT:9,EDGE_LEFT:0,EDGE_TOP:1,EDGE_RIGHT:2,EDGE_BOTTOM:3,EDGE_START:4,EDGE_END:5,EDGE_HORIZONTAL:6,EDGE_VERTICAL:7,EDGE_ALL:8,EXPERIMENTAL_FEATURE_COUNT:1,EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS:0,FLEX_DIRECTION_COUNT:4,FLEX_DIRECTION_COLUMN:0,FLEX_DIRECTION_COLUMN_REVERSE:1,FLEX_DIRECTION_ROW:2,FLEX_DIRECTION_ROW_REVERSE:3,JUSTIFY_COUNT:6,JUSTIFY_FLEX_START:0,JUSTIFY_CENTER:1,JUSTIFY_FLEX_END:2,JUSTIFY_SPACE_BETWEEN:3,JUSTIFY_SPACE_AROUND:4,JUSTIFY_SPACE_EVENLY:5,LOG_LEVEL_COUNT:6,LOG_LEVEL_ERROR:0,LOG_LEVEL_WARN:1,LOG_LEVEL_INFO:2,LOG_LEVEL_DEBUG:3,LOG_LEVEL_VERBOSE:4,LOG_LEVEL_FATAL:5,MEASURE_MODE_COUNT:3,MEASURE_MODE_UNDEFINED:0,MEASURE_MODE_EXACTLY:1,MEASURE_MODE_AT_MOST:2,NODE_TYPE_COUNT:2,NODE_TYPE_DEFAULT:0,NODE_TYPE_TEXT:1,OVERFLOW_COUNT:3,OVERFLOW_VISIBLE:0,OVERFLOW_HIDDEN:1,OVERFLOW_SCROLL:2,POSITION_TYPE_COUNT:2,POSITION_TYPE_RELATIVE:0,POSITION_TYPE_ABSOLUTE:1,PRINT_OPTIONS_COUNT:3,PRINT_OPTIONS_LAYOUT:1,PRINT_OPTIONS_STYLE:2,PRINT_OPTIONS_CHILDREN:4,UNIT_COUNT:4,UNIT_UNDEFINED:0,UNIT_POINT:1,UNIT_PERCENT:2,UNIT_AUTO:3,WRAP_COUNT:3,WRAP_NO_WRAP:0,WRAP_WRAP:1,WRAP_WRAP_REVERSE:2};Dwe.exports=Spt});var Qwe=_((DKt,kwe)=>{"use strict";var Dpt=Object.assign||function(t){for(var e=1;e"}}]),t}(),Pwe=function(){FF(t,null,[{key:"fromJS",value:function(r){var s=r.width,a=r.height;return new t(s,a)}}]);function t(e,r){J9(this,t),this.width=e,this.height=r}return FF(t,[{key:"fromJS",value:function(r){r(this.width,this.height)}},{key:"toString",value:function(){return""}}]),t}(),xwe=function(){function t(e,r){J9(this,t),this.unit=e,this.value=r}return FF(t,[{key:"fromJS",value:function(r){r(this.unit,this.value)}},{key:"toString",value:function(){switch(this.unit){case tf.UNIT_POINT:return String(this.value);case tf.UNIT_PERCENT:return this.value+"%";case tf.UNIT_AUTO:return"auto";default:return this.value+"?"}}},{key:"valueOf",value:function(){return this.value}}]),t}();kwe.exports=function(t,e){function r(c,f,p){var h=c[f];c[f]=function(){for(var E=arguments.length,C=Array(E),S=0;S1?C-1:0),P=1;P1&&arguments[1]!==void 0?arguments[1]:NaN,p=arguments.length>2&&arguments[2]!==void 0?arguments[2]:NaN,h=arguments.length>3&&arguments[3]!==void 0?arguments[3]:tf.DIRECTION_LTR;return c.call(this,f,p,h)}),Dpt({Config:e.Config,Node:e.Node,Layout:t("Layout",bpt),Size:t("Size",Pwe),Value:t("Value",xwe),getInstanceCount:function(){return e.getInstanceCount.apply(e,arguments)}},tf)}});var Twe=_((exports,module)=>{(function(t,e){typeof define=="function"&&define.amd?define([],function(){return e}):typeof module=="object"&&module.exports?module.exports=e:(t.nbind=t.nbind||{}).init=e})(exports,function(Module,cb){typeof Module=="function"&&(cb=Module,Module={}),Module.onRuntimeInitialized=function(t,e){return function(){t&&t.apply(this,arguments);try{Module.ccall("nbind_init")}catch(r){e(r);return}e(null,{bind:Module._nbind_value,reflect:Module.NBind.reflect,queryType:Module.NBind.queryType,toggleLightGC:Module.toggleLightGC,lib:Module})}}(Module.onRuntimeInitialized,cb);var Module;Module||(Module=(typeof Module<"u"?Module:null)||{});var moduleOverrides={};for(var key in Module)Module.hasOwnProperty(key)&&(moduleOverrides[key]=Module[key]);var ENVIRONMENT_IS_WEB=!1,ENVIRONMENT_IS_WORKER=!1,ENVIRONMENT_IS_NODE=!1,ENVIRONMENT_IS_SHELL=!1;if(Module.ENVIRONMENT)if(Module.ENVIRONMENT==="WEB")ENVIRONMENT_IS_WEB=!0;else if(Module.ENVIRONMENT==="WORKER")ENVIRONMENT_IS_WORKER=!0;else if(Module.ENVIRONMENT==="NODE")ENVIRONMENT_IS_NODE=!0;else if(Module.ENVIRONMENT==="SHELL")ENVIRONMENT_IS_SHELL=!0;else throw new Error("The provided Module['ENVIRONMENT'] value is not valid. It must be one of: WEB|WORKER|NODE|SHELL.");else ENVIRONMENT_IS_WEB=typeof window=="object",ENVIRONMENT_IS_WORKER=typeof importScripts=="function",ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof Ie=="function"&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER,ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;if(ENVIRONMENT_IS_NODE){Module.print||(Module.print=console.log),Module.printErr||(Module.printErr=console.warn);var nodeFS,nodePath;Module.read=function(e,r){nodeFS||(nodeFS={}("")),nodePath||(nodePath={}("")),e=nodePath.normalize(e);var s=nodeFS.readFileSync(e);return r?s:s.toString()},Module.readBinary=function(e){var r=Module.read(e,!0);return r.buffer||(r=new Uint8Array(r)),assert(r.buffer),r},Module.load=function(e){globalEval(read(e))},Module.thisProgram||(process.argv.length>1?Module.thisProgram=process.argv[1].replace(/\\/g,"/"):Module.thisProgram="unknown-program"),Module.arguments=process.argv.slice(2),typeof module<"u"&&(module.exports=Module),Module.inspect=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL)Module.print||(Module.print=print),typeof printErr<"u"&&(Module.printErr=printErr),typeof read<"u"?Module.read=read:Module.read=function(){throw"no read() available"},Module.readBinary=function(e){if(typeof readbuffer=="function")return new Uint8Array(readbuffer(e));var r=read(e,"binary");return assert(typeof r=="object"),r},typeof scriptArgs<"u"?Module.arguments=scriptArgs:typeof arguments<"u"&&(Module.arguments=arguments),typeof quit=="function"&&(Module.quit=function(t,e){quit(t)});else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(Module.read=function(e){var r=new XMLHttpRequest;return r.open("GET",e,!1),r.send(null),r.responseText},ENVIRONMENT_IS_WORKER&&(Module.readBinary=function(e){var r=new XMLHttpRequest;return r.open("GET",e,!1),r.responseType="arraybuffer",r.send(null),new Uint8Array(r.response)}),Module.readAsync=function(e,r,s){var a=new XMLHttpRequest;a.open("GET",e,!0),a.responseType="arraybuffer",a.onload=function(){a.status==200||a.status==0&&a.response?r(a.response):s()},a.onerror=s,a.send(null)},typeof arguments<"u"&&(Module.arguments=arguments),typeof console<"u")Module.print||(Module.print=function(e){console.log(e)}),Module.printErr||(Module.printErr=function(e){console.warn(e)});else{var TRY_USE_DUMP=!1;Module.print||(Module.print=TRY_USE_DUMP&&typeof dump<"u"?function(t){dump(t)}:function(t){})}ENVIRONMENT_IS_WORKER&&(Module.load=importScripts),typeof Module.setWindowTitle>"u"&&(Module.setWindowTitle=function(t){document.title=t})}else throw"Unknown runtime environment. Where are we?";function globalEval(t){eval.call(null,t)}!Module.load&&Module.read&&(Module.load=function(e){globalEval(Module.read(e))}),Module.print||(Module.print=function(){}),Module.printErr||(Module.printErr=Module.print),Module.arguments||(Module.arguments=[]),Module.thisProgram||(Module.thisProgram="./this.program"),Module.quit||(Module.quit=function(t,e){throw e}),Module.print=Module.print,Module.printErr=Module.printErr,Module.preRun=[],Module.postRun=[];for(var key in moduleOverrides)moduleOverrides.hasOwnProperty(key)&&(Module[key]=moduleOverrides[key]);moduleOverrides=void 0;var Runtime={setTempRet0:function(t){return tempRet0=t,t},getTempRet0:function(){return tempRet0},stackSave:function(){return STACKTOP},stackRestore:function(t){STACKTOP=t},getNativeTypeSize:function(t){switch(t){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(t[t.length-1]==="*")return Runtime.QUANTUM_SIZE;if(t[0]==="i"){var e=parseInt(t.substr(1));return assert(e%8===0),e/8}else return 0}}},getNativeFieldSize:function(t){return Math.max(Runtime.getNativeTypeSize(t),Runtime.QUANTUM_SIZE)},STACK_ALIGN:16,prepVararg:function(t,e){return e==="double"||e==="i64"?t&7&&(assert((t&7)===4),t+=4):assert((t&3)===0),t},getAlignSize:function(t,e,r){return!r&&(t=="i64"||t=="double")?8:t?Math.min(e||(t?Runtime.getNativeFieldSize(t):0),Runtime.QUANTUM_SIZE):Math.min(e,8)},dynCall:function(t,e,r){return r&&r.length?Module["dynCall_"+t].apply(null,[e].concat(r)):Module["dynCall_"+t].call(null,e)},functionPointers:[],addFunction:function(t){for(var e=0;e>2],r=(e+t+15|0)&-16;if(HEAP32[DYNAMICTOP_PTR>>2]=r,r>=TOTAL_MEMORY){var s=enlargeMemory();if(!s)return HEAP32[DYNAMICTOP_PTR>>2]=e,0}return e},alignMemory:function(t,e){var r=t=Math.ceil(t/(e||16))*(e||16);return r},makeBigInt:function(t,e,r){var s=r?+(t>>>0)+ +(e>>>0)*4294967296:+(t>>>0)+ +(e|0)*4294967296;return s},GLOBAL_BASE:8,QUANTUM_SIZE:4,__dummy__:0};Module.Runtime=Runtime;var ABORT=0,EXITSTATUS=0;function assert(t,e){t||abort("Assertion failed: "+e)}function getCFunc(ident){var func=Module["_"+ident];if(!func)try{func=eval("_"+ident)}catch(t){}return assert(func,"Cannot call unknown function "+ident+" (perhaps LLVM optimizations or closure removed it?)"),func}var cwrap,ccall;(function(){var JSfuncs={stackSave:function(){Runtime.stackSave()},stackRestore:function(){Runtime.stackRestore()},arrayToC:function(t){var e=Runtime.stackAlloc(t.length);return writeArrayToMemory(t,e),e},stringToC:function(t){var e=0;if(t!=null&&t!==0){var r=(t.length<<2)+1;e=Runtime.stackAlloc(r),stringToUTF8(t,e,r)}return e}},toC={string:JSfuncs.stringToC,array:JSfuncs.arrayToC};ccall=function(e,r,s,a,n){var c=getCFunc(e),f=[],p=0;if(a)for(var h=0;h>0]=e;break;case"i8":HEAP8[t>>0]=e;break;case"i16":HEAP16[t>>1]=e;break;case"i32":HEAP32[t>>2]=e;break;case"i64":tempI64=[e>>>0,(tempDouble=e,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[t>>2]=tempI64[0],HEAP32[t+4>>2]=tempI64[1];break;case"float":HEAPF32[t>>2]=e;break;case"double":HEAPF64[t>>3]=e;break;default:abort("invalid type for setValue: "+r)}}Module.setValue=setValue;function getValue(t,e,r){switch(e=e||"i8",e.charAt(e.length-1)==="*"&&(e="i32"),e){case"i1":return HEAP8[t>>0];case"i8":return HEAP8[t>>0];case"i16":return HEAP16[t>>1];case"i32":return HEAP32[t>>2];case"i64":return HEAP32[t>>2];case"float":return HEAPF32[t>>2];case"double":return HEAPF64[t>>3];default:abort("invalid type for setValue: "+e)}return null}Module.getValue=getValue;var ALLOC_NORMAL=0,ALLOC_STACK=1,ALLOC_STATIC=2,ALLOC_DYNAMIC=3,ALLOC_NONE=4;Module.ALLOC_NORMAL=ALLOC_NORMAL,Module.ALLOC_STACK=ALLOC_STACK,Module.ALLOC_STATIC=ALLOC_STATIC,Module.ALLOC_DYNAMIC=ALLOC_DYNAMIC,Module.ALLOC_NONE=ALLOC_NONE;function allocate(t,e,r,s){var a,n;typeof t=="number"?(a=!0,n=t):(a=!1,n=t.length);var c=typeof e=="string"?e:null,f;if(r==ALLOC_NONE?f=s:f=[typeof _malloc=="function"?_malloc:Runtime.staticAlloc,Runtime.stackAlloc,Runtime.staticAlloc,Runtime.dynamicAlloc][r===void 0?ALLOC_STATIC:r](Math.max(n,c?1:e.length)),a){var s=f,p;for(assert((f&3)==0),p=f+(n&-4);s>2]=0;for(p=f+n;s>0]=0;return f}if(c==="i8")return t.subarray||t.slice?HEAPU8.set(t,f):HEAPU8.set(new Uint8Array(t),f),f;for(var h=0,E,C,S;h>0],r|=s,!(s==0&&!e||(a++,e&&a==e)););e||(e=a);var n="";if(r<128){for(var c=1024,f;e>0;)f=String.fromCharCode.apply(String,HEAPU8.subarray(t,t+Math.min(e,c))),n=n?n+f:f,t+=c,e-=c;return n}return Module.UTF8ToString(t)}Module.Pointer_stringify=Pointer_stringify;function AsciiToString(t){for(var e="";;){var r=HEAP8[t++>>0];if(!r)return e;e+=String.fromCharCode(r)}}Module.AsciiToString=AsciiToString;function stringToAscii(t,e){return writeAsciiToMemory(t,e,!1)}Module.stringToAscii=stringToAscii;var UTF8Decoder=typeof TextDecoder<"u"?new TextDecoder("utf8"):void 0;function UTF8ArrayToString(t,e){for(var r=e;t[r];)++r;if(r-e>16&&t.subarray&&UTF8Decoder)return UTF8Decoder.decode(t.subarray(e,r));for(var s,a,n,c,f,p,h="";;){if(s=t[e++],!s)return h;if(!(s&128)){h+=String.fromCharCode(s);continue}if(a=t[e++]&63,(s&224)==192){h+=String.fromCharCode((s&31)<<6|a);continue}if(n=t[e++]&63,(s&240)==224?s=(s&15)<<12|a<<6|n:(c=t[e++]&63,(s&248)==240?s=(s&7)<<18|a<<12|n<<6|c:(f=t[e++]&63,(s&252)==248?s=(s&3)<<24|a<<18|n<<12|c<<6|f:(p=t[e++]&63,s=(s&1)<<30|a<<24|n<<18|c<<12|f<<6|p))),s<65536)h+=String.fromCharCode(s);else{var E=s-65536;h+=String.fromCharCode(55296|E>>10,56320|E&1023)}}}Module.UTF8ArrayToString=UTF8ArrayToString;function UTF8ToString(t){return UTF8ArrayToString(HEAPU8,t)}Module.UTF8ToString=UTF8ToString;function stringToUTF8Array(t,e,r,s){if(!(s>0))return 0;for(var a=r,n=r+s-1,c=0;c=55296&&f<=57343&&(f=65536+((f&1023)<<10)|t.charCodeAt(++c)&1023),f<=127){if(r>=n)break;e[r++]=f}else if(f<=2047){if(r+1>=n)break;e[r++]=192|f>>6,e[r++]=128|f&63}else if(f<=65535){if(r+2>=n)break;e[r++]=224|f>>12,e[r++]=128|f>>6&63,e[r++]=128|f&63}else if(f<=2097151){if(r+3>=n)break;e[r++]=240|f>>18,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}else if(f<=67108863){if(r+4>=n)break;e[r++]=248|f>>24,e[r++]=128|f>>18&63,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}else{if(r+5>=n)break;e[r++]=252|f>>30,e[r++]=128|f>>24&63,e[r++]=128|f>>18&63,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}}return e[r]=0,r-a}Module.stringToUTF8Array=stringToUTF8Array;function stringToUTF8(t,e,r){return stringToUTF8Array(t,HEAPU8,e,r)}Module.stringToUTF8=stringToUTF8;function lengthBytesUTF8(t){for(var e=0,r=0;r=55296&&s<=57343&&(s=65536+((s&1023)<<10)|t.charCodeAt(++r)&1023),s<=127?++e:s<=2047?e+=2:s<=65535?e+=3:s<=2097151?e+=4:s<=67108863?e+=5:e+=6}return e}Module.lengthBytesUTF8=lengthBytesUTF8;var UTF16Decoder=typeof TextDecoder<"u"?new TextDecoder("utf-16le"):void 0;function demangle(t){var e=Module.___cxa_demangle||Module.__cxa_demangle;if(e){try{var r=t.substr(1),s=lengthBytesUTF8(r)+1,a=_malloc(s);stringToUTF8(r,a,s);var n=_malloc(4),c=e(a,0,0,n);if(getValue(n,"i32")===0&&c)return Pointer_stringify(c)}catch{}finally{a&&_free(a),n&&_free(n),c&&_free(c)}return t}return Runtime.warnOnce("warning: build with -s DEMANGLE_SUPPORT=1 to link in libcxxabi demangling"),t}function demangleAll(t){var e=/__Z[\w\d_]+/g;return t.replace(e,function(r){var s=demangle(r);return r===s?r:r+" ["+s+"]"})}function jsStackTrace(){var t=new Error;if(!t.stack){try{throw new Error(0)}catch(e){t=e}if(!t.stack)return"(no stack trace available)"}return t.stack.toString()}function stackTrace(){var t=jsStackTrace();return Module.extraStackTrace&&(t+=` `+Module.extraStackTrace()),demangleAll(t)}Module.stackTrace=stackTrace;var HEAP,buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferViews(){Module.HEAP8=HEAP8=new Int8Array(buffer),Module.HEAP16=HEAP16=new Int16Array(buffer),Module.HEAP32=HEAP32=new Int32Array(buffer),Module.HEAPU8=HEAPU8=new Uint8Array(buffer),Module.HEAPU16=HEAPU16=new Uint16Array(buffer),Module.HEAPU32=HEAPU32=new Uint32Array(buffer),Module.HEAPF32=HEAPF32=new Float32Array(buffer),Module.HEAPF64=HEAPF64=new Float64Array(buffer)}var STATIC_BASE,STATICTOP,staticSealed,STACK_BASE,STACKTOP,STACK_MAX,DYNAMIC_BASE,DYNAMICTOP_PTR;STATIC_BASE=STATICTOP=STACK_BASE=STACKTOP=STACK_MAX=DYNAMIC_BASE=DYNAMICTOP_PTR=0,staticSealed=!1;function abortOnCannotGrowMemory(){abort("Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value "+TOTAL_MEMORY+", (2) compile with -s ALLOW_MEMORY_GROWTH=1 which allows increasing the size at runtime but prevents some optimizations, (3) set Module.TOTAL_MEMORY to a higher value before the program runs, or (4) if you want malloc to return NULL (0) instead of this abort, compile with -s ABORTING_MALLOC=0 ")}function enlargeMemory(){abortOnCannotGrowMemory()}var TOTAL_STACK=Module.TOTAL_STACK||5242880,TOTAL_MEMORY=Module.TOTAL_MEMORY||134217728;TOTAL_MEMORY0;){var e=t.shift();if(typeof e=="function"){e();continue}var r=e.func;typeof r=="number"?e.arg===void 0?Module.dynCall_v(r):Module.dynCall_vi(r,e.arg):r(e.arg===void 0?null:e.arg)}}var __ATPRERUN__=[],__ATINIT__=[],__ATMAIN__=[],__ATEXIT__=[],__ATPOSTRUN__=[],runtimeInitialized=!1,runtimeExited=!1;function preRun(){if(Module.preRun)for(typeof Module.preRun=="function"&&(Module.preRun=[Module.preRun]);Module.preRun.length;)addOnPreRun(Module.preRun.shift());callRuntimeCallbacks(__ATPRERUN__)}function ensureInitRuntime(){runtimeInitialized||(runtimeInitialized=!0,callRuntimeCallbacks(__ATINIT__))}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){callRuntimeCallbacks(__ATEXIT__),runtimeExited=!0}function postRun(){if(Module.postRun)for(typeof Module.postRun=="function"&&(Module.postRun=[Module.postRun]);Module.postRun.length;)addOnPostRun(Module.postRun.shift());callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(t){__ATPRERUN__.unshift(t)}Module.addOnPreRun=addOnPreRun;function addOnInit(t){__ATINIT__.unshift(t)}Module.addOnInit=addOnInit;function addOnPreMain(t){__ATMAIN__.unshift(t)}Module.addOnPreMain=addOnPreMain;function addOnExit(t){__ATEXIT__.unshift(t)}Module.addOnExit=addOnExit;function addOnPostRun(t){__ATPOSTRUN__.unshift(t)}Module.addOnPostRun=addOnPostRun;function intArrayFromString(t,e,r){var s=r>0?r:lengthBytesUTF8(t)+1,a=new Array(s),n=stringToUTF8Array(t,a,0,a.length);return e&&(a.length=n),a}Module.intArrayFromString=intArrayFromString;function intArrayToString(t){for(var e=[],r=0;r255&&(s&=255),e.push(String.fromCharCode(s))}return e.join("")}Module.intArrayToString=intArrayToString;function writeStringToMemory(t,e,r){Runtime.warnOnce("writeStringToMemory is deprecated and should not be called! Use stringToUTF8() instead!");var s,a;r&&(a=e+lengthBytesUTF8(t),s=HEAP8[a]),stringToUTF8(t,e,1/0),r&&(HEAP8[a]=s)}Module.writeStringToMemory=writeStringToMemory;function writeArrayToMemory(t,e){HEAP8.set(t,e)}Module.writeArrayToMemory=writeArrayToMemory;function writeAsciiToMemory(t,e,r){for(var s=0;s>0]=t.charCodeAt(s);r||(HEAP8[e>>0]=0)}if(Module.writeAsciiToMemory=writeAsciiToMemory,(!Math.imul||Math.imul(4294967295,5)!==-5)&&(Math.imul=function t(e,r){var s=e>>>16,a=e&65535,n=r>>>16,c=r&65535;return a*c+(s*c+a*n<<16)|0}),Math.imul=Math.imul,!Math.fround){var froundBuffer=new Float32Array(1);Math.fround=function(t){return froundBuffer[0]=t,froundBuffer[0]}}Math.fround=Math.fround,Math.clz32||(Math.clz32=function(t){t=t>>>0;for(var e=0;e<32;e++)if(t&1<<31-e)return e;return 32}),Math.clz32=Math.clz32,Math.trunc||(Math.trunc=function(t){return t<0?Math.ceil(t):Math.floor(t)}),Math.trunc=Math.trunc;var Math_abs=Math.abs,Math_cos=Math.cos,Math_sin=Math.sin,Math_tan=Math.tan,Math_acos=Math.acos,Math_asin=Math.asin,Math_atan=Math.atan,Math_atan2=Math.atan2,Math_exp=Math.exp,Math_log=Math.log,Math_sqrt=Math.sqrt,Math_ceil=Math.ceil,Math_floor=Math.floor,Math_pow=Math.pow,Math_imul=Math.imul,Math_fround=Math.fround,Math_round=Math.round,Math_min=Math.min,Math_clz32=Math.clz32,Math_trunc=Math.trunc,runDependencies=0,runDependencyWatcher=null,dependenciesFulfilled=null;function getUniqueRunDependency(t){return t}function addRunDependency(t){runDependencies++,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies)}Module.addRunDependency=addRunDependency;function removeRunDependency(t){if(runDependencies--,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies),runDependencies==0&&(runDependencyWatcher!==null&&(clearInterval(runDependencyWatcher),runDependencyWatcher=null),dependenciesFulfilled)){var e=dependenciesFulfilled;dependenciesFulfilled=null,e()}}Module.removeRunDependency=removeRunDependency,Module.preloadedImages={},Module.preloadedAudios={};var ASM_CONSTS=[function(t,e,r,s,a,n,c,f){return _nbind.callbackSignatureList[t].apply(this,arguments)}];function _emscripten_asm_const_iiiiiiii(t,e,r,s,a,n,c,f){return ASM_CONSTS[t](e,r,s,a,n,c,f)}function _emscripten_asm_const_iiiii(t,e,r,s,a){return ASM_CONSTS[t](e,r,s,a)}function _emscripten_asm_const_iiidddddd(t,e,r,s,a,n,c,f,p){return ASM_CONSTS[t](e,r,s,a,n,c,f,p)}function _emscripten_asm_const_iiididi(t,e,r,s,a,n,c){return ASM_CONSTS[t](e,r,s,a,n,c)}function _emscripten_asm_const_iiii(t,e,r,s){return ASM_CONSTS[t](e,r,s)}function _emscripten_asm_const_iiiid(t,e,r,s,a){return ASM_CONSTS[t](e,r,s,a)}function _emscripten_asm_const_iiiiii(t,e,r,s,a,n){return ASM_CONSTS[t](e,r,s,a,n)}STATIC_BASE=Runtime.GLOBAL_BASE,STATICTOP=STATIC_BASE+12800,__ATINIT__.push({func:function(){__GLOBAL__sub_I_Yoga_cpp()}},{func:function(){__GLOBAL__sub_I_nbind_cc()}},{func:function(){__GLOBAL__sub_I_common_cc()}},{func:function(){__GLOBAL__sub_I_Binding_cc()}}),allocate([0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,192,127,0,0,192,127,3,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,3,0,0,0,0,0,192,127,3,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,0,0,128,191,0,0,128,191,0,0,192,127,0,0,0,0,0,0,0,0,0,0,128,63,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,3,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,190,12,0,0,200,12,0,0,208,12,0,0,216,12,0,0,230,12,0,0,242,12,0,0,1,0,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,192,127,3,0,0,0,180,45,0,0,181,45,0,0,182,45,0,0,181,45,0,0,182,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,3,0,0,0,1,0,0,0,4,0,0,0,183,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,184,45,0,0,185,45,0,0,181,45,0,0,181,45,0,0,182,45,0,0,186,45,0,0,185,45,0,0,148,4,0,0,3,0,0,0,187,45,0,0,164,4,0,0,188,45,0,0,2,0,0,0,189,45,0,0,164,4,0,0,188,45,0,0,185,45,0,0,164,4,0,0,185,45,0,0,164,4,0,0,188,45,0,0,181,45,0,0,182,45,0,0,181,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,5,0,0,0,6,0,0,0,1,0,0,0,7,0,0,0,183,45,0,0,182,45,0,0,181,45,0,0,190,45,0,0,190,45,0,0,182,45,0,0,182,45,0,0,185,45,0,0,181,45,0,0,185,45,0,0,182,45,0,0,181,45,0,0,185,45,0,0,182,45,0,0,185,45,0,0,48,5,0,0,3,0,0,0,56,5,0,0,1,0,0,0,189,45,0,0,185,45,0,0,164,4,0,0,76,5,0,0,2,0,0,0,191,45,0,0,186,45,0,0,182,45,0,0,185,45,0,0,192,45,0,0,185,45,0,0,182,45,0,0,186,45,0,0,185,45,0,0,76,5,0,0,76,5,0,0,136,5,0,0,182,45,0,0,181,45,0,0,2,0,0,0,190,45,0,0,136,5,0,0,56,19,0,0,156,5,0,0,2,0,0,0,184,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,8,0,0,0,9,0,0,0,1,0,0,0,10,0,0,0,204,5,0,0,181,45,0,0,181,45,0,0,2,0,0,0,180,45,0,0,204,5,0,0,2,0,0,0,195,45,0,0,236,5,0,0,97,19,0,0,198,45,0,0,211,45,0,0,212,45,0,0,213,45,0,0,214,45,0,0,215,45,0,0,188,45,0,0,182,45,0,0,216,45,0,0,217,45,0,0,218,45,0,0,219,45,0,0,192,45,0,0,181,45,0,0,0,0,0,0,185,45,0,0,110,19,0,0,186,45,0,0,115,19,0,0,221,45,0,0,120,19,0,0,148,4,0,0,132,19,0,0,96,6,0,0,145,19,0,0,222,45,0,0,164,19,0,0,223,45,0,0,173,19,0,0,0,0,0,0,3,0,0,0,104,6,0,0,1,0,0,0,187,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,11,0,0,0,12,0,0,0,1,0,0,0,13,0,0,0,185,45,0,0,224,45,0,0,164,6,0,0,188,45,0,0,172,6,0,0,180,6,0,0,2,0,0,0,188,6,0,0,7,0,0,0,224,45,0,0,7,0,0,0,164,6,0,0,1,0,0,0,213,45,0,0,185,45,0,0,224,45,0,0,172,6,0,0,185,45,0,0,224,45,0,0,164,6,0,0,185,45,0,0,224,45,0,0,211,45,0,0,211,45,0,0,222,45,0,0,211,45,0,0,224,45,0,0,222,45,0,0,211,45,0,0,224,45,0,0,172,6,0,0,222,45,0,0,211,45,0,0,224,45,0,0,188,45,0,0,222,45,0,0,211,45,0,0,40,7,0,0,188,45,0,0,2,0,0,0,224,45,0,0,185,45,0,0,188,45,0,0,188,45,0,0,188,45,0,0,188,45,0,0,222,45,0,0,224,45,0,0,148,4,0,0,185,45,0,0,148,4,0,0,148,4,0,0,148,4,0,0,148,4,0,0,148,4,0,0,185,45,0,0,164,6,0,0,148,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,14,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,148,7,0,0,2,0,0,0,225,45,0,0,183,45,0,0,188,45,0,0,168,7,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,234,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,148,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,9,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,2,0,0,0,242,45,0,0,0,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,67,111,117,108,100,32,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,32,102,111,114,32,110,111,100,101,0,67,97,110,110,111,116,32,114,101,115,101,116,32,97,32,110,111,100,101,32,119,104,105,99,104,32,115,116,105,108,108,32,104,97,115,32,99,104,105,108,100,114,101,110,32,97,116,116,97,99,104,101,100,0,67,97,110,110,111,116,32,114,101,115,101,116,32,97,32,110,111,100,101,32,115,116,105,108,108,32,97,116,116,97,99,104,101,100,32,116,111,32,97,32,112,97,114,101,110,116,0,67,111,117,108,100,32,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,32,102,111,114,32,99,111,110,102,105,103,0,67,97,110,110,111,116,32,115,101,116,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,58,32,78,111,100,101,115,32,119,105,116,104,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,32,99,97,110,110,111,116,32,104,97,118,101,32,99,104,105,108,100,114,101,110,46,0,67,104,105,108,100,32,97,108,114,101,97,100,121,32,104,97,115,32,97,32,112,97,114,101,110,116,44,32,105,116,32,109,117,115,116,32,98,101,32,114,101,109,111,118,101,100,32,102,105,114,115,116,46,0,67,97,110,110,111,116,32,97,100,100,32,99,104,105,108,100,58,32,78,111,100,101,115,32,119,105,116,104,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,32,99,97,110,110,111,116,32,104,97,118,101,32,99,104,105,108,100,114,101,110,46,0,79,110,108,121,32,108,101,97,102,32,110,111,100,101,115,32,119,105,116,104,32,99,117,115,116,111,109,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,115,104,111,117,108,100,32,109,97,110,117,97,108,108,121,32,109,97,114,107,32,116,104,101,109,115,101,108,118,101,115,32,97,115,32,100,105,114,116,121,0,67,97,110,110,111,116,32,103,101,116,32,108,97,121,111,117,116,32,112,114,111,112,101,114,116,105,101,115,32,111,102,32,109,117,108,116,105,45,101,100,103,101,32,115,104,111,114,116,104,97,110,100,115,0,37,115,37,100,46,123,91,115,107,105,112,112,101,100,93,32,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,97,119,58,32,37,102,32,97,104,58,32,37,102,32,61,62,32,100,58,32,40,37,102,44,32,37,102,41,32,37,115,10,0,37,115,37,100,46,123,37,115,0,42,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,97,119,58,32,37,102,32,97,104,58,32,37,102,32,37,115,10,0,37,115,37,100,46,125,37,115,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,100,58,32,40,37,102,44,32,37,102,41,32,37,115,10,0,79,117,116,32,111,102,32,99,97,99,104,101,32,101,110,116,114,105,101,115,33,10,0,83,99,97,108,101,32,102,97,99,116,111,114,32,115,104,111,117,108,100,32,110,111,116,32,98,101,32,108,101,115,115,32,116,104,97,110,32,122,101,114,111,0,105,110,105,116,105,97,108,0,37,115,10,0,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,0,85,78,68,69,70,73,78,69,68,0,69,88,65,67,84,76,89,0,65,84,95,77,79,83,84,0,76,65,89,95,85,78,68,69,70,73,78,69,68,0,76,65,89,95,69,88,65,67,84,76,89,0,76,65,89,95,65,84,95,77,79,83,84,0,97,118,97,105,108,97,98,108,101,87,105,100,116,104,32,105,115,32,105,110,100,101,102,105,110,105,116,101,32,115,111,32,119,105,100,116,104,77,101,97,115,117,114,101,77,111,100,101,32,109,117,115,116,32,98,101,32,89,71,77,101,97,115,117,114,101,77,111,100,101,85,110,100,101,102,105,110,101,100,0,97,118,97,105,108,97,98,108,101,72,101,105,103,104,116,32,105,115,32,105,110,100,101,102,105,110,105,116,101,32,115,111,32,104,101,105,103,104,116,77,101,97,115,117,114,101,77,111,100,101,32,109,117,115,116,32,98,101,32,89,71,77,101,97,115,117,114,101,77,111,100,101,85,110,100,101,102,105,110,101,100,0,102,108,101,120,0,115,116,114,101,116,99,104,0,109,117,108,116,105,108,105,110,101,45,115,116,114,101,116,99,104,0,69,120,112,101,99,116,101,100,32,110,111,100,101,32,116,111,32,104,97,118,101,32,99,117,115,116,111,109,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,0,109,101,97,115,117,114,101,0,69,120,112,101,99,116,32,99,117,115,116,111,109,32,98,97,115,101,108,105,110,101,32,102,117,110,99,116,105,111,110,32,116,111,32,110,111,116,32,114,101,116,117,114,110,32,78,97,78,0,97,98,115,45,109,101,97,115,117,114,101,0,97,98,115,45,108,97,121,111,117,116,0,78,111,100,101,0,99,114,101,97,116,101,68,101,102,97,117,108,116,0,99,114,101,97,116,101,87,105,116,104,67,111,110,102,105,103,0,100,101,115,116,114,111,121,0,114,101,115,101,116,0,99,111,112,121,83,116,121,108,101,0,115,101,116,80,111,115,105,116,105,111,110,84,121,112,101,0,115,101,116,80,111,115,105,116,105,111,110,0,115,101,116,80,111,115,105,116,105,111,110,80,101,114,99,101,110,116,0,115,101,116,65,108,105,103,110,67,111,110,116,101,110,116,0,115,101,116,65,108,105,103,110,73,116,101,109,115,0,115,101,116,65,108,105,103,110,83,101,108,102,0,115,101,116,70,108,101,120,68,105,114,101,99,116,105,111,110,0,115,101,116,70,108,101,120,87,114,97,112,0,115,101,116,74,117,115,116,105,102,121,67,111,110,116,101,110,116,0,115,101,116,77,97,114,103,105,110,0,115,101,116,77,97,114,103,105,110,80,101,114,99,101,110,116,0,115,101,116,77,97,114,103,105,110,65,117,116,111,0,115,101,116,79,118,101,114,102,108,111,119,0,115,101,116,68,105,115,112,108,97,121,0,115,101,116,70,108,101,120,0,115,101,116,70,108,101,120,66,97,115,105,115,0,115,101,116,70,108,101,120,66,97,115,105,115,80,101,114,99,101,110,116,0,115,101,116,70,108,101,120,71,114,111,119,0,115,101,116,70,108,101,120,83,104,114,105,110,107,0,115,101,116,87,105,100,116,104,0,115,101,116,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,87,105,100,116,104,65,117,116,111,0,115,101,116,72,101,105,103,104,116,0,115,101,116,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,72,101,105,103,104,116,65,117,116,111,0,115,101,116,77,105,110,87,105,100,116,104,0,115,101,116,77,105,110,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,77,105,110,72,101,105,103,104,116,0,115,101,116,77,105,110,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,77,97,120,87,105,100,116,104,0,115,101,116,77,97,120,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,77,97,120,72,101,105,103,104,116,0,115,101,116,77,97,120,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,65,115,112,101,99,116,82,97,116,105,111,0,115,101,116,66,111,114,100,101,114,0,115,101,116,80,97,100,100,105,110,103,0,115,101,116,80,97,100,100,105,110,103,80,101,114,99,101,110,116,0,103,101,116,80,111,115,105,116,105,111,110,84,121,112,101,0,103,101,116,80,111,115,105,116,105,111,110,0,103,101,116,65,108,105,103,110,67,111,110,116,101,110,116,0,103,101,116,65,108,105,103,110,73,116,101,109,115,0,103,101,116,65,108,105,103,110,83,101,108,102,0,103,101,116,70,108,101,120,68,105,114,101,99,116,105,111,110,0,103,101,116,70,108,101,120,87,114,97,112,0,103,101,116,74,117,115,116,105,102,121,67,111,110,116,101,110,116,0,103,101,116,77,97,114,103,105,110,0,103,101,116,70,108,101,120,66,97,115,105,115,0,103,101,116,70,108,101,120,71,114,111,119,0,103,101,116,70,108,101,120,83,104,114,105,110,107,0,103,101,116,87,105,100,116,104,0,103,101,116,72,101,105,103,104,116,0,103,101,116,77,105,110,87,105,100,116,104,0,103,101,116,77,105,110,72,101,105,103,104,116,0,103,101,116,77,97,120,87,105,100,116,104,0,103,101,116,77,97,120,72,101,105,103,104,116,0,103,101,116,65,115,112,101,99,116,82,97,116,105,111,0,103,101,116,66,111,114,100,101,114,0,103,101,116,79,118,101,114,102,108,111,119,0,103,101,116,68,105,115,112,108,97,121,0,103,101,116,80,97,100,100,105,110,103,0,105,110,115,101,114,116,67,104,105,108,100,0,114,101,109,111,118,101,67,104,105,108,100,0,103,101,116,67,104,105,108,100,67,111,117,110,116,0,103,101,116,80,97,114,101,110,116,0,103,101,116,67,104,105,108,100,0,115,101,116,77,101,97,115,117,114,101,70,117,110,99,0,117,110,115,101,116,77,101,97,115,117,114,101,70,117,110,99,0,109,97,114,107,68,105,114,116,121,0,105,115,68,105,114,116,121,0,99,97,108,99,117,108,97,116,101,76,97,121,111,117,116,0,103,101,116,67,111,109,112,117,116,101,100,76,101,102,116,0,103,101,116,67,111,109,112,117,116,101,100,82,105,103,104,116,0,103,101,116,67,111,109,112,117,116,101,100,84,111,112,0,103,101,116,67,111,109,112,117,116,101,100,66,111,116,116,111,109,0,103,101,116,67,111,109,112,117,116,101,100,87,105,100,116,104,0,103,101,116,67,111,109,112,117,116,101,100,72,101,105,103,104,116,0,103,101,116,67,111,109,112,117,116,101,100,76,97,121,111,117,116,0,103,101,116,67,111,109,112,117,116,101,100,77,97,114,103,105,110,0,103,101,116,67,111,109,112,117,116,101,100,66,111,114,100,101,114,0,103,101,116,67,111,109,112,117,116,101,100,80,97,100,100,105,110,103,0,67,111,110,102,105,103,0,99,114,101,97,116,101,0,115,101,116,69,120,112,101,114,105,109,101,110,116,97,108,70,101,97,116,117,114,101,69,110,97,98,108,101,100,0,115,101,116,80,111,105,110,116,83,99,97,108,101,70,97,99,116,111,114,0,105,115,69,120,112,101,114,105,109,101,110,116,97,108,70,101,97,116,117,114,101,69,110,97,98,108,101,100,0,86,97,108,117,101,0,76,97,121,111,117,116,0,83,105,122,101,0,103,101,116,73,110,115,116,97,110,99,101,67,111,117,110,116,0,73,110,116,54,52,0,1,1,1,2,2,4,4,4,4,8,8,4,8,118,111,105,100,0,98,111,111,108,0,115,116,100,58,58,115,116,114,105,110,103,0,99,98,70,117,110,99,116,105,111,110,32,38,0,99,111,110,115,116,32,99,98,70,117,110,99,116,105,111,110,32,38,0,69,120,116,101,114,110,97,108,0,66,117,102,102,101,114,0,78,66,105,110,100,73,68,0,78,66,105,110,100,0,98,105,110,100,95,118,97,108,117,101,0,114,101,102,108,101,99,116,0,113,117,101,114,121,84,121,112,101,0,108,97,108,108,111,99,0,108,114,101,115,101,116,0,123,114,101,116,117,114,110,40,95,110,98,105,110,100,46,99,97,108,108,98,97,99,107,83,105,103,110,97,116,117,114,101,76,105,115,116,91,36,48,93,46,97,112,112,108,121,40,116,104,105,115,44,97,114,103,117,109,101,110,116,115,41,41,59,125,0,95,110,98,105,110,100,95,110,101,119,0,17,0,10,0,17,17,17,0,0,0,0,5,0,0,0,0,0,0,9,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,15,10,17,17,17,3,10,7,0,1,19,9,11,11,0,0,9,6,11,0,0,11,0,6,17,0,0,0,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,10,10,17,17,17,0,10,0,0,2,0,9,11,0,0,0,9,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,13,0,0,0,4,13,0,0,0,0,9,14,0,0,0,0,0,14,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,15,0,0,0,0,9,16,0,0,0,0,0,16,0,0,16,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,10,0,0,0,0,9,11,0,0,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,45,43,32,32,32,48,88,48,120,0,40,110,117,108,108,41,0,45,48,88,43,48,88,32,48,88,45,48,120,43,48,120,32,48,120,0,105,110,102,0,73,78,70,0,110,97,110,0,78,65,78,0,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,46,0,84,33,34,25,13,1,2,3,17,75,28,12,16,4,11,29,18,30,39,104,110,111,112,113,98,32,5,6,15,19,20,21,26,8,22,7,40,36,23,24,9,10,14,27,31,37,35,131,130,125,38,42,43,60,61,62,63,67,71,74,77,88,89,90,91,92,93,94,95,96,97,99,100,101,102,103,105,106,107,108,114,115,116,121,122,123,124,0,73,108,108,101,103,97,108,32,98,121,116,101,32,115,101,113,117,101,110,99,101,0,68,111,109,97,105,110,32,101,114,114,111,114,0,82,101,115,117,108,116,32,110,111,116,32,114,101,112,114,101,115,101,110,116,97,98,108,101,0,78,111,116,32,97,32,116,116,121,0,80,101,114,109,105,115,115,105,111,110,32,100,101,110,105,101,100,0,79,112,101,114,97,116,105,111,110,32,110,111,116,32,112,101,114,109,105,116,116,101,100,0,78,111,32,115,117,99,104,32,102,105,108,101,32,111,114,32,100,105,114,101,99,116,111,114,121,0,78,111,32,115,117,99,104,32,112,114,111,99,101,115,115,0,70,105,108,101,32,101,120,105,115,116,115,0,86,97,108,117,101,32,116,111,111,32,108,97,114,103,101,32,102,111,114,32,100,97,116,97,32,116,121,112,101,0,78,111,32,115,112,97,99,101,32,108,101,102,116,32,111,110,32,100,101,118,105,99,101,0,79,117,116,32,111,102,32,109,101,109,111,114,121,0,82,101,115,111,117,114,99,101,32,98,117,115,121,0,73,110,116,101,114,114,117,112,116,101,100,32,115,121,115,116,101,109,32,99,97,108,108,0,82,101,115,111,117,114,99,101,32,116,101,109,112,111,114,97,114,105,108,121,32,117,110,97,118,97,105,108,97,98,108,101,0,73,110,118,97,108,105,100,32,115,101,101,107,0,67,114,111,115,115,45,100,101,118,105,99,101,32,108,105,110,107,0,82,101,97,100,45,111,110,108,121,32,102,105,108,101,32,115,121,115,116,101,109,0,68,105,114,101,99,116,111,114,121,32,110,111,116,32,101,109,112,116,121,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,112,101,101,114,0,79,112,101,114,97,116,105,111,110,32,116,105,109,101,100,32,111,117,116,0,67,111,110,110,101,99,116,105,111,110,32,114,101,102,117,115,101,100,0,72,111,115,116,32,105,115,32,100,111,119,110,0,72,111,115,116,32,105,115,32,117,110,114,101,97,99,104,97,98,108,101,0,65,100,100,114,101,115,115,32,105,110,32,117,115,101,0,66,114,111,107,101,110,32,112,105,112,101,0,73,47,79,32,101,114,114,111,114,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,32,111,114,32,97,100,100,114,101,115,115,0,66,108,111,99,107,32,100,101,118,105,99,101,32,114,101,113,117,105,114,101,100,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,0,78,111,116,32,97,32,100,105,114,101,99,116,111,114,121,0,73,115,32,97,32,100,105,114,101,99,116,111,114,121,0,84,101,120,116,32,102,105,108,101,32,98,117,115,121,0,69,120,101,99,32,102,111,114,109,97,116,32,101,114,114,111,114,0,73,110,118,97,108,105,100,32,97,114,103,117,109,101,110,116,0,65,114,103,117,109,101,110,116,32,108,105,115,116,32,116,111,111,32,108,111,110,103,0,83,121,109,98,111,108,105,99,32,108,105,110,107,32,108,111,111,112,0,70,105,108,101,110,97,109,101,32,116,111,111,32,108,111,110,103,0,84,111,111,32,109,97,110,121,32,111,112,101,110,32,102,105,108,101,115,32,105,110,32,115,121,115,116,101,109,0,78,111,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,115,32,97,118,97,105,108,97,98,108,101,0,66,97,100,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,0,78,111,32,99,104,105,108,100,32,112,114,111,99,101,115,115,0,66,97,100,32,97,100,100,114,101,115,115,0,70,105,108,101,32,116,111,111,32,108,97,114,103,101,0,84,111,111,32,109,97,110,121,32,108,105,110,107,115,0,78,111,32,108,111,99,107,115,32,97,118,97,105,108,97,98,108,101,0,82,101,115,111,117,114,99,101,32,100,101,97,100,108,111,99,107,32,119,111,117,108,100,32,111,99,99,117,114,0,83,116,97,116,101,32,110,111,116,32,114,101,99,111,118,101,114,97,98,108,101,0,80,114,101,118,105,111,117,115,32,111,119,110,101,114,32,100,105,101,100,0,79,112,101,114,97,116,105,111,110,32,99,97,110,99,101,108,101,100,0,70,117,110,99,116,105,111,110,32,110,111,116,32,105,109,112,108,101,109,101,110,116,101,100,0,78,111,32,109,101,115,115,97,103,101,32,111,102,32,100,101,115,105,114,101,100,32,116,121,112,101,0,73,100,101,110,116,105,102,105,101,114,32,114,101,109,111,118,101,100,0,68,101,118,105,99,101,32,110,111,116,32,97,32,115,116,114,101,97,109,0,78,111,32,100,97,116,97,32,97,118,97,105,108,97,98,108,101,0,68,101,118,105,99,101,32,116,105,109,101,111,117,116,0,79,117,116,32,111,102,32,115,116,114,101,97,109,115,32,114,101,115,111,117,114,99,101,115,0,76,105,110,107,32,104,97,115,32,98,101,101,110,32,115,101,118,101,114,101,100,0,80,114,111,116,111,99,111,108,32,101,114,114,111,114,0,66,97,100,32,109,101,115,115,97,103,101,0,70,105,108,101,32,100,101,115,99,114,105,112,116,111,114,32,105,110,32,98,97,100,32,115,116,97,116,101,0,78,111,116,32,97,32,115,111,99,107,101,116,0,68,101,115,116,105,110,97,116,105,111,110,32,97,100,100,114,101,115,115,32,114,101,113,117,105,114,101,100,0,77,101,115,115,97,103,101,32,116,111,111,32,108,97,114,103,101,0,80,114,111,116,111,99,111,108,32,119,114,111,110,103,32,116,121,112,101,32,102,111,114,32,115,111,99,107,101,116,0,80,114,111,116,111,99,111,108,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,80,114,111,116,111,99,111,108,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,83,111,99,107,101,116,32,116,121,112,101,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,78,111,116,32,115,117,112,112,111,114,116,101,100,0,80,114,111,116,111,99,111,108,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,65,100,100,114,101,115,115,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,32,98,121,32,112,114,111,116,111,99,111,108,0,65,100,100,114,101,115,115,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,78,101,116,119,111,114,107,32,105,115,32,100,111,119,110,0,78,101,116,119,111,114,107,32,117,110,114,101,97,99,104,97,98,108,101,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,110,101,116,119,111,114,107,0,67,111,110,110,101,99,116,105,111,110,32,97,98,111,114,116,101,100,0,78,111,32,98,117,102,102,101,114,32,115,112,97,99,101,32,97,118,97,105,108,97,98,108,101,0,83,111,99,107,101,116,32,105,115,32,99,111,110,110,101,99,116,101,100,0,83,111,99,107,101,116,32,110,111,116,32,99,111,110,110,101,99,116,101,100,0,67,97,110,110,111,116,32,115,101,110,100,32,97,102,116,101,114,32,115,111,99,107,101,116,32,115,104,117,116,100,111,119,110,0,79,112,101,114,97,116,105,111,110,32,97,108,114,101,97,100,121,32,105,110,32,112,114,111,103,114,101,115,115,0,79,112,101,114,97,116,105,111,110,32,105,110,32,112,114,111,103,114,101,115,115,0,83,116,97,108,101,32,102,105,108,101,32,104,97,110,100,108,101,0,82,101,109,111,116,101,32,73,47,79,32,101,114,114,111,114,0,81,117,111,116,97,32,101,120,99,101,101,100,101,100,0,78,111,32,109,101,100,105,117,109,32,102,111,117,110,100,0,87,114,111,110,103,32,109,101,100,105,117,109,32,116,121,112,101,0,78,111,32,101,114,114,111,114,32,105,110,102,111,114,109,97,116,105,111,110,0,0],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE);var tempDoublePtr=STATICTOP;STATICTOP+=16;function _atexit(t,e){__ATEXIT__.unshift({func:t,arg:e})}function ___cxa_atexit(){return _atexit.apply(null,arguments)}function _abort(){Module.abort()}function __ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj(){Module.printErr("missing function: _ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj"),abort(-1)}function __decorate(t,e,r,s){var a=arguments.length,n=a<3?e:s===null?s=Object.getOwnPropertyDescriptor(e,r):s,c;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")n=Reflect.decorate(t,e,r,s);else for(var f=t.length-1;f>=0;f--)(c=t[f])&&(n=(a<3?c(n):a>3?c(e,r,n):c(e,r))||n);return a>3&&n&&Object.defineProperty(e,r,n),n}function _defineHidden(t){return function(e,r){Object.defineProperty(e,r,{configurable:!1,enumerable:!1,value:t,writable:!0})}}var _nbind={};function __nbind_free_external(t){_nbind.externalList[t].dereference(t)}function __nbind_reference_external(t){_nbind.externalList[t].reference()}function _llvm_stackrestore(t){var e=_llvm_stacksave,r=e.LLVM_SAVEDSTACKS[t];e.LLVM_SAVEDSTACKS.splice(t,1),Runtime.stackRestore(r)}function __nbind_register_pool(t,e,r,s){_nbind.Pool.pageSize=t,_nbind.Pool.usedPtr=e/4,_nbind.Pool.rootPtr=r,_nbind.Pool.pagePtr=s/4,HEAP32[e/4]=16909060,HEAP8[e]==1&&(_nbind.bigEndian=!0),HEAP32[e/4]=0,_nbind.makeTypeKindTbl=(n={},n[1024]=_nbind.PrimitiveType,n[64]=_nbind.Int64Type,n[2048]=_nbind.BindClass,n[3072]=_nbind.BindClassPtr,n[4096]=_nbind.SharedClassPtr,n[5120]=_nbind.ArrayType,n[6144]=_nbind.ArrayType,n[7168]=_nbind.CStringType,n[9216]=_nbind.CallbackType,n[10240]=_nbind.BindType,n),_nbind.makeTypeNameTbl={Buffer:_nbind.BufferType,External:_nbind.ExternalType,Int64:_nbind.Int64Type,_nbind_new:_nbind.CreateValueType,bool:_nbind.BooleanType,"cbFunction &":_nbind.CallbackType,"const cbFunction &":_nbind.CallbackType,"const std::string &":_nbind.StringType,"std::string":_nbind.StringType},Module.toggleLightGC=_nbind.toggleLightGC,_nbind.callUpcast=Module.dynCall_ii;var a=_nbind.makeType(_nbind.constructType,{flags:2048,id:0,name:""});a.proto=Module,_nbind.BindClass.list.push(a);var n}function _emscripten_set_main_loop_timing(t,e){if(Browser.mainLoop.timingMode=t,Browser.mainLoop.timingValue=e,!Browser.mainLoop.func)return 1;if(t==0)Browser.mainLoop.scheduler=function(){var c=Math.max(0,Browser.mainLoop.tickStartTime+e-_emscripten_get_now())|0;setTimeout(Browser.mainLoop.runner,c)},Browser.mainLoop.method="timeout";else if(t==1)Browser.mainLoop.scheduler=function(){Browser.requestAnimationFrame(Browser.mainLoop.runner)},Browser.mainLoop.method="rAF";else if(t==2){if(!window.setImmediate){let n=function(c){c.source===window&&c.data===s&&(c.stopPropagation(),r.shift()())};var a=n,r=[],s="setimmediate";window.addEventListener("message",n,!0),window.setImmediate=function(f){r.push(f),ENVIRONMENT_IS_WORKER?(Module.setImmediates===void 0&&(Module.setImmediates=[]),Module.setImmediates.push(f),window.postMessage({target:s})):window.postMessage(s,"*")}}Browser.mainLoop.scheduler=function(){window.setImmediate(Browser.mainLoop.runner)},Browser.mainLoop.method="immediate"}return 0}function _emscripten_get_now(){abort()}function _emscripten_set_main_loop(t,e,r,s,a){Module.noExitRuntime=!0,assert(!Browser.mainLoop.func,"emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters."),Browser.mainLoop.func=t,Browser.mainLoop.arg=s;var n;typeof s<"u"?n=function(){Module.dynCall_vi(t,s)}:n=function(){Module.dynCall_v(t)};var c=Browser.mainLoop.currentlyRunningMainloop;if(Browser.mainLoop.runner=function(){if(!ABORT){if(Browser.mainLoop.queue.length>0){var p=Date.now(),h=Browser.mainLoop.queue.shift();if(h.func(h.arg),Browser.mainLoop.remainingBlockers){var E=Browser.mainLoop.remainingBlockers,C=E%1==0?E-1:Math.floor(E);h.counted?Browser.mainLoop.remainingBlockers=C:(C=C+.5,Browser.mainLoop.remainingBlockers=(8*E+C)/9)}if(console.log('main loop blocker "'+h.name+'" took '+(Date.now()-p)+" ms"),Browser.mainLoop.updateStatus(),c1&&Browser.mainLoop.currentFrameNumber%Browser.mainLoop.timingValue!=0){Browser.mainLoop.scheduler();return}else Browser.mainLoop.timingMode==0&&(Browser.mainLoop.tickStartTime=_emscripten_get_now());Browser.mainLoop.method==="timeout"&&Module.ctx&&(Module.printErr("Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!"),Browser.mainLoop.method=""),Browser.mainLoop.runIter(n),!(c0?_emscripten_set_main_loop_timing(0,1e3/e):_emscripten_set_main_loop_timing(1,1),Browser.mainLoop.scheduler()),r)throw"SimulateInfiniteLoop"}var Browser={mainLoop:{scheduler:null,method:"",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:function(){Browser.mainLoop.scheduler=null,Browser.mainLoop.currentlyRunningMainloop++},resume:function(){Browser.mainLoop.currentlyRunningMainloop++;var t=Browser.mainLoop.timingMode,e=Browser.mainLoop.timingValue,r=Browser.mainLoop.func;Browser.mainLoop.func=null,_emscripten_set_main_loop(r,0,!1,Browser.mainLoop.arg,!0),_emscripten_set_main_loop_timing(t,e),Browser.mainLoop.scheduler()},updateStatus:function(){if(Module.setStatus){var t=Module.statusMessage||"Please wait...",e=Browser.mainLoop.remainingBlockers,r=Browser.mainLoop.expectedBlockers;e?e"u"&&(console.log("warning: Browser does not support creating object URLs. Built-in browser image decoding will not be available."),Module.noImageDecoding=!0);var t={};t.canHandle=function(n){return!Module.noImageDecoding&&/\.(jpg|jpeg|png|bmp)$/i.test(n)},t.handle=function(n,c,f,p){var h=null;if(Browser.hasBlobConstructor)try{h=new Blob([n],{type:Browser.getMimetype(c)}),h.size!==n.length&&(h=new Blob([new Uint8Array(n).buffer],{type:Browser.getMimetype(c)}))}catch(P){Runtime.warnOnce("Blob constructor present but fails: "+P+"; falling back to blob builder")}if(!h){var E=new Browser.BlobBuilder;E.append(new Uint8Array(n).buffer),h=E.getBlob()}var C=Browser.URLObject.createObjectURL(h),S=new Image;S.onload=function(){assert(S.complete,"Image "+c+" could not be decoded");var I=document.createElement("canvas");I.width=S.width,I.height=S.height;var R=I.getContext("2d");R.drawImage(S,0,0),Module.preloadedImages[c]=I,Browser.URLObject.revokeObjectURL(C),f&&f(n)},S.onerror=function(I){console.log("Image "+C+" could not be decoded"),p&&p()},S.src=C},Module.preloadPlugins.push(t);var e={};e.canHandle=function(n){return!Module.noAudioDecoding&&n.substr(-4)in{".ogg":1,".wav":1,".mp3":1}},e.handle=function(n,c,f,p){var h=!1;function E(R){h||(h=!0,Module.preloadedAudios[c]=R,f&&f(n))}function C(){h||(h=!0,Module.preloadedAudios[c]=new Audio,p&&p())}if(Browser.hasBlobConstructor){try{var S=new Blob([n],{type:Browser.getMimetype(c)})}catch{return C()}var P=Browser.URLObject.createObjectURL(S),I=new Audio;I.addEventListener("canplaythrough",function(){E(I)},!1),I.onerror=function(N){if(h)return;console.log("warning: browser could not fully decode audio "+c+", trying slower base64 approach");function U(W){for(var ee="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",ie="=",ue="",le=0,me=0,pe=0;pe=6;){var Be=le>>me-6&63;me-=6,ue+=ee[Be]}return me==2?(ue+=ee[(le&3)<<4],ue+=ie+ie):me==4&&(ue+=ee[(le&15)<<2],ue+=ie),ue}I.src="data:audio/x-"+c.substr(-3)+";base64,"+U(n),E(I)},I.src=P,Browser.safeSetTimeout(function(){E(I)},1e4)}else return C()},Module.preloadPlugins.push(e);function r(){Browser.pointerLock=document.pointerLockElement===Module.canvas||document.mozPointerLockElement===Module.canvas||document.webkitPointerLockElement===Module.canvas||document.msPointerLockElement===Module.canvas}var s=Module.canvas;s&&(s.requestPointerLock=s.requestPointerLock||s.mozRequestPointerLock||s.webkitRequestPointerLock||s.msRequestPointerLock||function(){},s.exitPointerLock=document.exitPointerLock||document.mozExitPointerLock||document.webkitExitPointerLock||document.msExitPointerLock||function(){},s.exitPointerLock=s.exitPointerLock.bind(document),document.addEventListener("pointerlockchange",r,!1),document.addEventListener("mozpointerlockchange",r,!1),document.addEventListener("webkitpointerlockchange",r,!1),document.addEventListener("mspointerlockchange",r,!1),Module.elementPointerLock&&s.addEventListener("click",function(a){!Browser.pointerLock&&Module.canvas.requestPointerLock&&(Module.canvas.requestPointerLock(),a.preventDefault())},!1))},createContext:function(t,e,r,s){if(e&&Module.ctx&&t==Module.canvas)return Module.ctx;var a,n;if(e){var c={antialias:!1,alpha:!1};if(s)for(var f in s)c[f]=s[f];n=GL.createContext(t,c),n&&(a=GL.getContext(n).GLctx)}else a=t.getContext("2d");return a?(r&&(e||assert(typeof GLctx>"u","cannot set in module if GLctx is used, but we are a non-GL context that would replace it"),Module.ctx=a,e&&GL.makeContextCurrent(n),Module.useWebGL=e,Browser.moduleContextCreatedCallbacks.forEach(function(p){p()}),Browser.init()),a):null},destroyContext:function(t,e,r){},fullscreenHandlersInstalled:!1,lockPointer:void 0,resizeCanvas:void 0,requestFullscreen:function(t,e,r){Browser.lockPointer=t,Browser.resizeCanvas=e,Browser.vrDevice=r,typeof Browser.lockPointer>"u"&&(Browser.lockPointer=!0),typeof Browser.resizeCanvas>"u"&&(Browser.resizeCanvas=!1),typeof Browser.vrDevice>"u"&&(Browser.vrDevice=null);var s=Module.canvas;function a(){Browser.isFullscreen=!1;var c=s.parentNode;(document.fullscreenElement||document.mozFullScreenElement||document.msFullscreenElement||document.webkitFullscreenElement||document.webkitCurrentFullScreenElement)===c?(s.exitFullscreen=document.exitFullscreen||document.cancelFullScreen||document.mozCancelFullScreen||document.msExitFullscreen||document.webkitCancelFullScreen||function(){},s.exitFullscreen=s.exitFullscreen.bind(document),Browser.lockPointer&&s.requestPointerLock(),Browser.isFullscreen=!0,Browser.resizeCanvas&&Browser.setFullscreenCanvasSize()):(c.parentNode.insertBefore(s,c),c.parentNode.removeChild(c),Browser.resizeCanvas&&Browser.setWindowedCanvasSize()),Module.onFullScreen&&Module.onFullScreen(Browser.isFullscreen),Module.onFullscreen&&Module.onFullscreen(Browser.isFullscreen),Browser.updateCanvasDimensions(s)}Browser.fullscreenHandlersInstalled||(Browser.fullscreenHandlersInstalled=!0,document.addEventListener("fullscreenchange",a,!1),document.addEventListener("mozfullscreenchange",a,!1),document.addEventListener("webkitfullscreenchange",a,!1),document.addEventListener("MSFullscreenChange",a,!1));var n=document.createElement("div");s.parentNode.insertBefore(n,s),n.appendChild(s),n.requestFullscreen=n.requestFullscreen||n.mozRequestFullScreen||n.msRequestFullscreen||(n.webkitRequestFullscreen?function(){n.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}:null)||(n.webkitRequestFullScreen?function(){n.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT)}:null),r?n.requestFullscreen({vrDisplay:r}):n.requestFullscreen()},requestFullScreen:function(t,e,r){return Module.printErr("Browser.requestFullScreen() is deprecated. Please call Browser.requestFullscreen instead."),Browser.requestFullScreen=function(s,a,n){return Browser.requestFullscreen(s,a,n)},Browser.requestFullscreen(t,e,r)},nextRAF:0,fakeRequestAnimationFrame:function(t){var e=Date.now();if(Browser.nextRAF===0)Browser.nextRAF=e+1e3/60;else for(;e+2>=Browser.nextRAF;)Browser.nextRAF+=1e3/60;var r=Math.max(Browser.nextRAF-e,0);setTimeout(t,r)},requestAnimationFrame:function t(e){typeof window>"u"?Browser.fakeRequestAnimationFrame(e):(window.requestAnimationFrame||(window.requestAnimationFrame=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||window.oRequestAnimationFrame||Browser.fakeRequestAnimationFrame),window.requestAnimationFrame(e))},safeCallback:function(t){return function(){if(!ABORT)return t.apply(null,arguments)}},allowAsyncCallbacks:!0,queuedAsyncCallbacks:[],pauseAsyncCallbacks:function(){Browser.allowAsyncCallbacks=!1},resumeAsyncCallbacks:function(){if(Browser.allowAsyncCallbacks=!0,Browser.queuedAsyncCallbacks.length>0){var t=Browser.queuedAsyncCallbacks;Browser.queuedAsyncCallbacks=[],t.forEach(function(e){e()})}},safeRequestAnimationFrame:function(t){return Browser.requestAnimationFrame(function(){ABORT||(Browser.allowAsyncCallbacks?t():Browser.queuedAsyncCallbacks.push(t))})},safeSetTimeout:function(t,e){return Module.noExitRuntime=!0,setTimeout(function(){ABORT||(Browser.allowAsyncCallbacks?t():Browser.queuedAsyncCallbacks.push(t))},e)},safeSetInterval:function(t,e){return Module.noExitRuntime=!0,setInterval(function(){ABORT||Browser.allowAsyncCallbacks&&t()},e)},getMimetype:function(t){return{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",bmp:"image/bmp",ogg:"audio/ogg",wav:"audio/wav",mp3:"audio/mpeg"}[t.substr(t.lastIndexOf(".")+1)]},getUserMedia:function(t){window.getUserMedia||(window.getUserMedia=navigator.getUserMedia||navigator.mozGetUserMedia),window.getUserMedia(t)},getMovementX:function(t){return t.movementX||t.mozMovementX||t.webkitMovementX||0},getMovementY:function(t){return t.movementY||t.mozMovementY||t.webkitMovementY||0},getMouseWheelDelta:function(t){var e=0;switch(t.type){case"DOMMouseScroll":e=t.detail;break;case"mousewheel":e=t.wheelDelta;break;case"wheel":e=t.deltaY;break;default:throw"unrecognized mouse wheel event: "+t.type}return e},mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:function(t){if(Browser.pointerLock)t.type!="mousemove"&&"mozMovementX"in t?Browser.mouseMovementX=Browser.mouseMovementY=0:(Browser.mouseMovementX=Browser.getMovementX(t),Browser.mouseMovementY=Browser.getMovementY(t)),typeof SDL<"u"?(Browser.mouseX=SDL.mouseX+Browser.mouseMovementX,Browser.mouseY=SDL.mouseY+Browser.mouseMovementY):(Browser.mouseX+=Browser.mouseMovementX,Browser.mouseY+=Browser.mouseMovementY);else{var e=Module.canvas.getBoundingClientRect(),r=Module.canvas.width,s=Module.canvas.height,a=typeof window.scrollX<"u"?window.scrollX:window.pageXOffset,n=typeof window.scrollY<"u"?window.scrollY:window.pageYOffset;if(t.type==="touchstart"||t.type==="touchend"||t.type==="touchmove"){var c=t.touch;if(c===void 0)return;var f=c.pageX-(a+e.left),p=c.pageY-(n+e.top);f=f*(r/e.width),p=p*(s/e.height);var h={x:f,y:p};if(t.type==="touchstart")Browser.lastTouches[c.identifier]=h,Browser.touches[c.identifier]=h;else if(t.type==="touchend"||t.type==="touchmove"){var E=Browser.touches[c.identifier];E||(E=h),Browser.lastTouches[c.identifier]=E,Browser.touches[c.identifier]=h}return}var C=t.pageX-(a+e.left),S=t.pageY-(n+e.top);C=C*(r/e.width),S=S*(s/e.height),Browser.mouseMovementX=C-Browser.mouseX,Browser.mouseMovementY=S-Browser.mouseY,Browser.mouseX=C,Browser.mouseY=S}},asyncLoad:function(t,e,r,s){var a=s?"":"al "+t;Module.readAsync(t,function(n){assert(n,'Loading data file "'+t+'" failed (no arrayBuffer).'),e(new Uint8Array(n)),a&&removeRunDependency(a)},function(n){if(r)r();else throw'Loading data file "'+t+'" failed.'}),a&&addRunDependency(a)},resizeListeners:[],updateResizeListeners:function(){var t=Module.canvas;Browser.resizeListeners.forEach(function(e){e(t.width,t.height)})},setCanvasSize:function(t,e,r){var s=Module.canvas;Browser.updateCanvasDimensions(s,t,e),r||Browser.updateResizeListeners()},windowedWidth:0,windowedHeight:0,setFullscreenCanvasSize:function(){if(typeof SDL<"u"){var t=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];t=t|8388608,HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=t}Browser.updateResizeListeners()},setWindowedCanvasSize:function(){if(typeof SDL<"u"){var t=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];t=t&-8388609,HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=t}Browser.updateResizeListeners()},updateCanvasDimensions:function(t,e,r){e&&r?(t.widthNative=e,t.heightNative=r):(e=t.widthNative,r=t.heightNative);var s=e,a=r;if(Module.forcedAspectRatio&&Module.forcedAspectRatio>0&&(s/a>2];return e},getStr:function(){var t=Pointer_stringify(SYSCALLS.get());return t},get64:function(){var t=SYSCALLS.get(),e=SYSCALLS.get();return t>=0?assert(e===0):assert(e===-1),t},getZero:function(){assert(SYSCALLS.get()===0)}};function ___syscall6(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.getStreamFromFD();return FS.close(r),0}catch(s){return(typeof FS>"u"||!(s instanceof FS.ErrnoError))&&abort(s),-s.errno}}function ___syscall54(t,e){SYSCALLS.varargs=e;try{return 0}catch(r){return(typeof FS>"u"||!(r instanceof FS.ErrnoError))&&abort(r),-r.errno}}function _typeModule(t){var e=[[0,1,"X"],[1,1,"const X"],[128,1,"X *"],[256,1,"X &"],[384,1,"X &&"],[512,1,"std::shared_ptr"],[640,1,"std::unique_ptr"],[5120,1,"std::vector"],[6144,2,"std::array"],[9216,-1,"std::function"]];function r(p,h,E,C,S,P){if(h==1){var I=C&896;(I==128||I==256||I==384)&&(p="X const")}var R;return P?R=E.replace("X",p).replace("Y",S):R=p.replace("X",E).replace("Y",S),R.replace(/([*&]) (?=[*&])/g,"$1")}function s(p,h,E,C,S){throw new Error(p+" type "+E.replace("X",h+"?")+(C?" with flag "+C:"")+" in "+S)}function a(p,h,E,C,S,P,I,R){P===void 0&&(P="X"),R===void 0&&(R=1);var N=E(p);if(N)return N;var U=C(p),W=U.placeholderFlag,ee=e[W];I&&ee&&(P=r(I[2],I[0],P,ee[0],"?",!0));var ie;W==0&&(ie="Unbound"),W>=10&&(ie="Corrupt"),R>20&&(ie="Deeply nested"),ie&&s(ie,p,P,W,S||"?");var ue=U.paramList[0],le=a(ue,h,E,C,S,P,ee,R+1),me,pe={flags:ee[0],id:p,name:"",paramList:[le]},Be=[],Ce="?";switch(U.placeholderFlag){case 1:me=le.spec;break;case 2:if((le.flags&15360)==1024&&le.spec.ptrSize==1){pe.flags=7168;break}case 3:case 6:case 5:me=le.spec,le.flags&15360;break;case 8:Ce=""+U.paramList[1],pe.paramList.push(U.paramList[1]);break;case 9:for(var g=0,we=U.paramList[1];g>2]=t),t}function _llvm_stacksave(){var t=_llvm_stacksave;return t.LLVM_SAVEDSTACKS||(t.LLVM_SAVEDSTACKS=[]),t.LLVM_SAVEDSTACKS.push(Runtime.stackSave()),t.LLVM_SAVEDSTACKS.length-1}function ___syscall140(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.getStreamFromFD(),s=SYSCALLS.get(),a=SYSCALLS.get(),n=SYSCALLS.get(),c=SYSCALLS.get(),f=a;return FS.llseek(r,f,c),HEAP32[n>>2]=r.position,r.getdents&&f===0&&c===0&&(r.getdents=null),0}catch(p){return(typeof FS>"u"||!(p instanceof FS.ErrnoError))&&abort(p),-p.errno}}function ___syscall146(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.get(),s=SYSCALLS.get(),a=SYSCALLS.get(),n=0;___syscall146.buffer||(___syscall146.buffers=[null,[],[]],___syscall146.printChar=function(E,C){var S=___syscall146.buffers[E];assert(S),C===0||C===10?((E===1?Module.print:Module.printErr)(UTF8ArrayToString(S,0)),S.length=0):S.push(C)});for(var c=0;c>2],p=HEAP32[s+(c*8+4)>>2],h=0;h"u"||!(E instanceof FS.ErrnoError))&&abort(E),-E.errno}}function __nbind_finish(){for(var t=0,e=_nbind.BindClass.list;tt.pageSize/2||e>t.pageSize-r){var s=_nbind.typeNameTbl.NBind.proto;return s.lalloc(e)}else return HEAPU32[t.usedPtr]=r+e,t.rootPtr+r},t.lreset=function(e,r){var s=HEAPU32[t.pagePtr];if(s){var a=_nbind.typeNameTbl.NBind.proto;a.lreset(e,r)}else HEAPU32[t.usedPtr]=e},t}();_nbind.Pool=Pool;function constructType(t,e){var r=t==10240?_nbind.makeTypeNameTbl[e.name]||_nbind.BindType:_nbind.makeTypeKindTbl[t],s=new r(e);return typeIdTbl[e.id]=s,_nbind.typeNameTbl[e.name]=s,s}_nbind.constructType=constructType;function getType(t){return typeIdTbl[t]}_nbind.getType=getType;function queryType(t){var e=HEAPU8[t],r=_nbind.structureList[e][1];t/=4,r<0&&(++t,r=HEAPU32[t]+1);var s=Array.prototype.slice.call(HEAPU32.subarray(t+1,t+1+r));return e==9&&(s=[s[0],s.slice(1)]),{paramList:s,placeholderFlag:e}}_nbind.queryType=queryType;function getTypes(t,e){return t.map(function(r){return typeof r=="number"?_nbind.getComplexType(r,constructType,getType,queryType,e):_nbind.typeNameTbl[r]})}_nbind.getTypes=getTypes;function readTypeIdList(t,e){return Array.prototype.slice.call(HEAPU32,t/4,t/4+e)}_nbind.readTypeIdList=readTypeIdList;function readAsciiString(t){for(var e=t;HEAPU8[e++];);return String.fromCharCode.apply("",HEAPU8.subarray(t,e-1))}_nbind.readAsciiString=readAsciiString;function readPolicyList(t){var e={};if(t)for(;;){var r=HEAPU32[t/4];if(!r)break;e[readAsciiString(r)]=!0,t+=4}return e}_nbind.readPolicyList=readPolicyList;function getDynCall(t,e){var r={float32_t:"d",float64_t:"d",int64_t:"d",uint64_t:"d",void:"v"},s=t.map(function(n){return r[n.name]||"i"}).join(""),a=Module["dynCall_"+s];if(!a)throw new Error("dynCall_"+s+" not found for "+e+"("+t.map(function(n){return n.name}).join(", ")+")");return a}_nbind.getDynCall=getDynCall;function addMethod(t,e,r,s){var a=t[e];t.hasOwnProperty(e)&&a?((a.arity||a.arity===0)&&(a=_nbind.makeOverloader(a,a.arity),t[e]=a),a.addMethod(r,s)):(r.arity=s,t[e]=r)}_nbind.addMethod=addMethod;function throwError(t){throw new Error(t)}_nbind.throwError=throwError,_nbind.bigEndian=!1,_a=_typeModule(_typeModule),_nbind.Type=_a.Type,_nbind.makeType=_a.makeType,_nbind.getComplexType=_a.getComplexType,_nbind.structureList=_a.structureList;var BindType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.heap=HEAPU32,r.ptrSize=4,r}return e.prototype.needsWireRead=function(r){return!!this.wireRead||!!this.makeWireRead},e.prototype.needsWireWrite=function(r){return!!this.wireWrite||!!this.makeWireWrite},e}(_nbind.Type);_nbind.BindType=BindType;var PrimitiveType=function(t){__extends(e,t);function e(r){var s=t.call(this,r)||this,a=r.flags&32?{32:HEAPF32,64:HEAPF64}:r.flags&8?{8:HEAPU8,16:HEAPU16,32:HEAPU32}:{8:HEAP8,16:HEAP16,32:HEAP32};return s.heap=a[r.ptrSize*8],s.ptrSize=r.ptrSize,s}return e.prototype.needsWireWrite=function(r){return!!r&&!!r.Strict},e.prototype.makeWireWrite=function(r,s){return s&&s.Strict&&function(a){if(typeof a=="number")return a;throw new Error("Type mismatch")}},e}(BindType);_nbind.PrimitiveType=PrimitiveType;function pushCString(t,e){if(t==null){if(e&&e.Nullable)return 0;throw new Error("Type mismatch")}if(e&&e.Strict){if(typeof t!="string")throw new Error("Type mismatch")}else t=t.toString();var r=Module.lengthBytesUTF8(t)+1,s=_nbind.Pool.lalloc(r);return Module.stringToUTF8Array(t,HEAPU8,s,r),s}_nbind.pushCString=pushCString;function popCString(t){return t===0?null:Module.Pointer_stringify(t)}_nbind.popCString=popCString;var CStringType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireRead=popCString,r.wireWrite=pushCString,r.readResources=[_nbind.resources.pool],r.writeResources=[_nbind.resources.pool],r}return e.prototype.makeWireWrite=function(r,s){return function(a){return pushCString(a,s)}},e}(BindType);_nbind.CStringType=CStringType;var BooleanType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireRead=function(s){return!!s},r}return e.prototype.needsWireWrite=function(r){return!!r&&!!r.Strict},e.prototype.makeWireRead=function(r){return"!!("+r+")"},e.prototype.makeWireWrite=function(r,s){return s&&s.Strict&&function(a){if(typeof a=="boolean")return a;throw new Error("Type mismatch")}||r},e}(BindType);_nbind.BooleanType=BooleanType;var Wrapper=function(){function t(){}return t.prototype.persist=function(){this.__nbindState|=1},t}();_nbind.Wrapper=Wrapper;function makeBound(t,e){var r=function(s){__extends(a,s);function a(n,c,f,p){var h=s.call(this)||this;if(!(h instanceof a))return new(Function.prototype.bind.apply(a,Array.prototype.concat.apply([null],arguments)));var E=c,C=f,S=p;if(n!==_nbind.ptrMarker){var P=h.__nbindConstructor.apply(h,arguments);E=4608,S=HEAPU32[P/4],C=HEAPU32[P/4+1]}var I={configurable:!0,enumerable:!1,value:null,writable:!1},R={__nbindFlags:E,__nbindPtr:C};S&&(R.__nbindShared=S,_nbind.mark(h));for(var N=0,U=Object.keys(R);N>=1;var r=_nbind.valueList[t];return _nbind.valueList[t]=firstFreeValue,firstFreeValue=t,r}else{if(e)return _nbind.popShared(t,e);throw new Error("Invalid value slot "+t)}}_nbind.popValue=popValue;var valueBase=18446744073709552e3;function push64(t){return typeof t=="number"?t:pushValue(t)*4096+valueBase}function pop64(t){return t=3?c=Buffer.from(n):c=new Buffer(n),c.copy(s)}else getBuffer(s).set(n)}}_nbind.commitBuffer=commitBuffer;var dirtyList=[],gcTimer=0;function sweep(){for(var t=0,e=dirtyList;t>2]=DYNAMIC_BASE,staticSealed=!0;function invoke_viiiii(t,e,r,s,a,n){try{Module.dynCall_viiiii(t,e,r,s,a,n)}catch(c){if(typeof c!="number"&&c!=="longjmp")throw c;Module.setThrew(1,0)}}function invoke_vif(t,e,r){try{Module.dynCall_vif(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_vid(t,e,r){try{Module.dynCall_vid(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_fiff(t,e,r,s){try{return Module.dynCall_fiff(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_vi(t,e){try{Module.dynCall_vi(t,e)}catch(r){if(typeof r!="number"&&r!=="longjmp")throw r;Module.setThrew(1,0)}}function invoke_vii(t,e,r){try{Module.dynCall_vii(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_ii(t,e){try{return Module.dynCall_ii(t,e)}catch(r){if(typeof r!="number"&&r!=="longjmp")throw r;Module.setThrew(1,0)}}function invoke_viddi(t,e,r,s,a){try{Module.dynCall_viddi(t,e,r,s,a)}catch(n){if(typeof n!="number"&&n!=="longjmp")throw n;Module.setThrew(1,0)}}function invoke_vidd(t,e,r,s){try{Module.dynCall_vidd(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_iiii(t,e,r,s){try{return Module.dynCall_iiii(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_diii(t,e,r,s){try{return Module.dynCall_diii(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_di(t,e){try{return Module.dynCall_di(t,e)}catch(r){if(typeof r!="number"&&r!=="longjmp")throw r;Module.setThrew(1,0)}}function invoke_iid(t,e,r){try{return Module.dynCall_iid(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_iii(t,e,r){try{return Module.dynCall_iii(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_viiddi(t,e,r,s,a,n){try{Module.dynCall_viiddi(t,e,r,s,a,n)}catch(c){if(typeof c!="number"&&c!=="longjmp")throw c;Module.setThrew(1,0)}}function invoke_viiiiii(t,e,r,s,a,n,c){try{Module.dynCall_viiiiii(t,e,r,s,a,n,c)}catch(f){if(typeof f!="number"&&f!=="longjmp")throw f;Module.setThrew(1,0)}}function invoke_dii(t,e,r){try{return Module.dynCall_dii(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_i(t){try{return Module.dynCall_i(t)}catch(e){if(typeof e!="number"&&e!=="longjmp")throw e;Module.setThrew(1,0)}}function invoke_iiiiii(t,e,r,s,a,n){try{return Module.dynCall_iiiiii(t,e,r,s,a,n)}catch(c){if(typeof c!="number"&&c!=="longjmp")throw c;Module.setThrew(1,0)}}function invoke_viiid(t,e,r,s,a){try{Module.dynCall_viiid(t,e,r,s,a)}catch(n){if(typeof n!="number"&&n!=="longjmp")throw n;Module.setThrew(1,0)}}function invoke_viififi(t,e,r,s,a,n,c){try{Module.dynCall_viififi(t,e,r,s,a,n,c)}catch(f){if(typeof f!="number"&&f!=="longjmp")throw f;Module.setThrew(1,0)}}function invoke_viii(t,e,r,s){try{Module.dynCall_viii(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_v(t){try{Module.dynCall_v(t)}catch(e){if(typeof e!="number"&&e!=="longjmp")throw e;Module.setThrew(1,0)}}function invoke_viid(t,e,r,s){try{Module.dynCall_viid(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_idd(t,e,r){try{return Module.dynCall_idd(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_viiii(t,e,r,s,a){try{Module.dynCall_viiii(t,e,r,s,a)}catch(n){if(typeof n!="number"&&n!=="longjmp")throw n;Module.setThrew(1,0)}}Module.asmGlobalArg={Math,Int8Array,Int16Array,Int32Array,Uint8Array,Uint16Array,Uint32Array,Float32Array,Float64Array,NaN:NaN,Infinity:1/0},Module.asmLibraryArg={abort,assert,enlargeMemory,getTotalMemory,abortOnCannotGrowMemory,invoke_viiiii,invoke_vif,invoke_vid,invoke_fiff,invoke_vi,invoke_vii,invoke_ii,invoke_viddi,invoke_vidd,invoke_iiii,invoke_diii,invoke_di,invoke_iid,invoke_iii,invoke_viiddi,invoke_viiiiii,invoke_dii,invoke_i,invoke_iiiiii,invoke_viiid,invoke_viififi,invoke_viii,invoke_v,invoke_viid,invoke_idd,invoke_viiii,_emscripten_asm_const_iiiii,_emscripten_asm_const_iiidddddd,_emscripten_asm_const_iiiid,__nbind_reference_external,_emscripten_asm_const_iiiiiiii,_removeAccessorPrefix,_typeModule,__nbind_register_pool,__decorate,_llvm_stackrestore,___cxa_atexit,__extends,__nbind_get_value_object,__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj,_emscripten_set_main_loop_timing,__nbind_register_primitive,__nbind_register_type,_emscripten_memcpy_big,__nbind_register_function,___setErrNo,__nbind_register_class,__nbind_finish,_abort,_nbind_value,_llvm_stacksave,___syscall54,_defineHidden,_emscripten_set_main_loop,_emscripten_get_now,__nbind_register_callback_signature,_emscripten_asm_const_iiiiii,__nbind_free_external,_emscripten_asm_const_iiii,_emscripten_asm_const_iiididi,___syscall6,_atexit,___syscall140,___syscall146,DYNAMICTOP_PTR,tempDoublePtr,ABORT,STACKTOP,STACK_MAX,cttz_i8,___dso_handle};var asm=function(t,e,r){var s=new t.Int8Array(r),a=new t.Int16Array(r),n=new t.Int32Array(r),c=new t.Uint8Array(r),f=new t.Uint16Array(r),p=new t.Uint32Array(r),h=new t.Float32Array(r),E=new t.Float64Array(r),C=e.DYNAMICTOP_PTR|0,S=e.tempDoublePtr|0,P=e.ABORT|0,I=e.STACKTOP|0,R=e.STACK_MAX|0,N=e.cttz_i8|0,U=e.___dso_handle|0,W=0,ee=0,ie=0,ue=0,le=t.NaN,me=t.Infinity,pe=0,Be=0,Ce=0,g=0,we=0,ye=0,Ae=t.Math.floor,se=t.Math.abs,Z=t.Math.sqrt,De=t.Math.pow,Re=t.Math.cos,mt=t.Math.sin,j=t.Math.tan,rt=t.Math.acos,Fe=t.Math.asin,Ne=t.Math.atan,Pe=t.Math.atan2,Ve=t.Math.exp,ke=t.Math.log,it=t.Math.ceil,Ue=t.Math.imul,x=t.Math.min,w=t.Math.max,b=t.Math.clz32,y=t.Math.fround,F=e.abort,z=e.assert,X=e.enlargeMemory,$=e.getTotalMemory,oe=e.abortOnCannotGrowMemory,xe=e.invoke_viiiii,Te=e.invoke_vif,lt=e.invoke_vid,Ct=e.invoke_fiff,qt=e.invoke_vi,ir=e.invoke_vii,Pt=e.invoke_ii,gn=e.invoke_viddi,Pr=e.invoke_vidd,Ir=e.invoke_iiii,Or=e.invoke_diii,on=e.invoke_di,ai=e.invoke_iid,Io=e.invoke_iii,rs=e.invoke_viiddi,$s=e.invoke_viiiiii,Co=e.invoke_dii,ji=e.invoke_i,eo=e.invoke_iiiiii,wo=e.invoke_viiid,QA=e.invoke_viififi,Af=e.invoke_viii,dh=e.invoke_v,mh=e.invoke_viid,to=e.invoke_idd,jn=e.invoke_viiii,Ts=e._emscripten_asm_const_iiiii,ro=e._emscripten_asm_const_iiidddddd,ou=e._emscripten_asm_const_iiiid,au=e.__nbind_reference_external,lu=e._emscripten_asm_const_iiiiiiii,TA=e._removeAccessorPrefix,RA=e._typeModule,oa=e.__nbind_register_pool,aa=e.__decorate,FA=e._llvm_stackrestore,gr=e.___cxa_atexit,Bo=e.__extends,Me=e.__nbind_get_value_object,cu=e.__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj,Cr=e._emscripten_set_main_loop_timing,pf=e.__nbind_register_primitive,NA=e.__nbind_register_type,OA=e._emscripten_memcpy_big,uu=e.__nbind_register_function,fu=e.___setErrNo,oc=e.__nbind_register_class,ve=e.__nbind_finish,Nt=e._abort,ac=e._nbind_value,Oi=e._llvm_stacksave,no=e.___syscall54,Rt=e._defineHidden,xn=e._emscripten_set_main_loop,la=e._emscripten_get_now,Gi=e.__nbind_register_callback_signature,Li=e._emscripten_asm_const_iiiiii,Na=e.__nbind_free_external,dn=e._emscripten_asm_const_iiii,Kn=e._emscripten_asm_const_iiididi,Au=e.___syscall6,yh=e._atexit,Oa=e.___syscall140,La=e.___syscall146,Ma=y(0);let $e=y(0);function Ua(o){o=o|0;var l=0;return l=I,I=I+o|0,I=I+15&-16,l|0}function hf(){return I|0}function lc(o){o=o|0,I=o}function wn(o,l){o=o|0,l=l|0,I=o,R=l}function ca(o,l){o=o|0,l=l|0,W||(W=o,ee=l)}function LA(o){o=o|0,ye=o}function MA(){return ye|0}function ua(){var o=0,l=0;Qr(8104,8,400)|0,Qr(8504,408,540)|0,o=9044,l=o+44|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));s[9088]=0,s[9089]=1,n[2273]=0,n[2274]=948,n[2275]=948,gr(17,8104,U|0)|0}function Bl(o){o=o|0,dt(o+948|0)}function Mt(o){return o=y(o),((fP(o)|0)&2147483647)>>>0>2139095040|0}function kn(o,l,u){o=o|0,l=l|0,u=u|0;e:do if(n[o+(l<<3)+4>>2]|0)o=o+(l<<3)|0;else{if((l|2|0)==3&&n[o+60>>2]|0){o=o+56|0;break}switch(l|0){case 0:case 2:case 4:case 5:{if(n[o+52>>2]|0){o=o+48|0;break e}break}default:}if(n[o+68>>2]|0){o=o+64|0;break}else{o=(l|1|0)==5?948:u;break}}while(!1);return o|0}function fa(o){o=o|0;var l=0;return l=_P(1e3)|0,Ha(o,(l|0)!=0,2456),n[2276]=(n[2276]|0)+1,Qr(l|0,8104,1e3)|0,s[o+2>>0]|0&&(n[l+4>>2]=2,n[l+12>>2]=4),n[l+976>>2]=o,l|0}function Ha(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;d=I,I=I+16|0,A=d,l||(n[A>>2]=u,Wg(o,5,3197,A)),I=d}function ns(){return fa(956)|0}function cc(o){o=o|0;var l=0;return l=Kt(1e3)|0,pu(l,o),Ha(n[o+976>>2]|0,1,2456),n[2276]=(n[2276]|0)+1,n[l+944>>2]=0,l|0}function pu(o,l){o=o|0,l=l|0;var u=0;Qr(o|0,l|0,948)|0,Dy(o+948|0,l+948|0),u=o+960|0,o=l+960|0,l=u+40|0;do n[u>>2]=n[o>>2],u=u+4|0,o=o+4|0;while((u|0)<(l|0))}function uc(o){o=o|0;var l=0,u=0,A=0,d=0;if(l=o+944|0,u=n[l>>2]|0,u|0&&(ja(u+948|0,o)|0,n[l>>2]=0),u=Mi(o)|0,u|0){l=0;do n[(Is(o,l)|0)+944>>2]=0,l=l+1|0;while((l|0)!=(u|0))}u=o+948|0,A=n[u>>2]|0,d=o+952|0,l=n[d>>2]|0,(l|0)!=(A|0)&&(n[d>>2]=l+(~((l+-4-A|0)>>>2)<<2)),vl(u),HP(o),n[2276]=(n[2276]|0)+-1}function ja(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0;A=n[o>>2]|0,k=o+4|0,u=n[k>>2]|0,m=u;e:do if((A|0)==(u|0))d=A,B=4;else for(o=A;;){if((n[o>>2]|0)==(l|0)){d=o,B=4;break e}if(o=o+4|0,(o|0)==(u|0)){o=0;break}}while(!1);return(B|0)==4&&((d|0)!=(u|0)?(A=d+4|0,o=m-A|0,l=o>>2,l&&(Q2(d|0,A|0,o|0)|0,u=n[k>>2]|0),o=d+(l<<2)|0,(u|0)==(o|0)||(n[k>>2]=u+(~((u+-4-o|0)>>>2)<<2)),o=1):o=0),o|0}function Mi(o){return o=o|0,(n[o+952>>2]|0)-(n[o+948>>2]|0)>>2|0}function Is(o,l){o=o|0,l=l|0;var u=0;return u=n[o+948>>2]|0,(n[o+952>>2]|0)-u>>2>>>0>l>>>0?o=n[u+(l<<2)>>2]|0:o=0,o|0}function vl(o){o=o|0;var l=0,u=0,A=0,d=0;A=I,I=I+32|0,l=A,d=n[o>>2]|0,u=(n[o+4>>2]|0)-d|0,((n[o+8>>2]|0)-d|0)>>>0>u>>>0&&(d=u>>2,ky(l,d,d,o+8|0),AP(o,l),Qy(l)),I=A}function gf(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0;M=Mi(o)|0;do if(M|0){if((n[(Is(o,0)|0)+944>>2]|0)==(o|0)){if(!(ja(o+948|0,l)|0))break;Qr(l+400|0,8504,540)|0,n[l+944>>2]=0,Oe(o);break}B=n[(n[o+976>>2]|0)+12>>2]|0,k=o+948|0,T=(B|0)==0,u=0,m=0;do A=n[(n[k>>2]|0)+(m<<2)>>2]|0,(A|0)==(l|0)?Oe(o):(d=cc(A)|0,n[(n[k>>2]|0)+(u<<2)>>2]=d,n[d+944>>2]=o,T||dU[B&15](A,d,o,u),u=u+1|0),m=m+1|0;while((m|0)!=(M|0));if(u>>>0>>0){T=o+948|0,k=o+952|0,B=u,u=n[k>>2]|0;do m=(n[T>>2]|0)+(B<<2)|0,A=m+4|0,d=u-A|0,l=d>>2,l&&(Q2(m|0,A|0,d|0)|0,u=n[k>>2]|0),d=u,A=m+(l<<2)|0,(d|0)!=(A|0)&&(u=d+(~((d+-4-A|0)>>>2)<<2)|0,n[k>>2]=u),B=B+1|0;while((B|0)!=(M|0))}}while(!1)}function fc(o){o=o|0;var l=0,u=0,A=0,d=0;wi(o,(Mi(o)|0)==0,2491),wi(o,(n[o+944>>2]|0)==0,2545),l=o+948|0,u=n[l>>2]|0,A=o+952|0,d=n[A>>2]|0,(d|0)!=(u|0)&&(n[A>>2]=d+(~((d+-4-u|0)>>>2)<<2)),vl(l),l=o+976|0,u=n[l>>2]|0,Qr(o|0,8104,1e3)|0,s[u+2>>0]|0&&(n[o+4>>2]=2,n[o+12>>2]=4),n[l>>2]=u}function wi(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;d=I,I=I+16|0,A=d,l||(n[A>>2]=u,xo(o,5,3197,A)),I=d}function Qn(){return n[2276]|0}function Ac(){var o=0;return o=_P(20)|0,Ke((o|0)!=0,2592),n[2277]=(n[2277]|0)+1,n[o>>2]=n[239],n[o+4>>2]=n[240],n[o+8>>2]=n[241],n[o+12>>2]=n[242],n[o+16>>2]=n[243],o|0}function Ke(o,l){o=o|0,l=l|0;var u=0,A=0;A=I,I=I+16|0,u=A,o||(n[u>>2]=l,xo(0,5,3197,u)),I=A}function st(o){o=o|0,HP(o),n[2277]=(n[2277]|0)+-1}function St(o,l){o=o|0,l=l|0;var u=0;l?(wi(o,(Mi(o)|0)==0,2629),u=1):(u=0,l=0),n[o+964>>2]=l,n[o+988>>2]=u}function lr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,m=A+8|0,d=A+4|0,B=A,n[d>>2]=l,wi(o,(n[l+944>>2]|0)==0,2709),wi(o,(n[o+964>>2]|0)==0,2763),te(o),l=o+948|0,n[B>>2]=(n[l>>2]|0)+(u<<2),n[m>>2]=n[B>>2],Ee(l,m,d)|0,n[(n[d>>2]|0)+944>>2]=o,Oe(o),I=A}function te(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;if(u=Mi(o)|0,u|0&&(n[(Is(o,0)|0)+944>>2]|0)!=(o|0)){A=n[(n[o+976>>2]|0)+12>>2]|0,d=o+948|0,m=(A|0)==0,l=0;do B=n[(n[d>>2]|0)+(l<<2)>>2]|0,k=cc(B)|0,n[(n[d>>2]|0)+(l<<2)>>2]=k,n[k+944>>2]=o,m||dU[A&15](B,k,o,l),l=l+1|0;while((l|0)!=(u|0))}}function Ee(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0;tt=I,I=I+64|0,q=tt+52|0,k=tt+48|0,ae=tt+28|0,Ye=tt+24|0,Le=tt+20|0,Qe=tt,A=n[o>>2]|0,m=A,l=A+((n[l>>2]|0)-m>>2<<2)|0,A=o+4|0,d=n[A>>2]|0,B=o+8|0;do if(d>>>0<(n[B>>2]|0)>>>0){if((l|0)==(d|0)){n[l>>2]=n[u>>2],n[A>>2]=(n[A>>2]|0)+4;break}pP(o,l,d,l+4|0),l>>>0<=u>>>0&&(u=(n[A>>2]|0)>>>0>u>>>0?u+4|0:u),n[l>>2]=n[u>>2]}else{A=(d-m>>2)+1|0,d=O(o)|0,d>>>0>>0&&an(o),L=n[o>>2]|0,M=(n[B>>2]|0)-L|0,m=M>>1,ky(Qe,M>>2>>>0>>1>>>0?m>>>0>>0?A:m:d,l-L>>2,o+8|0),L=Qe+8|0,A=n[L>>2]|0,m=Qe+12|0,M=n[m>>2]|0,B=M,T=A;do if((A|0)==(M|0)){if(M=Qe+4|0,A=n[M>>2]|0,Ze=n[Qe>>2]|0,d=Ze,A>>>0<=Ze>>>0){A=B-d>>1,A=A|0?A:1,ky(ae,A,A>>>2,n[Qe+16>>2]|0),n[Ye>>2]=n[M>>2],n[Le>>2]=n[L>>2],n[k>>2]=n[Ye>>2],n[q>>2]=n[Le>>2],o2(ae,k,q),A=n[Qe>>2]|0,n[Qe>>2]=n[ae>>2],n[ae>>2]=A,A=ae+4|0,Ze=n[M>>2]|0,n[M>>2]=n[A>>2],n[A>>2]=Ze,A=ae+8|0,Ze=n[L>>2]|0,n[L>>2]=n[A>>2],n[A>>2]=Ze,A=ae+12|0,Ze=n[m>>2]|0,n[m>>2]=n[A>>2],n[A>>2]=Ze,Qy(ae),A=n[L>>2]|0;break}m=A,B=((m-d>>2)+1|0)/-2|0,k=A+(B<<2)|0,d=T-m|0,m=d>>2,m&&(Q2(k|0,A|0,d|0)|0,A=n[M>>2]|0),Ze=k+(m<<2)|0,n[L>>2]=Ze,n[M>>2]=A+(B<<2),A=Ze}while(!1);n[A>>2]=n[u>>2],n[L>>2]=(n[L>>2]|0)+4,l=hP(o,Qe,l)|0,Qy(Qe)}while(!1);return I=tt,l|0}function Oe(o){o=o|0;var l=0;do{if(l=o+984|0,s[l>>0]|0)break;s[l>>0]=1,h[o+504>>2]=y(le),o=n[o+944>>2]|0}while(o|0)}function dt(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),It(u))}function Et(o){return o=o|0,n[o+944>>2]|0}function bt(o){o=o|0,wi(o,(n[o+964>>2]|0)!=0,2832),Oe(o)}function tr(o){return o=o|0,(s[o+984>>0]|0)!=0|0}function An(o,l){o=o|0,l=l|0,l6e(o,l,400)|0&&(Qr(o|0,l|0,400)|0,Oe(o))}function li(o){o=o|0;var l=$e;return l=y(h[o+44>>2]),o=Mt(l)|0,y(o?y(0):l)}function qi(o){o=o|0;var l=$e;return l=y(h[o+48>>2]),Mt(l)|0&&(l=s[(n[o+976>>2]|0)+2>>0]|0?y(1):y(0)),y(l)}function Tn(o,l){o=o|0,l=l|0,n[o+980>>2]=l}function Ga(o){return o=o|0,n[o+980>>2]|0}function my(o,l){o=o|0,l=l|0;var u=0;u=o+4|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Z1(o){return o=o|0,n[o+4>>2]|0}function vo(o,l){o=o|0,l=l|0;var u=0;u=o+8|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function yy(o){return o=o|0,n[o+8>>2]|0}function Eh(o,l){o=o|0,l=l|0;var u=0;u=o+12|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function $1(o){return o=o|0,n[o+12>>2]|0}function So(o,l){o=o|0,l=l|0;var u=0;u=o+16|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Ih(o){return o=o|0,n[o+16>>2]|0}function Ch(o,l){o=o|0,l=l|0;var u=0;u=o+20|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function hu(o){return o=o|0,n[o+20>>2]|0}function wh(o,l){o=o|0,l=l|0;var u=0;u=o+24|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Fg(o){return o=o|0,n[o+24>>2]|0}function Ng(o,l){o=o|0,l=l|0;var u=0;u=o+28|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Og(o){return o=o|0,n[o+28>>2]|0}function Ey(o,l){o=o|0,l=l|0;var u=0;u=o+32|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function df(o){return o=o|0,n[o+32>>2]|0}function Do(o,l){o=o|0,l=l|0;var u=0;u=o+36|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Sl(o){return o=o|0,n[o+36>>2]|0}function Bh(o,l){o=o|0,l=y(l);var u=0;u=o+40|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function Lg(o,l){o=o|0,l=y(l);var u=0;u=o+44|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function Dl(o,l){o=o|0,l=y(l);var u=0;u=o+48|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function bl(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+52|0,d=o+56|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Iy(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+52|0,u=o+56|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function UA(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+52|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function Cy(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+132+(l<<3)|0,l=o+132+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function wy(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+132+(l<<3)|0,l=o+132+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function _A(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+132+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function HA(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+60+(l<<3)|0,l=o+60+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function Y(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+60+(l<<3)|0,l=o+60+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function xt(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+60+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function jA(o,l){o=o|0,l=l|0;var u=0;u=o+60+(l<<3)+4|0,(n[u>>2]|0)!=3&&(h[o+60+(l<<3)>>2]=y(le),n[u>>2]=3,Oe(o))}function bo(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+204+(l<<3)|0,l=o+204+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function mf(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+204+(l<<3)|0,l=o+204+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function yt(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+204+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function gu(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+276+(l<<3)|0,l=o+276+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function By(o,l){return o=o|0,l=l|0,y(h[o+276+(l<<3)>>2])}function Mg(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+348|0,d=o+352|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function e2(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+348|0,u=o+352|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function vh(o){o=o|0;var l=0;l=o+352|0,(n[l>>2]|0)!=3&&(h[o+348>>2]=y(le),n[l>>2]=3,Oe(o))}function ur(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+348|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function zi(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+356|0,d=o+360|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function yf(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+356|0,u=o+360|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function qa(o){o=o|0;var l=0;l=o+360|0,(n[l>>2]|0)!=3&&(h[o+356>>2]=y(le),n[l>>2]=3,Oe(o))}function Ug(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+356|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function du(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+364|0,d=o+368|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Ef(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+364|0,d=o+368|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function wt(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+364|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function di(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+372|0,d=o+376|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function GA(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+372|0,d=o+376|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Wa(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+372|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function Aa(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+380|0,d=o+384|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Ya(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+380|0,d=o+384|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function _g(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+380|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function Sh(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+388|0,d=o+392|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Hg(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+388|0,d=o+392|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function vy(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+388|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function qA(o,l){o=o|0,l=y(l);var u=0;u=o+396|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function jg(o){return o=o|0,y(h[o+396>>2])}function mu(o){return o=o|0,y(h[o+400>>2])}function yu(o){return o=o|0,y(h[o+404>>2])}function If(o){return o=o|0,y(h[o+408>>2])}function Rs(o){return o=o|0,y(h[o+412>>2])}function Eu(o){return o=o|0,y(h[o+416>>2])}function Gn(o){return o=o|0,y(h[o+420>>2])}function is(o,l){switch(o=o|0,l=l|0,wi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+424+(l<<2)>>2])}function Pi(o,l){switch(o=o|0,l=l|0,wi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+448+(l<<2)>>2])}function WA(o,l){switch(o=o|0,l=l|0,wi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+472+(l<<2)>>2])}function Cf(o,l){o=o|0,l=l|0;var u=0,A=$e;return u=n[o+4>>2]|0,(u|0)==(n[l+4>>2]|0)?u?(A=y(h[o>>2]),o=y(se(y(A-y(h[l>>2]))))>2]=0,n[A+4>>2]=0,n[A+8>>2]=0,cu(A|0,o|0,l|0,0),xo(o,3,(s[A+11>>0]|0)<0?n[A>>2]|0:A,u),Q6e(A),I=u}function ss(o,l,u,A){o=y(o),l=y(l),u=u|0,A=A|0;var d=$e;o=y(o*l),d=y(uU(o,y(1)));do if(mn(d,y(0))|0)o=y(o-d);else{if(o=y(o-d),mn(d,y(1))|0){o=y(o+y(1));break}if(u){o=y(o+y(1));break}A||(d>y(.5)?d=y(1):(A=mn(d,y(.5))|0,d=y(A?1:0)),o=y(o+d))}while(!1);return y(o/l)}function Pl(o,l,u,A,d,m,B,k,T,M,L,q,ae){o=o|0,l=y(l),u=u|0,A=y(A),d=d|0,m=y(m),B=B|0,k=y(k),T=y(T),M=y(M),L=y(L),q=y(q),ae=ae|0;var Ye=0,Le=$e,Qe=$e,tt=$e,Ze=$e,ct=$e,He=$e;return T>2]),Le!=y(0))?(tt=y(ss(l,Le,0,0)),Ze=y(ss(A,Le,0,0)),Qe=y(ss(m,Le,0,0)),Le=y(ss(k,Le,0,0))):(Qe=m,tt=l,Le=k,Ze=A),(d|0)==(o|0)?Ye=mn(Qe,tt)|0:Ye=0,(B|0)==(u|0)?ae=mn(Le,Ze)|0:ae=0,!Ye&&(ct=y(l-L),!(Po(o,ct,T)|0))&&!(wf(o,ct,d,T)|0)?Ye=Bf(o,ct,d,m,T)|0:Ye=1,!ae&&(He=y(A-q),!(Po(u,He,M)|0))&&!(wf(u,He,B,M)|0)?ae=Bf(u,He,B,k,M)|0:ae=1,ae=Ye&ae),ae|0}function Po(o,l,u){return o=o|0,l=y(l),u=y(u),(o|0)==1?o=mn(l,u)|0:o=0,o|0}function wf(o,l,u,A){return o=o|0,l=y(l),u=u|0,A=y(A),(o|0)==2&(u|0)==0?l>=A?o=1:o=mn(l,A)|0:o=0,o|0}function Bf(o,l,u,A,d){return o=o|0,l=y(l),u=u|0,A=y(A),d=y(d),(o|0)==2&(u|0)==2&A>l?d<=l?o=1:o=mn(l,d)|0:o=0,o|0}function xl(o,l,u,A,d,m,B,k,T,M,L){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=m|0,B=y(B),k=y(k),T=T|0,M=M|0,L=L|0;var q=0,ae=0,Ye=0,Le=0,Qe=$e,tt=$e,Ze=0,ct=0,He=0,We=0,Lt=0,Gr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=$e,To=$e,Ro=$e,Fo=0,Za=0;cr=I,I=I+160|0,$t=cr+152|0,fr=cr+120|0,Gr=cr+104|0,He=cr+72|0,Le=cr+56|0,Lt=cr+8|0,ct=cr,We=(n[2279]|0)+1|0,n[2279]=We,Tr=o+984|0,s[Tr>>0]|0&&(n[o+512>>2]|0)!=(n[2278]|0)?Ze=4:(n[o+516>>2]|0)==(A|0)?Hr=0:Ze=4,(Ze|0)==4&&(n[o+520>>2]=0,n[o+924>>2]=-1,n[o+928>>2]=-1,h[o+932>>2]=y(-1),h[o+936>>2]=y(-1),Hr=1);e:do if(n[o+964>>2]|0)if(Qe=y(yn(o,2,B)),tt=y(yn(o,0,B)),q=o+916|0,Ro=y(h[q>>2]),To=y(h[o+920>>2]),Hn=y(h[o+932>>2]),Pl(d,l,m,u,n[o+924>>2]|0,Ro,n[o+928>>2]|0,To,Hn,y(h[o+936>>2]),Qe,tt,L)|0)Ze=22;else if(Ye=n[o+520>>2]|0,!Ye)Ze=21;else for(ae=0;;){if(q=o+524+(ae*24|0)|0,Hn=y(h[q>>2]),To=y(h[o+524+(ae*24|0)+4>>2]),Ro=y(h[o+524+(ae*24|0)+16>>2]),Pl(d,l,m,u,n[o+524+(ae*24|0)+8>>2]|0,Hn,n[o+524+(ae*24|0)+12>>2]|0,To,Ro,y(h[o+524+(ae*24|0)+20>>2]),Qe,tt,L)|0){Ze=22;break e}if(ae=ae+1|0,ae>>>0>=Ye>>>0){Ze=21;break}}else{if(T){if(q=o+916|0,!(mn(y(h[q>>2]),l)|0)){Ze=21;break}if(!(mn(y(h[o+920>>2]),u)|0)){Ze=21;break}if((n[o+924>>2]|0)!=(d|0)){Ze=21;break}q=(n[o+928>>2]|0)==(m|0)?q:0,Ze=22;break}if(Ye=n[o+520>>2]|0,!Ye)Ze=21;else for(ae=0;;){if(q=o+524+(ae*24|0)|0,mn(y(h[q>>2]),l)|0&&mn(y(h[o+524+(ae*24|0)+4>>2]),u)|0&&(n[o+524+(ae*24|0)+8>>2]|0)==(d|0)&&(n[o+524+(ae*24|0)+12>>2]|0)==(m|0)){Ze=22;break e}if(ae=ae+1|0,ae>>>0>=Ye>>>0){Ze=21;break}}}while(!1);do if((Ze|0)==21)s[11697]|0?(q=0,Ze=28):(q=0,Ze=31);else if((Ze|0)==22){if(ae=(s[11697]|0)!=0,!((q|0)!=0&(Hr^1)))if(ae){Ze=28;break}else{Ze=31;break}Le=q+16|0,n[o+908>>2]=n[Le>>2],Ye=q+20|0,n[o+912>>2]=n[Ye>>2],(s[11698]|0)==0|ae^1||(n[ct>>2]=Iu(We)|0,n[ct+4>>2]=We,xo(o,4,2972,ct),ae=n[o+972>>2]|0,ae|0&&ip[ae&127](o),d=pa(d,T)|0,m=pa(m,T)|0,Za=+y(h[Le>>2]),Fo=+y(h[Ye>>2]),n[Lt>>2]=d,n[Lt+4>>2]=m,E[Lt+8>>3]=+l,E[Lt+16>>3]=+u,E[Lt+24>>3]=Za,E[Lt+32>>3]=Fo,n[Lt+40>>2]=M,xo(o,4,2989,Lt))}while(!1);return(Ze|0)==28&&(ae=Iu(We)|0,n[Le>>2]=ae,n[Le+4>>2]=We,n[Le+8>>2]=Hr?3047:11699,xo(o,4,3038,Le),ae=n[o+972>>2]|0,ae|0&&ip[ae&127](o),Lt=pa(d,T)|0,Ze=pa(m,T)|0,n[He>>2]=Lt,n[He+4>>2]=Ze,E[He+8>>3]=+l,E[He+16>>3]=+u,n[He+24>>2]=M,xo(o,4,3049,He),Ze=31),(Ze|0)==31&&(Fs(o,l,u,A,d,m,B,k,T,L),s[11697]|0&&(ae=n[2279]|0,Lt=Iu(ae)|0,n[Gr>>2]=Lt,n[Gr+4>>2]=ae,n[Gr+8>>2]=Hr?3047:11699,xo(o,4,3083,Gr),ae=n[o+972>>2]|0,ae|0&&ip[ae&127](o),Lt=pa(d,T)|0,Gr=pa(m,T)|0,Fo=+y(h[o+908>>2]),Za=+y(h[o+912>>2]),n[fr>>2]=Lt,n[fr+4>>2]=Gr,E[fr+8>>3]=Fo,E[fr+16>>3]=Za,n[fr+24>>2]=M,xo(o,4,3092,fr)),n[o+516>>2]=A,q||(ae=o+520|0,q=n[ae>>2]|0,(q|0)==16&&(s[11697]|0&&xo(o,4,3124,$t),n[ae>>2]=0,q=0),T?q=o+916|0:(n[ae>>2]=q+1,q=o+524+(q*24|0)|0),h[q>>2]=l,h[q+4>>2]=u,n[q+8>>2]=d,n[q+12>>2]=m,n[q+16>>2]=n[o+908>>2],n[q+20>>2]=n[o+912>>2],q=0)),T&&(n[o+416>>2]=n[o+908>>2],n[o+420>>2]=n[o+912>>2],s[o+985>>0]=1,s[Tr>>0]=0),n[2279]=(n[2279]|0)+-1,n[o+512>>2]=n[2278],I=cr,Hr|(q|0)==0|0}function yn(o,l,u){o=o|0,l=l|0,u=y(u);var A=$e;return A=y(K(o,l,u)),y(A+y(re(o,l,u)))}function xo(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=I,I=I+16|0,d=m,n[d>>2]=A,o?A=n[o+976>>2]|0:A=0,Ph(A,o,l,u,d),I=m}function Iu(o){return o=o|0,(o>>>0>60?3201:3201+(60-o)|0)|0}function pa(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+32|0,u=d+12|0,A=d,n[u>>2]=n[254],n[u+4>>2]=n[255],n[u+8>>2]=n[256],n[A>>2]=n[257],n[A+4>>2]=n[258],n[A+8>>2]=n[259],(o|0)>2?o=11699:o=n[(l?A:u)+(o<<2)>>2]|0,I=d,o|0}function Fs(o,l,u,A,d,m,B,k,T,M){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=m|0,B=y(B),k=y(k),T=T|0,M=M|0;var L=0,q=0,ae=0,Ye=0,Le=$e,Qe=$e,tt=$e,Ze=$e,ct=$e,He=$e,We=$e,Lt=0,Gr=0,fr=0,$t=$e,Tr=$e,Hr=0,cr=$e,Hn=0,To=0,Ro=0,Fo=0,Za=0,Wh=0,Yh=0,gc=0,Vh=0,Rf=0,Ff=0,Jh=0,Kh=0,zh=0,ln=0,dc=0,Xh=0,Pu=0,Zh=$e,$h=$e,Nf=$e,Of=$e,xu=$e,oo=0,Ll=0,ma=0,mc=0,op=0,ap=$e,Lf=$e,lp=$e,cp=$e,ao=$e,Ms=$e,yc=0,Wn=$e,up=$e,No=$e,ku=$e,Oo=$e,Qu=$e,fp=0,Ap=0,Tu=$e,lo=$e,Ec=0,pp=0,hp=0,gp=0,Nr=$e,ui=0,Us=0,Lo=0,co=0,Mr=0,Ar=0,Ic=0,zt=$e,dp=0,Bi=0;Ic=I,I=I+16|0,oo=Ic+12|0,Ll=Ic+8|0,ma=Ic+4|0,mc=Ic,wi(o,(d|0)==0|(Mt(l)|0)^1,3326),wi(o,(m|0)==0|(Mt(u)|0)^1,3406),Us=At(o,A)|0,n[o+496>>2]=Us,Mr=dr(2,Us)|0,Ar=dr(0,Us)|0,h[o+440>>2]=y(K(o,Mr,B)),h[o+444>>2]=y(re(o,Mr,B)),h[o+428>>2]=y(K(o,Ar,B)),h[o+436>>2]=y(re(o,Ar,B)),h[o+464>>2]=y(vr(o,Mr)),h[o+468>>2]=y(Un(o,Mr)),h[o+452>>2]=y(vr(o,Ar)),h[o+460>>2]=y(Un(o,Ar)),h[o+488>>2]=y(mi(o,Mr,B)),h[o+492>>2]=y(Cs(o,Mr,B)),h[o+476>>2]=y(mi(o,Ar,B)),h[o+484>>2]=y(Cs(o,Ar,B));do if(n[o+964>>2]|0)JA(o,l,u,d,m,B,k);else{if(Lo=o+948|0,co=(n[o+952>>2]|0)-(n[Lo>>2]|0)>>2,!co){lP(o,l,u,d,m,B,k);break}if(!T&&t2(o,l,u,d,m,B,k)|0)break;te(o),dc=o+508|0,s[dc>>0]=0,Mr=dr(n[o+4>>2]|0,Us)|0,Ar=by(Mr,Us)|0,ui=de(Mr)|0,Xh=n[o+8>>2]|0,pp=o+28|0,Pu=(n[pp>>2]|0)!=0,Oo=ui?B:k,Tu=ui?k:B,Zh=y(kh(o,Mr,B)),$h=y(r2(o,Mr,B)),Le=y(kh(o,Ar,B)),Qu=y(Va(o,Mr,B)),lo=y(Va(o,Ar,B)),fr=ui?d:m,Ec=ui?m:d,Nr=ui?Qu:lo,ct=ui?lo:Qu,ku=y(yn(o,2,B)),Ze=y(yn(o,0,B)),Qe=y(y(Zr(o+364|0,B))-Nr),tt=y(y(Zr(o+380|0,B))-Nr),He=y(y(Zr(o+372|0,k))-ct),We=y(y(Zr(o+388|0,k))-ct),Nf=ui?Qe:He,Of=ui?tt:We,ku=y(l-ku),l=y(ku-Nr),Mt(l)|0?Nr=l:Nr=y($n(y(pd(l,tt)),Qe)),up=y(u-Ze),l=y(up-ct),Mt(l)|0?No=l:No=y($n(y(pd(l,We)),He)),Qe=ui?Nr:No,Wn=ui?No:Nr;e:do if((fr|0)==1)for(A=0,q=0;;){if(L=Is(o,q)|0,!A)y(KA(L))>y(0)&&y(Qh(L))>y(0)?A=L:A=0;else if(n2(L)|0){Ye=0;break e}if(q=q+1|0,q>>>0>=co>>>0){Ye=A;break}}else Ye=0;while(!1);Lt=Ye+500|0,Gr=Ye+504|0,A=0,L=0,l=y(0),ae=0;do{if(q=n[(n[Lo>>2]|0)+(ae<<2)>>2]|0,(n[q+36>>2]|0)==1)Py(q),s[q+985>>0]=1,s[q+984>>0]=0;else{vf(q),T&&bh(q,At(q,Us)|0,Qe,Wn,Nr);do if((n[q+24>>2]|0)!=1)if((q|0)==(Ye|0)){n[Lt>>2]=n[2278],h[Gr>>2]=y(0);break}else{cP(o,q,Nr,d,No,Nr,No,m,Us,M);break}else L|0&&(n[L+960>>2]=q),n[q+960>>2]=0,L=q,A=A|0?A:q;while(!1);Ms=y(h[q+504>>2]),l=y(l+y(Ms+y(yn(q,Mr,Nr))))}ae=ae+1|0}while((ae|0)!=(co|0));for(Ro=l>Qe,yc=Pu&((fr|0)==2&Ro)?1:fr,Hn=(Ec|0)==1,Za=Hn&(T^1),Wh=(yc|0)==1,Yh=(yc|0)==2,gc=976+(Mr<<2)|0,Vh=(Ec|2|0)==2,zh=Hn&(Pu^1),Rf=1040+(Ar<<2)|0,Ff=1040+(Mr<<2)|0,Jh=976+(Ar<<2)|0,Kh=(Ec|0)!=1,Ro=Pu&((fr|0)!=0&Ro),To=o+976|0,Hn=Hn^1,l=Qe,Hr=0,Fo=0,Ms=y(0),xu=y(0);;){e:do if(Hr>>>0>>0)for(Gr=n[Lo>>2]|0,ae=0,We=y(0),He=y(0),tt=y(0),Qe=y(0),q=0,L=0,Ye=Hr;;){if(Lt=n[Gr+(Ye<<2)>>2]|0,(n[Lt+36>>2]|0)!=1&&(n[Lt+940>>2]=Fo,(n[Lt+24>>2]|0)!=1)){if(Ze=y(yn(Lt,Mr,Nr)),ln=n[gc>>2]|0,u=y(Zr(Lt+380+(ln<<3)|0,Oo)),ct=y(h[Lt+504>>2]),u=y(pd(u,ct)),u=y($n(y(Zr(Lt+364+(ln<<3)|0,Oo)),u)),Pu&(ae|0)!=0&y(Ze+y(He+u))>l){m=ae,Ze=We,fr=Ye;break e}Ze=y(Ze+u),u=y(He+Ze),Ze=y(We+Ze),n2(Lt)|0&&(tt=y(tt+y(KA(Lt))),Qe=y(Qe-y(ct*y(Qh(Lt))))),L|0&&(n[L+960>>2]=Lt),n[Lt+960>>2]=0,ae=ae+1|0,L=Lt,q=q|0?q:Lt}else Ze=We,u=He;if(Ye=Ye+1|0,Ye>>>0>>0)We=Ze,He=u;else{m=ae,fr=Ye;break}}else m=0,Ze=y(0),tt=y(0),Qe=y(0),q=0,fr=Hr;while(!1);ln=tt>y(0)&tty(0)&QeOf&((Mt(Of)|0)^1))l=Of,ln=51;else if(s[(n[To>>2]|0)+3>>0]|0)ln=51;else{if($t!=y(0)&&y(KA(o))!=y(0)){ln=53;break}l=Ze,ln=53}while(!1);if((ln|0)==51&&(ln=0,Mt(l)|0?ln=53:(Tr=y(l-Ze),cr=l)),(ln|0)==53&&(ln=0,Ze>2]|0,Ye=Try(0),He=y(Tr/$t),tt=y(0),Ze=y(0),l=y(0),L=q;do u=y(Zr(L+380+(ae<<3)|0,Oo)),Qe=y(Zr(L+364+(ae<<3)|0,Oo)),Qe=y(pd(u,y($n(Qe,y(h[L+504>>2]))))),Ye?(u=y(Qe*y(Qh(L))),u!=y(-0)&&(zt=y(Qe-y(ct*u)),ap=y(qn(L,Mr,zt,cr,Nr)),zt!=ap)&&(tt=y(tt-y(ap-Qe)),l=y(l+u))):Lt&&(Lf=y(KA(L)),Lf!=y(0))&&(zt=y(Qe+y(He*Lf)),lp=y(qn(L,Mr,zt,cr,Nr)),zt!=lp)&&(tt=y(tt-y(lp-Qe)),Ze=y(Ze-Lf)),L=n[L+960>>2]|0;while(L|0);if(l=y(We+l),Qe=y(Tr+tt),op)l=y(0);else{ct=y($t+Ze),Ye=n[gc>>2]|0,Lt=Qey(0),ct=y(Qe/ct),l=y(0);do{zt=y(Zr(q+380+(Ye<<3)|0,Oo)),tt=y(Zr(q+364+(Ye<<3)|0,Oo)),tt=y(pd(zt,y($n(tt,y(h[q+504>>2]))))),Lt?(zt=y(tt*y(Qh(q))),Qe=y(-zt),zt!=y(-0)?(zt=y(He*Qe),Qe=y(qn(q,Mr,y(tt+(Gr?Qe:zt)),cr,Nr))):Qe=tt):ae&&(cp=y(KA(q)),cp!=y(0))?Qe=y(qn(q,Mr,y(tt+y(ct*cp)),cr,Nr)):Qe=tt,l=y(l-y(Qe-tt)),Ze=y(yn(q,Mr,Nr)),u=y(yn(q,Ar,Nr)),Qe=y(Qe+Ze),h[Ll>>2]=Qe,n[mc>>2]=1,tt=y(h[q+396>>2]);e:do if(Mt(tt)|0){L=Mt(Wn)|0;do if(!L){if(Ro|(io(q,Ar,Wn)|0|Hn)||(os(o,q)|0)!=4||(n[(kl(q,Ar)|0)+4>>2]|0)==3||(n[(Ql(q,Ar)|0)+4>>2]|0)==3)break;h[oo>>2]=Wn,n[ma>>2]=1;break e}while(!1);if(io(q,Ar,Wn)|0){L=n[q+992+(n[Jh>>2]<<2)>>2]|0,zt=y(u+y(Zr(L,Wn))),h[oo>>2]=zt,L=Kh&(n[L+4>>2]|0)==2,n[ma>>2]=((Mt(zt)|0|L)^1)&1;break}else{h[oo>>2]=Wn,n[ma>>2]=L?0:2;break}}else zt=y(Qe-Ze),$t=y(zt/tt),zt=y(tt*zt),n[ma>>2]=1,h[oo>>2]=y(u+(ui?$t:zt));while(!1);Cu(q,Mr,cr,Nr,mc,Ll),Cu(q,Ar,Wn,Nr,ma,oo);do if(!(io(q,Ar,Wn)|0)&&(os(o,q)|0)==4){if((n[(kl(q,Ar)|0)+4>>2]|0)==3){L=0;break}L=(n[(Ql(q,Ar)|0)+4>>2]|0)!=3}else L=0;while(!1);zt=y(h[Ll>>2]),$t=y(h[oo>>2]),dp=n[mc>>2]|0,Bi=n[ma>>2]|0,xl(q,ui?zt:$t,ui?$t:zt,Us,ui?dp:Bi,ui?Bi:dp,Nr,No,T&(L^1),3488,M)|0,s[dc>>0]=s[dc>>0]|s[q+508>>0],q=n[q+960>>2]|0}while(q|0)}}else l=y(0);if(l=y(Tr+l),Bi=l>0]=Bi|c[dc>>0],Yh&l>y(0)?(L=n[gc>>2]|0,n[o+364+(L<<3)+4>>2]|0&&(ao=y(Zr(o+364+(L<<3)|0,Oo)),ao>=y(0))?Qe=y($n(y(0),y(ao-y(cr-l)))):Qe=y(0)):Qe=l,Lt=Hr>>>0>>0,Lt){Ye=n[Lo>>2]|0,ae=Hr,L=0;do q=n[Ye+(ae<<2)>>2]|0,n[q+24>>2]|0||(L=((n[(kl(q,Mr)|0)+4>>2]|0)==3&1)+L|0,L=L+((n[(Ql(q,Mr)|0)+4>>2]|0)==3&1)|0),ae=ae+1|0;while((ae|0)!=(fr|0));L?(Ze=y(0),u=y(0)):ln=101}else ln=101;e:do if((ln|0)==101)switch(ln=0,Xh|0){case 1:{L=0,Ze=y(Qe*y(.5)),u=y(0);break e}case 2:{L=0,Ze=Qe,u=y(0);break e}case 3:{if(m>>>0<=1){L=0,Ze=y(0),u=y(0);break e}u=y((m+-1|0)>>>0),L=0,Ze=y(0),u=y(y($n(Qe,y(0)))/u);break e}case 5:{u=y(Qe/y((m+1|0)>>>0)),L=0,Ze=u;break e}case 4:{u=y(Qe/y(m>>>0)),L=0,Ze=y(u*y(.5));break e}default:{L=0,Ze=y(0),u=y(0);break e}}while(!1);if(l=y(Zh+Ze),Lt){tt=y(Qe/y(L|0)),ae=n[Lo>>2]|0,q=Hr,Qe=y(0);do{L=n[ae+(q<<2)>>2]|0;e:do if((n[L+36>>2]|0)!=1){switch(n[L+24>>2]|0){case 1:{if(ha(L,Mr)|0){if(!T)break e;zt=y(zA(L,Mr,cr)),zt=y(zt+y(vr(o,Mr))),zt=y(zt+y(K(L,Mr,Nr))),h[L+400+(n[Ff>>2]<<2)>>2]=zt;break e}break}case 0:if(Bi=(n[(kl(L,Mr)|0)+4>>2]|0)==3,zt=y(tt+l),l=Bi?zt:l,T&&(Bi=L+400+(n[Ff>>2]<<2)|0,h[Bi>>2]=y(l+y(h[Bi>>2]))),Bi=(n[(Ql(L,Mr)|0)+4>>2]|0)==3,zt=y(tt+l),l=Bi?zt:l,Za){zt=y(u+y(yn(L,Mr,Nr))),Qe=Wn,l=y(l+y(zt+y(h[L+504>>2])));break e}else{l=y(l+y(u+y(XA(L,Mr,Nr)))),Qe=y($n(Qe,y(XA(L,Ar,Nr))));break e}default:}T&&(zt=y(Ze+y(vr(o,Mr))),Bi=L+400+(n[Ff>>2]<<2)|0,h[Bi>>2]=y(zt+y(h[Bi>>2])))}while(!1);q=q+1|0}while((q|0)!=(fr|0))}else Qe=y(0);if(u=y($h+l),Vh?Ze=y(y(qn(o,Ar,y(lo+Qe),Tu,B))-lo):Ze=Wn,tt=y(y(qn(o,Ar,y(lo+(zh?Wn:Qe)),Tu,B))-lo),Lt&T){q=Hr;do{ae=n[(n[Lo>>2]|0)+(q<<2)>>2]|0;do if((n[ae+36>>2]|0)!=1){if((n[ae+24>>2]|0)==1){if(ha(ae,Ar)|0){if(zt=y(zA(ae,Ar,Wn)),zt=y(zt+y(vr(o,Ar))),zt=y(zt+y(K(ae,Ar,Nr))),L=n[Rf>>2]|0,h[ae+400+(L<<2)>>2]=zt,!(Mt(zt)|0))break}else L=n[Rf>>2]|0;zt=y(vr(o,Ar)),h[ae+400+(L<<2)>>2]=y(zt+y(K(ae,Ar,Nr)));break}L=os(o,ae)|0;do if((L|0)==4){if((n[(kl(ae,Ar)|0)+4>>2]|0)==3){ln=139;break}if((n[(Ql(ae,Ar)|0)+4>>2]|0)==3){ln=139;break}if(io(ae,Ar,Wn)|0){l=Le;break}dp=n[ae+908+(n[gc>>2]<<2)>>2]|0,n[oo>>2]=dp,l=y(h[ae+396>>2]),Bi=Mt(l)|0,Qe=(n[S>>2]=dp,y(h[S>>2])),Bi?l=tt:(Tr=y(yn(ae,Ar,Nr)),zt=y(Qe/l),l=y(l*Qe),l=y(Tr+(ui?zt:l))),h[Ll>>2]=l,h[oo>>2]=y(y(yn(ae,Mr,Nr))+Qe),n[ma>>2]=1,n[mc>>2]=1,Cu(ae,Mr,cr,Nr,ma,oo),Cu(ae,Ar,Wn,Nr,mc,Ll),l=y(h[oo>>2]),Tr=y(h[Ll>>2]),zt=ui?l:Tr,l=ui?Tr:l,Bi=((Mt(zt)|0)^1)&1,xl(ae,zt,l,Us,Bi,((Mt(l)|0)^1)&1,Nr,No,1,3493,M)|0,l=Le}else ln=139;while(!1);e:do if((ln|0)==139){ln=0,l=y(Ze-y(XA(ae,Ar,Nr)));do if((n[(kl(ae,Ar)|0)+4>>2]|0)==3){if((n[(Ql(ae,Ar)|0)+4>>2]|0)!=3)break;l=y(Le+y($n(y(0),y(l*y(.5)))));break e}while(!1);if((n[(Ql(ae,Ar)|0)+4>>2]|0)==3){l=Le;break}if((n[(kl(ae,Ar)|0)+4>>2]|0)==3){l=y(Le+y($n(y(0),l)));break}switch(L|0){case 1:{l=Le;break e}case 2:{l=y(Le+y(l*y(.5)));break e}default:{l=y(Le+l);break e}}}while(!1);zt=y(Ms+l),Bi=ae+400+(n[Rf>>2]<<2)|0,h[Bi>>2]=y(zt+y(h[Bi>>2]))}while(!1);q=q+1|0}while((q|0)!=(fr|0))}if(Ms=y(Ms+tt),xu=y($n(xu,u)),m=Fo+1|0,fr>>>0>=co>>>0)break;l=cr,Hr=fr,Fo=m}do if(T){if(L=m>>>0>1,!L&&!(jL(o)|0))break;if(!(Mt(Wn)|0)){l=y(Wn-Ms);e:do switch(n[o+12>>2]|0){case 3:{Le=y(Le+l),He=y(0);break}case 2:{Le=y(Le+y(l*y(.5))),He=y(0);break}case 4:{Wn>Ms?He=y(l/y(m>>>0)):He=y(0);break}case 7:if(Wn>Ms){Le=y(Le+y(l/y(m<<1>>>0))),He=y(l/y(m>>>0)),He=L?He:y(0);break e}else{Le=y(Le+y(l*y(.5))),He=y(0);break e}case 6:{He=y(l/y(Fo>>>0)),He=Wn>Ms&L?He:y(0);break}default:He=y(0)}while(!1);if(m|0)for(Lt=1040+(Ar<<2)|0,Gr=976+(Ar<<2)|0,Ye=0,q=0;;){e:do if(q>>>0>>0)for(Qe=y(0),tt=y(0),l=y(0),ae=q;;){L=n[(n[Lo>>2]|0)+(ae<<2)>>2]|0;do if((n[L+36>>2]|0)!=1&&!(n[L+24>>2]|0)){if((n[L+940>>2]|0)!=(Ye|0))break e;if(qL(L,Ar)|0&&(zt=y(h[L+908+(n[Gr>>2]<<2)>>2]),l=y($n(l,y(zt+y(yn(L,Ar,Nr)))))),(os(o,L)|0)!=5)break;ao=y(Yg(L)),ao=y(ao+y(K(L,0,Nr))),zt=y(h[L+912>>2]),zt=y(y(zt+y(yn(L,0,Nr)))-ao),ao=y($n(tt,ao)),zt=y($n(Qe,zt)),Qe=zt,tt=ao,l=y($n(l,y(ao+zt)))}while(!1);if(L=ae+1|0,L>>>0>>0)ae=L;else{ae=L;break}}else tt=y(0),l=y(0),ae=q;while(!1);if(ct=y(He+l),u=Le,Le=y(Le+ct),q>>>0>>0){Ze=y(u+tt),L=q;do{q=n[(n[Lo>>2]|0)+(L<<2)>>2]|0;e:do if((n[q+36>>2]|0)!=1&&!(n[q+24>>2]|0))switch(os(o,q)|0){case 1:{zt=y(u+y(K(q,Ar,Nr))),h[q+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 3:{zt=y(y(Le-y(re(q,Ar,Nr)))-y(h[q+908+(n[Gr>>2]<<2)>>2])),h[q+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 2:{zt=y(u+y(y(ct-y(h[q+908+(n[Gr>>2]<<2)>>2]))*y(.5))),h[q+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 4:{if(zt=y(u+y(K(q,Ar,Nr))),h[q+400+(n[Lt>>2]<<2)>>2]=zt,io(q,Ar,Wn)|0||(ui?(Qe=y(h[q+908>>2]),l=y(Qe+y(yn(q,Mr,Nr))),tt=ct):(tt=y(h[q+912>>2]),tt=y(tt+y(yn(q,Ar,Nr))),l=ct,Qe=y(h[q+908>>2])),mn(l,Qe)|0&&mn(tt,y(h[q+912>>2]))|0))break e;xl(q,l,tt,Us,1,1,Nr,No,1,3501,M)|0;break e}case 5:{h[q+404>>2]=y(y(Ze-y(Yg(q)))+y(zA(q,0,Wn)));break e}default:break e}while(!1);L=L+1|0}while((L|0)!=(ae|0))}if(Ye=Ye+1|0,(Ye|0)==(m|0))break;q=ae}}}while(!1);if(h[o+908>>2]=y(qn(o,2,ku,B,B)),h[o+912>>2]=y(qn(o,0,up,k,B)),yc|0&&(fp=n[o+32>>2]|0,Ap=(yc|0)==2,!(Ap&(fp|0)!=2))?Ap&(fp|0)==2&&(l=y(Qu+cr),l=y($n(y(pd(l,y(Vg(o,Mr,xu,Oo)))),Qu)),ln=198):(l=y(qn(o,Mr,xu,Oo,B)),ln=198),(ln|0)==198&&(h[o+908+(n[976+(Mr<<2)>>2]<<2)>>2]=l),Ec|0&&(hp=n[o+32>>2]|0,gp=(Ec|0)==2,!(gp&(hp|0)!=2))?gp&(hp|0)==2&&(l=y(lo+Wn),l=y($n(y(pd(l,y(Vg(o,Ar,y(lo+Ms),Tu)))),lo)),ln=204):(l=y(qn(o,Ar,y(lo+Ms),Tu,B)),ln=204),(ln|0)==204&&(h[o+908+(n[976+(Ar<<2)>>2]<<2)>>2]=l),T){if((n[pp>>2]|0)==2){q=976+(Ar<<2)|0,ae=1040+(Ar<<2)|0,L=0;do Ye=Is(o,L)|0,n[Ye+24>>2]|0||(dp=n[q>>2]|0,zt=y(h[o+908+(dp<<2)>>2]),Bi=Ye+400+(n[ae>>2]<<2)|0,zt=y(zt-y(h[Bi>>2])),h[Bi>>2]=y(zt-y(h[Ye+908+(dp<<2)>>2]))),L=L+1|0;while((L|0)!=(co|0))}if(A|0){L=ui?yc:d;do WL(o,A,Nr,L,No,Us,M),A=n[A+960>>2]|0;while(A|0)}if(L=(Mr|2|0)==3,q=(Ar|2|0)==3,L|q){A=0;do ae=n[(n[Lo>>2]|0)+(A<<2)>>2]|0,(n[ae+36>>2]|0)!=1&&(L&&i2(o,ae,Mr),q&&i2(o,ae,Ar)),A=A+1|0;while((A|0)!=(co|0))}}}while(!1);I=Ic}function Dh(o,l){o=o|0,l=y(l);var u=0;Ha(o,l>=y(0),3147),u=l==y(0),h[o+4>>2]=u?y(0):l}function YA(o,l,u,A){o=o|0,l=y(l),u=y(u),A=A|0;var d=$e,m=$e,B=0,k=0,T=0;n[2278]=(n[2278]|0)+1,vf(o),io(o,2,l)|0?(d=y(Zr(n[o+992>>2]|0,l)),T=1,d=y(d+y(yn(o,2,l)))):(d=y(Zr(o+380|0,l)),d>=y(0)?T=2:(T=((Mt(l)|0)^1)&1,d=l)),io(o,0,u)|0?(m=y(Zr(n[o+996>>2]|0,u)),k=1,m=y(m+y(yn(o,0,l)))):(m=y(Zr(o+388|0,u)),m>=y(0)?k=2:(k=((Mt(u)|0)^1)&1,m=u)),B=o+976|0,xl(o,d,m,A,T,k,l,u,1,3189,n[B>>2]|0)|0&&(bh(o,n[o+496>>2]|0,l,u,l),VA(o,y(h[(n[B>>2]|0)+4>>2]),y(0),y(0)),s[11696]|0)&&Gg(o,7)}function vf(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;k=I,I=I+32|0,B=k+24|0,m=k+16|0,A=k+8|0,d=k,u=0;do l=o+380+(u<<3)|0,n[o+380+(u<<3)+4>>2]|0&&(T=l,M=n[T+4>>2]|0,L=A,n[L>>2]=n[T>>2],n[L+4>>2]=M,L=o+364+(u<<3)|0,M=n[L+4>>2]|0,T=d,n[T>>2]=n[L>>2],n[T+4>>2]=M,n[m>>2]=n[A>>2],n[m+4>>2]=n[A+4>>2],n[B>>2]=n[d>>2],n[B+4>>2]=n[d+4>>2],Cf(m,B)|0)||(l=o+348+(u<<3)|0),n[o+992+(u<<2)>>2]=l,u=u+1|0;while((u|0)!=2);I=k}function io(o,l,u){o=o|0,l=l|0,u=y(u);var A=0;switch(o=n[o+992+(n[976+(l<<2)>>2]<<2)>>2]|0,n[o+4>>2]|0){case 0:case 3:{o=0;break}case 1:{y(h[o>>2])>2])>2]|0){case 2:{l=y(y(y(h[o>>2])*l)/y(100));break}case 1:{l=y(h[o>>2]);break}default:l=y(le)}return y(l)}function bh(o,l,u,A,d){o=o|0,l=l|0,u=y(u),A=y(A),d=y(d);var m=0,B=$e;l=n[o+944>>2]|0?l:1,m=dr(n[o+4>>2]|0,l)|0,l=by(m,l)|0,u=y(uP(o,m,u)),A=y(uP(o,l,A)),B=y(u+y(K(o,m,d))),h[o+400+(n[1040+(m<<2)>>2]<<2)>>2]=B,u=y(u+y(re(o,m,d))),h[o+400+(n[1e3+(m<<2)>>2]<<2)>>2]=u,u=y(A+y(K(o,l,d))),h[o+400+(n[1040+(l<<2)>>2]<<2)>>2]=u,d=y(A+y(re(o,l,d))),h[o+400+(n[1e3+(l<<2)>>2]<<2)>>2]=d}function VA(o,l,u,A){o=o|0,l=y(l),u=y(u),A=y(A);var d=0,m=0,B=$e,k=$e,T=0,M=0,L=$e,q=0,ae=$e,Ye=$e,Le=$e,Qe=$e;if(l!=y(0)&&(d=o+400|0,Qe=y(h[d>>2]),m=o+404|0,Le=y(h[m>>2]),q=o+416|0,Ye=y(h[q>>2]),M=o+420|0,B=y(h[M>>2]),ae=y(Qe+u),L=y(Le+A),A=y(ae+Ye),k=y(L+B),T=(n[o+988>>2]|0)==1,h[d>>2]=y(ss(Qe,l,0,T)),h[m>>2]=y(ss(Le,l,0,T)),u=y(uU(y(Ye*l),y(1))),mn(u,y(0))|0?m=0:m=(mn(u,y(1))|0)^1,u=y(uU(y(B*l),y(1))),mn(u,y(0))|0?d=0:d=(mn(u,y(1))|0)^1,Qe=y(ss(A,l,T&m,T&(m^1))),h[q>>2]=y(Qe-y(ss(ae,l,0,T))),Qe=y(ss(k,l,T&d,T&(d^1))),h[M>>2]=y(Qe-y(ss(L,l,0,T))),m=(n[o+952>>2]|0)-(n[o+948>>2]|0)>>2,m|0)){d=0;do VA(Is(o,d)|0,l,ae,L),d=d+1|0;while((d|0)!=(m|0))}}function Sy(o,l,u,A,d){switch(o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,u|0){case 5:case 0:{o=IZ(n[489]|0,A,d)|0;break}default:o=b6e(A,d)|0}return o|0}function Wg(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;d=I,I=I+16|0,m=d,n[m>>2]=A,Ph(o,0,l,u,m),I=d}function Ph(o,l,u,A,d){if(o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,o=o|0?o:956,HZ[n[o+8>>2]&1](o,l,u,A,d)|0,(u|0)==5)Nt();else return}function pc(o,l,u){o=o|0,l=l|0,u=u|0,s[o+l>>0]=u&1}function Dy(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(xh(o,A),kt(o,n[l>>2]|0,n[u>>2]|0,A))}function xh(o,l){o=o|0,l=l|0;var u=0;if((O(o)|0)>>>0>>0&&an(o),l>>>0>1073741823)Nt();else{u=Kt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function kt(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function O(o){return o=o|0,1073741823}function K(o,l,u){return o=o|0,l=l|0,u=y(u),de(l)|0&&n[o+96>>2]|0?o=o+92|0:o=kn(o+60|0,n[1040+(l<<2)>>2]|0,992)|0,y(Je(o,u))}function re(o,l,u){return o=o|0,l=l|0,u=y(u),de(l)|0&&n[o+104>>2]|0?o=o+100|0:o=kn(o+60|0,n[1e3+(l<<2)>>2]|0,992)|0,y(Je(o,u))}function de(o){return o=o|0,(o|1|0)==3|0}function Je(o,l){return o=o|0,l=y(l),(n[o+4>>2]|0)==3?l=y(0):l=y(Zr(o,l)),y(l)}function At(o,l){return o=o|0,l=l|0,o=n[o>>2]|0,(o|0?o:(l|0)>1?l:1)|0}function dr(o,l){o=o|0,l=l|0;var u=0;e:do if((l|0)==2){switch(o|0){case 2:{o=3;break e}case 3:break;default:{u=4;break e}}o=2}else u=4;while(!1);return o|0}function vr(o,l){o=o|0,l=l|0;var u=$e;return de(l)|0&&n[o+312>>2]|0&&(u=y(h[o+308>>2]),u>=y(0))||(u=y($n(y(h[(kn(o+276|0,n[1040+(l<<2)>>2]|0,992)|0)>>2]),y(0)))),y(u)}function Un(o,l){o=o|0,l=l|0;var u=$e;return de(l)|0&&n[o+320>>2]|0&&(u=y(h[o+316>>2]),u>=y(0))||(u=y($n(y(h[(kn(o+276|0,n[1e3+(l<<2)>>2]|0,992)|0)>>2]),y(0)))),y(u)}function mi(o,l,u){o=o|0,l=l|0,u=y(u);var A=$e;return de(l)|0&&n[o+240>>2]|0&&(A=y(Zr(o+236|0,u)),A>=y(0))||(A=y($n(y(Zr(kn(o+204|0,n[1040+(l<<2)>>2]|0,992)|0,u)),y(0)))),y(A)}function Cs(o,l,u){o=o|0,l=l|0,u=y(u);var A=$e;return de(l)|0&&n[o+248>>2]|0&&(A=y(Zr(o+244|0,u)),A>=y(0))||(A=y($n(y(Zr(kn(o+204|0,n[1e3+(l<<2)>>2]|0,992)|0,u)),y(0)))),y(A)}function JA(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=$e,T=$e,M=$e,L=$e,q=$e,ae=$e,Ye=0,Le=0,Qe=0;Qe=I,I=I+16|0,Ye=Qe,Le=o+964|0,wi(o,(n[Le>>2]|0)!=0,3519),k=y(Va(o,2,l)),T=y(Va(o,0,l)),M=y(yn(o,2,l)),L=y(yn(o,0,l)),Mt(l)|0?q=l:q=y($n(y(0),y(y(l-M)-k))),Mt(u)|0?ae=u:ae=y($n(y(0),y(y(u-L)-T))),(A|0)==1&(d|0)==1?(h[o+908>>2]=y(qn(o,2,y(l-M),m,m)),l=y(qn(o,0,y(u-L),B,m))):(jZ[n[Le>>2]&1](Ye,o,q,A,ae,d),q=y(k+y(h[Ye>>2])),ae=y(l-M),h[o+908>>2]=y(qn(o,2,(A|2|0)==2?q:ae,m,m)),ae=y(T+y(h[Ye+4>>2])),l=y(u-L),l=y(qn(o,0,(d|2|0)==2?ae:l,B,m))),h[o+912>>2]=l,I=Qe}function lP(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=$e,T=$e,M=$e,L=$e;M=y(Va(o,2,m)),k=y(Va(o,0,m)),L=y(yn(o,2,m)),T=y(yn(o,0,m)),l=y(l-L),h[o+908>>2]=y(qn(o,2,(A|2|0)==2?M:l,m,m)),u=y(u-T),h[o+912>>2]=y(qn(o,0,(d|2|0)==2?k:u,B,m))}function t2(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=0,T=$e,M=$e;return k=(A|0)==2,!(l<=y(0)&k)&&!(u<=y(0)&(d|0)==2)&&!((A|0)==1&(d|0)==1)?o=0:(T=y(yn(o,0,m)),M=y(yn(o,2,m)),k=l>2]=y(qn(o,2,k?y(0):l,m,m)),l=y(u-T),k=u>2]=y(qn(o,0,k?y(0):l,B,m)),o=1),o|0}function by(o,l){return o=o|0,l=l|0,Jg(o)|0?o=dr(2,l)|0:o=0,o|0}function kh(o,l,u){return o=o|0,l=l|0,u=y(u),u=y(mi(o,l,u)),y(u+y(vr(o,l)))}function r2(o,l,u){return o=o|0,l=l|0,u=y(u),u=y(Cs(o,l,u)),y(u+y(Un(o,l)))}function Va(o,l,u){o=o|0,l=l|0,u=y(u);var A=$e;return A=y(kh(o,l,u)),y(A+y(r2(o,l,u)))}function n2(o){return o=o|0,n[o+24>>2]|0?o=0:y(KA(o))!=y(0)?o=1:o=y(Qh(o))!=y(0),o|0}function KA(o){o=o|0;var l=$e;if(n[o+944>>2]|0){if(l=y(h[o+44>>2]),Mt(l)|0)return l=y(h[o+40>>2]),o=l>y(0)&((Mt(l)|0)^1),y(o?l:y(0))}else l=y(0);return y(l)}function Qh(o){o=o|0;var l=$e,u=0,A=$e;do if(n[o+944>>2]|0){if(l=y(h[o+48>>2]),Mt(l)|0){if(u=s[(n[o+976>>2]|0)+2>>0]|0,!(u<<24>>24)&&(A=y(h[o+40>>2]),A>24?y(1):y(0)}}else l=y(0);while(!1);return y(l)}function Py(o){o=o|0;var l=0,u=0;if(eE(o+400|0,0,540)|0,s[o+985>>0]=1,te(o),u=Mi(o)|0,u|0){l=o+948|0,o=0;do Py(n[(n[l>>2]|0)+(o<<2)>>2]|0),o=o+1|0;while((o|0)!=(u|0))}}function cP(o,l,u,A,d,m,B,k,T,M){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=y(m),B=y(B),k=k|0,T=T|0,M=M|0;var L=0,q=$e,ae=0,Ye=0,Le=$e,Qe=$e,tt=0,Ze=$e,ct=0,He=$e,We=0,Lt=0,Gr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0,To=0;Hn=I,I=I+16|0,Gr=Hn+12|0,fr=Hn+8|0,$t=Hn+4|0,Tr=Hn,cr=dr(n[o+4>>2]|0,T)|0,We=de(cr)|0,q=y(Zr(YL(l)|0,We?m:B)),Lt=io(l,2,m)|0,Hr=io(l,0,B)|0;do if(!(Mt(q)|0)&&!(Mt(We?u:d)|0)){if(L=l+504|0,!(Mt(y(h[L>>2]))|0)&&(!(s2(n[l+976>>2]|0,0)|0)||(n[l+500>>2]|0)==(n[2278]|0)))break;h[L>>2]=y($n(q,y(Va(l,cr,m))))}else ae=7;while(!1);do if((ae|0)==7){if(ct=We^1,!(ct|Lt^1)){B=y(Zr(n[l+992>>2]|0,m)),h[l+504>>2]=y($n(B,y(Va(l,2,m))));break}if(!(We|Hr^1)){B=y(Zr(n[l+996>>2]|0,B)),h[l+504>>2]=y($n(B,y(Va(l,0,m))));break}h[Gr>>2]=y(le),h[fr>>2]=y(le),n[$t>>2]=0,n[Tr>>2]=0,Ze=y(yn(l,2,m)),He=y(yn(l,0,m)),Lt?(Le=y(Ze+y(Zr(n[l+992>>2]|0,m))),h[Gr>>2]=Le,n[$t>>2]=1,Ye=1):(Ye=0,Le=y(le)),Hr?(q=y(He+y(Zr(n[l+996>>2]|0,B))),h[fr>>2]=q,n[Tr>>2]=1,L=1):(L=0,q=y(le)),ae=n[o+32>>2]|0,We&(ae|0)==2?ae=2:Mt(Le)|0&&!(Mt(u)|0)&&(h[Gr>>2]=u,n[$t>>2]=2,Ye=2,Le=u),!((ae|0)==2&ct)&&Mt(q)|0&&!(Mt(d)|0)&&(h[fr>>2]=d,n[Tr>>2]=2,L=2,q=d),Qe=y(h[l+396>>2]),tt=Mt(Qe)|0;do if(tt)ae=Ye;else{if((Ye|0)==1&ct){h[fr>>2]=y(y(Le-Ze)/Qe),n[Tr>>2]=1,L=1,ae=1;break}We&(L|0)==1?(h[Gr>>2]=y(Qe*y(q-He)),n[$t>>2]=1,L=1,ae=1):ae=Ye}while(!1);To=Mt(u)|0,Ye=(os(o,l)|0)!=4,!(We|Lt|((A|0)!=1|To)|(Ye|(ae|0)==1))&&(h[Gr>>2]=u,n[$t>>2]=1,!tt)&&(h[fr>>2]=y(y(u-Ze)/Qe),n[Tr>>2]=1,L=1),!(Hr|ct|((k|0)!=1|(Mt(d)|0))|(Ye|(L|0)==1))&&(h[fr>>2]=d,n[Tr>>2]=1,!tt)&&(h[Gr>>2]=y(Qe*y(d-He)),n[$t>>2]=1),Cu(l,2,m,m,$t,Gr),Cu(l,0,B,m,Tr,fr),u=y(h[Gr>>2]),d=y(h[fr>>2]),xl(l,u,d,T,n[$t>>2]|0,n[Tr>>2]|0,m,B,0,3565,M)|0,B=y(h[l+908+(n[976+(cr<<2)>>2]<<2)>>2]),h[l+504>>2]=y($n(B,y(Va(l,cr,m))))}while(!1);n[l+500>>2]=n[2278],I=Hn}function qn(o,l,u,A,d){return o=o|0,l=l|0,u=y(u),A=y(A),d=y(d),A=y(Vg(o,l,u,A)),y($n(A,y(Va(o,l,d))))}function os(o,l){return o=o|0,l=l|0,l=l+20|0,l=n[(n[l>>2]|0?l:o+16|0)>>2]|0,(l|0)==5&&Jg(n[o+4>>2]|0)|0&&(l=1),l|0}function kl(o,l){return o=o|0,l=l|0,de(l)|0&&n[o+96>>2]|0?l=4:l=n[1040+(l<<2)>>2]|0,o+60+(l<<3)|0}function Ql(o,l){return o=o|0,l=l|0,de(l)|0&&n[o+104>>2]|0?l=5:l=n[1e3+(l<<2)>>2]|0,o+60+(l<<3)|0}function Cu(o,l,u,A,d,m){switch(o=o|0,l=l|0,u=y(u),A=y(A),d=d|0,m=m|0,u=y(Zr(o+380+(n[976+(l<<2)>>2]<<3)|0,u)),u=y(u+y(yn(o,l,A))),n[d>>2]|0){case 2:case 1:{d=Mt(u)|0,A=y(h[m>>2]),h[m>>2]=d|A>2]=2,h[m>>2]=u);break}default:}}function ha(o,l){return o=o|0,l=l|0,o=o+132|0,de(l)|0&&n[(kn(o,4,948)|0)+4>>2]|0?o=1:o=(n[(kn(o,n[1040+(l<<2)>>2]|0,948)|0)+4>>2]|0)!=0,o|0}function zA(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0;return o=o+132|0,de(l)|0&&(A=kn(o,4,948)|0,(n[A+4>>2]|0)!=0)?d=4:(A=kn(o,n[1040+(l<<2)>>2]|0,948)|0,n[A+4>>2]|0?d=4:u=y(0)),(d|0)==4&&(u=y(Zr(A,u))),y(u)}function XA(o,l,u){o=o|0,l=l|0,u=y(u);var A=$e;return A=y(h[o+908+(n[976+(l<<2)>>2]<<2)>>2]),A=y(A+y(K(o,l,u))),y(A+y(re(o,l,u)))}function jL(o){o=o|0;var l=0,u=0,A=0;e:do if(Jg(n[o+4>>2]|0)|0)l=0;else if((n[o+16>>2]|0)!=5)if(u=Mi(o)|0,!u)l=0;else for(l=0;;){if(A=Is(o,l)|0,!(n[A+24>>2]|0)&&(n[A+20>>2]|0)==5){l=1;break e}if(l=l+1|0,l>>>0>=u>>>0){l=0;break}}else l=1;while(!1);return l|0}function qL(o,l){o=o|0,l=l|0;var u=$e;return u=y(h[o+908+(n[976+(l<<2)>>2]<<2)>>2]),u>=y(0)&((Mt(u)|0)^1)|0}function Yg(o){o=o|0;var l=$e,u=0,A=0,d=0,m=0,B=0,k=0,T=$e;if(u=n[o+968>>2]|0,u)T=y(h[o+908>>2]),l=y(h[o+912>>2]),l=y(LZ[u&0](o,T,l)),wi(o,(Mt(l)|0)^1,3573);else{m=Mi(o)|0;do if(m|0){for(u=0,d=0;;){if(A=Is(o,d)|0,n[A+940>>2]|0){B=8;break}if((n[A+24>>2]|0)!=1)if(k=(os(o,A)|0)==5,k){u=A;break}else u=u|0?u:A;if(d=d+1|0,d>>>0>=m>>>0){B=8;break}}if((B|0)==8&&!u)break;return l=y(Yg(u)),y(l+y(h[u+404>>2]))}while(!1);l=y(h[o+912>>2])}return y(l)}function Vg(o,l,u,A){o=o|0,l=l|0,u=y(u),A=y(A);var d=$e,m=0;return Jg(l)|0?(l=1,m=3):de(l)|0?(l=0,m=3):(A=y(le),d=y(le)),(m|0)==3&&(d=y(Zr(o+364+(l<<3)|0,A)),A=y(Zr(o+380+(l<<3)|0,A))),m=A=y(0)&((Mt(A)|0)^1)),u=m?A:u,m=d>=y(0)&((Mt(d)|0)^1)&u>2]|0,m)|0,Le=by(tt,m)|0,Qe=de(tt)|0,q=y(yn(l,2,u)),ae=y(yn(l,0,u)),io(l,2,u)|0?k=y(q+y(Zr(n[l+992>>2]|0,u))):ha(l,2)|0&&xy(l,2)|0?(k=y(h[o+908>>2]),T=y(vr(o,2)),T=y(k-y(T+y(Un(o,2)))),k=y(zA(l,2,u)),k=y(qn(l,2,y(T-y(k+y(Th(l,2,u)))),u,u))):k=y(le),io(l,0,d)|0?T=y(ae+y(Zr(n[l+996>>2]|0,d))):ha(l,0)|0&&xy(l,0)|0?(T=y(h[o+912>>2]),ct=y(vr(o,0)),ct=y(T-y(ct+y(Un(o,0)))),T=y(zA(l,0,d)),T=y(qn(l,0,y(ct-y(T+y(Th(l,0,d)))),d,u))):T=y(le),M=Mt(k)|0,L=Mt(T)|0;do if(M^L&&(Ye=y(h[l+396>>2]),!(Mt(Ye)|0)))if(M){k=y(q+y(y(T-ae)*Ye));break}else{ct=y(ae+y(y(k-q)/Ye)),T=L?ct:T;break}while(!1);L=Mt(k)|0,M=Mt(T)|0,L|M&&(He=(L^1)&1,A=u>y(0)&((A|0)!=0&L),k=Qe?k:A?u:k,xl(l,k,T,m,Qe?He:A?2:He,L&(M^1)&1,k,T,0,3623,B)|0,k=y(h[l+908>>2]),k=y(k+y(yn(l,2,u))),T=y(h[l+912>>2]),T=y(T+y(yn(l,0,u)))),xl(l,k,T,m,1,1,k,T,1,3635,B)|0,xy(l,tt)|0&&!(ha(l,tt)|0)?(He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),ct=y(ct-y(Un(o,tt))),ct=y(ct-y(re(l,tt,u))),ct=y(ct-y(Th(l,tt,Qe?u:d))),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct):Ze=21;do if((Ze|0)==21){if(!(ha(l,tt)|0)&&(n[o+8>>2]|0)==1){He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(y(ct-y(h[l+908+(He<<2)>>2]))*y(.5)),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct;break}!(ha(l,tt)|0)&&(n[o+8>>2]|0)==2&&(He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct)}while(!1);xy(l,Le)|0&&!(ha(l,Le)|0)?(He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),ct=y(ct-y(Un(o,Le))),ct=y(ct-y(re(l,Le,u))),ct=y(ct-y(Th(l,Le,Qe?d:u))),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct):Ze=30;do if((Ze|0)==30&&!(ha(l,Le)|0)){if((os(o,l)|0)==2){He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(y(ct-y(h[l+908+(He<<2)>>2]))*y(.5)),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct;break}He=(os(o,l)|0)==3,He^(n[o+28>>2]|0)==2&&(He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct)}while(!1)}function i2(o,l,u){o=o|0,l=l|0,u=u|0;var A=$e,d=0;d=n[976+(u<<2)>>2]|0,A=y(h[l+908+(d<<2)>>2]),A=y(y(h[o+908+(d<<2)>>2])-A),A=y(A-y(h[l+400+(n[1040+(u<<2)>>2]<<2)>>2])),h[l+400+(n[1e3+(u<<2)>>2]<<2)>>2]=A}function Jg(o){return o=o|0,(o|1|0)==1|0}function YL(o){o=o|0;var l=$e;switch(n[o+56>>2]|0){case 0:case 3:{l=y(h[o+40>>2]),l>y(0)&((Mt(l)|0)^1)?o=s[(n[o+976>>2]|0)+2>>0]|0?1056:992:o=1056;break}default:o=o+52|0}return o|0}function s2(o,l){return o=o|0,l=l|0,(s[o+l>>0]|0)!=0|0}function xy(o,l){return o=o|0,l=l|0,o=o+132|0,de(l)|0&&n[(kn(o,5,948)|0)+4>>2]|0?o=1:o=(n[(kn(o,n[1e3+(l<<2)>>2]|0,948)|0)+4>>2]|0)!=0,o|0}function Th(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0;return o=o+132|0,de(l)|0&&(A=kn(o,5,948)|0,(n[A+4>>2]|0)!=0)?d=4:(A=kn(o,n[1e3+(l<<2)>>2]|0,948)|0,n[A+4>>2]|0?d=4:u=y(0)),(d|0)==4&&(u=y(Zr(A,u))),y(u)}function uP(o,l,u){return o=o|0,l=l|0,u=y(u),ha(o,l)|0?u=y(zA(o,l,u)):u=y(-y(Th(o,l,u))),y(u)}function fP(o){return o=y(o),h[S>>2]=o,n[S>>2]|0|0}function ky(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Kt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function AP(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function Qy(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&It(o)}function pP(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;if(B=o+4|0,k=n[B>>2]|0,d=k-A|0,m=d>>2,o=l+(m<<2)|0,o>>>0>>0){A=k;do n[A>>2]=n[o>>2],o=o+4|0,A=(n[B>>2]|0)+4|0,n[B>>2]=A;while(o>>>0>>0)}m|0&&Q2(k+(0-m<<2)|0,l|0,d|0)|0}function hP(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0;return k=l+4|0,T=n[k>>2]|0,d=n[o>>2]|0,B=u,m=B-d|0,A=T+(0-(m>>2)<<2)|0,n[k>>2]=A,(m|0)>0&&Qr(A|0,d|0,m|0)|0,d=o+4|0,m=l+8|0,A=(n[d>>2]|0)-B|0,(A|0)>0&&(Qr(n[m>>2]|0,u|0,A|0)|0,n[m>>2]=(n[m>>2]|0)+(A>>>2<<2)),B=n[o>>2]|0,n[o>>2]=n[k>>2],n[k>>2]=B,B=n[d>>2]|0,n[d>>2]=n[m>>2],n[m>>2]=B,B=o+8|0,u=l+12|0,o=n[B>>2]|0,n[B>>2]=n[u>>2],n[u>>2]=o,n[l>>2]=n[k>>2],T|0}function o2(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;if(B=n[l>>2]|0,m=n[u>>2]|0,(B|0)!=(m|0)){d=o+8|0,u=((m+-4-B|0)>>>2)+1|0,o=B,A=n[d>>2]|0;do n[A>>2]=n[o>>2],A=(n[d>>2]|0)+4|0,n[d>>2]=A,o=o+4|0;while((o|0)!=(m|0));n[l>>2]=B+(u<<2)}}function a2(){ua()}function gP(){var o=0;return o=Kt(4)|0,l2(o),o|0}function l2(o){o=o|0,n[o>>2]=Ac()|0}function dP(o){o=o|0,o|0&&(Kg(o),It(o))}function Kg(o){o=o|0,st(n[o>>2]|0)}function VL(o,l,u){o=o|0,l=l|0,u=u|0,pc(n[o>>2]|0,l,u)}function Ty(o,l){o=o|0,l=y(l),Dh(n[o>>2]|0,l)}function Ry(o,l){return o=o|0,l=l|0,s2(n[o>>2]|0,l)|0}function Fy(){var o=0;return o=Kt(8)|0,zg(o,0),o|0}function zg(o,l){o=o|0,l=l|0,l?l=fa(n[l>>2]|0)|0:l=ns()|0,n[o>>2]=l,n[o+4>>2]=0,Tn(l,o)}function Ny(o){o=o|0;var l=0;return l=Kt(8)|0,zg(l,o),l|0}function Xg(o){o=o|0,o|0&&(Oy(o),It(o))}function Oy(o){o=o|0;var l=0;uc(n[o>>2]|0),l=o+4|0,o=n[l>>2]|0,n[l>>2]=0,o|0&&(Sf(o),It(o))}function Sf(o){o=o|0,Df(o)}function Df(o){o=o|0,o=n[o>>2]|0,o|0&&Na(o|0)}function c2(o){return o=o|0,Ga(o)|0}function u2(o){o=o|0;var l=0,u=0;u=o+4|0,l=n[u>>2]|0,n[u>>2]=0,l|0&&(Sf(l),It(l)),fc(n[o>>2]|0)}function Ly(o,l){o=o|0,l=l|0,An(n[o>>2]|0,n[l>>2]|0)}function JL(o,l){o=o|0,l=l|0,wh(n[o>>2]|0,l)}function KL(o,l,u){o=o|0,l=l|0,u=+u,Cy(n[o>>2]|0,l,y(u))}function My(o,l,u){o=o|0,l=l|0,u=+u,wy(n[o>>2]|0,l,y(u))}function f2(o,l){o=o|0,l=l|0,Eh(n[o>>2]|0,l)}function A2(o,l){o=o|0,l=l|0,So(n[o>>2]|0,l)}function xr(o,l){o=o|0,l=l|0,Ch(n[o>>2]|0,l)}function so(o,l){o=o|0,l=l|0,my(n[o>>2]|0,l)}function Xi(o,l){o=o|0,l=l|0,Ng(n[o>>2]|0,l)}function Ns(o,l){o=o|0,l=l|0,vo(n[o>>2]|0,l)}function ZA(o,l,u){o=o|0,l=l|0,u=+u,HA(n[o>>2]|0,l,y(u))}function p2(o,l,u){o=o|0,l=l|0,u=+u,Y(n[o>>2]|0,l,y(u))}function ws(o,l){o=o|0,l=l|0,jA(n[o>>2]|0,l)}function Uy(o,l){o=o|0,l=l|0,Ey(n[o>>2]|0,l)}function Rh(o,l){o=o|0,l=l|0,Do(n[o>>2]|0,l)}function Zg(o,l){o=o|0,l=+l,Bh(n[o>>2]|0,y(l))}function Fh(o,l){o=o|0,l=+l,bl(n[o>>2]|0,y(l))}function h2(o,l){o=o|0,l=+l,Iy(n[o>>2]|0,y(l))}function g2(o,l){o=o|0,l=+l,Lg(n[o>>2]|0,y(l))}function d2(o,l){o=o|0,l=+l,Dl(n[o>>2]|0,y(l))}function m2(o,l){o=o|0,l=+l,Mg(n[o>>2]|0,y(l))}function bf(o,l){o=o|0,l=+l,e2(n[o>>2]|0,y(l))}function sr(o){o=o|0,vh(n[o>>2]|0)}function _y(o,l){o=o|0,l=+l,zi(n[o>>2]|0,y(l))}function y2(o,l){o=o|0,l=+l,yf(n[o>>2]|0,y(l))}function hc(o){o=o|0,qa(n[o>>2]|0)}function Pf(o,l){o=o|0,l=+l,du(n[o>>2]|0,y(l))}function $g(o,l){o=o|0,l=+l,Ef(n[o>>2]|0,y(l))}function ed(o,l){o=o|0,l=+l,di(n[o>>2]|0,y(l))}function E2(o,l){o=o|0,l=+l,GA(n[o>>2]|0,y(l))}function I2(o,l){o=o|0,l=+l,Aa(n[o>>2]|0,y(l))}function wu(o,l){o=o|0,l=+l,Ya(n[o>>2]|0,y(l))}function td(o,l){o=o|0,l=+l,Sh(n[o>>2]|0,y(l))}function C2(o,l){o=o|0,l=+l,Hg(n[o>>2]|0,y(l))}function Hy(o,l){o=o|0,l=+l,qA(n[o>>2]|0,y(l))}function Bu(o,l,u){o=o|0,l=l|0,u=+u,gu(n[o>>2]|0,l,y(u))}function jy(o,l,u){o=o|0,l=l|0,u=+u,bo(n[o>>2]|0,l,y(u))}function rd(o,l,u){o=o|0,l=l|0,u=+u,mf(n[o>>2]|0,l,y(u))}function nd(o){return o=o|0,Fg(n[o>>2]|0)|0}function ko(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,_A(d,n[l>>2]|0,u),Bs(o,d),I=A}function Bs(o,l){o=o|0,l=l|0,Tl(o,n[l+4>>2]|0,+y(h[l>>2]))}function Tl(o,l,u){o=o|0,l=l|0,u=+u,n[o>>2]=l,E[o+8>>3]=u}function Gy(o){return o=o|0,$1(n[o>>2]|0)|0}function ga(o){return o=o|0,Ih(n[o>>2]|0)|0}function mP(o){return o=o|0,hu(n[o>>2]|0)|0}function Nh(o){return o=o|0,Z1(n[o>>2]|0)|0}function w2(o){return o=o|0,Og(n[o>>2]|0)|0}function zL(o){return o=o|0,yy(n[o>>2]|0)|0}function yP(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,xt(d,n[l>>2]|0,u),Bs(o,d),I=A}function EP(o){return o=o|0,df(n[o>>2]|0)|0}function qy(o){return o=o|0,Sl(n[o>>2]|0)|0}function B2(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,UA(A,n[l>>2]|0),Bs(o,A),I=u}function Oh(o){return o=o|0,+ +y(li(n[o>>2]|0))}function IP(o){return o=o|0,+ +y(qi(n[o>>2]|0))}function CP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,ur(A,n[l>>2]|0),Bs(o,A),I=u}function id(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,Ug(A,n[l>>2]|0),Bs(o,A),I=u}function XL(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,wt(A,n[l>>2]|0),Bs(o,A),I=u}function ZL(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,Wa(A,n[l>>2]|0),Bs(o,A),I=u}function wP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,_g(A,n[l>>2]|0),Bs(o,A),I=u}function BP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,vy(A,n[l>>2]|0),Bs(o,A),I=u}function $A(o){return o=o|0,+ +y(jg(n[o>>2]|0))}function $L(o,l){return o=o|0,l=l|0,+ +y(By(n[o>>2]|0,l))}function eM(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,yt(d,n[l>>2]|0,u),Bs(o,d),I=A}function vu(o,l,u){o=o|0,l=l|0,u=u|0,lr(n[o>>2]|0,n[l>>2]|0,u)}function tM(o,l){o=o|0,l=l|0,gf(n[o>>2]|0,n[l>>2]|0)}function vP(o){return o=o|0,Mi(n[o>>2]|0)|0}function rM(o){return o=o|0,o=Et(n[o>>2]|0)|0,o?o=c2(o)|0:o=0,o|0}function SP(o,l){return o=o|0,l=l|0,o=Is(n[o>>2]|0,l)|0,o?o=c2(o)|0:o=0,o|0}function xf(o,l){o=o|0,l=l|0;var u=0,A=0;A=Kt(4)|0,DP(A,l),u=o+4|0,l=n[u>>2]|0,n[u>>2]=A,l|0&&(Sf(l),It(l)),St(n[o>>2]|0,1)}function DP(o,l){o=o|0,l=l|0,oM(o,l)}function nM(o,l,u,A,d,m){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=m|0;var B=0,k=0;B=I,I=I+16|0,k=B,bP(k,Ga(l)|0,+u,A,+d,m),h[o>>2]=y(+E[k>>3]),h[o+4>>2]=y(+E[k+8>>3]),I=B}function bP(o,l,u,A,d,m){o=o|0,l=l|0,u=+u,A=A|0,d=+d,m=m|0;var B=0,k=0,T=0,M=0,L=0;B=I,I=I+32|0,L=B+8|0,M=B+20|0,T=B,k=B+16|0,E[L>>3]=u,n[M>>2]=A,E[T>>3]=d,n[k>>2]=m,Wy(o,n[l+4>>2]|0,L,M,T,k),I=B}function Wy(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0;B=I,I=I+16|0,k=B,Fl(k),l=Os(l)|0,PP(o,l,+E[u>>3],n[A>>2]|0,+E[d>>3],n[m>>2]|0),Nl(k),I=B}function Os(o){return o=o|0,n[o>>2]|0}function PP(o,l,u,A,d,m){o=o|0,l=l|0,u=+u,A=A|0,d=+d,m=m|0;var B=0;B=da(v2()|0)|0,u=+Ja(u),A=Yy(A)|0,d=+Ja(d),iM(o,Kn(0,B|0,l|0,+u,A|0,+d,Yy(m)|0)|0)}function v2(){var o=0;return s[7608]|0||(D2(9120),o=7608,n[o>>2]=1,n[o+4>>2]=0),9120}function da(o){return o=o|0,n[o+8>>2]|0}function Ja(o){return o=+o,+ +kf(o)}function Yy(o){return o=o|0,sd(o)|0}function iM(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+32|0,u=d,A=l,A&1?(Ka(u,0),Me(A|0,u|0)|0,S2(o,u),sM(u)):(n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2]),I=d}function Ka(o,l){o=o|0,l=l|0,Su(o,l),n[o+8>>2]=0,s[o+24>>0]=0}function S2(o,l){o=o|0,l=l|0,l=l+8|0,n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2]}function sM(o){o=o|0,s[o+24>>0]=0}function Su(o,l){o=o|0,l=l|0,n[o>>2]=l}function sd(o){return o=o|0,o|0}function kf(o){return o=+o,+o}function D2(o){o=o|0,Qo(o,b2()|0,4)}function b2(){return 1064}function Qo(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=Gi(l|0,u+1|0)|0}function oM(o,l){o=o|0,l=l|0,l=n[l>>2]|0,n[o>>2]=l,au(l|0)}function xP(o){o=o|0;var l=0,u=0;u=o+4|0,l=n[u>>2]|0,n[u>>2]=0,l|0&&(Sf(l),It(l)),St(n[o>>2]|0,0)}function kP(o){o=o|0,bt(n[o>>2]|0)}function Vy(o){return o=o|0,tr(n[o>>2]|0)|0}function aM(o,l,u,A){o=o|0,l=+l,u=+u,A=A|0,YA(n[o>>2]|0,y(l),y(u),A)}function lM(o){return o=o|0,+ +y(mu(n[o>>2]|0))}function v(o){return o=o|0,+ +y(If(n[o>>2]|0))}function D(o){return o=o|0,+ +y(yu(n[o>>2]|0))}function Q(o){return o=o|0,+ +y(Rs(n[o>>2]|0))}function H(o){return o=o|0,+ +y(Eu(n[o>>2]|0))}function V(o){return o=o|0,+ +y(Gn(n[o>>2]|0))}function ne(o,l){o=o|0,l=l|0,E[o>>3]=+y(mu(n[l>>2]|0)),E[o+8>>3]=+y(If(n[l>>2]|0)),E[o+16>>3]=+y(yu(n[l>>2]|0)),E[o+24>>3]=+y(Rs(n[l>>2]|0)),E[o+32>>3]=+y(Eu(n[l>>2]|0)),E[o+40>>3]=+y(Gn(n[l>>2]|0))}function Se(o,l){return o=o|0,l=l|0,+ +y(is(n[o>>2]|0,l))}function _e(o,l){return o=o|0,l=l|0,+ +y(Pi(n[o>>2]|0,l))}function pt(o,l){return o=o|0,l=l|0,+ +y(WA(n[o>>2]|0,l))}function Wt(){return Qn()|0}function Sr(){Lr(),Zt(),zn(),yi(),za(),et()}function Lr(){p4e(11713,4938,1)}function Zt(){T_e(10448)}function zn(){p_e(10408)}function yi(){OUe(10324)}function za(){qLe(10096)}function et(){qe(9132)}function qe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,We=0,Lt=0,Gr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0,To=0,Ro=0,Fo=0,Za=0,Wh=0,Yh=0,gc=0,Vh=0,Rf=0,Ff=0,Jh=0,Kh=0,zh=0,ln=0,dc=0,Xh=0,Pu=0,Zh=0,$h=0,Nf=0,Of=0,xu=0,oo=0,Ll=0,ma=0,mc=0,op=0,ap=0,Lf=0,lp=0,cp=0,ao=0,Ms=0,yc=0,Wn=0,up=0,No=0,ku=0,Oo=0,Qu=0,fp=0,Ap=0,Tu=0,lo=0,Ec=0,pp=0,hp=0,gp=0,Nr=0,ui=0,Us=0,Lo=0,co=0,Mr=0,Ar=0,Ic=0;l=I,I=I+672|0,u=l+656|0,Ic=l+648|0,Ar=l+640|0,Mr=l+632|0,co=l+624|0,Lo=l+616|0,Us=l+608|0,ui=l+600|0,Nr=l+592|0,gp=l+584|0,hp=l+576|0,pp=l+568|0,Ec=l+560|0,lo=l+552|0,Tu=l+544|0,Ap=l+536|0,fp=l+528|0,Qu=l+520|0,Oo=l+512|0,ku=l+504|0,No=l+496|0,up=l+488|0,Wn=l+480|0,yc=l+472|0,Ms=l+464|0,ao=l+456|0,cp=l+448|0,lp=l+440|0,Lf=l+432|0,ap=l+424|0,op=l+416|0,mc=l+408|0,ma=l+400|0,Ll=l+392|0,oo=l+384|0,xu=l+376|0,Of=l+368|0,Nf=l+360|0,$h=l+352|0,Zh=l+344|0,Pu=l+336|0,Xh=l+328|0,dc=l+320|0,ln=l+312|0,zh=l+304|0,Kh=l+296|0,Jh=l+288|0,Ff=l+280|0,Rf=l+272|0,Vh=l+264|0,gc=l+256|0,Yh=l+248|0,Wh=l+240|0,Za=l+232|0,Fo=l+224|0,Ro=l+216|0,To=l+208|0,Hn=l+200|0,cr=l+192|0,Hr=l+184|0,Tr=l+176|0,$t=l+168|0,fr=l+160|0,Gr=l+152|0,Lt=l+144|0,We=l+136|0,He=l+128|0,ct=l+120|0,Ze=l+112|0,tt=l+104|0,Qe=l+96|0,Le=l+88|0,Ye=l+80|0,ae=l+72|0,q=l+64|0,L=l+56|0,M=l+48|0,T=l+40|0,k=l+32|0,B=l+24|0,m=l+16|0,d=l+8|0,A=l,gt(o,3646),Xt(o,3651,2)|0,Dr(o,3665,2)|0,Zn(o,3682,18)|0,n[Ic>>2]=19,n[Ic+4>>2]=0,n[u>>2]=n[Ic>>2],n[u+4>>2]=n[Ic+4>>2],kr(o,3690,u)|0,n[Ar>>2]=1,n[Ar+4>>2]=0,n[u>>2]=n[Ar>>2],n[u+4>>2]=n[Ar+4>>2],Rn(o,3696,u)|0,n[Mr>>2]=2,n[Mr+4>>2]=0,n[u>>2]=n[Mr>>2],n[u+4>>2]=n[Mr+4>>2],_n(o,3706,u)|0,n[co>>2]=1,n[co+4>>2]=0,n[u>>2]=n[co>>2],n[u+4>>2]=n[co+4>>2],zr(o,3722,u)|0,n[Lo>>2]=2,n[Lo+4>>2]=0,n[u>>2]=n[Lo>>2],n[u+4>>2]=n[Lo+4>>2],zr(o,3734,u)|0,n[Us>>2]=3,n[Us+4>>2]=0,n[u>>2]=n[Us>>2],n[u+4>>2]=n[Us+4>>2],_n(o,3753,u)|0,n[ui>>2]=4,n[ui+4>>2]=0,n[u>>2]=n[ui>>2],n[u+4>>2]=n[ui+4>>2],_n(o,3769,u)|0,n[Nr>>2]=5,n[Nr+4>>2]=0,n[u>>2]=n[Nr>>2],n[u+4>>2]=n[Nr+4>>2],_n(o,3783,u)|0,n[gp>>2]=6,n[gp+4>>2]=0,n[u>>2]=n[gp>>2],n[u+4>>2]=n[gp+4>>2],_n(o,3796,u)|0,n[hp>>2]=7,n[hp+4>>2]=0,n[u>>2]=n[hp>>2],n[u+4>>2]=n[hp+4>>2],_n(o,3813,u)|0,n[pp>>2]=8,n[pp+4>>2]=0,n[u>>2]=n[pp>>2],n[u+4>>2]=n[pp+4>>2],_n(o,3825,u)|0,n[Ec>>2]=3,n[Ec+4>>2]=0,n[u>>2]=n[Ec>>2],n[u+4>>2]=n[Ec+4>>2],zr(o,3843,u)|0,n[lo>>2]=4,n[lo+4>>2]=0,n[u>>2]=n[lo>>2],n[u+4>>2]=n[lo+4>>2],zr(o,3853,u)|0,n[Tu>>2]=9,n[Tu+4>>2]=0,n[u>>2]=n[Tu>>2],n[u+4>>2]=n[Tu+4>>2],_n(o,3870,u)|0,n[Ap>>2]=10,n[Ap+4>>2]=0,n[u>>2]=n[Ap>>2],n[u+4>>2]=n[Ap+4>>2],_n(o,3884,u)|0,n[fp>>2]=11,n[fp+4>>2]=0,n[u>>2]=n[fp>>2],n[u+4>>2]=n[fp+4>>2],_n(o,3896,u)|0,n[Qu>>2]=1,n[Qu+4>>2]=0,n[u>>2]=n[Qu>>2],n[u+4>>2]=n[Qu+4>>2],ci(o,3907,u)|0,n[Oo>>2]=2,n[Oo+4>>2]=0,n[u>>2]=n[Oo>>2],n[u+4>>2]=n[Oo+4>>2],ci(o,3915,u)|0,n[ku>>2]=3,n[ku+4>>2]=0,n[u>>2]=n[ku>>2],n[u+4>>2]=n[ku+4>>2],ci(o,3928,u)|0,n[No>>2]=4,n[No+4>>2]=0,n[u>>2]=n[No>>2],n[u+4>>2]=n[No+4>>2],ci(o,3948,u)|0,n[up>>2]=5,n[up+4>>2]=0,n[u>>2]=n[up>>2],n[u+4>>2]=n[up+4>>2],ci(o,3960,u)|0,n[Wn>>2]=6,n[Wn+4>>2]=0,n[u>>2]=n[Wn>>2],n[u+4>>2]=n[Wn+4>>2],ci(o,3974,u)|0,n[yc>>2]=7,n[yc+4>>2]=0,n[u>>2]=n[yc>>2],n[u+4>>2]=n[yc+4>>2],ci(o,3983,u)|0,n[Ms>>2]=20,n[Ms+4>>2]=0,n[u>>2]=n[Ms>>2],n[u+4>>2]=n[Ms+4>>2],kr(o,3999,u)|0,n[ao>>2]=8,n[ao+4>>2]=0,n[u>>2]=n[ao>>2],n[u+4>>2]=n[ao+4>>2],ci(o,4012,u)|0,n[cp>>2]=9,n[cp+4>>2]=0,n[u>>2]=n[cp>>2],n[u+4>>2]=n[cp+4>>2],ci(o,4022,u)|0,n[lp>>2]=21,n[lp+4>>2]=0,n[u>>2]=n[lp>>2],n[u+4>>2]=n[lp+4>>2],kr(o,4039,u)|0,n[Lf>>2]=10,n[Lf+4>>2]=0,n[u>>2]=n[Lf>>2],n[u+4>>2]=n[Lf+4>>2],ci(o,4053,u)|0,n[ap>>2]=11,n[ap+4>>2]=0,n[u>>2]=n[ap>>2],n[u+4>>2]=n[ap+4>>2],ci(o,4065,u)|0,n[op>>2]=12,n[op+4>>2]=0,n[u>>2]=n[op>>2],n[u+4>>2]=n[op+4>>2],ci(o,4084,u)|0,n[mc>>2]=13,n[mc+4>>2]=0,n[u>>2]=n[mc>>2],n[u+4>>2]=n[mc+4>>2],ci(o,4097,u)|0,n[ma>>2]=14,n[ma+4>>2]=0,n[u>>2]=n[ma>>2],n[u+4>>2]=n[ma+4>>2],ci(o,4117,u)|0,n[Ll>>2]=15,n[Ll+4>>2]=0,n[u>>2]=n[Ll>>2],n[u+4>>2]=n[Ll+4>>2],ci(o,4129,u)|0,n[oo>>2]=16,n[oo+4>>2]=0,n[u>>2]=n[oo>>2],n[u+4>>2]=n[oo+4>>2],ci(o,4148,u)|0,n[xu>>2]=17,n[xu+4>>2]=0,n[u>>2]=n[xu>>2],n[u+4>>2]=n[xu+4>>2],ci(o,4161,u)|0,n[Of>>2]=18,n[Of+4>>2]=0,n[u>>2]=n[Of>>2],n[u+4>>2]=n[Of+4>>2],ci(o,4181,u)|0,n[Nf>>2]=5,n[Nf+4>>2]=0,n[u>>2]=n[Nf>>2],n[u+4>>2]=n[Nf+4>>2],zr(o,4196,u)|0,n[$h>>2]=6,n[$h+4>>2]=0,n[u>>2]=n[$h>>2],n[u+4>>2]=n[$h+4>>2],zr(o,4206,u)|0,n[Zh>>2]=7,n[Zh+4>>2]=0,n[u>>2]=n[Zh>>2],n[u+4>>2]=n[Zh+4>>2],zr(o,4217,u)|0,n[Pu>>2]=3,n[Pu+4>>2]=0,n[u>>2]=n[Pu>>2],n[u+4>>2]=n[Pu+4>>2],Du(o,4235,u)|0,n[Xh>>2]=1,n[Xh+4>>2]=0,n[u>>2]=n[Xh>>2],n[u+4>>2]=n[Xh+4>>2],cM(o,4251,u)|0,n[dc>>2]=4,n[dc+4>>2]=0,n[u>>2]=n[dc>>2],n[u+4>>2]=n[dc+4>>2],Du(o,4263,u)|0,n[ln>>2]=5,n[ln+4>>2]=0,n[u>>2]=n[ln>>2],n[u+4>>2]=n[ln+4>>2],Du(o,4279,u)|0,n[zh>>2]=6,n[zh+4>>2]=0,n[u>>2]=n[zh>>2],n[u+4>>2]=n[zh+4>>2],Du(o,4293,u)|0,n[Kh>>2]=7,n[Kh+4>>2]=0,n[u>>2]=n[Kh>>2],n[u+4>>2]=n[Kh+4>>2],Du(o,4306,u)|0,n[Jh>>2]=8,n[Jh+4>>2]=0,n[u>>2]=n[Jh>>2],n[u+4>>2]=n[Jh+4>>2],Du(o,4323,u)|0,n[Ff>>2]=9,n[Ff+4>>2]=0,n[u>>2]=n[Ff>>2],n[u+4>>2]=n[Ff+4>>2],Du(o,4335,u)|0,n[Rf>>2]=2,n[Rf+4>>2]=0,n[u>>2]=n[Rf>>2],n[u+4>>2]=n[Rf+4>>2],cM(o,4353,u)|0,n[Vh>>2]=12,n[Vh+4>>2]=0,n[u>>2]=n[Vh>>2],n[u+4>>2]=n[Vh+4>>2],od(o,4363,u)|0,n[gc>>2]=1,n[gc+4>>2]=0,n[u>>2]=n[gc>>2],n[u+4>>2]=n[gc+4>>2],ep(o,4376,u)|0,n[Yh>>2]=2,n[Yh+4>>2]=0,n[u>>2]=n[Yh>>2],n[u+4>>2]=n[Yh+4>>2],ep(o,4388,u)|0,n[Wh>>2]=13,n[Wh+4>>2]=0,n[u>>2]=n[Wh>>2],n[u+4>>2]=n[Wh+4>>2],od(o,4402,u)|0,n[Za>>2]=14,n[Za+4>>2]=0,n[u>>2]=n[Za>>2],n[u+4>>2]=n[Za+4>>2],od(o,4411,u)|0,n[Fo>>2]=15,n[Fo+4>>2]=0,n[u>>2]=n[Fo>>2],n[u+4>>2]=n[Fo+4>>2],od(o,4421,u)|0,n[Ro>>2]=16,n[Ro+4>>2]=0,n[u>>2]=n[Ro>>2],n[u+4>>2]=n[Ro+4>>2],od(o,4433,u)|0,n[To>>2]=17,n[To+4>>2]=0,n[u>>2]=n[To>>2],n[u+4>>2]=n[To+4>>2],od(o,4446,u)|0,n[Hn>>2]=18,n[Hn+4>>2]=0,n[u>>2]=n[Hn>>2],n[u+4>>2]=n[Hn+4>>2],od(o,4458,u)|0,n[cr>>2]=3,n[cr+4>>2]=0,n[u>>2]=n[cr>>2],n[u+4>>2]=n[cr+4>>2],ep(o,4471,u)|0,n[Hr>>2]=1,n[Hr+4>>2]=0,n[u>>2]=n[Hr>>2],n[u+4>>2]=n[Hr+4>>2],QP(o,4486,u)|0,n[Tr>>2]=10,n[Tr+4>>2]=0,n[u>>2]=n[Tr>>2],n[u+4>>2]=n[Tr+4>>2],Du(o,4496,u)|0,n[$t>>2]=11,n[$t+4>>2]=0,n[u>>2]=n[$t>>2],n[u+4>>2]=n[$t+4>>2],Du(o,4508,u)|0,n[fr>>2]=3,n[fr+4>>2]=0,n[u>>2]=n[fr>>2],n[u+4>>2]=n[fr+4>>2],cM(o,4519,u)|0,n[Gr>>2]=4,n[Gr+4>>2]=0,n[u>>2]=n[Gr>>2],n[u+4>>2]=n[Gr+4>>2],Cke(o,4530,u)|0,n[Lt>>2]=19,n[Lt+4>>2]=0,n[u>>2]=n[Lt>>2],n[u+4>>2]=n[Lt+4>>2],wke(o,4542,u)|0,n[We>>2]=12,n[We+4>>2]=0,n[u>>2]=n[We>>2],n[u+4>>2]=n[We+4>>2],Bke(o,4554,u)|0,n[He>>2]=13,n[He+4>>2]=0,n[u>>2]=n[He>>2],n[u+4>>2]=n[He+4>>2],vke(o,4568,u)|0,n[ct>>2]=2,n[ct+4>>2]=0,n[u>>2]=n[ct>>2],n[u+4>>2]=n[ct+4>>2],Ske(o,4578,u)|0,n[Ze>>2]=20,n[Ze+4>>2]=0,n[u>>2]=n[Ze>>2],n[u+4>>2]=n[Ze+4>>2],Dke(o,4587,u)|0,n[tt>>2]=22,n[tt+4>>2]=0,n[u>>2]=n[tt>>2],n[u+4>>2]=n[tt+4>>2],kr(o,4602,u)|0,n[Qe>>2]=23,n[Qe+4>>2]=0,n[u>>2]=n[Qe>>2],n[u+4>>2]=n[Qe+4>>2],kr(o,4619,u)|0,n[Le>>2]=14,n[Le+4>>2]=0,n[u>>2]=n[Le>>2],n[u+4>>2]=n[Le+4>>2],bke(o,4629,u)|0,n[Ye>>2]=1,n[Ye+4>>2]=0,n[u>>2]=n[Ye>>2],n[u+4>>2]=n[Ye+4>>2],Pke(o,4637,u)|0,n[ae>>2]=4,n[ae+4>>2]=0,n[u>>2]=n[ae>>2],n[u+4>>2]=n[ae+4>>2],ep(o,4653,u)|0,n[q>>2]=5,n[q+4>>2]=0,n[u>>2]=n[q>>2],n[u+4>>2]=n[q+4>>2],ep(o,4669,u)|0,n[L>>2]=6,n[L+4>>2]=0,n[u>>2]=n[L>>2],n[u+4>>2]=n[L+4>>2],ep(o,4686,u)|0,n[M>>2]=7,n[M+4>>2]=0,n[u>>2]=n[M>>2],n[u+4>>2]=n[M+4>>2],ep(o,4701,u)|0,n[T>>2]=8,n[T+4>>2]=0,n[u>>2]=n[T>>2],n[u+4>>2]=n[T+4>>2],ep(o,4719,u)|0,n[k>>2]=9,n[k+4>>2]=0,n[u>>2]=n[k>>2],n[u+4>>2]=n[k+4>>2],ep(o,4736,u)|0,n[B>>2]=21,n[B+4>>2]=0,n[u>>2]=n[B>>2],n[u+4>>2]=n[B+4>>2],xke(o,4754,u)|0,n[m>>2]=2,n[m+4>>2]=0,n[u>>2]=n[m>>2],n[u+4>>2]=n[m+4>>2],QP(o,4772,u)|0,n[d>>2]=3,n[d+4>>2]=0,n[u>>2]=n[d>>2],n[u+4>>2]=n[d+4>>2],QP(o,4790,u)|0,n[A>>2]=4,n[A+4>>2]=0,n[u>>2]=n[A>>2],n[u+4>>2]=n[A+4>>2],QP(o,4808,u)|0,I=l}function gt(o,l){o=o|0,l=l|0;var u=0;u=NLe()|0,n[o>>2]=u,OLe(u,l),jh(n[o>>2]|0)}function Xt(o,l,u){return o=o|0,l=l|0,u=u|0,CLe(o,Bn(l)|0,u,0),o|0}function Dr(o,l,u){return o=o|0,l=l|0,u=u|0,sLe(o,Bn(l)|0,u,0),o|0}function Zn(o,l,u){return o=o|0,l=l|0,u=u|0,WOe(o,Bn(l)|0,u,0),o|0}function kr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],xOe(o,l,d),I=A,o|0}function Rn(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],uOe(o,l,d),I=A,o|0}function _n(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],JNe(o,l,d),I=A,o|0}function zr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],TNe(o,l,d),I=A,o|0}function ci(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],dNe(o,l,d),I=A,o|0}function Du(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],eNe(o,l,d),I=A,o|0}function cM(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],MFe(o,l,d),I=A,o|0}function od(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],uFe(o,l,d),I=A,o|0}function ep(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],JRe(o,l,d),I=A,o|0}function QP(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],TRe(o,l,d),I=A,o|0}function Cke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],dRe(o,l,d),I=A,o|0}function wke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],eRe(o,l,d),I=A,o|0}function Bke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],UTe(o,l,d),I=A,o|0}function vke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],vTe(o,l,d),I=A,o|0}function Ske(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],aTe(o,l,d),I=A,o|0}function Dke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],qQe(o,l,d),I=A,o|0}function bke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],PQe(o,l,d),I=A,o|0}function Pke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],uQe(o,l,d),I=A,o|0}function xke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],kke(o,l,d),I=A,o|0}function kke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],Qke(o,u,d,1),I=A}function Bn(o){return o=o|0,o|0}function Qke(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=uM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=Tke(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,Rke(m,A)|0,A),I=d}function uM(){var o=0,l=0;if(s[7616]|0||(mz(9136),gr(24,9136,U|0)|0,l=7616,n[l>>2]=1,n[l+4>>2]=0),!(_r(9136)|0)){o=9136,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));mz(9136)}return 9136}function Tke(o){return o=o|0,0}function Rke(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=uM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],dz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(Oke(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function vn(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0;B=I,I=I+32|0,ae=B+24|0,q=B+20|0,T=B+16|0,L=B+12|0,M=B+8|0,k=B+4|0,Ye=B,n[q>>2]=l,n[T>>2]=u,n[L>>2]=A,n[M>>2]=d,n[k>>2]=m,m=o+28|0,n[Ye>>2]=n[m>>2],n[ae>>2]=n[Ye>>2],Fke(o+24|0,ae,q,L,M,T,k)|0,n[m>>2]=n[n[m>>2]>>2],I=B}function Fke(o,l,u,A,d,m,B){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,o=Nke(l)|0,l=Kt(24)|0,gz(l+4|0,n[u>>2]|0,n[A>>2]|0,n[d>>2]|0,n[m>>2]|0,n[B>>2]|0),n[l>>2]=n[o>>2],n[o>>2]=l,l|0}function Nke(o){return o=o|0,n[o>>2]|0}function gz(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=A,n[o+12>>2]=d,n[o+16>>2]=m}function yr(o,l){return o=o|0,l=l|0,l|o|0}function dz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function Oke(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=Lke(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,Mke(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],dz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,Uke(o,k),_ke(k),I=M;return}}function Lke(o){return o=o|0,357913941}function Mke(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function Uke(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function _ke(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function mz(o){o=o|0,Gke(o)}function Hke(o){o=o|0,jke(o+24|0)}function _r(o){return o=o|0,n[o>>2]|0}function jke(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function Gke(o){o=o|0;var l=0;l=tn()|0,rn(o,2,3,l,qke()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function tn(){return 9228}function qke(){return 1140}function Wke(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=Yke(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=Vke(l,A)|0,I=u,l|0}function rn(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=A,n[o+12>>2]=d,n[o+16>>2]=m}function Yke(o){return o=o|0,(n[(uM()|0)+24>>2]|0)+(o*12|0)|0}function Vke(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+48|0,A=d,u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),sp[u&31](A,o),A=Jke(A)|0,I=d,A|0}function Jke(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=fM(yz()|0)|0,A?(AM(l,A),pM(u,l),Kke(o,u),o=hM(l)|0):o=zke(o)|0,I=d,o|0}function yz(){var o=0;return s[7632]|0||(oQe(9184),gr(25,9184,U|0)|0,o=7632,n[o>>2]=1,n[o+4>>2]=0),9184}function fM(o){return o=o|0,n[o+36>>2]|0}function AM(o,l){o=o|0,l=l|0,n[o>>2]=l,n[o+4>>2]=o,n[o+8>>2]=0}function pM(o,l){o=o|0,l=l|0,n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=0}function Kke(o,l){o=o|0,l=l|0,eQe(l,o,o+8|0,o+16|0,o+24|0,o+32|0,o+40|0)|0}function hM(o){return o=o|0,n[(n[o+4>>2]|0)+8>>2]|0}function zke(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0;T=I,I=I+16|0,u=T+4|0,A=T,d=Rl(8)|0,m=d,B=Kt(48)|0,k=B,l=k+48|0;do n[k>>2]=n[o>>2],k=k+4|0,o=o+4|0;while((k|0)<(l|0));return l=m+4|0,n[l>>2]=B,k=Kt(8)|0,B=n[l>>2]|0,n[A>>2]=0,n[u>>2]=n[A>>2],Ez(k,B,u),n[d>>2]=k,I=T,m|0}function Ez(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Kt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1092,n[u+12>>2]=l,n[o+4>>2]=u}function Xke(o){o=o|0,$y(o),It(o)}function Zke(o){o=o|0,o=n[o+12>>2]|0,o|0&&It(o)}function $ke(o){o=o|0,It(o)}function eQe(o,l,u,A,d,m,B){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,m=tQe(n[o>>2]|0,l,u,A,d,m,B)|0,B=o+4|0,n[(n[B>>2]|0)+8>>2]=m,n[(n[B>>2]|0)+8>>2]|0}function tQe(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0;var k=0,T=0;return k=I,I=I+16|0,T=k,Fl(T),o=Os(o)|0,B=rQe(o,+E[l>>3],+E[u>>3],+E[A>>3],+E[d>>3],+E[m>>3],+E[B>>3])|0,Nl(T),I=k,B|0}function rQe(o,l,u,A,d,m,B){o=o|0,l=+l,u=+u,A=+A,d=+d,m=+m,B=+B;var k=0;return k=da(nQe()|0)|0,l=+Ja(l),u=+Ja(u),A=+Ja(A),d=+Ja(d),m=+Ja(m),ro(0,k|0,o|0,+l,+u,+A,+d,+m,+ +Ja(B))|0}function nQe(){var o=0;return s[7624]|0||(iQe(9172),o=7624,n[o>>2]=1,n[o+4>>2]=0),9172}function iQe(o){o=o|0,Qo(o,sQe()|0,6)}function sQe(){return 1112}function oQe(o){o=o|0,Lh(o)}function aQe(o){o=o|0,Iz(o+24|0),Cz(o+16|0)}function Iz(o){o=o|0,cQe(o)}function Cz(o){o=o|0,lQe(o)}function lQe(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,It(u);while(l|0);n[o>>2]=0}function cQe(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,It(u);while(l|0);n[o>>2]=0}function Lh(o){o=o|0;var l=0;n[o+16>>2]=0,n[o+20>>2]=0,l=o+24|0,n[l>>2]=0,n[o+28>>2]=l,n[o+36>>2]=0,s[o+40>>0]=0,s[o+41>>0]=0}function uQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],fQe(o,u,d,0),I=A}function fQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=gM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=AQe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,pQe(m,A)|0,A),I=d}function gM(){var o=0,l=0;if(s[7640]|0||(Bz(9232),gr(26,9232,U|0)|0,l=7640,n[l>>2]=1,n[l+4>>2]=0),!(_r(9232)|0)){o=9232,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Bz(9232)}return 9232}function AQe(o){return o=o|0,0}function pQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=gM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],wz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(hQe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function wz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function hQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=gQe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,dQe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],wz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,mQe(o,k),yQe(k),I=M;return}}function gQe(o){return o=o|0,357913941}function dQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function mQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function yQe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Bz(o){o=o|0,CQe(o)}function EQe(o){o=o|0,IQe(o+24|0)}function IQe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function CQe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,1,l,wQe()|0,3),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function wQe(){return 1144}function BQe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+16|0,B=m+8|0,k=m,T=vQe(o)|0,o=n[T+4>>2]|0,n[k>>2]=n[T>>2],n[k+4>>2]=o,n[B>>2]=n[k>>2],n[B+4>>2]=n[k+4>>2],SQe(l,B,u,A,d),I=m}function vQe(o){return o=o|0,(n[(gM()|0)+24>>2]|0)+(o*12|0)|0}function SQe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0;var m=0,B=0,k=0,T=0,M=0;M=I,I=I+16|0,B=M+2|0,k=M+1|0,T=M,m=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(m=n[(n[o>>2]|0)+m>>2]|0),Qf(B,u),u=+Tf(B,u),Qf(k,A),A=+Tf(k,A),tp(T,d),T=rp(T,d)|0,MZ[m&1](o,u,A,T),I=M}function Qf(o,l){o=o|0,l=+l}function Tf(o,l){return o=o|0,l=+l,+ +bQe(l)}function tp(o,l){o=o|0,l=l|0}function rp(o,l){return o=o|0,l=l|0,DQe(l)|0}function DQe(o){return o=o|0,o|0}function bQe(o){return o=+o,+o}function PQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],xQe(o,u,d,1),I=A}function xQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=dM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=kQe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,QQe(m,A)|0,A),I=d}function dM(){var o=0,l=0;if(s[7648]|0||(Sz(9268),gr(27,9268,U|0)|0,l=7648,n[l>>2]=1,n[l+4>>2]=0),!(_r(9268)|0)){o=9268,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Sz(9268)}return 9268}function kQe(o){return o=o|0,0}function QQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=dM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],vz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(TQe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function vz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function TQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=RQe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,FQe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],vz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,NQe(o,k),OQe(k),I=M;return}}function RQe(o){return o=o|0,357913941}function FQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function NQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function OQe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Sz(o){o=o|0,UQe(o)}function LQe(o){o=o|0,MQe(o+24|0)}function MQe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function UQe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,4,l,_Qe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function _Qe(){return 1160}function HQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=jQe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=GQe(l,A)|0,I=u,l|0}function jQe(o){return o=o|0,(n[(dM()|0)+24>>2]|0)+(o*12|0)|0}function GQe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),Dz(gd[u&31](o)|0)|0}function Dz(o){return o=o|0,o&1|0}function qQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],WQe(o,u,d,0),I=A}function WQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=mM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=YQe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,VQe(m,A)|0,A),I=d}function mM(){var o=0,l=0;if(s[7656]|0||(Pz(9304),gr(28,9304,U|0)|0,l=7656,n[l>>2]=1,n[l+4>>2]=0),!(_r(9304)|0)){o=9304,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Pz(9304)}return 9304}function YQe(o){return o=o|0,0}function VQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=mM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],bz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(JQe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function bz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function JQe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=KQe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,zQe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],bz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,XQe(o,k),ZQe(k),I=M;return}}function KQe(o){return o=o|0,357913941}function zQe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function XQe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function ZQe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Pz(o){o=o|0,tTe(o)}function $Qe(o){o=o|0,eTe(o+24|0)}function eTe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function tTe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,5,l,rTe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function rTe(){return 1164}function nTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=iTe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],sTe(l,d,u),I=A}function iTe(o){return o=o|0,(n[(mM()|0)+24>>2]|0)+(o*12|0)|0}function sTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),Mh(d,u),u=Uh(d,u)|0,sp[A&31](o,u),_h(d),I=m}function Mh(o,l){o=o|0,l=l|0,oTe(o,l)}function Uh(o,l){return o=o|0,l=l|0,o|0}function _h(o){o=o|0,Sf(o)}function oTe(o,l){o=o|0,l=l|0,yM(o,l)}function yM(o,l){o=o|0,l=l|0,n[o>>2]=l}function aTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],lTe(o,u,d,0),I=A}function lTe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=EM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=cTe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,uTe(m,A)|0,A),I=d}function EM(){var o=0,l=0;if(s[7664]|0||(kz(9340),gr(29,9340,U|0)|0,l=7664,n[l>>2]=1,n[l+4>>2]=0),!(_r(9340)|0)){o=9340,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));kz(9340)}return 9340}function cTe(o){return o=o|0,0}function uTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=EM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],xz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(fTe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function xz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function fTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=ATe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,pTe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],xz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,hTe(o,k),gTe(k),I=M;return}}function ATe(o){return o=o|0,357913941}function pTe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function hTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function gTe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function kz(o){o=o|0,yTe(o)}function dTe(o){o=o|0,mTe(o+24|0)}function mTe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function yTe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,4,l,ETe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function ETe(){return 1180}function ITe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=CTe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=wTe(l,d,u)|0,I=A,u|0}function CTe(o){return o=o|0,(n[(EM()|0)+24>>2]|0)+(o*12|0)|0}function wTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),ad(d,u),d=ld(d,u)|0,d=TP(gU[A&15](o,d)|0)|0,I=m,d|0}function ad(o,l){o=o|0,l=l|0}function ld(o,l){return o=o|0,l=l|0,BTe(l)|0}function TP(o){return o=o|0,o|0}function BTe(o){return o=o|0,o|0}function vTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],STe(o,u,d,0),I=A}function STe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=IM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=DTe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,bTe(m,A)|0,A),I=d}function IM(){var o=0,l=0;if(s[7672]|0||(Tz(9376),gr(30,9376,U|0)|0,l=7672,n[l>>2]=1,n[l+4>>2]=0),!(_r(9376)|0)){o=9376,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Tz(9376)}return 9376}function DTe(o){return o=o|0,0}function bTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=IM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Qz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(PTe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function Qz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function PTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=xTe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,kTe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Qz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,QTe(o,k),TTe(k),I=M;return}}function xTe(o){return o=o|0,357913941}function kTe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function QTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function TTe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Tz(o){o=o|0,NTe(o)}function RTe(o){o=o|0,FTe(o+24|0)}function FTe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function NTe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,5,l,Rz()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Rz(){return 1196}function OTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=LTe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=MTe(l,A)|0,I=u,l|0}function LTe(o){return o=o|0,(n[(IM()|0)+24>>2]|0)+(o*12|0)|0}function MTe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),TP(gd[u&31](o)|0)|0}function UTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],_Te(o,u,d,1),I=A}function _Te(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=CM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=HTe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,jTe(m,A)|0,A),I=d}function CM(){var o=0,l=0;if(s[7680]|0||(Nz(9412),gr(31,9412,U|0)|0,l=7680,n[l>>2]=1,n[l+4>>2]=0),!(_r(9412)|0)){o=9412,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Nz(9412)}return 9412}function HTe(o){return o=o|0,0}function jTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=CM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Fz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(GTe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function Fz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function GTe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=qTe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,WTe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Fz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,YTe(o,k),VTe(k),I=M;return}}function qTe(o){return o=o|0,357913941}function WTe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function YTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function VTe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Nz(o){o=o|0,zTe(o)}function JTe(o){o=o|0,KTe(o+24|0)}function KTe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function zTe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,6,l,Oz()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Oz(){return 1200}function XTe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=ZTe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=$Te(l,A)|0,I=u,l|0}function ZTe(o){return o=o|0,(n[(CM()|0)+24>>2]|0)+(o*12|0)|0}function $Te(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),RP(gd[u&31](o)|0)|0}function RP(o){return o=o|0,o|0}function eRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],tRe(o,u,d,0),I=A}function tRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=wM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=rRe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,nRe(m,A)|0,A),I=d}function wM(){var o=0,l=0;if(s[7688]|0||(Mz(9448),gr(32,9448,U|0)|0,l=7688,n[l>>2]=1,n[l+4>>2]=0),!(_r(9448)|0)){o=9448,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Mz(9448)}return 9448}function rRe(o){return o=o|0,0}function nRe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=wM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Lz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(iRe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function Lz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function iRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=sRe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,oRe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Lz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,aRe(o,k),lRe(k),I=M;return}}function sRe(o){return o=o|0,357913941}function oRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function aRe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function lRe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Mz(o){o=o|0,fRe(o)}function cRe(o){o=o|0,uRe(o+24|0)}function uRe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function fRe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,6,l,Uz()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Uz(){return 1204}function ARe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=pRe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],hRe(l,d,u),I=A}function pRe(o){return o=o|0,(n[(wM()|0)+24>>2]|0)+(o*12|0)|0}function hRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),BM(d,u),d=vM(d,u)|0,sp[A&31](o,d),I=m}function BM(o,l){o=o|0,l=l|0}function vM(o,l){return o=o|0,l=l|0,gRe(l)|0}function gRe(o){return o=o|0,o|0}function dRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],mRe(o,u,d,0),I=A}function mRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=SM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=yRe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,ERe(m,A)|0,A),I=d}function SM(){var o=0,l=0;if(s[7696]|0||(Hz(9484),gr(33,9484,U|0)|0,l=7696,n[l>>2]=1,n[l+4>>2]=0),!(_r(9484)|0)){o=9484,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Hz(9484)}return 9484}function yRe(o){return o=o|0,0}function ERe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=SM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],_z(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(IRe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function _z(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function IRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=CRe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,wRe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],_z(m,A,u),n[T>>2]=(n[T>>2]|0)+12,BRe(o,k),vRe(k),I=M;return}}function CRe(o){return o=o|0,357913941}function wRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function BRe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function vRe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Hz(o){o=o|0,bRe(o)}function SRe(o){o=o|0,DRe(o+24|0)}function DRe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function bRe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,1,l,PRe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function PRe(){return 1212}function xRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=kRe(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],QRe(l,m,u,A),I=d}function kRe(o){return o=o|0,(n[(SM()|0)+24>>2]|0)+(o*12|0)|0}function QRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),BM(m,u),m=vM(m,u)|0,ad(B,A),B=ld(B,A)|0,F2[d&15](o,m,B),I=k}function TRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],RRe(o,u,d,1),I=A}function RRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=DM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=FRe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,NRe(m,A)|0,A),I=d}function DM(){var o=0,l=0;if(s[7704]|0||(Gz(9520),gr(34,9520,U|0)|0,l=7704,n[l>>2]=1,n[l+4>>2]=0),!(_r(9520)|0)){o=9520,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Gz(9520)}return 9520}function FRe(o){return o=o|0,0}function NRe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=DM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],jz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(ORe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function jz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function ORe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=LRe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,MRe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],jz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,URe(o,k),_Re(k),I=M;return}}function LRe(o){return o=o|0,357913941}function MRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function URe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function _Re(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Gz(o){o=o|0,GRe(o)}function HRe(o){o=o|0,jRe(o+24|0)}function jRe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function GRe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,1,l,qRe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function qRe(){return 1224}function WRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;return d=I,I=I+16|0,m=d+8|0,B=d,k=YRe(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],A=+VRe(l,m,u),I=d,+A}function YRe(o){return o=o|0,(n[(DM()|0)+24>>2]|0)+(o*12|0)|0}function VRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),tp(d,u),d=rp(d,u)|0,B=+kf(+_Z[A&7](o,d)),I=m,+B}function JRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],KRe(o,u,d,1),I=A}function KRe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=bM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=zRe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,XRe(m,A)|0,A),I=d}function bM(){var o=0,l=0;if(s[7712]|0||(Wz(9556),gr(35,9556,U|0)|0,l=7712,n[l>>2]=1,n[l+4>>2]=0),!(_r(9556)|0)){o=9556,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Wz(9556)}return 9556}function zRe(o){return o=o|0,0}function XRe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=bM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],qz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(ZRe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function qz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function ZRe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=$Re(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,eFe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],qz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,tFe(o,k),rFe(k),I=M;return}}function $Re(o){return o=o|0,357913941}function eFe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function tFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function rFe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Wz(o){o=o|0,sFe(o)}function nFe(o){o=o|0,iFe(o+24|0)}function iFe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function sFe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,5,l,oFe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function oFe(){return 1232}function aFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=lFe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=+cFe(l,d),I=A,+u}function lFe(o){return o=o|0,(n[(bM()|0)+24>>2]|0)+(o*12|0)|0}function cFe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),+ +kf(+UZ[u&15](o))}function uFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],fFe(o,u,d,1),I=A}function fFe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=PM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=AFe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,pFe(m,A)|0,A),I=d}function PM(){var o=0,l=0;if(s[7720]|0||(Vz(9592),gr(36,9592,U|0)|0,l=7720,n[l>>2]=1,n[l+4>>2]=0),!(_r(9592)|0)){o=9592,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Vz(9592)}return 9592}function AFe(o){return o=o|0,0}function pFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=PM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Yz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(hFe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function Yz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function hFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=gFe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,dFe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Yz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,mFe(o,k),yFe(k),I=M;return}}function gFe(o){return o=o|0,357913941}function dFe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function mFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function yFe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Vz(o){o=o|0,CFe(o)}function EFe(o){o=o|0,IFe(o+24|0)}function IFe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function CFe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,7,l,wFe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function wFe(){return 1276}function BFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=vFe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=SFe(l,A)|0,I=u,l|0}function vFe(o){return o=o|0,(n[(PM()|0)+24>>2]|0)+(o*12|0)|0}function SFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+16|0,A=d,u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),sp[u&31](A,o),A=Jz(A)|0,I=d,A|0}function Jz(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=fM(Kz()|0)|0,A?(AM(l,A),pM(u,l),DFe(o,u),o=hM(l)|0):o=bFe(o)|0,I=d,o|0}function Kz(){var o=0;return s[7736]|0||(LFe(9640),gr(25,9640,U|0)|0,o=7736,n[o>>2]=1,n[o+4>>2]=0),9640}function DFe(o,l){o=o|0,l=l|0,QFe(l,o,o+8|0)|0}function bFe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Rl(8)|0,l=A,k=Kt(16)|0,n[k>>2]=n[o>>2],n[k+4>>2]=n[o+4>>2],n[k+8>>2]=n[o+8>>2],n[k+12>>2]=n[o+12>>2],m=l+4|0,n[m>>2]=k,o=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],xM(o,m,d),n[A>>2]=o,I=u,l|0}function xM(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Kt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1244,n[u+12>>2]=l,n[o+4>>2]=u}function PFe(o){o=o|0,$y(o),It(o)}function xFe(o){o=o|0,o=n[o+12>>2]|0,o|0&&It(o)}function kFe(o){o=o|0,It(o)}function QFe(o,l,u){return o=o|0,l=l|0,u=u|0,l=TFe(n[o>>2]|0,l,u)|0,u=o+4|0,n[(n[u>>2]|0)+8>>2]=l,n[(n[u>>2]|0)+8>>2]|0}function TFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;return A=I,I=I+16|0,d=A,Fl(d),o=Os(o)|0,u=RFe(o,n[l>>2]|0,+E[u>>3])|0,Nl(d),I=A,u|0}function RFe(o,l,u){o=o|0,l=l|0,u=+u;var A=0;return A=da(FFe()|0)|0,l=Yy(l)|0,ou(0,A|0,o|0,l|0,+ +Ja(u))|0}function FFe(){var o=0;return s[7728]|0||(NFe(9628),o=7728,n[o>>2]=1,n[o+4>>2]=0),9628}function NFe(o){o=o|0,Qo(o,OFe()|0,2)}function OFe(){return 1264}function LFe(o){o=o|0,Lh(o)}function MFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],UFe(o,u,d,1),I=A}function UFe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=kM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=_Fe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,HFe(m,A)|0,A),I=d}function kM(){var o=0,l=0;if(s[7744]|0||(Xz(9684),gr(37,9684,U|0)|0,l=7744,n[l>>2]=1,n[l+4>>2]=0),!(_r(9684)|0)){o=9684,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));Xz(9684)}return 9684}function _Fe(o){return o=o|0,0}function HFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=kM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],zz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(jFe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function zz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function jFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=GFe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,qFe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],zz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,WFe(o,k),YFe(k),I=M;return}}function GFe(o){return o=o|0,357913941}function qFe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function WFe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function YFe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function Xz(o){o=o|0,KFe(o)}function VFe(o){o=o|0,JFe(o+24|0)}function JFe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function KFe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,5,l,zFe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function zFe(){return 1280}function XFe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=ZFe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=$Fe(l,d,u)|0,I=A,u|0}function ZFe(o){return o=o|0,(n[(kM()|0)+24>>2]|0)+(o*12|0)|0}function $Fe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return B=I,I=I+32|0,d=B,m=B+16|0,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),tp(m,u),m=rp(m,u)|0,F2[A&15](d,o,m),m=Jz(d)|0,I=B,m|0}function eNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],tNe(o,u,d,1),I=A}function tNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=QM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=rNe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,nNe(m,A)|0,A),I=d}function QM(){var o=0,l=0;if(s[7752]|0||($z(9720),gr(38,9720,U|0)|0,l=7752,n[l>>2]=1,n[l+4>>2]=0),!(_r(9720)|0)){o=9720,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));$z(9720)}return 9720}function rNe(o){return o=o|0,0}function nNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=QM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],Zz(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(iNe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function Zz(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function iNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=sNe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,oNe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],Zz(m,A,u),n[T>>2]=(n[T>>2]|0)+12,aNe(o,k),lNe(k),I=M;return}}function sNe(o){return o=o|0,357913941}function oNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function aNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function lNe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function $z(o){o=o|0,fNe(o)}function cNe(o){o=o|0,uNe(o+24|0)}function uNe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function fNe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,8,l,ANe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function ANe(){return 1288}function pNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=hNe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=gNe(l,A)|0,I=u,l|0}function hNe(o){return o=o|0,(n[(QM()|0)+24>>2]|0)+(o*12|0)|0}function gNe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),sd(gd[u&31](o)|0)|0}function dNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],mNe(o,u,d,0),I=A}function mNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=TM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=yNe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,ENe(m,A)|0,A),I=d}function TM(){var o=0,l=0;if(s[7760]|0||(tX(9756),gr(39,9756,U|0)|0,l=7760,n[l>>2]=1,n[l+4>>2]=0),!(_r(9756)|0)){o=9756,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));tX(9756)}return 9756}function yNe(o){return o=o|0,0}function ENe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=TM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],eX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(INe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function eX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function INe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=CNe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,wNe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],eX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,BNe(o,k),vNe(k),I=M;return}}function CNe(o){return o=o|0,357913941}function wNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function BNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function vNe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function tX(o){o=o|0,bNe(o)}function SNe(o){o=o|0,DNe(o+24|0)}function DNe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function bNe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,8,l,PNe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function PNe(){return 1292}function xNe(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=kNe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],QNe(l,d,u),I=A}function kNe(o){return o=o|0,(n[(TM()|0)+24>>2]|0)+(o*12|0)|0}function QNe(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),Qf(d,u),u=+Tf(d,u),OZ[A&31](o,u),I=m}function TNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],RNe(o,u,d,0),I=A}function RNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=RM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=FNe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,NNe(m,A)|0,A),I=d}function RM(){var o=0,l=0;if(s[7768]|0||(nX(9792),gr(40,9792,U|0)|0,l=7768,n[l>>2]=1,n[l+4>>2]=0),!(_r(9792)|0)){o=9792,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));nX(9792)}return 9792}function FNe(o){return o=o|0,0}function NNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=RM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],rX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(ONe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function rX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function ONe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=LNe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,MNe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],rX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,UNe(o,k),_Ne(k),I=M;return}}function LNe(o){return o=o|0,357913941}function MNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function UNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function _Ne(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function nX(o){o=o|0,GNe(o)}function HNe(o){o=o|0,jNe(o+24|0)}function jNe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function GNe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,1,l,qNe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function qNe(){return 1300}function WNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=YNe(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],VNe(l,m,u,A),I=d}function YNe(o){return o=o|0,(n[(RM()|0)+24>>2]|0)+(o*12|0)|0}function VNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),tp(m,u),m=rp(m,u)|0,Qf(B,A),A=+Tf(B,A),qZ[d&15](o,m,A),I=k}function JNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],KNe(o,u,d,0),I=A}function KNe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=FM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=zNe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,XNe(m,A)|0,A),I=d}function FM(){var o=0,l=0;if(s[7776]|0||(sX(9828),gr(41,9828,U|0)|0,l=7776,n[l>>2]=1,n[l+4>>2]=0),!(_r(9828)|0)){o=9828,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));sX(9828)}return 9828}function zNe(o){return o=o|0,0}function XNe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=FM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],iX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(ZNe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function iX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function ZNe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=$Ne(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,eOe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],iX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,tOe(o,k),rOe(k),I=M;return}}function $Ne(o){return o=o|0,357913941}function eOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function tOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function rOe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function sX(o){o=o|0,sOe(o)}function nOe(o){o=o|0,iOe(o+24|0)}function iOe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function sOe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,7,l,oOe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function oOe(){return 1312}function aOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=lOe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],cOe(l,d,u),I=A}function lOe(o){return o=o|0,(n[(FM()|0)+24>>2]|0)+(o*12|0)|0}function cOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),tp(d,u),d=rp(d,u)|0,sp[A&31](o,d),I=m}function uOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],fOe(o,u,d,0),I=A}function fOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=NM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=AOe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,pOe(m,A)|0,A),I=d}function NM(){var o=0,l=0;if(s[7784]|0||(aX(9864),gr(42,9864,U|0)|0,l=7784,n[l>>2]=1,n[l+4>>2]=0),!(_r(9864)|0)){o=9864,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));aX(9864)}return 9864}function AOe(o){return o=o|0,0}function pOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=NM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],oX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(hOe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function oX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function hOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=gOe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,dOe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],oX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,mOe(o,k),yOe(k),I=M;return}}function gOe(o){return o=o|0,357913941}function dOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function mOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function yOe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function aX(o){o=o|0,COe(o)}function EOe(o){o=o|0,IOe(o+24|0)}function IOe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function COe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,8,l,wOe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function wOe(){return 1320}function BOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=vOe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],SOe(l,d,u),I=A}function vOe(o){return o=o|0,(n[(NM()|0)+24>>2]|0)+(o*12|0)|0}function SOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),DOe(d,u),d=bOe(d,u)|0,sp[A&31](o,d),I=m}function DOe(o,l){o=o|0,l=l|0}function bOe(o,l){return o=o|0,l=l|0,POe(l)|0}function POe(o){return o=o|0,o|0}function xOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],kOe(o,u,d,0),I=A}function kOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=OM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=QOe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,TOe(m,A)|0,A),I=d}function OM(){var o=0,l=0;if(s[7792]|0||(cX(9900),gr(43,9900,U|0)|0,l=7792,n[l>>2]=1,n[l+4>>2]=0),!(_r(9900)|0)){o=9900,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));cX(9900)}return 9900}function QOe(o){return o=o|0,0}function TOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=OM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],lX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(ROe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function lX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function ROe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=FOe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,NOe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],lX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,OOe(o,k),LOe(k),I=M;return}}function FOe(o){return o=o|0,357913941}function NOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function OOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function LOe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function cX(o){o=o|0,_Oe(o)}function MOe(o){o=o|0,UOe(o+24|0)}function UOe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function _Oe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,22,l,HOe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function HOe(){return 1344}function jOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;u=I,I=I+16|0,A=u+8|0,d=u,m=GOe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],qOe(l,A),I=u}function GOe(o){return o=o|0,(n[(OM()|0)+24>>2]|0)+(o*12|0)|0}function qOe(o,l){o=o|0,l=l|0;var u=0;u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),ip[u&127](o)}function WOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=LM()|0,o=YOe(u)|0,vn(m,l,d,o,VOe(u,A)|0,A)}function LM(){var o=0,l=0;if(s[7800]|0||(fX(9936),gr(44,9936,U|0)|0,l=7800,n[l>>2]=1,n[l+4>>2]=0),!(_r(9936)|0)){o=9936,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));fX(9936)}return 9936}function YOe(o){return o=o|0,o|0}function VOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=LM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(uX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(JOe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function uX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function JOe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=KOe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,zOe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,uX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,XOe(o,d),ZOe(d),I=k;return}}function KOe(o){return o=o|0,536870911}function zOe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function XOe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function ZOe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function fX(o){o=o|0,tLe(o)}function $Oe(o){o=o|0,eLe(o+24|0)}function eLe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function tLe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,23,l,Uz()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function rLe(o,l){o=o|0,l=l|0,iLe(n[(nLe(o)|0)>>2]|0,l)}function nLe(o){return o=o|0,(n[(LM()|0)+24>>2]|0)+(o<<3)|0}function iLe(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,BM(A,l),l=vM(A,l)|0,ip[o&127](l),I=u}function sLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=MM()|0,o=oLe(u)|0,vn(m,l,d,o,aLe(u,A)|0,A)}function MM(){var o=0,l=0;if(s[7808]|0||(pX(9972),gr(45,9972,U|0)|0,l=7808,n[l>>2]=1,n[l+4>>2]=0),!(_r(9972)|0)){o=9972,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));pX(9972)}return 9972}function oLe(o){return o=o|0,o|0}function aLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=MM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(AX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(lLe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function AX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function lLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=cLe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,uLe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,AX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,fLe(o,d),ALe(d),I=k;return}}function cLe(o){return o=o|0,536870911}function uLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function fLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function ALe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function pX(o){o=o|0,gLe(o)}function pLe(o){o=o|0,hLe(o+24|0)}function hLe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function gLe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,9,l,dLe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function dLe(){return 1348}function mLe(o,l){return o=o|0,l=l|0,ELe(n[(yLe(o)|0)>>2]|0,l)|0}function yLe(o){return o=o|0,(n[(MM()|0)+24>>2]|0)+(o<<3)|0}function ELe(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,hX(A,l),l=gX(A,l)|0,l=TP(gd[o&31](l)|0)|0,I=u,l|0}function hX(o,l){o=o|0,l=l|0}function gX(o,l){return o=o|0,l=l|0,ILe(l)|0}function ILe(o){return o=o|0,o|0}function CLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=UM()|0,o=wLe(u)|0,vn(m,l,d,o,BLe(u,A)|0,A)}function UM(){var o=0,l=0;if(s[7816]|0||(mX(10008),gr(46,10008,U|0)|0,l=7816,n[l>>2]=1,n[l+4>>2]=0),!(_r(10008)|0)){o=10008,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));mX(10008)}return 10008}function wLe(o){return o=o|0,o|0}function BLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=UM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(dX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(vLe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function dX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function vLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=SLe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,DLe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,dX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,bLe(o,d),PLe(d),I=k;return}}function SLe(o){return o=o|0,536870911}function DLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function bLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function PLe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function mX(o){o=o|0,QLe(o)}function xLe(o){o=o|0,kLe(o+24|0)}function kLe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function QLe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,15,l,Rz()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function TLe(o){return o=o|0,FLe(n[(RLe(o)|0)>>2]|0)|0}function RLe(o){return o=o|0,(n[(UM()|0)+24>>2]|0)+(o<<3)|0}function FLe(o){return o=o|0,TP(VP[o&7]()|0)|0}function NLe(){var o=0;return s[7832]|0||(GLe(10052),gr(25,10052,U|0)|0,o=7832,n[o>>2]=1,n[o+4>>2]=0),10052}function OLe(o,l){o=o|0,l=l|0,n[o>>2]=LLe()|0,n[o+4>>2]=MLe()|0,n[o+12>>2]=l,n[o+8>>2]=ULe()|0,n[o+32>>2]=2}function LLe(){return 11709}function MLe(){return 1188}function ULe(){return FP()|0}function _Le(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(HLe(u),It(u)):l|0&&(Oy(l),It(l))}function Hh(o,l){return o=o|0,l=l|0,l&o|0}function HLe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function FP(){var o=0;return s[7824]|0||(n[2511]=jLe()|0,n[2512]=0,o=7824,n[o>>2]=1,n[o+4>>2]=0),10044}function jLe(){return 0}function GLe(o){o=o|0,Lh(o)}function qLe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0;l=I,I=I+32|0,u=l+24|0,m=l+16|0,d=l+8|0,A=l,WLe(o,4827),YLe(o,4834,3)|0,VLe(o,3682,47)|0,n[m>>2]=9,n[m+4>>2]=0,n[u>>2]=n[m>>2],n[u+4>>2]=n[m+4>>2],JLe(o,4841,u)|0,n[d>>2]=1,n[d+4>>2]=0,n[u>>2]=n[d>>2],n[u+4>>2]=n[d+4>>2],KLe(o,4871,u)|0,n[A>>2]=10,n[A+4>>2]=0,n[u>>2]=n[A>>2],n[u+4>>2]=n[A+4>>2],zLe(o,4891,u)|0,I=l}function WLe(o,l){o=o|0,l=l|0;var u=0;u=PUe()|0,n[o>>2]=u,xUe(u,l),jh(n[o>>2]|0)}function YLe(o,l,u){return o=o|0,l=l|0,u=u|0,AUe(o,Bn(l)|0,u,0),o|0}function VLe(o,l,u){return o=o|0,l=l|0,u=u|0,XMe(o,Bn(l)|0,u,0),o|0}function JLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],TMe(o,l,d),I=A,o|0}function KLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],pMe(o,l,d),I=A,o|0}function zLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],XLe(o,l,d),I=A,o|0}function XLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],ZLe(o,u,d,1),I=A}function ZLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=_M()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=$Le(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,eMe(m,A)|0,A),I=d}function _M(){var o=0,l=0;if(s[7840]|0||(EX(10100),gr(48,10100,U|0)|0,l=7840,n[l>>2]=1,n[l+4>>2]=0),!(_r(10100)|0)){o=10100,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));EX(10100)}return 10100}function $Le(o){return o=o|0,0}function eMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=_M()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],yX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(tMe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function yX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function tMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=rMe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,nMe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],yX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,iMe(o,k),sMe(k),I=M;return}}function rMe(o){return o=o|0,357913941}function nMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function iMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function sMe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function EX(o){o=o|0,lMe(o)}function oMe(o){o=o|0,aMe(o+24|0)}function aMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function lMe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,6,l,cMe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function cMe(){return 1364}function uMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=fMe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=AMe(l,d,u)|0,I=A,u|0}function fMe(o){return o=o|0,(n[(_M()|0)+24>>2]|0)+(o*12|0)|0}function AMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),tp(d,u),d=rp(d,u)|0,d=Dz(gU[A&15](o,d)|0)|0,I=m,d|0}function pMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],hMe(o,u,d,0),I=A}function hMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=HM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=gMe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,dMe(m,A)|0,A),I=d}function HM(){var o=0,l=0;if(s[7848]|0||(CX(10136),gr(49,10136,U|0)|0,l=7848,n[l>>2]=1,n[l+4>>2]=0),!(_r(10136)|0)){o=10136,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));CX(10136)}return 10136}function gMe(o){return o=o|0,0}function dMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=HM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],IX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(mMe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function IX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function mMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=yMe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,EMe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],IX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,IMe(o,k),CMe(k),I=M;return}}function yMe(o){return o=o|0,357913941}function EMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function IMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function CMe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function CX(o){o=o|0,vMe(o)}function wMe(o){o=o|0,BMe(o+24|0)}function BMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function vMe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,9,l,SMe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function SMe(){return 1372}function DMe(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=bMe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],PMe(l,d,u),I=A}function bMe(o){return o=o|0,(n[(HM()|0)+24>>2]|0)+(o*12|0)|0}function PMe(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=$e;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),xMe(d,u),B=y(kMe(d,u)),NZ[A&1](o,B),I=m}function xMe(o,l){o=o|0,l=+l}function kMe(o,l){return o=o|0,l=+l,y(QMe(l))}function QMe(o){return o=+o,y(o)}function TMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],RMe(o,u,d,0),I=A}function RMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,M=0,L=0;d=I,I=I+32|0,m=d+16|0,L=d+8|0,k=d,M=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=jM()|0,n[L>>2]=M,n[L+4>>2]=T,n[m>>2]=n[L>>2],n[m+4>>2]=n[L+4>>2],u=FMe(m)|0,n[k>>2]=M,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,NMe(m,A)|0,A),I=d}function jM(){var o=0,l=0;if(s[7856]|0||(BX(10172),gr(50,10172,U|0)|0,l=7856,n[l>>2]=1,n[l+4>>2]=0),!(_r(10172)|0)){o=10172,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));BX(10172)}return 10172}function FMe(o){return o=o|0,0}function NMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0;return L=I,I=I+32|0,d=L+24|0,B=L+16|0,k=L,T=L+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,q=jM()|0,M=q+24|0,o=yr(l,4)|0,n[T>>2]=o,l=q+28|0,u=n[l>>2]|0,u>>>0<(n[q+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],wX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(OMe(M,k,T),o=n[l>>2]|0),I=L,((o-(n[M>>2]|0)|0)/12|0)+-1|0}function wX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function OMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;if(M=I,I=I+48|0,A=M+32|0,B=M+24|0,k=M,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=LMe(o)|0,m>>>0>>0)an(o);else{L=n[o>>2]|0,ae=((n[o+8>>2]|0)-L|0)/12|0,q=ae<<1,MMe(k,ae>>>0>>1>>>0?q>>>0>>0?d:q:m,((n[T>>2]|0)-L|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],wX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,UMe(o,k),_Me(k),I=M;return}}function LMe(o){return o=o|0,357913941}function MMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Kt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function UMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function _Me(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&It(o)}function BX(o){o=o|0,GMe(o)}function HMe(o){o=o|0,jMe(o+24|0)}function jMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),It(u))}function GMe(o){o=o|0;var l=0;l=tn()|0,rn(o,2,3,l,qMe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function qMe(){return 1380}function WMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=YMe(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],VMe(l,m,u,A),I=d}function YMe(o){return o=o|0,(n[(jM()|0)+24>>2]|0)+(o*12|0)|0}function VMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),tp(m,u),m=rp(m,u)|0,JMe(B,A),B=KMe(B,A)|0,F2[d&15](o,m,B),I=k}function JMe(o,l){o=o|0,l=l|0}function KMe(o,l){return o=o|0,l=l|0,zMe(l)|0}function zMe(o){return o=o|0,(o|0)!=0|0}function XMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=GM()|0,o=ZMe(u)|0,vn(m,l,d,o,$Me(u,A)|0,A)}function GM(){var o=0,l=0;if(s[7864]|0||(SX(10208),gr(51,10208,U|0)|0,l=7864,n[l>>2]=1,n[l+4>>2]=0),!(_r(10208)|0)){o=10208,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));SX(10208)}return 10208}function ZMe(o){return o=o|0,o|0}function $Me(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=GM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(vX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(eUe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function vX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function eUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=tUe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,rUe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,vX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,nUe(o,d),iUe(d),I=k;return}}function tUe(o){return o=o|0,536870911}function rUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function nUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function iUe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function SX(o){o=o|0,aUe(o)}function sUe(o){o=o|0,oUe(o+24|0)}function oUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function aUe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,24,l,lUe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function lUe(){return 1392}function cUe(o,l){o=o|0,l=l|0,fUe(n[(uUe(o)|0)>>2]|0,l)}function uUe(o){return o=o|0,(n[(GM()|0)+24>>2]|0)+(o<<3)|0}function fUe(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,hX(A,l),l=gX(A,l)|0,ip[o&127](l),I=u}function AUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=qM()|0,o=pUe(u)|0,vn(m,l,d,o,hUe(u,A)|0,A)}function qM(){var o=0,l=0;if(s[7872]|0||(bX(10244),gr(52,10244,U|0)|0,l=7872,n[l>>2]=1,n[l+4>>2]=0),!(_r(10244)|0)){o=10244,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));bX(10244)}return 10244}function pUe(o){return o=o|0,o|0}function hUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=qM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(DX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(gUe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function DX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function gUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=dUe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,mUe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,DX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,yUe(o,d),EUe(d),I=k;return}}function dUe(o){return o=o|0,536870911}function mUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function yUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function EUe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function bX(o){o=o|0,wUe(o)}function IUe(o){o=o|0,CUe(o+24|0)}function CUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function wUe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,16,l,BUe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function BUe(){return 1400}function vUe(o){return o=o|0,DUe(n[(SUe(o)|0)>>2]|0)|0}function SUe(o){return o=o|0,(n[(qM()|0)+24>>2]|0)+(o<<3)|0}function DUe(o){return o=o|0,bUe(VP[o&7]()|0)|0}function bUe(o){return o=o|0,o|0}function PUe(){var o=0;return s[7880]|0||(NUe(10280),gr(25,10280,U|0)|0,o=7880,n[o>>2]=1,n[o+4>>2]=0),10280}function xUe(o,l){o=o|0,l=l|0,n[o>>2]=kUe()|0,n[o+4>>2]=QUe()|0,n[o+12>>2]=l,n[o+8>>2]=TUe()|0,n[o+32>>2]=4}function kUe(){return 11711}function QUe(){return 1356}function TUe(){return FP()|0}function RUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(FUe(u),It(u)):l|0&&(Kg(l),It(l))}function FUe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function NUe(o){o=o|0,Lh(o)}function OUe(o){o=o|0,LUe(o,4920),MUe(o)|0,UUe(o)|0}function LUe(o,l){o=o|0,l=l|0;var u=0;u=Kz()|0,n[o>>2]=u,o_e(u,l),jh(n[o>>2]|0)}function MUe(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,zUe()|0),o|0}function UUe(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,_Ue()|0),o|0}function _Ue(){var o=0;return s[7888]|0||(PX(10328),gr(53,10328,U|0)|0,o=7888,n[o>>2]=1,n[o+4>>2]=0),_r(10328)|0||PX(10328),10328}function cd(o,l){o=o|0,l=l|0,vn(o,0,l,0,0,0)}function PX(o){o=o|0,GUe(o),ud(o,10)}function HUe(o){o=o|0,jUe(o+24|0)}function jUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function GUe(o){o=o|0;var l=0;l=tn()|0,rn(o,5,1,l,VUe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function qUe(o,l,u){o=o|0,l=l|0,u=+u,WUe(o,l,u)}function ud(o,l){o=o|0,l=l|0,n[o+20>>2]=l}function WUe(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,m=A+8|0,k=A+13|0,d=A,B=A+12|0,tp(k,l),n[m>>2]=rp(k,l)|0,Qf(B,u),E[d>>3]=+Tf(B,u),YUe(o,m,d),I=A}function YUe(o,l,u){o=o|0,l=l|0,u=u|0,Tl(o+8|0,n[l>>2]|0,+E[u>>3]),s[o+24>>0]=1}function VUe(){return 1404}function JUe(o,l){return o=o|0,l=+l,KUe(o,l)|0}function KUe(o,l){o=o|0,l=+l;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+16|0,m=A+4|0,B=A+8|0,k=A,d=Rl(8)|0,u=d,T=Kt(16)|0,tp(m,o),o=rp(m,o)|0,Qf(B,l),Tl(T,o,+Tf(B,l)),B=u+4|0,n[B>>2]=T,o=Kt(8)|0,B=n[B>>2]|0,n[k>>2]=0,n[m>>2]=n[k>>2],xM(o,B,m),n[d>>2]=o,I=A,u|0}function zUe(){var o=0;return s[7896]|0||(xX(10364),gr(54,10364,U|0)|0,o=7896,n[o>>2]=1,n[o+4>>2]=0),_r(10364)|0||xX(10364),10364}function xX(o){o=o|0,$Ue(o),ud(o,55)}function XUe(o){o=o|0,ZUe(o+24|0)}function ZUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function $Ue(o){o=o|0;var l=0;l=tn()|0,rn(o,5,4,l,n_e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function e_e(o){o=o|0,t_e(o)}function t_e(o){o=o|0,r_e(o)}function r_e(o){o=o|0,kX(o+8|0),s[o+24>>0]=1}function kX(o){o=o|0,n[o>>2]=0,E[o+8>>3]=0}function n_e(){return 1424}function i_e(){return s_e()|0}function s_e(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Rl(8)|0,o=u,A=Kt(16)|0,kX(A),m=o+4|0,n[m>>2]=A,A=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],xM(A,m,d),n[u>>2]=A,I=l,o|0}function o_e(o,l){o=o|0,l=l|0,n[o>>2]=a_e()|0,n[o+4>>2]=l_e()|0,n[o+12>>2]=l,n[o+8>>2]=c_e()|0,n[o+32>>2]=5}function a_e(){return 11710}function l_e(){return 1416}function c_e(){return NP()|0}function u_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(f_e(u),It(u)):l|0&&It(l)}function f_e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function NP(){var o=0;return s[7904]|0||(n[2600]=A_e()|0,n[2601]=0,o=7904,n[o>>2]=1,n[o+4>>2]=0),10400}function A_e(){return n[357]|0}function p_e(o){o=o|0,h_e(o,4926),g_e(o)|0}function h_e(o,l){o=o|0,l=l|0;var u=0;u=yz()|0,n[o>>2]=u,D_e(u,l),jh(n[o>>2]|0)}function g_e(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,d_e()|0),o|0}function d_e(){var o=0;return s[7912]|0||(QX(10412),gr(56,10412,U|0)|0,o=7912,n[o>>2]=1,n[o+4>>2]=0),_r(10412)|0||QX(10412),10412}function QX(o){o=o|0,E_e(o),ud(o,57)}function m_e(o){o=o|0,y_e(o+24|0)}function y_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function E_e(o){o=o|0;var l=0;l=tn()|0,rn(o,5,5,l,B_e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function I_e(o){o=o|0,C_e(o)}function C_e(o){o=o|0,w_e(o)}function w_e(o){o=o|0;var l=0,u=0;l=o+8|0,u=l+48|0;do n[l>>2]=0,l=l+4|0;while((l|0)<(u|0));s[o+56>>0]=1}function B_e(){return 1432}function v_e(){return S_e()|0}function S_e(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0,k=0;B=I,I=I+16|0,o=B+4|0,l=B,u=Rl(8)|0,A=u,d=Kt(48)|0,m=d,k=m+48|0;do n[m>>2]=0,m=m+4|0;while((m|0)<(k|0));return m=A+4|0,n[m>>2]=d,k=Kt(8)|0,m=n[m>>2]|0,n[l>>2]=0,n[o>>2]=n[l>>2],Ez(k,m,o),n[u>>2]=k,I=B,A|0}function D_e(o,l){o=o|0,l=l|0,n[o>>2]=b_e()|0,n[o+4>>2]=P_e()|0,n[o+12>>2]=l,n[o+8>>2]=x_e()|0,n[o+32>>2]=6}function b_e(){return 11704}function P_e(){return 1436}function x_e(){return NP()|0}function k_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(Q_e(u),It(u)):l|0&&It(l)}function Q_e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function T_e(o){o=o|0,R_e(o,4933),F_e(o)|0,N_e(o)|0}function R_e(o,l){o=o|0,l=l|0;var u=0;u=s4e()|0,n[o>>2]=u,o4e(u,l),jh(n[o>>2]|0)}function F_e(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,K_e()|0),o|0}function N_e(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,O_e()|0),o|0}function O_e(){var o=0;return s[7920]|0||(TX(10452),gr(58,10452,U|0)|0,o=7920,n[o>>2]=1,n[o+4>>2]=0),_r(10452)|0||TX(10452),10452}function TX(o){o=o|0,U_e(o),ud(o,1)}function L_e(o){o=o|0,M_e(o+24|0)}function M_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function U_e(o){o=o|0;var l=0;l=tn()|0,rn(o,5,1,l,G_e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function __e(o,l,u){o=o|0,l=+l,u=+u,H_e(o,l,u)}function H_e(o,l,u){o=o|0,l=+l,u=+u;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,m=A+8|0,k=A+17|0,d=A,B=A+16|0,Qf(k,l),E[m>>3]=+Tf(k,l),Qf(B,u),E[d>>3]=+Tf(B,u),j_e(o,m,d),I=A}function j_e(o,l,u){o=o|0,l=l|0,u=u|0,RX(o+8|0,+E[l>>3],+E[u>>3]),s[o+24>>0]=1}function RX(o,l,u){o=o|0,l=+l,u=+u,E[o>>3]=l,E[o+8>>3]=u}function G_e(){return 1472}function q_e(o,l){return o=+o,l=+l,W_e(o,l)|0}function W_e(o,l){o=+o,l=+l;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+16|0,B=A+4|0,k=A+8|0,T=A,d=Rl(8)|0,u=d,m=Kt(16)|0,Qf(B,o),o=+Tf(B,o),Qf(k,l),RX(m,o,+Tf(k,l)),k=u+4|0,n[k>>2]=m,m=Kt(8)|0,k=n[k>>2]|0,n[T>>2]=0,n[B>>2]=n[T>>2],FX(m,k,B),n[d>>2]=m,I=A,u|0}function FX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Kt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1452,n[u+12>>2]=l,n[o+4>>2]=u}function Y_e(o){o=o|0,$y(o),It(o)}function V_e(o){o=o|0,o=n[o+12>>2]|0,o|0&&It(o)}function J_e(o){o=o|0,It(o)}function K_e(){var o=0;return s[7928]|0||(NX(10488),gr(59,10488,U|0)|0,o=7928,n[o>>2]=1,n[o+4>>2]=0),_r(10488)|0||NX(10488),10488}function NX(o){o=o|0,Z_e(o),ud(o,60)}function z_e(o){o=o|0,X_e(o+24|0)}function X_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function Z_e(o){o=o|0;var l=0;l=tn()|0,rn(o,5,6,l,r4e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function $_e(o){o=o|0,e4e(o)}function e4e(o){o=o|0,t4e(o)}function t4e(o){o=o|0,OX(o+8|0),s[o+24>>0]=1}function OX(o){o=o|0,n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,n[o+12>>2]=0}function r4e(){return 1492}function n4e(){return i4e()|0}function i4e(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Rl(8)|0,o=u,A=Kt(16)|0,OX(A),m=o+4|0,n[m>>2]=A,A=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],FX(A,m,d),n[u>>2]=A,I=l,o|0}function s4e(){var o=0;return s[7936]|0||(A4e(10524),gr(25,10524,U|0)|0,o=7936,n[o>>2]=1,n[o+4>>2]=0),10524}function o4e(o,l){o=o|0,l=l|0,n[o>>2]=a4e()|0,n[o+4>>2]=l4e()|0,n[o+12>>2]=l,n[o+8>>2]=c4e()|0,n[o+32>>2]=7}function a4e(){return 11700}function l4e(){return 1484}function c4e(){return NP()|0}function u4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(f4e(u),It(u)):l|0&&It(l)}function f4e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function A4e(o){o=o|0,Lh(o)}function p4e(o,l,u){o=o|0,l=l|0,u=u|0,o=Bn(l)|0,l=h4e(u)|0,u=g4e(u,0)|0,W4e(o,l,u,WM()|0,0)}function h4e(o){return o=o|0,o|0}function g4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=WM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(MX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(w4e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function WM(){var o=0,l=0;if(s[7944]|0||(LX(10568),gr(61,10568,U|0)|0,l=7944,n[l>>2]=1,n[l+4>>2]=0),!(_r(10568)|0)){o=10568,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));LX(10568)}return 10568}function LX(o){o=o|0,y4e(o)}function d4e(o){o=o|0,m4e(o+24|0)}function m4e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function y4e(o){o=o|0;var l=0;l=tn()|0,rn(o,1,17,l,Oz()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function E4e(o){return o=o|0,C4e(n[(I4e(o)|0)>>2]|0)|0}function I4e(o){return o=o|0,(n[(WM()|0)+24>>2]|0)+(o<<3)|0}function C4e(o){return o=o|0,RP(VP[o&7]()|0)|0}function MX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function w4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=B4e(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,v4e(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,MX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,S4e(o,d),D4e(d),I=k;return}}function B4e(o){return o=o|0,536870911}function v4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function S4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function D4e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function b4e(){P4e()}function P4e(){x4e(10604)}function x4e(o){o=o|0,k4e(o,4955)}function k4e(o,l){o=o|0,l=l|0;var u=0;u=Q4e()|0,n[o>>2]=u,T4e(u,l),jh(n[o>>2]|0)}function Q4e(){var o=0;return s[7952]|0||(H4e(10612),gr(25,10612,U|0)|0,o=7952,n[o>>2]=1,n[o+4>>2]=0),10612}function T4e(o,l){o=o|0,l=l|0,n[o>>2]=O4e()|0,n[o+4>>2]=L4e()|0,n[o+12>>2]=l,n[o+8>>2]=M4e()|0,n[o+32>>2]=8}function jh(o){o=o|0;var l=0,u=0;l=I,I=I+16|0,u=l,Jy()|0,n[u>>2]=o,R4e(10608,u),I=l}function Jy(){return s[11714]|0||(n[2652]=0,gr(62,10608,U|0)|0,s[11714]=1),10608}function R4e(o,l){o=o|0,l=l|0;var u=0;u=Kt(8)|0,n[u+4>>2]=n[l>>2],n[u>>2]=n[o>>2],n[o>>2]=u}function F4e(o){o=o|0,N4e(o)}function N4e(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,It(u);while(l|0);n[o>>2]=0}function O4e(){return 11715}function L4e(){return 1496}function M4e(){return FP()|0}function U4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(_4e(u),It(u)):l|0&&It(l)}function _4e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function H4e(o){o=o|0,Lh(o)}function j4e(o,l){o=o|0,l=l|0;var u=0,A=0;Jy()|0,u=n[2652]|0;e:do if(u|0){for(;A=n[u+4>>2]|0,!(A|0&&!(EZ(YM(A)|0,o)|0));)if(u=n[u>>2]|0,!u)break e;G4e(A,l)}while(!1)}function YM(o){return o=o|0,n[o+12>>2]|0}function G4e(o,l){o=o|0,l=l|0;var u=0;o=o+36|0,u=n[o>>2]|0,u|0&&(Sf(u),It(u)),u=Kt(4)|0,DP(u,l),n[o>>2]=u}function VM(){return s[11716]|0||(n[2664]=0,gr(63,10656,U|0)|0,s[11716]=1),10656}function UX(){var o=0;return s[11717]|0?o=n[2665]|0:(q4e(),n[2665]=1504,s[11717]=1,o=1504),o|0}function q4e(){s[11740]|0||(s[11718]=yr(yr(8,0)|0,0)|0,s[11719]=yr(yr(0,0)|0,0)|0,s[11720]=yr(yr(0,16)|0,0)|0,s[11721]=yr(yr(8,0)|0,0)|0,s[11722]=yr(yr(0,0)|0,0)|0,s[11723]=yr(yr(8,0)|0,0)|0,s[11724]=yr(yr(0,0)|0,0)|0,s[11725]=yr(yr(8,0)|0,0)|0,s[11726]=yr(yr(0,0)|0,0)|0,s[11727]=yr(yr(8,0)|0,0)|0,s[11728]=yr(yr(0,0)|0,0)|0,s[11729]=yr(yr(0,0)|0,32)|0,s[11730]=yr(yr(0,0)|0,32)|0,s[11740]=1)}function _X(){return 1572}function W4e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0,L=0;m=I,I=I+32|0,L=m+16|0,M=m+12|0,T=m+8|0,k=m+4|0,B=m,n[L>>2]=o,n[M>>2]=l,n[T>>2]=u,n[k>>2]=A,n[B>>2]=d,VM()|0,Y4e(10656,L,M,T,k,B),I=m}function Y4e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0;B=Kt(24)|0,gz(B+4|0,n[l>>2]|0,n[u>>2]|0,n[A>>2]|0,n[d>>2]|0,n[m>>2]|0),n[B>>2]=n[o>>2],n[o>>2]=B}function HX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0,ct=0;if(ct=I,I=I+32|0,Le=ct+20|0,Qe=ct+8|0,tt=ct+4|0,Ze=ct,l=n[l>>2]|0,l|0){Ye=Le+4|0,T=Le+8|0,M=Qe+4|0,L=Qe+8|0,q=Qe+8|0,ae=Le+8|0;do{if(B=l+4|0,k=JM(B)|0,k|0){if(d=P2(k)|0,n[Le>>2]=0,n[Ye>>2]=0,n[T>>2]=0,A=(x2(k)|0)+1|0,V4e(Le,A),A|0)for(;A=A+-1|0,bu(Qe,n[d>>2]|0),m=n[Ye>>2]|0,m>>>0<(n[ae>>2]|0)>>>0?(n[m>>2]=n[Qe>>2],n[Ye>>2]=(n[Ye>>2]|0)+4):KM(Le,Qe),A;)d=d+4|0;A=k2(k)|0,n[Qe>>2]=0,n[M>>2]=0,n[L>>2]=0;e:do if(n[A>>2]|0)for(d=0,m=0;;){if((d|0)==(m|0)?J4e(Qe,A):(n[d>>2]=n[A>>2],n[M>>2]=(n[M>>2]|0)+4),A=A+4|0,!(n[A>>2]|0))break e;d=n[M>>2]|0,m=n[q>>2]|0}while(!1);n[tt>>2]=OP(B)|0,n[Ze>>2]=_r(k)|0,K4e(u,o,tt,Ze,Le,Qe),zM(Qe),np(Le)}l=n[l>>2]|0}while(l|0)}I=ct}function JM(o){return o=o|0,n[o+12>>2]|0}function P2(o){return o=o|0,n[o+12>>2]|0}function x2(o){return o=o|0,n[o+16>>2]|0}function V4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+32|0,u=d,A=n[o>>2]|0,(n[o+8>>2]|0)-A>>2>>>0>>0&&(KX(u,l,(n[o+4>>2]|0)-A>>2,o+8|0),zX(o,u),XX(u)),I=d}function KM(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0;if(B=I,I=I+32|0,u=B,A=o+4|0,d=((n[A>>2]|0)-(n[o>>2]|0)>>2)+1|0,m=JX(o)|0,m>>>0>>0)an(o);else{k=n[o>>2]|0,M=(n[o+8>>2]|0)-k|0,T=M>>1,KX(u,M>>2>>>0>>1>>>0?T>>>0>>0?d:T:m,(n[A>>2]|0)-k>>2,o+8|0),m=u+8|0,n[n[m>>2]>>2]=n[l>>2],n[m>>2]=(n[m>>2]|0)+4,zX(o,u),XX(u),I=B;return}}function k2(o){return o=o|0,n[o+8>>2]|0}function J4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0;if(B=I,I=I+32|0,u=B,A=o+4|0,d=((n[A>>2]|0)-(n[o>>2]|0)>>2)+1|0,m=VX(o)|0,m>>>0>>0)an(o);else{k=n[o>>2]|0,M=(n[o+8>>2]|0)-k|0,T=M>>1,h3e(u,M>>2>>>0>>1>>>0?T>>>0>>0?d:T:m,(n[A>>2]|0)-k>>2,o+8|0),m=u+8|0,n[n[m>>2]>>2]=n[l>>2],n[m>>2]=(n[m>>2]|0)+4,g3e(o,u),d3e(u),I=B;return}}function OP(o){return o=o|0,n[o>>2]|0}function K4e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,z4e(o,l,u,A,d,m)}function zM(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),It(u))}function np(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),It(u))}function z4e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,M=0,L=0,q=0;B=I,I=I+48|0,L=B+40|0,k=B+32|0,q=B+24|0,T=B+12|0,M=B,Fl(k),o=Os(o)|0,n[q>>2]=n[l>>2],u=n[u>>2]|0,A=n[A>>2]|0,XM(T,d),X4e(M,m),n[L>>2]=n[q>>2],Z4e(o,L,u,A,T,M),zM(M),np(T),Nl(k),I=B}function XM(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(A3e(o,A),p3e(o,n[l>>2]|0,n[u>>2]|0,A))}function X4e(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(u3e(o,A),f3e(o,n[l>>2]|0,n[u>>2]|0,A))}function Z4e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,M=0,L=0,q=0;B=I,I=I+32|0,L=B+28|0,q=B+24|0,k=B+12|0,T=B,M=da($4e()|0)|0,n[q>>2]=n[l>>2],n[L>>2]=n[q>>2],l=fd(L)|0,u=jX(u)|0,A=ZM(A)|0,n[k>>2]=n[d>>2],L=d+4|0,n[k+4>>2]=n[L>>2],q=d+8|0,n[k+8>>2]=n[q>>2],n[q>>2]=0,n[L>>2]=0,n[d>>2]=0,d=$M(k)|0,n[T>>2]=n[m>>2],L=m+4|0,n[T+4>>2]=n[L>>2],q=m+8|0,n[T+8>>2]=n[q>>2],n[q>>2]=0,n[L>>2]=0,n[m>>2]=0,lu(0,M|0,o|0,l|0,u|0,A|0,d|0,e3e(T)|0)|0,zM(T),np(k),I=B}function $4e(){var o=0;return s[7968]|0||(l3e(10708),o=7968,n[o>>2]=1,n[o+4>>2]=0),10708}function fd(o){return o=o|0,qX(o)|0}function jX(o){return o=o|0,GX(o)|0}function ZM(o){return o=o|0,RP(o)|0}function $M(o){return o=o|0,r3e(o)|0}function e3e(o){return o=o|0,t3e(o)|0}function t3e(o){o=o|0;var l=0,u=0,A=0;if(A=(n[o+4>>2]|0)-(n[o>>2]|0)|0,u=A>>2,A=Rl(A+4|0)|0,n[A>>2]=u,u|0){l=0;do n[A+4+(l<<2)>>2]=GX(n[(n[o>>2]|0)+(l<<2)>>2]|0)|0,l=l+1|0;while((l|0)!=(u|0))}return A|0}function GX(o){return o=o|0,o|0}function r3e(o){o=o|0;var l=0,u=0,A=0;if(A=(n[o+4>>2]|0)-(n[o>>2]|0)|0,u=A>>2,A=Rl(A+4|0)|0,n[A>>2]=u,u|0){l=0;do n[A+4+(l<<2)>>2]=qX((n[o>>2]|0)+(l<<2)|0)|0,l=l+1|0;while((l|0)!=(u|0))}return A|0}function qX(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=fM(WX()|0)|0,A?(AM(l,A),pM(u,l),Mje(o,u),o=hM(l)|0):o=n3e(o)|0,I=d,o|0}function WX(){var o=0;return s[7960]|0||(a3e(10664),gr(25,10664,U|0)|0,o=7960,n[o>>2]=1,n[o+4>>2]=0),10664}function n3e(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Rl(8)|0,l=A,k=Kt(4)|0,n[k>>2]=n[o>>2],m=l+4|0,n[m>>2]=k,o=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],YX(o,m,d),n[A>>2]=o,I=u,l|0}function YX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Kt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1656,n[u+12>>2]=l,n[o+4>>2]=u}function i3e(o){o=o|0,$y(o),It(o)}function s3e(o){o=o|0,o=n[o+12>>2]|0,o|0&&It(o)}function o3e(o){o=o|0,It(o)}function a3e(o){o=o|0,Lh(o)}function l3e(o){o=o|0,Qo(o,c3e()|0,5)}function c3e(){return 1676}function u3e(o,l){o=o|0,l=l|0;var u=0;if((VX(o)|0)>>>0>>0&&an(o),l>>>0>1073741823)Nt();else{u=Kt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function f3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function VX(o){return o=o|0,1073741823}function A3e(o,l){o=o|0,l=l|0;var u=0;if((JX(o)|0)>>>0>>0&&an(o),l>>>0>1073741823)Nt();else{u=Kt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function p3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function JX(o){return o=o|0,1073741823}function h3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Kt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function g3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function d3e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&It(o)}function KX(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Kt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function zX(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function XX(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&It(o)}function m3e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0;if(Qe=I,I=I+32|0,L=Qe+20|0,q=Qe+12|0,M=Qe+16|0,ae=Qe+4|0,Ye=Qe,Le=Qe+8|0,k=UX()|0,m=n[k>>2]|0,B=n[m>>2]|0,B|0)for(T=n[k+8>>2]|0,k=n[k+4>>2]|0;bu(L,B),y3e(o,L,k,T),m=m+4|0,B=n[m>>2]|0,B;)T=T+1|0,k=k+1|0;if(m=_X()|0,B=n[m>>2]|0,B|0)do bu(L,B),n[q>>2]=n[m+4>>2],E3e(l,L,q),m=m+8|0,B=n[m>>2]|0;while(B|0);if(m=n[(Jy()|0)>>2]|0,m|0)do l=n[m+4>>2]|0,bu(L,n[(Ky(l)|0)>>2]|0),n[q>>2]=YM(l)|0,I3e(u,L,q),m=n[m>>2]|0;while(m|0);if(bu(M,0),m=VM()|0,n[L>>2]=n[M>>2],HX(L,m,d),m=n[(Jy()|0)>>2]|0,m|0){o=L+4|0,l=L+8|0,u=L+8|0;do{if(T=n[m+4>>2]|0,bu(q,n[(Ky(T)|0)>>2]|0),C3e(ae,ZX(T)|0),B=n[ae>>2]|0,B|0){n[L>>2]=0,n[o>>2]=0,n[l>>2]=0;do bu(Ye,n[(Ky(n[B+4>>2]|0)|0)>>2]|0),k=n[o>>2]|0,k>>>0<(n[u>>2]|0)>>>0?(n[k>>2]=n[Ye>>2],n[o>>2]=(n[o>>2]|0)+4):KM(L,Ye),B=n[B>>2]|0;while(B|0);w3e(A,q,L),np(L)}n[Le>>2]=n[q>>2],M=$X(T)|0,n[L>>2]=n[Le>>2],HX(L,M,d),Cz(ae),m=n[m>>2]|0}while(m|0)}I=Qe}function y3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,F3e(o,l,u,A)}function E3e(o,l,u){o=o|0,l=l|0,u=u|0,R3e(o,l,u)}function Ky(o){return o=o|0,o|0}function I3e(o,l,u){o=o|0,l=l|0,u=u|0,x3e(o,l,u)}function ZX(o){return o=o|0,o+16|0}function C3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;if(m=I,I=I+16|0,d=m+8|0,u=m,n[o>>2]=0,A=n[l>>2]|0,n[d>>2]=A,n[u>>2]=o,u=P3e(u)|0,A|0){if(A=Kt(12)|0,B=(eZ(d)|0)+4|0,o=n[B+4>>2]|0,l=A+4|0,n[l>>2]=n[B>>2],n[l+4>>2]=o,l=n[n[d>>2]>>2]|0,n[d>>2]=l,!l)o=A;else for(l=A;o=Kt(12)|0,T=(eZ(d)|0)+4|0,k=n[T+4>>2]|0,B=o+4|0,n[B>>2]=n[T>>2],n[B+4>>2]=k,n[l>>2]=o,B=n[n[d>>2]>>2]|0,n[d>>2]=B,B;)l=o;n[o>>2]=n[u>>2],n[u>>2]=A}I=m}function w3e(o,l,u){o=o|0,l=l|0,u=u|0,B3e(o,l,u)}function $X(o){return o=o|0,o+24|0}function B3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,B=A+24|0,d=A+16|0,k=A+12|0,m=A,Fl(d),o=Os(o)|0,n[k>>2]=n[l>>2],XM(m,u),n[B>>2]=n[k>>2],v3e(o,B,m),np(m),Nl(d),I=A}function v3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,B=A+16|0,k=A+12|0,d=A,m=da(S3e()|0)|0,n[k>>2]=n[l>>2],n[B>>2]=n[k>>2],l=fd(B)|0,n[d>>2]=n[u>>2],B=u+4|0,n[d+4>>2]=n[B>>2],k=u+8|0,n[d+8>>2]=n[k>>2],n[k>>2]=0,n[B>>2]=0,n[u>>2]=0,Ts(0,m|0,o|0,l|0,$M(d)|0)|0,np(d),I=A}function S3e(){var o=0;return s[7976]|0||(D3e(10720),o=7976,n[o>>2]=1,n[o+4>>2]=0),10720}function D3e(o){o=o|0,Qo(o,b3e()|0,2)}function b3e(){return 1732}function P3e(o){return o=o|0,n[o>>2]|0}function eZ(o){return o=o|0,n[o>>2]|0}function x3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+32|0,m=A+16|0,d=A+8|0,B=A,Fl(d),o=Os(o)|0,n[B>>2]=n[l>>2],u=n[u>>2]|0,n[m>>2]=n[B>>2],tZ(o,m,u),Nl(d),I=A}function tZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,m=A+4|0,B=A,d=da(k3e()|0)|0,n[B>>2]=n[l>>2],n[m>>2]=n[B>>2],l=fd(m)|0,Ts(0,d|0,o|0,l|0,jX(u)|0)|0,I=A}function k3e(){var o=0;return s[7984]|0||(Q3e(10732),o=7984,n[o>>2]=1,n[o+4>>2]=0),10732}function Q3e(o){o=o|0,Qo(o,T3e()|0,2)}function T3e(){return 1744}function R3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+32|0,m=A+16|0,d=A+8|0,B=A,Fl(d),o=Os(o)|0,n[B>>2]=n[l>>2],u=n[u>>2]|0,n[m>>2]=n[B>>2],tZ(o,m,u),Nl(d),I=A}function F3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+32|0,B=d+16|0,m=d+8|0,k=d,Fl(m),o=Os(o)|0,n[k>>2]=n[l>>2],u=s[u>>0]|0,A=s[A>>0]|0,n[B>>2]=n[k>>2],N3e(o,B,u,A),Nl(m),I=d}function N3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,B=d+4|0,k=d,m=da(O3e()|0)|0,n[k>>2]=n[l>>2],n[B>>2]=n[k>>2],l=fd(B)|0,u=zy(u)|0,Li(0,m|0,o|0,l|0,u|0,zy(A)|0)|0,I=d}function O3e(){var o=0;return s[7992]|0||(M3e(10744),o=7992,n[o>>2]=1,n[o+4>>2]=0),10744}function zy(o){return o=o|0,L3e(o)|0}function L3e(o){return o=o|0,o&255|0}function M3e(o){o=o|0,Qo(o,U3e()|0,3)}function U3e(){return 1756}function _3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;switch(ae=I,I=I+32|0,k=ae+8|0,T=ae+4|0,M=ae+20|0,L=ae,yM(o,0),A=Lje(l)|0,n[k>>2]=0,q=k+4|0,n[q>>2]=0,n[k+8>>2]=0,A<<24>>24){case 0:{s[M>>0]=0,H3e(T,u,M),LP(o,T)|0,Df(T);break}case 8:{q=sU(l)|0,s[M>>0]=8,bu(L,n[q+4>>2]|0),j3e(T,u,M,L,q+8|0),LP(o,T)|0,Df(T);break}case 9:{if(m=sU(l)|0,l=n[m+4>>2]|0,l|0)for(B=k+8|0,d=m+12|0;l=l+-1|0,bu(T,n[d>>2]|0),A=n[q>>2]|0,A>>>0<(n[B>>2]|0)>>>0?(n[A>>2]=n[T>>2],n[q>>2]=(n[q>>2]|0)+4):KM(k,T),l;)d=d+4|0;s[M>>0]=9,bu(L,n[m+8>>2]|0),G3e(T,u,M,L,k),LP(o,T)|0,Df(T);break}default:q=sU(l)|0,s[M>>0]=A,bu(L,n[q+4>>2]|0),q3e(T,u,M,L),LP(o,T)|0,Df(T)}np(k),I=ae}function H3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,Fl(d),l=Os(l)|0,n8e(o,l,s[u>>0]|0),Nl(d),I=A}function LP(o,l){o=o|0,l=l|0;var u=0;return u=n[o>>2]|0,u|0&&Na(u|0),n[o>>2]=n[l>>2],n[l>>2]=0,o|0}function j3e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+32|0,k=m+16|0,B=m+8|0,T=m,Fl(B),l=Os(l)|0,u=s[u>>0]|0,n[T>>2]=n[A>>2],d=n[d>>2]|0,n[k>>2]=n[T>>2],$3e(o,l,u,k,d),Nl(B),I=m}function G3e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0;m=I,I=I+32|0,T=m+24|0,B=m+16|0,M=m+12|0,k=m,Fl(B),l=Os(l)|0,u=s[u>>0]|0,n[M>>2]=n[A>>2],XM(k,d),n[T>>2]=n[M>>2],K3e(o,l,u,T,k),np(k),Nl(B),I=m}function q3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+32|0,B=d+16|0,m=d+8|0,k=d,Fl(m),l=Os(l)|0,u=s[u>>0]|0,n[k>>2]=n[A>>2],n[B>>2]=n[k>>2],W3e(o,l,u,B),Nl(m),I=d}function W3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+4|0,k=d,B=da(Y3e()|0)|0,u=zy(u)|0,n[k>>2]=n[A>>2],n[m>>2]=n[k>>2],MP(o,Ts(0,B|0,l|0,u|0,fd(m)|0)|0),I=d}function Y3e(){var o=0;return s[8e3]|0||(V3e(10756),o=8e3,n[o>>2]=1,n[o+4>>2]=0),10756}function MP(o,l){o=o|0,l=l|0,yM(o,l)}function V3e(o){o=o|0,Qo(o,J3e()|0,2)}function J3e(){return 1772}function K3e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0;m=I,I=I+32|0,T=m+16|0,M=m+12|0,B=m,k=da(z3e()|0)|0,u=zy(u)|0,n[M>>2]=n[A>>2],n[T>>2]=n[M>>2],A=fd(T)|0,n[B>>2]=n[d>>2],T=d+4|0,n[B+4>>2]=n[T>>2],M=d+8|0,n[B+8>>2]=n[M>>2],n[M>>2]=0,n[T>>2]=0,n[d>>2]=0,MP(o,Li(0,k|0,l|0,u|0,A|0,$M(B)|0)|0),np(B),I=m}function z3e(){var o=0;return s[8008]|0||(X3e(10768),o=8008,n[o>>2]=1,n[o+4>>2]=0),10768}function X3e(o){o=o|0,Qo(o,Z3e()|0,3)}function Z3e(){return 1784}function $3e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+16|0,k=m+4|0,T=m,B=da(e8e()|0)|0,u=zy(u)|0,n[T>>2]=n[A>>2],n[k>>2]=n[T>>2],A=fd(k)|0,MP(o,Li(0,B|0,l|0,u|0,A|0,ZM(d)|0)|0),I=m}function e8e(){var o=0;return s[8016]|0||(t8e(10780),o=8016,n[o>>2]=1,n[o+4>>2]=0),10780}function t8e(o){o=o|0,Qo(o,r8e()|0,3)}function r8e(){return 1800}function n8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=da(i8e()|0)|0,MP(o,dn(0,A|0,l|0,zy(u)|0)|0)}function i8e(){var o=0;return s[8024]|0||(s8e(10792),o=8024,n[o>>2]=1,n[o+4>>2]=0),10792}function s8e(o){o=o|0,Qo(o,o8e()|0,1)}function o8e(){return 1816}function a8e(){l8e(),c8e(),u8e()}function l8e(){n[2702]=xZ(65536)|0}function c8e(){k8e(10856)}function u8e(){f8e(10816)}function f8e(o){o=o|0,A8e(o,5044),p8e(o)|0}function A8e(o,l){o=o|0,l=l|0;var u=0;u=WX()|0,n[o>>2]=u,v8e(u,l),jh(n[o>>2]|0)}function p8e(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,h8e()|0),o|0}function h8e(){var o=0;return s[8032]|0||(rZ(10820),gr(64,10820,U|0)|0,o=8032,n[o>>2]=1,n[o+4>>2]=0),_r(10820)|0||rZ(10820),10820}function rZ(o){o=o|0,m8e(o),ud(o,25)}function g8e(o){o=o|0,d8e(o+24|0)}function d8e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function m8e(o){o=o|0;var l=0;l=tn()|0,rn(o,5,18,l,C8e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function y8e(o,l){o=o|0,l=l|0,E8e(o,l)}function E8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;u=I,I=I+16|0,A=u,d=u+4|0,ad(d,l),n[A>>2]=ld(d,l)|0,I8e(o,A),I=u}function I8e(o,l){o=o|0,l=l|0,nZ(o+4|0,n[l>>2]|0),s[o+8>>0]=1}function nZ(o,l){o=o|0,l=l|0,n[o>>2]=l}function C8e(){return 1824}function w8e(o){return o=o|0,B8e(o)|0}function B8e(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Rl(8)|0,l=A,k=Kt(4)|0,ad(d,o),nZ(k,ld(d,o)|0),m=l+4|0,n[m>>2]=k,o=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],YX(o,m,d),n[A>>2]=o,I=u,l|0}function Rl(o){o=o|0;var l=0,u=0;return o=o+7&-8,o>>>0<=32768&&(l=n[2701]|0,o>>>0<=(65536-l|0)>>>0)?(u=(n[2702]|0)+l|0,n[2701]=l+o,o=u):(o=xZ(o+8|0)|0,n[o>>2]=n[2703],n[2703]=o,o=o+8|0),o|0}function v8e(o,l){o=o|0,l=l|0,n[o>>2]=S8e()|0,n[o+4>>2]=D8e()|0,n[o+12>>2]=l,n[o+8>>2]=b8e()|0,n[o+32>>2]=9}function S8e(){return 11744}function D8e(){return 1832}function b8e(){return NP()|0}function P8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(x8e(u),It(u)):l|0&&It(l)}function x8e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function k8e(o){o=o|0,Q8e(o,5052),T8e(o)|0,R8e(o,5058,26)|0,F8e(o,5069,1)|0,N8e(o,5077,10)|0,O8e(o,5087,19)|0,L8e(o,5094,27)|0}function Q8e(o,l){o=o|0,l=l|0;var u=0;u=xje()|0,n[o>>2]=u,kje(u,l),jh(n[o>>2]|0)}function T8e(o){o=o|0;var l=0;return l=n[o>>2]|0,cd(l,gje()|0),o|0}function R8e(o,l,u){return o=o|0,l=l|0,u=u|0,XHe(o,Bn(l)|0,u,0),o|0}function F8e(o,l,u){return o=o|0,l=l|0,u=u|0,OHe(o,Bn(l)|0,u,0),o|0}function N8e(o,l,u){return o=o|0,l=l|0,u=u|0,hHe(o,Bn(l)|0,u,0),o|0}function O8e(o,l,u){return o=o|0,l=l|0,u=u|0,$8e(o,Bn(l)|0,u,0),o|0}function iZ(o,l){o=o|0,l=l|0;var u=0,A=0;e:for(;;){for(u=n[2703]|0;;){if((u|0)==(l|0))break e;if(A=n[u>>2]|0,n[2703]=A,!u)u=A;else break}It(u)}n[2701]=o}function L8e(o,l,u){return o=o|0,l=l|0,u=u|0,M8e(o,Bn(l)|0,u,0),o|0}function M8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=eU()|0,o=U8e(u)|0,vn(m,l,d,o,_8e(u,A)|0,A)}function eU(){var o=0,l=0;if(s[8040]|0||(oZ(10860),gr(65,10860,U|0)|0,l=8040,n[l>>2]=1,n[l+4>>2]=0),!(_r(10860)|0)){o=10860,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));oZ(10860)}return 10860}function U8e(o){return o=o|0,o|0}function _8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=eU()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(sZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(H8e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function sZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function H8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=j8e(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,G8e(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,sZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,q8e(o,d),W8e(d),I=k;return}}function j8e(o){return o=o|0,536870911}function G8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function q8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function W8e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function oZ(o){o=o|0,J8e(o)}function Y8e(o){o=o|0,V8e(o+24|0)}function V8e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function J8e(o){o=o|0;var l=0;l=tn()|0,rn(o,1,11,l,K8e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function K8e(){return 1840}function z8e(o,l,u){o=o|0,l=l|0,u=u|0,Z8e(n[(X8e(o)|0)>>2]|0,l,u)}function X8e(o){return o=o|0,(n[(eU()|0)+24>>2]|0)+(o<<3)|0}function Z8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;A=I,I=I+16|0,m=A+1|0,d=A,ad(m,l),l=ld(m,l)|0,ad(d,u),u=ld(d,u)|0,sp[o&31](l,u),I=A}function $8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=tU()|0,o=eHe(u)|0,vn(m,l,d,o,tHe(u,A)|0,A)}function tU(){var o=0,l=0;if(s[8048]|0||(lZ(10896),gr(66,10896,U|0)|0,l=8048,n[l>>2]=1,n[l+4>>2]=0),!(_r(10896)|0)){o=10896,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));lZ(10896)}return 10896}function eHe(o){return o=o|0,o|0}function tHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=tU()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(aZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(rHe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function aZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function rHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=nHe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,iHe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,aZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,sHe(o,d),oHe(d),I=k;return}}function nHe(o){return o=o|0,536870911}function iHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function sHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function oHe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function lZ(o){o=o|0,cHe(o)}function aHe(o){o=o|0,lHe(o+24|0)}function lHe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function cHe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,11,l,uHe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function uHe(){return 1852}function fHe(o,l){return o=o|0,l=l|0,pHe(n[(AHe(o)|0)>>2]|0,l)|0}function AHe(o){return o=o|0,(n[(tU()|0)+24>>2]|0)+(o<<3)|0}function pHe(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,ad(A,l),l=ld(A,l)|0,l=RP(gd[o&31](l)|0)|0,I=u,l|0}function hHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=rU()|0,o=gHe(u)|0,vn(m,l,d,o,dHe(u,A)|0,A)}function rU(){var o=0,l=0;if(s[8056]|0||(uZ(10932),gr(67,10932,U|0)|0,l=8056,n[l>>2]=1,n[l+4>>2]=0),!(_r(10932)|0)){o=10932,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));uZ(10932)}return 10932}function gHe(o){return o=o|0,o|0}function dHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=rU()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(cZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(mHe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function cZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function mHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=yHe(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,EHe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,cZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,IHe(o,d),CHe(d),I=k;return}}function yHe(o){return o=o|0,536870911}function EHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function IHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function CHe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function uZ(o){o=o|0,vHe(o)}function wHe(o){o=o|0,BHe(o+24|0)}function BHe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function vHe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,7,l,SHe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function SHe(){return 1860}function DHe(o,l,u){return o=o|0,l=l|0,u=u|0,PHe(n[(bHe(o)|0)>>2]|0,l,u)|0}function bHe(o){return o=o|0,(n[(rU()|0)+24>>2]|0)+(o<<3)|0}function PHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+32|0,B=A+12|0,m=A+8|0,k=A,T=A+16|0,d=A+4|0,xHe(T,l),kHe(k,T,l),Mh(d,u),u=Uh(d,u)|0,n[B>>2]=n[k>>2],F2[o&15](m,B,u),u=QHe(m)|0,Df(m),_h(d),I=A,u|0}function xHe(o,l){o=o|0,l=l|0}function kHe(o,l,u){o=o|0,l=l|0,u=u|0,THe(o,u)}function QHe(o){return o=o|0,Os(o)|0}function THe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+16|0,u=d,A=l,A&1?(RHe(u,0),Me(A|0,u|0)|0,FHe(o,u),NHe(u)):n[o>>2]=n[l>>2],I=d}function RHe(o,l){o=o|0,l=l|0,Su(o,l),n[o+4>>2]=0,s[o+8>>0]=0}function FHe(o,l){o=o|0,l=l|0,n[o>>2]=n[l+4>>2]}function NHe(o){o=o|0,s[o+8>>0]=0}function OHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=nU()|0,o=LHe(u)|0,vn(m,l,d,o,MHe(u,A)|0,A)}function nU(){var o=0,l=0;if(s[8064]|0||(AZ(10968),gr(68,10968,U|0)|0,l=8064,n[l>>2]=1,n[l+4>>2]=0),!(_r(10968)|0)){o=10968,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));AZ(10968)}return 10968}function LHe(o){return o=o|0,o|0}function MHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=nU()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(fZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(UHe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function fZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function UHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=_He(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,HHe(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,fZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,jHe(o,d),GHe(d),I=k;return}}function _He(o){return o=o|0,536870911}function HHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function jHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function GHe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function AZ(o){o=o|0,YHe(o)}function qHe(o){o=o|0,WHe(o+24|0)}function WHe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function YHe(o){o=o|0;var l=0;l=tn()|0,rn(o,1,1,l,VHe()|0,5),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function VHe(){return 1872}function JHe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,zHe(n[(KHe(o)|0)>>2]|0,l,u,A,d,m)}function KHe(o){return o=o|0,(n[(nU()|0)+24>>2]|0)+(o<<3)|0}function zHe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,M=0,L=0,q=0;B=I,I=I+32|0,k=B+16|0,T=B+12|0,M=B+8|0,L=B+4|0,q=B,Mh(k,l),l=Uh(k,l)|0,Mh(T,u),u=Uh(T,u)|0,Mh(M,A),A=Uh(M,A)|0,Mh(L,d),d=Uh(L,d)|0,Mh(q,m),m=Uh(q,m)|0,FZ[o&1](l,u,A,d,m),_h(q),_h(L),_h(M),_h(T),_h(k),I=B}function XHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=iU()|0,o=ZHe(u)|0,vn(m,l,d,o,$He(u,A)|0,A)}function iU(){var o=0,l=0;if(s[8072]|0||(hZ(11004),gr(69,11004,U|0)|0,l=8072,n[l>>2]=1,n[l+4>>2]=0),!(_r(11004)|0)){o=11004,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));hZ(11004)}return 11004}function ZHe(o){return o=o|0,o|0}function $He(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=iU()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(pZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(eje(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function pZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function eje(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=tje(o)|0,A>>>0>>0)an(o);else{T=n[o>>2]|0,L=(n[o+8>>2]|0)-T|0,M=L>>2,rje(d,L>>3>>>0>>1>>>0?M>>>0>>0?B:M:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,pZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,nje(o,d),ije(d),I=k;return}}function tje(o){return o=o|0,536870911}function rje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Kt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function nje(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function ije(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&It(o)}function hZ(o){o=o|0,aje(o)}function sje(o){o=o|0,oje(o+24|0)}function oje(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function aje(o){o=o|0;var l=0;l=tn()|0,rn(o,1,12,l,lje()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function lje(){return 1896}function cje(o,l,u){o=o|0,l=l|0,u=u|0,fje(n[(uje(o)|0)>>2]|0,l,u)}function uje(o){return o=o|0,(n[(iU()|0)+24>>2]|0)+(o<<3)|0}function fje(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;A=I,I=I+16|0,m=A+4|0,d=A,Aje(m,l),l=pje(m,l)|0,Mh(d,u),u=Uh(d,u)|0,sp[o&31](l,u),_h(d),I=A}function Aje(o,l){o=o|0,l=l|0}function pje(o,l){return o=o|0,l=l|0,hje(l)|0}function hje(o){return o=o|0,o|0}function gje(){var o=0;return s[8080]|0||(gZ(11040),gr(70,11040,U|0)|0,o=8080,n[o>>2]=1,n[o+4>>2]=0),_r(11040)|0||gZ(11040),11040}function gZ(o){o=o|0,yje(o),ud(o,71)}function dje(o){o=o|0,mje(o+24|0)}function mje(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),It(u))}function yje(o){o=o|0;var l=0;l=tn()|0,rn(o,5,7,l,wje()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Eje(o){o=o|0,Ije(o)}function Ije(o){o=o|0,Cje(o)}function Cje(o){o=o|0,s[o+8>>0]=1}function wje(){return 1936}function Bje(){return vje()|0}function vje(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Rl(8)|0,o=u,m=o+4|0,n[m>>2]=Kt(1)|0,A=Kt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],Sje(A,m,d),n[u>>2]=A,I=l,o|0}function Sje(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Kt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1916,n[u+12>>2]=l,n[o+4>>2]=u}function Dje(o){o=o|0,$y(o),It(o)}function bje(o){o=o|0,o=n[o+12>>2]|0,o|0&&It(o)}function Pje(o){o=o|0,It(o)}function xje(){var o=0;return s[8088]|0||(Oje(11076),gr(25,11076,U|0)|0,o=8088,n[o>>2]=1,n[o+4>>2]=0),11076}function kje(o,l){o=o|0,l=l|0,n[o>>2]=Qje()|0,n[o+4>>2]=Tje()|0,n[o+12>>2]=l,n[o+8>>2]=Rje()|0,n[o+32>>2]=10}function Qje(){return 11745}function Tje(){return 1940}function Rje(){return FP()|0}function Fje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Hh(A,896)|0)==512?u|0&&(Nje(u),It(u)):l|0&&It(l)}function Nje(o){o=o|0,o=n[o+4>>2]|0,o|0&&Gh(o)}function Oje(o){o=o|0,Lh(o)}function bu(o,l){o=o|0,l=l|0,n[o>>2]=l}function sU(o){return o=o|0,n[o>>2]|0}function Lje(o){return o=o|0,s[n[o>>2]>>0]|0}function Mje(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,n[A>>2]=n[o>>2],Uje(l,A)|0,I=u}function Uje(o,l){o=o|0,l=l|0;var u=0;return u=_je(n[o>>2]|0,l)|0,l=o+4|0,n[(n[l>>2]|0)+8>>2]=u,n[(n[l>>2]|0)+8>>2]|0}function _je(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,Fl(A),o=Os(o)|0,l=Hje(o,n[l>>2]|0)|0,Nl(A),I=u,l|0}function Fl(o){o=o|0,n[o>>2]=n[2701],n[o+4>>2]=n[2703]}function Hje(o,l){o=o|0,l=l|0;var u=0;return u=da(jje()|0)|0,dn(0,u|0,o|0,ZM(l)|0)|0}function Nl(o){o=o|0,iZ(n[o>>2]|0,n[o+4>>2]|0)}function jje(){var o=0;return s[8096]|0||(Gje(11120),o=8096,n[o>>2]=1,n[o+4>>2]=0),11120}function Gje(o){o=o|0,Qo(o,qje()|0,1)}function qje(){return 1948}function Wje(){Yje()}function Yje(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0;if(Le=I,I=I+16|0,L=Le+4|0,q=Le,oa(65536,10804,n[2702]|0,10812),u=UX()|0,l=n[u>>2]|0,o=n[l>>2]|0,o|0)for(A=n[u+8>>2]|0,u=n[u+4>>2]|0;pf(o|0,c[u>>0]|0|0,s[A>>0]|0),l=l+4|0,o=n[l>>2]|0,o;)A=A+1|0,u=u+1|0;if(o=_X()|0,l=n[o>>2]|0,l|0)do NA(l|0,n[o+4>>2]|0),o=o+8|0,l=n[o>>2]|0;while(l|0);NA(Vje()|0,5167),M=Jy()|0,o=n[M>>2]|0;e:do if(o|0){do Jje(n[o+4>>2]|0),o=n[o>>2]|0;while(o|0);if(o=n[M>>2]|0,o|0){T=M;do{for(;d=o,o=n[o>>2]|0,d=n[d+4>>2]|0,!!(Kje(d)|0);)if(n[q>>2]=T,n[L>>2]=n[q>>2],zje(M,L)|0,!o)break e;if(Xje(d),T=n[T>>2]|0,l=dZ(d)|0,m=Oi()|0,B=I,I=I+((1*(l<<2)|0)+15&-16)|0,k=I,I=I+((1*(l<<2)|0)+15&-16)|0,l=n[(ZX(d)|0)>>2]|0,l|0)for(u=B,A=k;n[u>>2]=n[(Ky(n[l+4>>2]|0)|0)>>2],n[A>>2]=n[l+8>>2],l=n[l>>2]|0,l;)u=u+4|0,A=A+4|0;Qe=Ky(d)|0,l=Zje(d)|0,u=dZ(d)|0,A=$je(d)|0,oc(Qe|0,l|0,B|0,k|0,u|0,A|0,YM(d)|0),FA(m|0)}while(o|0)}}while(!1);if(o=n[(VM()|0)>>2]|0,o|0)do Qe=o+4|0,M=JM(Qe)|0,d=k2(M)|0,m=P2(M)|0,B=(x2(M)|0)+1|0,k=UP(M)|0,T=mZ(Qe)|0,M=_r(M)|0,L=OP(Qe)|0,q=oU(Qe)|0,uu(0,d|0,m|0,B|0,k|0,T|0,M|0,L|0,q|0,aU(Qe)|0),o=n[o>>2]|0;while(o|0);o=n[(Jy()|0)>>2]|0;e:do if(o|0){t:for(;;){if(l=n[o+4>>2]|0,l|0&&(ae=n[(Ky(l)|0)>>2]|0,Ye=n[($X(l)|0)>>2]|0,Ye|0)){u=Ye;do{l=u+4|0,A=JM(l)|0;r:do if(A|0)switch(_r(A)|0){case 0:break t;case 4:case 3:case 2:{k=k2(A)|0,T=P2(A)|0,M=(x2(A)|0)+1|0,L=UP(A)|0,q=_r(A)|0,Qe=OP(l)|0,uu(ae|0,k|0,T|0,M|0,L|0,0,q|0,Qe|0,oU(l)|0,aU(l)|0);break r}case 1:{B=k2(A)|0,k=P2(A)|0,T=(x2(A)|0)+1|0,M=UP(A)|0,L=mZ(l)|0,q=_r(A)|0,Qe=OP(l)|0,uu(ae|0,B|0,k|0,T|0,M|0,L|0,q|0,Qe|0,oU(l)|0,aU(l)|0);break r}case 5:{M=k2(A)|0,L=P2(A)|0,q=(x2(A)|0)+1|0,Qe=UP(A)|0,uu(ae|0,M|0,L|0,q|0,Qe|0,e6e(A)|0,_r(A)|0,0,0,0);break r}default:break r}while(!1);u=n[u>>2]|0}while(u|0)}if(o=n[o>>2]|0,!o)break e}Nt()}while(!1);ve(),I=Le}function Vje(){return 11703}function Jje(o){o=o|0,s[o+40>>0]=0}function Kje(o){return o=o|0,(s[o+40>>0]|0)!=0|0}function zje(o,l){return o=o|0,l=l|0,l=t6e(l)|0,o=n[l>>2]|0,n[l>>2]=n[o>>2],It(o),n[l>>2]|0}function Xje(o){o=o|0,s[o+40>>0]=1}function dZ(o){return o=o|0,n[o+20>>2]|0}function Zje(o){return o=o|0,n[o+8>>2]|0}function $je(o){return o=o|0,n[o+32>>2]|0}function UP(o){return o=o|0,n[o+4>>2]|0}function mZ(o){return o=o|0,n[o+4>>2]|0}function oU(o){return o=o|0,n[o+8>>2]|0}function aU(o){return o=o|0,n[o+16>>2]|0}function e6e(o){return o=o|0,n[o+20>>2]|0}function t6e(o){return o=o|0,n[o>>2]|0}function _P(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,We=0,Lt=0;Lt=I,I=I+16|0,ae=Lt;do if(o>>>0<245){if(M=o>>>0<11?16:o+11&-8,o=M>>>3,q=n[2783]|0,u=q>>>o,u&3|0)return l=(u&1^1)+o|0,o=11172+(l<<1<<2)|0,u=o+8|0,A=n[u>>2]|0,d=A+8|0,m=n[d>>2]|0,(o|0)==(m|0)?n[2783]=q&~(1<>2]=o,n[u>>2]=m),We=l<<3,n[A+4>>2]=We|3,We=A+We+4|0,n[We>>2]=n[We>>2]|1,We=d,I=Lt,We|0;if(L=n[2785]|0,M>>>0>L>>>0){if(u|0)return l=2<>>12&16,l=l>>>B,u=l>>>5&8,l=l>>>u,d=l>>>2&4,l=l>>>d,o=l>>>1&2,l=l>>>o,A=l>>>1&1,A=(u|B|d|o|A)+(l>>>A)|0,l=11172+(A<<1<<2)|0,o=l+8|0,d=n[o>>2]|0,B=d+8|0,u=n[B>>2]|0,(l|0)==(u|0)?(o=q&~(1<>2]=l,n[o>>2]=u,o=q),m=(A<<3)-M|0,n[d+4>>2]=M|3,A=d+M|0,n[A+4>>2]=m|1,n[A+m>>2]=m,L|0&&(d=n[2788]|0,l=L>>>3,u=11172+(l<<1<<2)|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=d,n[l+12>>2]=d,n[d+8>>2]=l,n[d+12>>2]=u),n[2785]=m,n[2788]=A,We=B,I=Lt,We|0;if(k=n[2784]|0,k){if(u=(k&0-k)+-1|0,B=u>>>12&16,u=u>>>B,m=u>>>5&8,u=u>>>m,T=u>>>2&4,u=u>>>T,A=u>>>1&2,u=u>>>A,o=u>>>1&1,o=n[11436+((m|B|T|A|o)+(u>>>o)<<2)>>2]|0,u=(n[o+4>>2]&-8)-M|0,A=n[o+16+(((n[o+16>>2]|0)==0&1)<<2)>>2]|0,!A)T=o,m=u;else{do B=(n[A+4>>2]&-8)-M|0,T=B>>>0>>0,u=T?B:u,o=T?A:o,A=n[A+16+(((n[A+16>>2]|0)==0&1)<<2)>>2]|0;while(A|0);T=o,m=u}if(B=T+M|0,T>>>0>>0){d=n[T+24>>2]|0,l=n[T+12>>2]|0;do if((l|0)==(T|0)){if(o=T+20|0,l=n[o>>2]|0,!l&&(o=T+16|0,l=n[o>>2]|0,!l)){u=0;break}for(;;){if(u=l+20|0,A=n[u>>2]|0,A|0){l=A,o=u;continue}if(u=l+16|0,A=n[u>>2]|0,A)l=A,o=u;else break}n[o>>2]=0,u=l}else u=n[T+8>>2]|0,n[u+12>>2]=l,n[l+8>>2]=u,u=l;while(!1);do if(d|0){if(l=n[T+28>>2]|0,o=11436+(l<<2)|0,(T|0)==(n[o>>2]|0)){if(n[o>>2]=u,!u){n[2784]=k&~(1<>2]|0)!=(T|0)&1)<<2)>>2]=u,!u)break;n[u+24>>2]=d,l=n[T+16>>2]|0,l|0&&(n[u+16>>2]=l,n[l+24>>2]=u),l=n[T+20>>2]|0,l|0&&(n[u+20>>2]=l,n[l+24>>2]=u)}while(!1);return m>>>0<16?(We=m+M|0,n[T+4>>2]=We|3,We=T+We+4|0,n[We>>2]=n[We>>2]|1):(n[T+4>>2]=M|3,n[B+4>>2]=m|1,n[B+m>>2]=m,L|0&&(A=n[2788]|0,l=L>>>3,u=11172+(l<<1<<2)|0,l=1<>2]|0):(n[2783]=q|l,l=u,o=u+8|0),n[o>>2]=A,n[l+12>>2]=A,n[A+8>>2]=l,n[A+12>>2]=u),n[2785]=m,n[2788]=B),We=T+8|0,I=Lt,We|0}else q=M}else q=M}else q=M}else if(o>>>0<=4294967231)if(o=o+11|0,M=o&-8,T=n[2784]|0,T){A=0-M|0,o=o>>>8,o?M>>>0>16777215?k=31:(q=(o+1048320|0)>>>16&8,He=o<>>16&4,He=He<>>16&2,k=14-(L|q|k)+(He<>>15)|0,k=M>>>(k+7|0)&1|k<<1):k=0,u=n[11436+(k<<2)>>2]|0;e:do if(!u)u=0,o=0,He=57;else for(o=0,B=M<<((k|0)==31?0:25-(k>>>1)|0),m=0;;){if(d=(n[u+4>>2]&-8)-M|0,d>>>0>>0)if(d)o=u,A=d;else{o=u,A=0,d=u,He=61;break e}if(d=n[u+20>>2]|0,u=n[u+16+(B>>>31<<2)>>2]|0,m=(d|0)==0|(d|0)==(u|0)?m:d,d=(u|0)==0,d){u=m,He=57;break}else B=B<<((d^1)&1)}while(!1);if((He|0)==57){if((u|0)==0&(o|0)==0){if(o=2<>>12&16,q=q>>>B,m=q>>>5&8,q=q>>>m,k=q>>>2&4,q=q>>>k,L=q>>>1&2,q=q>>>L,u=q>>>1&1,o=0,u=n[11436+((m|B|k|L|u)+(q>>>u)<<2)>>2]|0}u?(d=u,He=61):(k=o,B=A)}if((He|0)==61)for(;;)if(He=0,u=(n[d+4>>2]&-8)-M|0,q=u>>>0>>0,u=q?u:A,o=q?d:o,d=n[d+16+(((n[d+16>>2]|0)==0&1)<<2)>>2]|0,d)A=u,He=61;else{k=o,B=u;break}if(k|0&&B>>>0<((n[2785]|0)-M|0)>>>0){if(m=k+M|0,k>>>0>=m>>>0)return We=0,I=Lt,We|0;d=n[k+24>>2]|0,l=n[k+12>>2]|0;do if((l|0)==(k|0)){if(o=k+20|0,l=n[o>>2]|0,!l&&(o=k+16|0,l=n[o>>2]|0,!l)){l=0;break}for(;;){if(u=l+20|0,A=n[u>>2]|0,A|0){l=A,o=u;continue}if(u=l+16|0,A=n[u>>2]|0,A)l=A,o=u;else break}n[o>>2]=0}else We=n[k+8>>2]|0,n[We+12>>2]=l,n[l+8>>2]=We;while(!1);do if(d){if(o=n[k+28>>2]|0,u=11436+(o<<2)|0,(k|0)==(n[u>>2]|0)){if(n[u>>2]=l,!l){A=T&~(1<>2]|0)!=(k|0)&1)<<2)>>2]=l,!l){A=T;break}n[l+24>>2]=d,o=n[k+16>>2]|0,o|0&&(n[l+16>>2]=o,n[o+24>>2]=l),o=n[k+20>>2]|0,o&&(n[l+20>>2]=o,n[o+24>>2]=l),A=T}else A=T;while(!1);do if(B>>>0>=16){if(n[k+4>>2]=M|3,n[m+4>>2]=B|1,n[m+B>>2]=B,l=B>>>3,B>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=m,n[l+12>>2]=m,n[m+8>>2]=l,n[m+12>>2]=u;break}if(l=B>>>8,l?B>>>0>16777215?l=31:(He=(l+1048320|0)>>>16&8,We=l<>>16&4,We=We<>>16&2,l=14-(ct|He|l)+(We<>>15)|0,l=B>>>(l+7|0)&1|l<<1):l=0,u=11436+(l<<2)|0,n[m+28>>2]=l,o=m+16|0,n[o+4>>2]=0,n[o>>2]=0,o=1<>2]=m,n[m+24>>2]=u,n[m+12>>2]=m,n[m+8>>2]=m;break}for(o=B<<((l|0)==31?0:25-(l>>>1)|0),u=n[u>>2]|0;;){if((n[u+4>>2]&-8|0)==(B|0)){He=97;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=96;break}}if((He|0)==96){n[A>>2]=m,n[m+24>>2]=u,n[m+12>>2]=m,n[m+8>>2]=m;break}else if((He|0)==97){He=u+8|0,We=n[He>>2]|0,n[We+12>>2]=m,n[He>>2]=m,n[m+8>>2]=We,n[m+12>>2]=u,n[m+24>>2]=0;break}}else We=B+M|0,n[k+4>>2]=We|3,We=k+We+4|0,n[We>>2]=n[We>>2]|1;while(!1);return We=k+8|0,I=Lt,We|0}else q=M}else q=M;else q=-1;while(!1);if(u=n[2785]|0,u>>>0>=q>>>0)return l=u-q|0,o=n[2788]|0,l>>>0>15?(We=o+q|0,n[2788]=We,n[2785]=l,n[We+4>>2]=l|1,n[We+l>>2]=l,n[o+4>>2]=q|3):(n[2785]=0,n[2788]=0,n[o+4>>2]=u|3,We=o+u+4|0,n[We>>2]=n[We>>2]|1),We=o+8|0,I=Lt,We|0;if(B=n[2786]|0,B>>>0>q>>>0)return ct=B-q|0,n[2786]=ct,We=n[2789]|0,He=We+q|0,n[2789]=He,n[He+4>>2]=ct|1,n[We+4>>2]=q|3,We=We+8|0,I=Lt,We|0;if(n[2901]|0?o=n[2903]|0:(n[2903]=4096,n[2902]=4096,n[2904]=-1,n[2905]=-1,n[2906]=0,n[2894]=0,o=ae&-16^1431655768,n[ae>>2]=o,n[2901]=o,o=4096),k=q+48|0,T=q+47|0,m=o+T|0,d=0-o|0,M=m&d,M>>>0<=q>>>0||(o=n[2893]|0,o|0&&(L=n[2891]|0,ae=L+M|0,ae>>>0<=L>>>0|ae>>>0>o>>>0)))return We=0,I=Lt,We|0;e:do if(n[2894]&4)l=0,He=133;else{u=n[2789]|0;t:do if(u){for(A=11580;o=n[A>>2]|0,!(o>>>0<=u>>>0&&(Qe=A+4|0,(o+(n[Qe>>2]|0)|0)>>>0>u>>>0));)if(o=n[A+8>>2]|0,o)A=o;else{He=118;break t}if(l=m-B&d,l>>>0<2147483647)if(o=qh(l|0)|0,(o|0)==((n[A>>2]|0)+(n[Qe>>2]|0)|0)){if((o|0)!=-1){B=l,m=o,He=135;break e}}else A=o,He=126;else l=0}else He=118;while(!1);do if((He|0)==118)if(u=qh(0)|0,(u|0)!=-1&&(l=u,Ye=n[2902]|0,Le=Ye+-1|0,l=(Le&l|0?(Le+l&0-Ye)-l|0:0)+M|0,Ye=n[2891]|0,Le=l+Ye|0,l>>>0>q>>>0&l>>>0<2147483647)){if(Qe=n[2893]|0,Qe|0&&Le>>>0<=Ye>>>0|Le>>>0>Qe>>>0){l=0;break}if(o=qh(l|0)|0,(o|0)==(u|0)){B=l,m=u,He=135;break e}else A=o,He=126}else l=0;while(!1);do if((He|0)==126){if(u=0-l|0,!(k>>>0>l>>>0&(l>>>0<2147483647&(A|0)!=-1)))if((A|0)==-1){l=0;break}else{B=l,m=A,He=135;break e}if(o=n[2903]|0,o=T-l+o&0-o,o>>>0>=2147483647){B=l,m=A,He=135;break e}if((qh(o|0)|0)==-1){qh(u|0)|0,l=0;break}else{B=o+l|0,m=A,He=135;break e}}while(!1);n[2894]=n[2894]|4,He=133}while(!1);if((He|0)==133&&M>>>0<2147483647&&(ct=qh(M|0)|0,Qe=qh(0)|0,tt=Qe-ct|0,Ze=tt>>>0>(q+40|0)>>>0,!((ct|0)==-1|Ze^1|ct>>>0>>0&((ct|0)!=-1&(Qe|0)!=-1)^1))&&(B=Ze?tt:l,m=ct,He=135),(He|0)==135){l=(n[2891]|0)+B|0,n[2891]=l,l>>>0>(n[2892]|0)>>>0&&(n[2892]=l),T=n[2789]|0;do if(T){for(l=11580;;){if(o=n[l>>2]|0,u=l+4|0,A=n[u>>2]|0,(m|0)==(o+A|0)){He=145;break}if(d=n[l+8>>2]|0,d)l=d;else break}if((He|0)==145&&!(n[l+12>>2]&8|0)&&T>>>0>>0&T>>>0>=o>>>0){n[u>>2]=A+B,We=T+8|0,We=We&7|0?0-We&7:0,He=T+We|0,We=(n[2786]|0)+(B-We)|0,n[2789]=He,n[2786]=We,n[He+4>>2]=We|1,n[He+We+4>>2]=40,n[2790]=n[2905];break}for(m>>>0<(n[2787]|0)>>>0&&(n[2787]=m),u=m+B|0,l=11580;;){if((n[l>>2]|0)==(u|0)){He=153;break}if(o=n[l+8>>2]|0,o)l=o;else break}if((He|0)==153&&!(n[l+12>>2]&8|0)){n[l>>2]=m,L=l+4|0,n[L>>2]=(n[L>>2]|0)+B,L=m+8|0,L=m+(L&7|0?0-L&7:0)|0,l=u+8|0,l=u+(l&7|0?0-l&7:0)|0,M=L+q|0,k=l-L-q|0,n[L+4>>2]=q|3;do if((l|0)!=(T|0)){if((l|0)==(n[2788]|0)){We=(n[2785]|0)+k|0,n[2785]=We,n[2788]=M,n[M+4>>2]=We|1,n[M+We>>2]=We;break}if(o=n[l+4>>2]|0,(o&3|0)==1){B=o&-8,A=o>>>3;e:do if(o>>>0<256)if(o=n[l+8>>2]|0,u=n[l+12>>2]|0,(u|0)==(o|0)){n[2783]=n[2783]&~(1<>2]=u,n[u+8>>2]=o;break}else{m=n[l+24>>2]|0,o=n[l+12>>2]|0;do if((o|0)==(l|0)){if(A=l+16|0,u=A+4|0,o=n[u>>2]|0,!o)if(o=n[A>>2]|0,o)u=A;else{o=0;break}for(;;){if(A=o+20|0,d=n[A>>2]|0,d|0){o=d,u=A;continue}if(A=o+16|0,d=n[A>>2]|0,d)o=d,u=A;else break}n[u>>2]=0}else We=n[l+8>>2]|0,n[We+12>>2]=o,n[o+8>>2]=We;while(!1);if(!m)break;u=n[l+28>>2]|0,A=11436+(u<<2)|0;do if((l|0)!=(n[A>>2]|0)){if(n[m+16+(((n[m+16>>2]|0)!=(l|0)&1)<<2)>>2]=o,!o)break e}else{if(n[A>>2]=o,o|0)break;n[2784]=n[2784]&~(1<>2]=m,u=l+16|0,A=n[u>>2]|0,A|0&&(n[o+16>>2]=A,n[A+24>>2]=o),u=n[u+4>>2]|0,!u)break;n[o+20>>2]=u,n[u+24>>2]=o}while(!1);l=l+B|0,d=B+k|0}else d=k;if(l=l+4|0,n[l>>2]=n[l>>2]&-2,n[M+4>>2]=d|1,n[M+d>>2]=d,l=d>>>3,d>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=M,n[l+12>>2]=M,n[M+8>>2]=l,n[M+12>>2]=u;break}l=d>>>8;do if(!l)l=0;else{if(d>>>0>16777215){l=31;break}He=(l+1048320|0)>>>16&8,We=l<>>16&4,We=We<>>16&2,l=14-(ct|He|l)+(We<>>15)|0,l=d>>>(l+7|0)&1|l<<1}while(!1);if(A=11436+(l<<2)|0,n[M+28>>2]=l,o=M+16|0,n[o+4>>2]=0,n[o>>2]=0,o=n[2784]|0,u=1<>2]=M,n[M+24>>2]=A,n[M+12>>2]=M,n[M+8>>2]=M;break}for(o=d<<((l|0)==31?0:25-(l>>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(d|0)){He=194;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=193;break}}if((He|0)==193){n[A>>2]=M,n[M+24>>2]=u,n[M+12>>2]=M,n[M+8>>2]=M;break}else if((He|0)==194){He=u+8|0,We=n[He>>2]|0,n[We+12>>2]=M,n[He>>2]=M,n[M+8>>2]=We,n[M+12>>2]=u,n[M+24>>2]=0;break}}else We=(n[2786]|0)+k|0,n[2786]=We,n[2789]=M,n[M+4>>2]=We|1;while(!1);return We=L+8|0,I=Lt,We|0}for(l=11580;o=n[l>>2]|0,!(o>>>0<=T>>>0&&(We=o+(n[l+4>>2]|0)|0,We>>>0>T>>>0));)l=n[l+8>>2]|0;d=We+-47|0,o=d+8|0,o=d+(o&7|0?0-o&7:0)|0,d=T+16|0,o=o>>>0>>0?T:o,l=o+8|0,u=m+8|0,u=u&7|0?0-u&7:0,He=m+u|0,u=B+-40-u|0,n[2789]=He,n[2786]=u,n[He+4>>2]=u|1,n[He+u+4>>2]=40,n[2790]=n[2905],u=o+4|0,n[u>>2]=27,n[l>>2]=n[2895],n[l+4>>2]=n[2896],n[l+8>>2]=n[2897],n[l+12>>2]=n[2898],n[2895]=m,n[2896]=B,n[2898]=0,n[2897]=l,l=o+24|0;do He=l,l=l+4|0,n[l>>2]=7;while((He+8|0)>>>0>>0);if((o|0)!=(T|0)){if(m=o-T|0,n[u>>2]=n[u>>2]&-2,n[T+4>>2]=m|1,n[o>>2]=m,l=m>>>3,m>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=T,n[l+12>>2]=T,n[T+8>>2]=l,n[T+12>>2]=u;break}if(l=m>>>8,l?m>>>0>16777215?u=31:(He=(l+1048320|0)>>>16&8,We=l<>>16&4,We=We<>>16&2,u=14-(ct|He|u)+(We<>>15)|0,u=m>>>(u+7|0)&1|u<<1):u=0,A=11436+(u<<2)|0,n[T+28>>2]=u,n[T+20>>2]=0,n[d>>2]=0,l=n[2784]|0,o=1<>2]=T,n[T+24>>2]=A,n[T+12>>2]=T,n[T+8>>2]=T;break}for(o=m<<((u|0)==31?0:25-(u>>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(m|0)){He=216;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=215;break}}if((He|0)==215){n[A>>2]=T,n[T+24>>2]=u,n[T+12>>2]=T,n[T+8>>2]=T;break}else if((He|0)==216){He=u+8|0,We=n[He>>2]|0,n[We+12>>2]=T,n[He>>2]=T,n[T+8>>2]=We,n[T+12>>2]=u,n[T+24>>2]=0;break}}}else{We=n[2787]|0,(We|0)==0|m>>>0>>0&&(n[2787]=m),n[2895]=m,n[2896]=B,n[2898]=0,n[2792]=n[2901],n[2791]=-1,l=0;do We=11172+(l<<1<<2)|0,n[We+12>>2]=We,n[We+8>>2]=We,l=l+1|0;while((l|0)!=32);We=m+8|0,We=We&7|0?0-We&7:0,He=m+We|0,We=B+-40-We|0,n[2789]=He,n[2786]=We,n[He+4>>2]=We|1,n[He+We+4>>2]=40,n[2790]=n[2905]}while(!1);if(l=n[2786]|0,l>>>0>q>>>0)return ct=l-q|0,n[2786]=ct,We=n[2789]|0,He=We+q|0,n[2789]=He,n[He+4>>2]=ct|1,n[We+4>>2]=q|3,We=We+8|0,I=Lt,We|0}return n[(Xy()|0)>>2]=12,We=0,I=Lt,We|0}function HP(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0;if(o){u=o+-8|0,d=n[2787]|0,o=n[o+-4>>2]|0,l=o&-8,T=u+l|0;do if(o&1)k=u,B=u;else{if(A=n[u>>2]|0,!(o&3)||(B=u+(0-A)|0,m=A+l|0,B>>>0>>0))return;if((B|0)==(n[2788]|0)){if(o=T+4|0,l=n[o>>2]|0,(l&3|0)!=3){k=B,l=m;break}n[2785]=m,n[o>>2]=l&-2,n[B+4>>2]=m|1,n[B+m>>2]=m;return}if(u=A>>>3,A>>>0<256)if(o=n[B+8>>2]|0,l=n[B+12>>2]|0,(l|0)==(o|0)){n[2783]=n[2783]&~(1<>2]=l,n[l+8>>2]=o,k=B,l=m;break}d=n[B+24>>2]|0,o=n[B+12>>2]|0;do if((o|0)==(B|0)){if(u=B+16|0,l=u+4|0,o=n[l>>2]|0,!o)if(o=n[u>>2]|0,o)l=u;else{o=0;break}for(;;){if(u=o+20|0,A=n[u>>2]|0,A|0){o=A,l=u;continue}if(u=o+16|0,A=n[u>>2]|0,A)o=A,l=u;else break}n[l>>2]=0}else k=n[B+8>>2]|0,n[k+12>>2]=o,n[o+8>>2]=k;while(!1);if(d){if(l=n[B+28>>2]|0,u=11436+(l<<2)|0,(B|0)==(n[u>>2]|0)){if(n[u>>2]=o,!o){n[2784]=n[2784]&~(1<>2]|0)!=(B|0)&1)<<2)>>2]=o,!o){k=B,l=m;break}n[o+24>>2]=d,l=B+16|0,u=n[l>>2]|0,u|0&&(n[o+16>>2]=u,n[u+24>>2]=o),l=n[l+4>>2]|0,l?(n[o+20>>2]=l,n[l+24>>2]=o,k=B,l=m):(k=B,l=m)}else k=B,l=m}while(!1);if(!(B>>>0>=T>>>0)&&(o=T+4|0,A=n[o>>2]|0,!!(A&1))){if(A&2)n[o>>2]=A&-2,n[k+4>>2]=l|1,n[B+l>>2]=l,d=l;else{if(o=n[2788]|0,(T|0)==(n[2789]|0)){if(T=(n[2786]|0)+l|0,n[2786]=T,n[2789]=k,n[k+4>>2]=T|1,(k|0)!=(o|0))return;n[2788]=0,n[2785]=0;return}if((T|0)==(o|0)){T=(n[2785]|0)+l|0,n[2785]=T,n[2788]=B,n[k+4>>2]=T|1,n[B+T>>2]=T;return}d=(A&-8)+l|0,u=A>>>3;do if(A>>>0<256)if(l=n[T+8>>2]|0,o=n[T+12>>2]|0,(o|0)==(l|0)){n[2783]=n[2783]&~(1<>2]=o,n[o+8>>2]=l;break}else{m=n[T+24>>2]|0,o=n[T+12>>2]|0;do if((o|0)==(T|0)){if(u=T+16|0,l=u+4|0,o=n[l>>2]|0,!o)if(o=n[u>>2]|0,o)l=u;else{u=0;break}for(;;){if(u=o+20|0,A=n[u>>2]|0,A|0){o=A,l=u;continue}if(u=o+16|0,A=n[u>>2]|0,A)o=A,l=u;else break}n[l>>2]=0,u=o}else u=n[T+8>>2]|0,n[u+12>>2]=o,n[o+8>>2]=u,u=o;while(!1);if(m|0){if(o=n[T+28>>2]|0,l=11436+(o<<2)|0,(T|0)==(n[l>>2]|0)){if(n[l>>2]=u,!u){n[2784]=n[2784]&~(1<>2]|0)!=(T|0)&1)<<2)>>2]=u,!u)break;n[u+24>>2]=m,o=T+16|0,l=n[o>>2]|0,l|0&&(n[u+16>>2]=l,n[l+24>>2]=u),o=n[o+4>>2]|0,o|0&&(n[u+20>>2]=o,n[o+24>>2]=u)}}while(!1);if(n[k+4>>2]=d|1,n[B+d>>2]=d,(k|0)==(n[2788]|0)){n[2785]=d;return}}if(o=d>>>3,d>>>0<256){u=11172+(o<<1<<2)|0,l=n[2783]|0,o=1<>2]|0):(n[2783]=l|o,o=u,l=u+8|0),n[l>>2]=k,n[o+12>>2]=k,n[k+8>>2]=o,n[k+12>>2]=u;return}o=d>>>8,o?d>>>0>16777215?o=31:(B=(o+1048320|0)>>>16&8,T=o<>>16&4,T=T<>>16&2,o=14-(m|B|o)+(T<>>15)|0,o=d>>>(o+7|0)&1|o<<1):o=0,A=11436+(o<<2)|0,n[k+28>>2]=o,n[k+20>>2]=0,n[k+16>>2]=0,l=n[2784]|0,u=1<>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(d|0)){o=73;break}if(A=u+16+(l>>>31<<2)|0,o=n[A>>2]|0,o)l=l<<1,u=o;else{o=72;break}}if((o|0)==72){n[A>>2]=k,n[k+24>>2]=u,n[k+12>>2]=k,n[k+8>>2]=k;break}else if((o|0)==73){B=u+8|0,T=n[B>>2]|0,n[T+12>>2]=k,n[B>>2]=k,n[k+8>>2]=T,n[k+12>>2]=u,n[k+24>>2]=0;break}}else n[2784]=l|u,n[A>>2]=k,n[k+24>>2]=A,n[k+12>>2]=k,n[k+8>>2]=k;while(!1);if(T=(n[2791]|0)+-1|0,n[2791]=T,!T)o=11588;else return;for(;o=n[o>>2]|0,o;)o=o+8|0;n[2791]=-1}}}function r6e(){return 11628}function n6e(o){o=o|0;var l=0,u=0;return l=I,I=I+16|0,u=l,n[u>>2]=o6e(n[o+60>>2]|0)|0,o=jP(Au(6,u|0)|0)|0,I=l,o|0}function yZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0;q=I,I=I+48|0,M=q+16|0,m=q,d=q+32|0,k=o+28|0,A=n[k>>2]|0,n[d>>2]=A,T=o+20|0,A=(n[T>>2]|0)-A|0,n[d+4>>2]=A,n[d+8>>2]=l,n[d+12>>2]=u,A=A+u|0,B=o+60|0,n[m>>2]=n[B>>2],n[m+4>>2]=d,n[m+8>>2]=2,m=jP(La(146,m|0)|0)|0;e:do if((A|0)!=(m|0)){for(l=2;!((m|0)<0);)if(A=A-m|0,Ye=n[d+4>>2]|0,ae=m>>>0>Ye>>>0,d=ae?d+8|0:d,l=(ae<<31>>31)+l|0,Ye=m-(ae?Ye:0)|0,n[d>>2]=(n[d>>2]|0)+Ye,ae=d+4|0,n[ae>>2]=(n[ae>>2]|0)-Ye,n[M>>2]=n[B>>2],n[M+4>>2]=d,n[M+8>>2]=l,m=jP(La(146,M|0)|0)|0,(A|0)==(m|0)){L=3;break e}n[o+16>>2]=0,n[k>>2]=0,n[T>>2]=0,n[o>>2]=n[o>>2]|32,(l|0)==2?u=0:u=u-(n[d+4>>2]|0)|0}else L=3;while(!1);return(L|0)==3&&(Ye=n[o+44>>2]|0,n[o+16>>2]=Ye+(n[o+48>>2]|0),n[k>>2]=Ye,n[T>>2]=Ye),I=q,u|0}function i6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return d=I,I=I+32|0,m=d,A=d+20|0,n[m>>2]=n[o+60>>2],n[m+4>>2]=0,n[m+8>>2]=l,n[m+12>>2]=A,n[m+16>>2]=u,(jP(Oa(140,m|0)|0)|0)<0?(n[A>>2]=-1,o=-1):o=n[A>>2]|0,I=d,o|0}function jP(o){return o=o|0,o>>>0>4294963200&&(n[(Xy()|0)>>2]=0-o,o=-1),o|0}function Xy(){return(s6e()|0)+64|0}function s6e(){return lU()|0}function lU(){return 2084}function o6e(o){return o=o|0,o|0}function a6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;return d=I,I=I+32|0,A=d,n[o+36>>2]=1,!(n[o>>2]&64|0)&&(n[A>>2]=n[o+60>>2],n[A+4>>2]=21523,n[A+8>>2]=d+16,no(54,A|0)|0)&&(s[o+75>>0]=-1),A=yZ(o,l,u)|0,I=d,A|0}function EZ(o,l){o=o|0,l=l|0;var u=0,A=0;if(u=s[o>>0]|0,A=s[l>>0]|0,!(u<<24>>24)||u<<24>>24!=A<<24>>24)o=A;else{do o=o+1|0,l=l+1|0,u=s[o>>0]|0,A=s[l>>0]|0;while(!(!(u<<24>>24)||u<<24>>24!=A<<24>>24));o=A}return(u&255)-(o&255)|0}function l6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;e:do if(!u)o=0;else{for(;A=s[o>>0]|0,d=s[l>>0]|0,A<<24>>24==d<<24>>24;)if(u=u+-1|0,u)o=o+1|0,l=l+1|0;else{o=0;break e}o=(A&255)-(d&255)|0}while(!1);return o|0}function IZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0;Qe=I,I=I+224|0,L=Qe+120|0,q=Qe+80|0,Ye=Qe,Le=Qe+136|0,A=q,d=A+40|0;do n[A>>2]=0,A=A+4|0;while((A|0)<(d|0));return n[L>>2]=n[u>>2],(cU(0,l,L,Ye,q)|0)<0?u=-1:((n[o+76>>2]|0)>-1?ae=c6e(o)|0:ae=0,u=n[o>>2]|0,M=u&32,(s[o+74>>0]|0)<1&&(n[o>>2]=u&-33),A=o+48|0,n[A>>2]|0?u=cU(o,l,L,Ye,q)|0:(d=o+44|0,m=n[d>>2]|0,n[d>>2]=Le,B=o+28|0,n[B>>2]=Le,k=o+20|0,n[k>>2]=Le,n[A>>2]=80,T=o+16|0,n[T>>2]=Le+80,u=cU(o,l,L,Ye,q)|0,m&&(YP[n[o+36>>2]&7](o,0,0)|0,u=n[k>>2]|0?u:-1,n[d>>2]=m,n[A>>2]=0,n[T>>2]=0,n[B>>2]=0,n[k>>2]=0)),A=n[o>>2]|0,n[o>>2]=A|M,ae|0&&u6e(o),u=A&32|0?-1:u),I=Qe,u|0}function cU(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,We=0,Lt=0,Gr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0;cr=I,I=I+64|0,fr=cr+16|0,$t=cr,Lt=cr+24|0,Tr=cr+8|0,Hr=cr+20|0,n[fr>>2]=l,ct=(o|0)!=0,He=Lt+40|0,We=He,Lt=Lt+39|0,Gr=Tr+4|0,B=0,m=0,L=0;e:for(;;){do if((m|0)>-1)if((B|0)>(2147483647-m|0)){n[(Xy()|0)>>2]=75,m=-1;break}else{m=B+m|0;break}while(!1);if(B=s[l>>0]|0,B<<24>>24)k=l;else{Ze=87;break}t:for(;;){switch(B<<24>>24){case 37:{B=k,Ze=9;break t}case 0:{B=k;break t}default:}tt=k+1|0,n[fr>>2]=tt,B=s[tt>>0]|0,k=tt}t:do if((Ze|0)==9)for(;;){if(Ze=0,(s[k+1>>0]|0)!=37)break t;if(B=B+1|0,k=k+2|0,n[fr>>2]=k,(s[k>>0]|0)==37)Ze=9;else break}while(!1);if(B=B-l|0,ct&&vs(o,l,B),B|0){l=k;continue}T=k+1|0,B=(s[T>>0]|0)+-48|0,B>>>0<10?(tt=(s[k+2>>0]|0)==36,Qe=tt?B:-1,L=tt?1:L,T=tt?k+3|0:T):Qe=-1,n[fr>>2]=T,B=s[T>>0]|0,k=(B<<24>>24)+-32|0;t:do if(k>>>0<32)for(M=0,q=B;;){if(B=1<>2]=T,B=s[T>>0]|0,k=(B<<24>>24)+-32|0,k>>>0>=32)break;q=B}else M=0;while(!1);if(B<<24>>24==42){if(k=T+1|0,B=(s[k>>0]|0)+-48|0,B>>>0<10&&(s[T+2>>0]|0)==36)n[d+(B<<2)>>2]=10,B=n[A+((s[k>>0]|0)+-48<<3)>>2]|0,L=1,T=T+3|0;else{if(L|0){m=-1;break}ct?(L=(n[u>>2]|0)+3&-4,B=n[L>>2]|0,n[u>>2]=L+4,L=0,T=k):(B=0,L=0,T=k)}n[fr>>2]=T,tt=(B|0)<0,B=tt?0-B|0:B,M=tt?M|8192:M}else{if(B=CZ(fr)|0,(B|0)<0){m=-1;break}T=n[fr>>2]|0}do if((s[T>>0]|0)==46){if((s[T+1>>0]|0)!=42){n[fr>>2]=T+1,k=CZ(fr)|0,T=n[fr>>2]|0;break}if(q=T+2|0,k=(s[q>>0]|0)+-48|0,k>>>0<10&&(s[T+3>>0]|0)==36){n[d+(k<<2)>>2]=10,k=n[A+((s[q>>0]|0)+-48<<3)>>2]|0,T=T+4|0,n[fr>>2]=T;break}if(L|0){m=-1;break e}ct?(tt=(n[u>>2]|0)+3&-4,k=n[tt>>2]|0,n[u>>2]=tt+4):k=0,n[fr>>2]=q,T=q}else k=-1;while(!1);for(Le=0;;){if(((s[T>>0]|0)+-65|0)>>>0>57){m=-1;break e}if(tt=T+1|0,n[fr>>2]=tt,q=s[(s[T>>0]|0)+-65+(5178+(Le*58|0))>>0]|0,ae=q&255,(ae+-1|0)>>>0<8)Le=ae,T=tt;else break}if(!(q<<24>>24)){m=-1;break}Ye=(Qe|0)>-1;do if(q<<24>>24==19)if(Ye){m=-1;break e}else Ze=49;else{if(Ye){n[d+(Qe<<2)>>2]=ae,Ye=A+(Qe<<3)|0,Qe=n[Ye+4>>2]|0,Ze=$t,n[Ze>>2]=n[Ye>>2],n[Ze+4>>2]=Qe,Ze=49;break}if(!ct){m=0;break e}wZ($t,ae,u)}while(!1);if((Ze|0)==49&&(Ze=0,!ct)){B=0,l=tt;continue}T=s[T>>0]|0,T=(Le|0)!=0&(T&15|0)==3?T&-33:T,Ye=M&-65537,Qe=M&8192|0?Ye:M;t:do switch(T|0){case 110:switch((Le&255)<<24>>24){case 0:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 1:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 2:{B=n[$t>>2]|0,n[B>>2]=m,n[B+4>>2]=((m|0)<0)<<31>>31,B=0,l=tt;continue e}case 3:{a[n[$t>>2]>>1]=m,B=0,l=tt;continue e}case 4:{s[n[$t>>2]>>0]=m,B=0,l=tt;continue e}case 6:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 7:{B=n[$t>>2]|0,n[B>>2]=m,n[B+4>>2]=((m|0)<0)<<31>>31,B=0,l=tt;continue e}default:{B=0,l=tt;continue e}}case 112:{T=120,k=k>>>0>8?k:8,l=Qe|8,Ze=61;break}case 88:case 120:{l=Qe,Ze=61;break}case 111:{T=$t,l=n[T>>2]|0,T=n[T+4>>2]|0,ae=A6e(l,T,He)|0,Ye=We-ae|0,M=0,q=5642,k=(Qe&8|0)==0|(k|0)>(Ye|0)?k:Ye+1|0,Ye=Qe,Ze=67;break}case 105:case 100:if(T=$t,l=n[T>>2]|0,T=n[T+4>>2]|0,(T|0)<0){l=GP(0,0,l|0,T|0)|0,T=ye,M=$t,n[M>>2]=l,n[M+4>>2]=T,M=1,q=5642,Ze=66;break t}else{M=(Qe&2049|0)!=0&1,q=Qe&2048|0?5643:Qe&1|0?5644:5642,Ze=66;break t}case 117:{T=$t,M=0,q=5642,l=n[T>>2]|0,T=n[T+4>>2]|0,Ze=66;break}case 99:{s[Lt>>0]=n[$t>>2],l=Lt,M=0,q=5642,ae=He,T=1,k=Ye;break}case 109:{T=p6e(n[(Xy()|0)>>2]|0)|0,Ze=71;break}case 115:{T=n[$t>>2]|0,T=T|0?T:5652,Ze=71;break}case 67:{n[Tr>>2]=n[$t>>2],n[Gr>>2]=0,n[$t>>2]=Tr,ae=-1,T=Tr,Ze=75;break}case 83:{l=n[$t>>2]|0,k?(ae=k,T=l,Ze=75):(Ls(o,32,B,0,Qe),l=0,Ze=84);break}case 65:case 71:case 70:case 69:case 97:case 103:case 102:case 101:{B=g6e(o,+E[$t>>3],B,k,Qe,T)|0,l=tt;continue e}default:M=0,q=5642,ae=He,T=k,k=Qe}while(!1);t:do if((Ze|0)==61)Qe=$t,Le=n[Qe>>2]|0,Qe=n[Qe+4>>2]|0,ae=f6e(Le,Qe,He,T&32)|0,q=(l&8|0)==0|(Le|0)==0&(Qe|0)==0,M=q?0:2,q=q?5642:5642+(T>>4)|0,Ye=l,l=Le,T=Qe,Ze=67;else if((Ze|0)==66)ae=Zy(l,T,He)|0,Ye=Qe,Ze=67;else if((Ze|0)==71)Ze=0,Qe=h6e(T,0,k)|0,Le=(Qe|0)==0,l=T,M=0,q=5642,ae=Le?T+k|0:Qe,T=Le?k:Qe-T|0,k=Ye;else if((Ze|0)==75){for(Ze=0,q=T,l=0,k=0;M=n[q>>2]|0,!(!M||(k=BZ(Hr,M)|0,(k|0)<0|k>>>0>(ae-l|0)>>>0));)if(l=k+l|0,ae>>>0>l>>>0)q=q+4|0;else break;if((k|0)<0){m=-1;break e}if(Ls(o,32,B,l,Qe),!l)l=0,Ze=84;else for(M=0;;){if(k=n[T>>2]|0,!k){Ze=84;break t}if(k=BZ(Hr,k)|0,M=k+M|0,(M|0)>(l|0)){Ze=84;break t}if(vs(o,Hr,k),M>>>0>=l>>>0){Ze=84;break}else T=T+4|0}}while(!1);if((Ze|0)==67)Ze=0,T=(l|0)!=0|(T|0)!=0,Qe=(k|0)!=0|T,T=((T^1)&1)+(We-ae)|0,l=Qe?ae:He,ae=He,T=Qe?(k|0)>(T|0)?k:T:k,k=(k|0)>-1?Ye&-65537:Ye;else if((Ze|0)==84){Ze=0,Ls(o,32,B,l,Qe^8192),B=(B|0)>(l|0)?B:l,l=tt;continue}Le=ae-l|0,Ye=(T|0)<(Le|0)?Le:T,Qe=Ye+M|0,B=(B|0)<(Qe|0)?Qe:B,Ls(o,32,B,Qe,k),vs(o,q,M),Ls(o,48,B,Qe,k^65536),Ls(o,48,Ye,Le,0),vs(o,l,Le),Ls(o,32,B,Qe,k^8192),l=tt}e:do if((Ze|0)==87&&!o)if(!L)m=0;else{for(m=1;l=n[d+(m<<2)>>2]|0,!!l;)if(wZ(A+(m<<3)|0,l,u),m=m+1|0,(m|0)>=10){m=1;break e}for(;;){if(n[d+(m<<2)>>2]|0){m=-1;break e}if(m=m+1|0,(m|0)>=10){m=1;break}}}while(!1);return I=cr,m|0}function c6e(o){return o=o|0,0}function u6e(o){o=o|0}function vs(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]&32||v6e(l,u,o)|0}function CZ(o){o=o|0;var l=0,u=0,A=0;if(u=n[o>>2]|0,A=(s[u>>0]|0)+-48|0,A>>>0<10){l=0;do l=A+(l*10|0)|0,u=u+1|0,n[o>>2]=u,A=(s[u>>0]|0)+-48|0;while(A>>>0<10)}else l=0;return l|0}function wZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;e:do if(l>>>0<=20)do switch(l|0){case 9:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,n[o>>2]=l;break e}case 10:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,A=o,n[A>>2]=l,n[A+4>>2]=((l|0)<0)<<31>>31;break e}case 11:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,A=o,n[A>>2]=l,n[A+4>>2]=0;break e}case 12:{A=(n[u>>2]|0)+7&-8,l=A,d=n[l>>2]|0,l=n[l+4>>2]|0,n[u>>2]=A+8,A=o,n[A>>2]=d,n[A+4>>2]=l;break e}case 13:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,A=(A&65535)<<16>>16,d=o,n[d>>2]=A,n[d+4>>2]=((A|0)<0)<<31>>31;break e}case 14:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,d=o,n[d>>2]=A&65535,n[d+4>>2]=0;break e}case 15:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,A=(A&255)<<24>>24,d=o,n[d>>2]=A,n[d+4>>2]=((A|0)<0)<<31>>31;break e}case 16:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,d=o,n[d>>2]=A&255,n[d+4>>2]=0;break e}case 17:{d=(n[u>>2]|0)+7&-8,m=+E[d>>3],n[u>>2]=d+8,E[o>>3]=m;break e}case 18:{d=(n[u>>2]|0)+7&-8,m=+E[d>>3],n[u>>2]=d+8,E[o>>3]=m;break e}default:break e}while(!1);while(!1)}function f6e(o,l,u,A){if(o=o|0,l=l|0,u=u|0,A=A|0,!((o|0)==0&(l|0)==0))do u=u+-1|0,s[u>>0]=c[5694+(o&15)>>0]|0|A,o=qP(o|0,l|0,4)|0,l=ye;while(!((o|0)==0&(l|0)==0));return u|0}function A6e(o,l,u){if(o=o|0,l=l|0,u=u|0,!((o|0)==0&(l|0)==0))do u=u+-1|0,s[u>>0]=o&7|48,o=qP(o|0,l|0,3)|0,l=ye;while(!((o|0)==0&(l|0)==0));return u|0}function Zy(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;if(l>>>0>0|(l|0)==0&o>>>0>4294967295){for(;A=pU(o|0,l|0,10,0)|0,u=u+-1|0,s[u>>0]=A&255|48,A=o,o=AU(o|0,l|0,10,0)|0,l>>>0>9|(l|0)==9&A>>>0>4294967295;)l=ye;l=o}else l=o;if(l)for(;u=u+-1|0,s[u>>0]=(l>>>0)%10|0|48,!(l>>>0<10);)l=(l>>>0)/10|0;return u|0}function p6e(o){return o=o|0,I6e(o,n[(E6e()|0)+188>>2]|0)|0}function h6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;m=l&255,A=(u|0)!=0;e:do if(A&(o&3|0)!=0)for(d=l&255;;){if((s[o>>0]|0)==d<<24>>24){B=6;break e}if(o=o+1|0,u=u+-1|0,A=(u|0)!=0,!(A&(o&3|0)!=0)){B=5;break}}else B=5;while(!1);(B|0)==5&&(A?B=6:u=0);e:do if((B|0)==6&&(d=l&255,(s[o>>0]|0)!=d<<24>>24)){A=Ue(m,16843009)|0;t:do if(u>>>0>3){for(;m=n[o>>2]^A,!((m&-2139062144^-2139062144)&m+-16843009|0);)if(o=o+4|0,u=u+-4|0,u>>>0<=3){B=11;break t}}else B=11;while(!1);if((B|0)==11&&!u){u=0;break}for(;;){if((s[o>>0]|0)==d<<24>>24)break e;if(o=o+1|0,u=u+-1|0,!u){u=0;break}}}while(!1);return(u|0?o:0)|0}function Ls(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0;if(B=I,I=I+256|0,m=B,(u|0)>(A|0)&(d&73728|0)==0){if(d=u-A|0,eE(m|0,l|0,(d>>>0<256?d:256)|0)|0,d>>>0>255){l=u-A|0;do vs(o,m,256),d=d+-256|0;while(d>>>0>255);d=l&255}vs(o,m,d)}I=B}function BZ(o,l){return o=o|0,l=l|0,o?o=m6e(o,l,0)|0:o=0,o|0}function g6e(o,l,u,A,d,m){o=o|0,l=+l,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,We=0,Lt=0,Gr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0;Hn=I,I=I+560|0,T=Hn+8|0,tt=Hn,cr=Hn+524|0,Hr=cr,M=Hn+512|0,n[tt>>2]=0,Tr=M+12|0,vZ(l)|0,(ye|0)<0?(l=-l,fr=1,Gr=5659):(fr=(d&2049|0)!=0&1,Gr=d&2048|0?5662:d&1|0?5665:5660),vZ(l)|0,$t=ye&2146435072;do if($t>>>0<2146435072|($t|0)==2146435072&!1){if(Ye=+d6e(l,tt)*2,B=Ye!=0,B&&(n[tt>>2]=(n[tt>>2]|0)+-1),ct=m|32,(ct|0)==97){Le=m&32,ae=Le|0?Gr+9|0:Gr,q=fr|2,B=12-A|0;do if(A>>>0>11|(B|0)==0)l=Ye;else{l=8;do B=B+-1|0,l=l*16;while(B|0);if((s[ae>>0]|0)==45){l=-(l+(-Ye-l));break}else{l=Ye+l-l;break}}while(!1);k=n[tt>>2]|0,B=(k|0)<0?0-k|0:k,B=Zy(B,((B|0)<0)<<31>>31,Tr)|0,(B|0)==(Tr|0)&&(B=M+11|0,s[B>>0]=48),s[B+-1>>0]=(k>>31&2)+43,L=B+-2|0,s[L>>0]=m+15,M=(A|0)<1,T=(d&8|0)==0,B=cr;do $t=~~l,k=B+1|0,s[B>>0]=c[5694+$t>>0]|Le,l=(l-+($t|0))*16,(k-Hr|0)==1&&!(T&(M&l==0))?(s[k>>0]=46,B=B+2|0):B=k;while(l!=0);$t=B-Hr|0,Hr=Tr-L|0,Tr=(A|0)!=0&($t+-2|0)<(A|0)?A+2|0:$t,B=Hr+q+Tr|0,Ls(o,32,u,B,d),vs(o,ae,q),Ls(o,48,u,B,d^65536),vs(o,cr,$t),Ls(o,48,Tr-$t|0,0,0),vs(o,L,Hr),Ls(o,32,u,B,d^8192);break}k=(A|0)<0?6:A,B?(B=(n[tt>>2]|0)+-28|0,n[tt>>2]=B,l=Ye*268435456):(l=Ye,B=n[tt>>2]|0),$t=(B|0)<0?T:T+288|0,T=$t;do We=~~l>>>0,n[T>>2]=We,T=T+4|0,l=(l-+(We>>>0))*1e9;while(l!=0);if((B|0)>0)for(M=$t,q=T;;){if(L=(B|0)<29?B:29,B=q+-4|0,B>>>0>=M>>>0){T=0;do He=kZ(n[B>>2]|0,0,L|0)|0,He=fU(He|0,ye|0,T|0,0)|0,We=ye,Ze=pU(He|0,We|0,1e9,0)|0,n[B>>2]=Ze,T=AU(He|0,We|0,1e9,0)|0,B=B+-4|0;while(B>>>0>=M>>>0);T&&(M=M+-4|0,n[M>>2]=T)}for(T=q;!(T>>>0<=M>>>0);)if(B=T+-4|0,!(n[B>>2]|0))T=B;else break;if(B=(n[tt>>2]|0)-L|0,n[tt>>2]=B,(B|0)>0)q=T;else break}else M=$t;if((B|0)<0){A=((k+25|0)/9|0)+1|0,Qe=(ct|0)==102;do{if(Le=0-B|0,Le=(Le|0)<9?Le:9,M>>>0>>0){L=(1<>>Le,ae=0,B=M;do We=n[B>>2]|0,n[B>>2]=(We>>>Le)+ae,ae=Ue(We&L,q)|0,B=B+4|0;while(B>>>0>>0);B=n[M>>2]|0?M:M+4|0,ae?(n[T>>2]=ae,M=B,B=T+4|0):(M=B,B=T)}else M=n[M>>2]|0?M:M+4|0,B=T;T=Qe?$t:M,T=(B-T>>2|0)>(A|0)?T+(A<<2)|0:B,B=(n[tt>>2]|0)+Le|0,n[tt>>2]=B}while((B|0)<0);B=M,A=T}else B=M,A=T;if(We=$t,B>>>0>>0){if(T=(We-B>>2)*9|0,L=n[B>>2]|0,L>>>0>=10){M=10;do M=M*10|0,T=T+1|0;while(L>>>0>=M>>>0)}}else T=0;if(Qe=(ct|0)==103,Ze=(k|0)!=0,M=k-((ct|0)!=102?T:0)+((Ze&Qe)<<31>>31)|0,(M|0)<(((A-We>>2)*9|0)+-9|0)){if(M=M+9216|0,Le=$t+4+(((M|0)/9|0)+-1024<<2)|0,M=((M|0)%9|0)+1|0,(M|0)<9){L=10;do L=L*10|0,M=M+1|0;while((M|0)!=9)}else L=10;if(q=n[Le>>2]|0,ae=(q>>>0)%(L>>>0)|0,M=(Le+4|0)==(A|0),M&(ae|0)==0)M=Le;else if(Ye=((q>>>0)/(L>>>0)|0)&1|0?9007199254740994:9007199254740992,He=(L|0)/2|0,l=ae>>>0>>0?.5:M&(ae|0)==(He|0)?1:1.5,fr&&(He=(s[Gr>>0]|0)==45,l=He?-l:l,Ye=He?-Ye:Ye),M=q-ae|0,n[Le>>2]=M,Ye+l!=Ye){if(He=M+L|0,n[Le>>2]=He,He>>>0>999999999)for(T=Le;M=T+-4|0,n[T>>2]=0,M>>>0>>0&&(B=B+-4|0,n[B>>2]=0),He=(n[M>>2]|0)+1|0,n[M>>2]=He,He>>>0>999999999;)T=M;else M=Le;if(T=(We-B>>2)*9|0,q=n[B>>2]|0,q>>>0>=10){L=10;do L=L*10|0,T=T+1|0;while(q>>>0>=L>>>0)}}else M=Le;M=M+4|0,M=A>>>0>M>>>0?M:A,He=B}else M=A,He=B;for(ct=M;;){if(ct>>>0<=He>>>0){tt=0;break}if(B=ct+-4|0,!(n[B>>2]|0))ct=B;else{tt=1;break}}A=0-T|0;do if(Qe)if(B=((Ze^1)&1)+k|0,(B|0)>(T|0)&(T|0)>-5?(L=m+-1|0,k=B+-1-T|0):(L=m+-2|0,k=B+-1|0),B=d&8,B)Le=B;else{if(tt&&(Lt=n[ct+-4>>2]|0,(Lt|0)!=0))if((Lt>>>0)%10|0)M=0;else{M=0,B=10;do B=B*10|0,M=M+1|0;while(!((Lt>>>0)%(B>>>0)|0|0))}else M=9;if(B=((ct-We>>2)*9|0)+-9|0,(L|32|0)==102){Le=B-M|0,Le=(Le|0)>0?Le:0,k=(k|0)<(Le|0)?k:Le,Le=0;break}else{Le=B+T-M|0,Le=(Le|0)>0?Le:0,k=(k|0)<(Le|0)?k:Le,Le=0;break}}else L=m,Le=d&8;while(!1);if(Qe=k|Le,q=(Qe|0)!=0&1,ae=(L|32|0)==102,ae)Ze=0,B=(T|0)>0?T:0;else{if(B=(T|0)<0?A:T,B=Zy(B,((B|0)<0)<<31>>31,Tr)|0,M=Tr,(M-B|0)<2)do B=B+-1|0,s[B>>0]=48;while((M-B|0)<2);s[B+-1>>0]=(T>>31&2)+43,B=B+-2|0,s[B>>0]=L,Ze=B,B=M-B|0}if(B=fr+1+k+q+B|0,Ls(o,32,u,B,d),vs(o,Gr,fr),Ls(o,48,u,B,d^65536),ae){L=He>>>0>$t>>>0?$t:He,Le=cr+9|0,q=Le,ae=cr+8|0,M=L;do{if(T=Zy(n[M>>2]|0,0,Le)|0,(M|0)==(L|0))(T|0)==(Le|0)&&(s[ae>>0]=48,T=ae);else if(T>>>0>cr>>>0){eE(cr|0,48,T-Hr|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}vs(o,T,q-T|0),M=M+4|0}while(M>>>0<=$t>>>0);if(Qe|0&&vs(o,5710,1),M>>>0>>0&(k|0)>0)for(;;){if(T=Zy(n[M>>2]|0,0,Le)|0,T>>>0>cr>>>0){eE(cr|0,48,T-Hr|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}if(vs(o,T,(k|0)<9?k:9),M=M+4|0,T=k+-9|0,M>>>0>>0&(k|0)>9)k=T;else{k=T;break}}Ls(o,48,k+9|0,9,0)}else{if(Qe=tt?ct:He+4|0,(k|0)>-1){tt=cr+9|0,Le=(Le|0)==0,A=tt,q=0-Hr|0,ae=cr+8|0,L=He;do{T=Zy(n[L>>2]|0,0,tt)|0,(T|0)==(tt|0)&&(s[ae>>0]=48,T=ae);do if((L|0)==(He|0)){if(M=T+1|0,vs(o,T,1),Le&(k|0)<1){T=M;break}vs(o,5710,1),T=M}else{if(T>>>0<=cr>>>0)break;eE(cr|0,48,T+q|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}while(!1);Hr=A-T|0,vs(o,T,(k|0)>(Hr|0)?Hr:k),k=k-Hr|0,L=L+4|0}while(L>>>0>>0&(k|0)>-1)}Ls(o,48,k+18|0,18,0),vs(o,Ze,Tr-Ze|0)}Ls(o,32,u,B,d^8192)}else cr=(m&32|0)!=0,B=fr+3|0,Ls(o,32,u,B,d&-65537),vs(o,Gr,fr),vs(o,l!=l|!1?cr?5686:5690:cr?5678:5682,3),Ls(o,32,u,B,d^8192);while(!1);return I=Hn,((B|0)<(u|0)?u:B)|0}function vZ(o){o=+o;var l=0;return E[S>>3]=o,l=n[S>>2]|0,ye=n[S+4>>2]|0,l|0}function d6e(o,l){return o=+o,l=l|0,+ +SZ(o,l)}function SZ(o,l){o=+o,l=l|0;var u=0,A=0,d=0;switch(E[S>>3]=o,u=n[S>>2]|0,A=n[S+4>>2]|0,d=qP(u|0,A|0,52)|0,d&2047){case 0:{o!=0?(o=+SZ(o*18446744073709552e3,l),u=(n[l>>2]|0)+-64|0):u=0,n[l>>2]=u;break}case 2047:break;default:n[l>>2]=(d&2047)+-1022,n[S>>2]=u,n[S+4>>2]=A&-2146435073|1071644672,o=+E[S>>3]}return+o}function m6e(o,l,u){o=o|0,l=l|0,u=u|0;do if(o){if(l>>>0<128){s[o>>0]=l,o=1;break}if(!(n[n[(y6e()|0)+188>>2]>>2]|0))if((l&-128|0)==57216){s[o>>0]=l,o=1;break}else{n[(Xy()|0)>>2]=84,o=-1;break}if(l>>>0<2048){s[o>>0]=l>>>6|192,s[o+1>>0]=l&63|128,o=2;break}if(l>>>0<55296|(l&-8192|0)==57344){s[o>>0]=l>>>12|224,s[o+1>>0]=l>>>6&63|128,s[o+2>>0]=l&63|128,o=3;break}if((l+-65536|0)>>>0<1048576){s[o>>0]=l>>>18|240,s[o+1>>0]=l>>>12&63|128,s[o+2>>0]=l>>>6&63|128,s[o+3>>0]=l&63|128,o=4;break}else{n[(Xy()|0)>>2]=84,o=-1;break}}else o=1;while(!1);return o|0}function y6e(){return lU()|0}function E6e(){return lU()|0}function I6e(o,l){o=o|0,l=l|0;var u=0,A=0;for(A=0;;){if((c[5712+A>>0]|0)==(o|0)){o=2;break}if(u=A+1|0,(u|0)==87){u=5800,A=87,o=5;break}else A=u}if((o|0)==2&&(A?(u=5800,o=5):u=5800),(o|0)==5)for(;;){do o=u,u=u+1|0;while(s[o>>0]|0);if(A=A+-1|0,A)o=5;else break}return C6e(u,n[l+20>>2]|0)|0}function C6e(o,l){return o=o|0,l=l|0,w6e(o,l)|0}function w6e(o,l){return o=o|0,l=l|0,l?l=B6e(n[l>>2]|0,n[l+4>>2]|0,o)|0:l=0,(l|0?l:o)|0}function B6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0;ae=(n[o>>2]|0)+1794895138|0,m=Ad(n[o+8>>2]|0,ae)|0,A=Ad(n[o+12>>2]|0,ae)|0,d=Ad(n[o+16>>2]|0,ae)|0;e:do if(m>>>0>>2>>>0&&(q=l-(m<<2)|0,A>>>0>>0&d>>>0>>0)&&!((d|A)&3|0)){for(q=A>>>2,L=d>>>2,M=0;;){if(k=m>>>1,T=M+k|0,B=T<<1,d=B+q|0,A=Ad(n[o+(d<<2)>>2]|0,ae)|0,d=Ad(n[o+(d+1<<2)>>2]|0,ae)|0,!(d>>>0>>0&A>>>0<(l-d|0)>>>0)){A=0;break e}if(s[o+(d+A)>>0]|0){A=0;break e}if(A=EZ(u,o+d|0)|0,!A)break;if(A=(A|0)<0,(m|0)==1){A=0;break e}else M=A?M:T,m=A?k:m-k|0}A=B+L|0,d=Ad(n[o+(A<<2)>>2]|0,ae)|0,A=Ad(n[o+(A+1<<2)>>2]|0,ae)|0,A>>>0>>0&d>>>0<(l-A|0)>>>0?A=s[o+(A+d)>>0]|0?0:o+A|0:A=0}else A=0;while(!1);return A|0}function Ad(o,l){o=o|0,l=l|0;var u=0;return u=RZ(o|0)|0,(l|0?u:o)|0}function v6e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=u+16|0,d=n[A>>2]|0,d?m=5:S6e(u)|0?A=0:(d=n[A>>2]|0,m=5);e:do if((m|0)==5){if(k=u+20|0,B=n[k>>2]|0,A=B,(d-B|0)>>>0>>0){A=YP[n[u+36>>2]&7](u,o,l)|0;break}t:do if((s[u+75>>0]|0)>-1){for(B=l;;){if(!B){m=0,d=o;break t}if(d=B+-1|0,(s[o+d>>0]|0)==10)break;B=d}if(A=YP[n[u+36>>2]&7](u,o,B)|0,A>>>0>>0)break e;m=B,d=o+B|0,l=l-B|0,A=n[k>>2]|0}else m=0,d=o;while(!1);Qr(A|0,d|0,l|0)|0,n[k>>2]=(n[k>>2]|0)+l,A=m+l|0}while(!1);return A|0}function S6e(o){o=o|0;var l=0,u=0;return l=o+74|0,u=s[l>>0]|0,s[l>>0]=u+255|u,l=n[o>>2]|0,l&8?(n[o>>2]=l|32,o=-1):(n[o+8>>2]=0,n[o+4>>2]=0,u=n[o+44>>2]|0,n[o+28>>2]=u,n[o+20>>2]=u,n[o+16>>2]=u+(n[o+48>>2]|0),o=0),o|0}function $n(o,l){o=y(o),l=y(l);var u=0,A=0;u=DZ(o)|0;do if((u&2147483647)>>>0<=2139095040){if(A=DZ(l)|0,(A&2147483647)>>>0<=2139095040)if((A^u|0)<0){o=(u|0)<0?l:o;break}else{o=o>2]=o,n[S>>2]|0|0}function pd(o,l){o=y(o),l=y(l);var u=0,A=0;u=bZ(o)|0;do if((u&2147483647)>>>0<=2139095040){if(A=bZ(l)|0,(A&2147483647)>>>0<=2139095040)if((A^u|0)<0){o=(u|0)<0?o:l;break}else{o=o>2]=o,n[S>>2]|0|0}function uU(o,l){o=y(o),l=y(l);var u=0,A=0,d=0,m=0,B=0,k=0,T=0,M=0;m=(h[S>>2]=o,n[S>>2]|0),k=(h[S>>2]=l,n[S>>2]|0),u=m>>>23&255,B=k>>>23&255,T=m&-2147483648,d=k<<1;e:do if(d|0&&!((u|0)==255|((D6e(l)|0)&2147483647)>>>0>2139095040)){if(A=m<<1,A>>>0<=d>>>0)return l=y(o*y(0)),y((A|0)==(d|0)?l:o);if(u)A=m&8388607|8388608;else{if(u=m<<9,(u|0)>-1){A=u,u=0;do u=u+-1|0,A=A<<1;while((A|0)>-1)}else u=0;A=m<<1-u}if(B)k=k&8388607|8388608;else{if(m=k<<9,(m|0)>-1){d=0;do d=d+-1|0,m=m<<1;while((m|0)>-1)}else d=0;B=d,k=k<<1-d}d=A-k|0,m=(d|0)>-1;t:do if((u|0)>(B|0)){for(;;){if(m)if(d)A=d;else break;if(A=A<<1,u=u+-1|0,d=A-k|0,m=(d|0)>-1,(u|0)<=(B|0))break t}l=y(o*y(0));break e}while(!1);if(m)if(d)A=d;else{l=y(o*y(0));break}if(A>>>0<8388608)do A=A<<1,u=u+-1|0;while(A>>>0<8388608);(u|0)>0?u=A+-8388608|u<<23:u=A>>>(1-u|0),l=(n[S>>2]=u|T,y(h[S>>2]))}else M=3;while(!1);return(M|0)==3&&(l=y(o*l),l=y(l/l)),y(l)}function D6e(o){return o=y(o),h[S>>2]=o,n[S>>2]|0|0}function b6e(o,l){return o=o|0,l=l|0,IZ(n[582]|0,o,l)|0}function an(o){o=o|0,Nt()}function $y(o){o=o|0}function P6e(o,l){return o=o|0,l=l|0,0}function x6e(o){return o=o|0,(PZ(o+4|0)|0)==-1?(ip[n[(n[o>>2]|0)+8>>2]&127](o),o=1):o=0,o|0}function PZ(o){o=o|0;var l=0;return l=n[o>>2]|0,n[o>>2]=l+-1,l+-1|0}function Gh(o){o=o|0,x6e(o)|0&&k6e(o)}function k6e(o){o=o|0;var l=0;l=o+8|0,n[l>>2]|0&&(PZ(l)|0)!=-1||ip[n[(n[o>>2]|0)+16>>2]&127](o)}function Kt(o){o=o|0;var l=0;for(l=o|0?o:1;o=_P(l)|0,!(o|0);){if(o=T6e()|0,!o){o=0;break}GZ[o&0]()}return o|0}function xZ(o){return o=o|0,Kt(o)|0}function It(o){o=o|0,HP(o)}function Q6e(o){o=o|0,(s[o+11>>0]|0)<0&&It(n[o>>2]|0)}function T6e(){var o=0;return o=n[2923]|0,n[2923]=o+0,o|0}function R6e(){}function GP(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,A=l-A-(u>>>0>o>>>0|0)>>>0,ye=A,o-u>>>0|0|0}function fU(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,u=o+u>>>0,ye=l+A+(u>>>0>>0|0)>>>0,u|0|0}function eE(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;if(m=o+u|0,l=l&255,(u|0)>=67){for(;o&3;)s[o>>0]=l,o=o+1|0;for(A=m&-4|0,d=A-64|0,B=l|l<<8|l<<16|l<<24;(o|0)<=(d|0);)n[o>>2]=B,n[o+4>>2]=B,n[o+8>>2]=B,n[o+12>>2]=B,n[o+16>>2]=B,n[o+20>>2]=B,n[o+24>>2]=B,n[o+28>>2]=B,n[o+32>>2]=B,n[o+36>>2]=B,n[o+40>>2]=B,n[o+44>>2]=B,n[o+48>>2]=B,n[o+52>>2]=B,n[o+56>>2]=B,n[o+60>>2]=B,o=o+64|0;for(;(o|0)<(A|0);)n[o>>2]=B,o=o+4|0}for(;(o|0)<(m|0);)s[o>>0]=l,o=o+1|0;return m-u|0}function kZ(o,l,u){return o=o|0,l=l|0,u=u|0,(u|0)<32?(ye=l<>>32-u,o<>>u,o>>>u|(l&(1<>>u-32|0)}function Qr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;if((u|0)>=8192)return OA(o|0,l|0,u|0)|0;if(m=o|0,d=o+u|0,(o&3)==(l&3)){for(;o&3;){if(!u)return m|0;s[o>>0]=s[l>>0]|0,o=o+1|0,l=l+1|0,u=u-1|0}for(u=d&-4|0,A=u-64|0;(o|0)<=(A|0);)n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2],n[o+16>>2]=n[l+16>>2],n[o+20>>2]=n[l+20>>2],n[o+24>>2]=n[l+24>>2],n[o+28>>2]=n[l+28>>2],n[o+32>>2]=n[l+32>>2],n[o+36>>2]=n[l+36>>2],n[o+40>>2]=n[l+40>>2],n[o+44>>2]=n[l+44>>2],n[o+48>>2]=n[l+48>>2],n[o+52>>2]=n[l+52>>2],n[o+56>>2]=n[l+56>>2],n[o+60>>2]=n[l+60>>2],o=o+64|0,l=l+64|0;for(;(o|0)<(u|0);)n[o>>2]=n[l>>2],o=o+4|0,l=l+4|0}else for(u=d-4|0;(o|0)<(u|0);)s[o>>0]=s[l>>0]|0,s[o+1>>0]=s[l+1>>0]|0,s[o+2>>0]=s[l+2>>0]|0,s[o+3>>0]=s[l+3>>0]|0,o=o+4|0,l=l+4|0;for(;(o|0)<(d|0);)s[o>>0]=s[l>>0]|0,o=o+1|0,l=l+1|0;return m|0}function QZ(o){o=o|0;var l=0;return l=s[N+(o&255)>>0]|0,(l|0)<8?l|0:(l=s[N+(o>>8&255)>>0]|0,(l|0)<8?l+8|0:(l=s[N+(o>>16&255)>>0]|0,(l|0)<8?l+16|0:(s[N+(o>>>24)>>0]|0)+24|0))}function TZ(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,M=0,L=0,q=0,ae=0,Ye=0,Le=0;if(L=o,T=l,M=T,B=u,ae=A,k=ae,!M)return m=(d|0)!=0,k?m?(n[d>>2]=o|0,n[d+4>>2]=l&0,ae=0,d=0,ye=ae,d|0):(ae=0,d=0,ye=ae,d|0):(m&&(n[d>>2]=(L>>>0)%(B>>>0),n[d+4>>2]=0),ae=0,d=(L>>>0)/(B>>>0)>>>0,ye=ae,d|0);m=(k|0)==0;do if(B){if(!m){if(m=(b(k|0)|0)-(b(M|0)|0)|0,m>>>0<=31){q=m+1|0,k=31-m|0,l=m-31>>31,B=q,o=L>>>(q>>>0)&l|M<>>(q>>>0)&l,m=0,k=L<>2]=o|0,n[d+4>>2]=T|l&0,ae=0,d=0,ye=ae,d|0):(ae=0,d=0,ye=ae,d|0)}if(m=B-1|0,m&B|0){k=(b(B|0)|0)+33-(b(M|0)|0)|0,Le=64-k|0,q=32-k|0,T=q>>31,Ye=k-32|0,l=Ye>>31,B=k,o=q-1>>31&M>>>(Ye>>>0)|(M<>>(k>>>0))&l,l=l&M>>>(k>>>0),m=L<>>(Ye>>>0))&T|L<>31;break}return d|0&&(n[d>>2]=m&L,n[d+4>>2]=0),(B|0)==1?(Ye=T|l&0,Le=o|0|0,ye=Ye,Le|0):(Le=QZ(B|0)|0,Ye=M>>>(Le>>>0)|0,Le=M<<32-Le|L>>>(Le>>>0)|0,ye=Ye,Le|0)}else{if(m)return d|0&&(n[d>>2]=(M>>>0)%(B>>>0),n[d+4>>2]=0),Ye=0,Le=(M>>>0)/(B>>>0)>>>0,ye=Ye,Le|0;if(!L)return d|0&&(n[d>>2]=0,n[d+4>>2]=(M>>>0)%(k>>>0)),Ye=0,Le=(M>>>0)/(k>>>0)>>>0,ye=Ye,Le|0;if(m=k-1|0,!(m&k))return d|0&&(n[d>>2]=o|0,n[d+4>>2]=m&M|l&0),Ye=0,Le=M>>>((QZ(k|0)|0)>>>0),ye=Ye,Le|0;if(m=(b(k|0)|0)-(b(M|0)|0)|0,m>>>0<=30){l=m+1|0,k=31-m|0,B=l,o=M<>>(l>>>0),l=M>>>(l>>>0),m=0,k=L<>2]=o|0,n[d+4>>2]=T|l&0,Ye=0,Le=0,ye=Ye,Le|0):(Ye=0,Le=0,ye=Ye,Le|0)}while(!1);if(!B)M=k,T=0,k=0;else{q=u|0|0,L=ae|A&0,M=fU(q|0,L|0,-1,-1)|0,u=ye,T=k,k=0;do A=T,T=m>>>31|T<<1,m=k|m<<1,A=o<<1|A>>>31|0,ae=o>>>31|l<<1|0,GP(M|0,u|0,A|0,ae|0)|0,Le=ye,Ye=Le>>31|((Le|0)<0?-1:0)<<1,k=Ye&1,o=GP(A|0,ae|0,Ye&q|0,(((Le|0)<0?-1:0)>>31|((Le|0)<0?-1:0)<<1)&L|0)|0,l=ye,B=B-1|0;while(B|0);M=T,T=0}return B=0,d|0&&(n[d>>2]=o,n[d+4>>2]=l),Ye=(m|0)>>>31|(M|B)<<1|(B<<1|m>>>31)&0|T,Le=(m<<1|0)&-2|k,ye=Ye,Le|0}function AU(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,TZ(o,l,u,A,0)|0}function qh(o){o=o|0;var l=0,u=0;return u=o+15&-16|0,l=n[C>>2]|0,o=l+u|0,(u|0)>0&(o|0)<(l|0)|(o|0)<0?(oe()|0,fu(12),-1):(n[C>>2]=o,(o|0)>($()|0)&&!(X()|0)?(n[C>>2]=l,fu(12),-1):l|0)}function Q2(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;if((l|0)<(o|0)&(o|0)<(l+u|0)){for(A=o,l=l+u|0,o=o+u|0;(u|0)>0;)o=o-1|0,l=l-1|0,u=u-1|0,s[o>>0]=s[l>>0]|0;o=A}else Qr(o,l,u)|0;return o|0}function pU(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;return m=I,I=I+16|0,d=m|0,TZ(o,l,u,A,d)|0,I=m,ye=n[d+4>>2]|0,n[d>>2]|0|0}function RZ(o){return o=o|0,(o&255)<<24|(o>>8&255)<<16|(o>>16&255)<<8|o>>>24|0}function F6e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,FZ[o&1](l|0,u|0,A|0,d|0,m|0)}function N6e(o,l,u){o=o|0,l=l|0,u=y(u),NZ[o&1](l|0,y(u))}function O6e(o,l,u){o=o|0,l=l|0,u=+u,OZ[o&31](l|0,+u)}function L6e(o,l,u,A){return o=o|0,l=l|0,u=y(u),A=y(A),y(LZ[o&0](l|0,y(u),y(A)))}function M6e(o,l){o=o|0,l=l|0,ip[o&127](l|0)}function U6e(o,l,u){o=o|0,l=l|0,u=u|0,sp[o&31](l|0,u|0)}function _6e(o,l){return o=o|0,l=l|0,gd[o&31](l|0)|0}function H6e(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0,MZ[o&1](l|0,+u,+A,d|0)}function j6e(o,l,u,A){o=o|0,l=l|0,u=+u,A=+A,wGe[o&1](l|0,+u,+A)}function G6e(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,YP[o&7](l|0,u|0,A|0)|0}function q6e(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,+BGe[o&1](l|0,u|0,A|0)}function W6e(o,l){return o=o|0,l=l|0,+UZ[o&15](l|0)}function Y6e(o,l,u){return o=o|0,l=l|0,u=+u,vGe[o&1](l|0,+u)|0}function V6e(o,l,u){return o=o|0,l=l|0,u=u|0,gU[o&15](l|0,u|0)|0}function J6e(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=+A,d=+d,m=m|0,SGe[o&1](l|0,u|0,+A,+d,m|0)}function K6e(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,DGe[o&1](l|0,u|0,A|0,d|0,m|0,B|0)}function z6e(o,l,u){return o=o|0,l=l|0,u=u|0,+_Z[o&7](l|0,u|0)}function X6e(o){return o=o|0,VP[o&7]()|0}function Z6e(o,l,u,A,d,m){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,HZ[o&1](l|0,u|0,A|0,d|0,m|0)|0}function $6e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=+d,bGe[o&1](l|0,u|0,A|0,+d)}function eGe(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=y(A),d=d|0,m=y(m),B=B|0,jZ[o&1](l|0,u|0,y(A),d|0,y(m),B|0)}function tGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,F2[o&15](l|0,u|0,A|0)}function rGe(o){o=o|0,GZ[o&0]()}function nGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A,qZ[o&15](l|0,u|0,+A)}function iGe(o,l,u){return o=o|0,l=+l,u=+u,PGe[o&1](+l,+u)|0}function sGe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,dU[o&15](l|0,u|0,A|0,d|0)}function oGe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,F(0)}function aGe(o,l){o=o|0,l=y(l),F(1)}function Xa(o,l){o=o|0,l=+l,F(2)}function lGe(o,l,u){return o=o|0,l=y(l),u=y(u),F(3),$e}function wr(o){o=o|0,F(4)}function T2(o,l){o=o|0,l=l|0,F(5)}function Ol(o){return o=o|0,F(6),0}function cGe(o,l,u,A){o=o|0,l=+l,u=+u,A=A|0,F(7)}function uGe(o,l,u){o=o|0,l=+l,u=+u,F(8)}function fGe(o,l,u){return o=o|0,l=l|0,u=u|0,F(9),0}function AGe(o,l,u){return o=o|0,l=l|0,u=u|0,F(10),0}function hd(o){return o=o|0,F(11),0}function pGe(o,l){return o=o|0,l=+l,F(12),0}function R2(o,l){return o=o|0,l=l|0,F(13),0}function hGe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0,F(14)}function gGe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,F(15)}function hU(o,l){return o=o|0,l=l|0,F(16),0}function dGe(){return F(17),0}function mGe(o,l,u,A,d){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,F(18),0}function yGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A,F(19)}function EGe(o,l,u,A,d,m){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=m|0,F(20)}function WP(o,l,u){o=o|0,l=l|0,u=u|0,F(21)}function IGe(){F(22)}function tE(o,l,u){o=o|0,l=l|0,u=+u,F(23)}function CGe(o,l){return o=+o,l=+l,F(24),0}function rE(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,F(25)}var FZ=[oGe,m3e],NZ=[aGe,Ty],OZ=[Xa,Zg,Fh,h2,g2,d2,m2,bf,_y,y2,Pf,$g,ed,E2,I2,wu,td,C2,Hy,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa],LZ=[lGe],ip=[wr,$y,Xke,Zke,$ke,PFe,xFe,kFe,Y_e,V_e,J_e,i3e,s3e,o3e,Dje,bje,Pje,Bl,Xg,u2,sr,hc,xP,kP,Hke,aQe,EQe,LQe,$Qe,dTe,RTe,JTe,cRe,SRe,HRe,nFe,EFe,VFe,cNe,SNe,HNe,nOe,EOe,MOe,$Oe,pLe,xLe,dP,oMe,wMe,HMe,sUe,IUe,HUe,XUe,e_e,m_e,I_e,L_e,z_e,$_e,d4e,F4e,Iz,g8e,Y8e,aHe,wHe,qHe,sje,dje,Eje,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr],sp=[T2,Ly,JL,f2,A2,xr,so,Xi,Ns,ws,Uy,Rh,B2,CP,id,XL,ZL,wP,BP,tM,xf,ne,jOe,rLe,cUe,y8e,j4e,iZ,T2,T2,T2,T2],gd=[Ol,n6e,Ny,nd,Gy,ga,mP,Nh,w2,zL,EP,qy,vP,rM,Vy,TLe,vUe,E4e,w8e,Rl,Ol,Ol,Ol,Ol,Ol,Ol,Ol,Ol,Ol,Ol,Ol,Ol],MZ=[cGe,aM],wGe=[uGe,__e],YP=[fGe,yZ,i6e,a6e,ITe,XFe,uMe,DHe],BGe=[AGe,WRe],UZ=[hd,Oh,IP,$A,lM,v,D,Q,H,V,hd,hd,hd,hd,hd,hd],vGe=[pGe,JUe],gU=[R2,P6e,SP,Wke,HQe,OTe,XTe,BFe,pNe,mLe,Ry,fHe,R2,R2,R2,R2],SGe=[hGe,BQe],DGe=[gGe,JHe],_Z=[hU,$L,Se,_e,pt,aFe,hU,hU],VP=[dGe,Wt,Fy,gP,i_e,v_e,n4e,Bje],HZ=[mGe,Sy],bGe=[yGe,WNe],jZ=[EGe,nM],F2=[WP,ko,yP,eM,vu,nTe,ARe,aOe,BOe,VL,_3e,z8e,cje,WP,WP,WP],GZ=[IGe],qZ=[tE,KL,My,ZA,p2,Bu,jy,rd,xNe,DMe,qUe,tE,tE,tE,tE,tE],PGe=[CGe,q_e],dU=[rE,xRe,_Le,WMe,RUe,u_e,k_e,u4e,U4e,P8e,Fje,rE,rE,rE,rE,rE];return{_llvm_bswap_i32:RZ,dynCall_idd:iGe,dynCall_i:X6e,_i64Subtract:GP,___udivdi3:AU,dynCall_vif:N6e,setThrew:ca,dynCall_viii:tGe,_bitshift64Lshr:qP,_bitshift64Shl:kZ,dynCall_vi:M6e,dynCall_viiddi:J6e,dynCall_diii:q6e,dynCall_iii:V6e,_memset:eE,_sbrk:qh,_memcpy:Qr,__GLOBAL__sub_I_Yoga_cpp:a2,dynCall_vii:U6e,___uremdi3:pU,dynCall_vid:O6e,stackAlloc:Ua,_nbind_init:Wje,getTempRet0:MA,dynCall_di:W6e,dynCall_iid:Y6e,setTempRet0:LA,_i64Add:fU,dynCall_fiff:L6e,dynCall_iiii:G6e,_emscripten_get_global_libc:r6e,dynCall_viid:nGe,dynCall_viiid:$6e,dynCall_viififi:eGe,dynCall_ii:_6e,__GLOBAL__sub_I_Binding_cc:a8e,dynCall_viiii:sGe,dynCall_iiiiii:Z6e,stackSave:hf,dynCall_viiiii:F6e,__GLOBAL__sub_I_nbind_cc:Sr,dynCall_vidd:j6e,_free:HP,runPostSets:R6e,dynCall_viiiiii:K6e,establishStackSpace:wn,_memmove:Q2,stackRestore:lc,_malloc:_P,__GLOBAL__sub_I_common_cc:b4e,dynCall_viddi:H6e,dynCall_dii:z6e,dynCall_v:rGe}}(Module.asmGlobalArg,Module.asmLibraryArg,buffer),_llvm_bswap_i32=Module._llvm_bswap_i32=asm._llvm_bswap_i32,getTempRet0=Module.getTempRet0=asm.getTempRet0,___udivdi3=Module.___udivdi3=asm.___udivdi3,setThrew=Module.setThrew=asm.setThrew,_bitshift64Lshr=Module._bitshift64Lshr=asm._bitshift64Lshr,_bitshift64Shl=Module._bitshift64Shl=asm._bitshift64Shl,_memset=Module._memset=asm._memset,_sbrk=Module._sbrk=asm._sbrk,_memcpy=Module._memcpy=asm._memcpy,stackAlloc=Module.stackAlloc=asm.stackAlloc,___uremdi3=Module.___uremdi3=asm.___uremdi3,_nbind_init=Module._nbind_init=asm._nbind_init,_i64Subtract=Module._i64Subtract=asm._i64Subtract,setTempRet0=Module.setTempRet0=asm.setTempRet0,_i64Add=Module._i64Add=asm._i64Add,_emscripten_get_global_libc=Module._emscripten_get_global_libc=asm._emscripten_get_global_libc,__GLOBAL__sub_I_Yoga_cpp=Module.__GLOBAL__sub_I_Yoga_cpp=asm.__GLOBAL__sub_I_Yoga_cpp,__GLOBAL__sub_I_Binding_cc=Module.__GLOBAL__sub_I_Binding_cc=asm.__GLOBAL__sub_I_Binding_cc,stackSave=Module.stackSave=asm.stackSave,__GLOBAL__sub_I_nbind_cc=Module.__GLOBAL__sub_I_nbind_cc=asm.__GLOBAL__sub_I_nbind_cc,_free=Module._free=asm._free,runPostSets=Module.runPostSets=asm.runPostSets,establishStackSpace=Module.establishStackSpace=asm.establishStackSpace,_memmove=Module._memmove=asm._memmove,stackRestore=Module.stackRestore=asm.stackRestore,_malloc=Module._malloc=asm._malloc,__GLOBAL__sub_I_common_cc=Module.__GLOBAL__sub_I_common_cc=asm.__GLOBAL__sub_I_common_cc,dynCall_viiiii=Module.dynCall_viiiii=asm.dynCall_viiiii,dynCall_vif=Module.dynCall_vif=asm.dynCall_vif,dynCall_vid=Module.dynCall_vid=asm.dynCall_vid,dynCall_fiff=Module.dynCall_fiff=asm.dynCall_fiff,dynCall_vi=Module.dynCall_vi=asm.dynCall_vi,dynCall_vii=Module.dynCall_vii=asm.dynCall_vii,dynCall_ii=Module.dynCall_ii=asm.dynCall_ii,dynCall_viddi=Module.dynCall_viddi=asm.dynCall_viddi,dynCall_vidd=Module.dynCall_vidd=asm.dynCall_vidd,dynCall_iiii=Module.dynCall_iiii=asm.dynCall_iiii,dynCall_diii=Module.dynCall_diii=asm.dynCall_diii,dynCall_di=Module.dynCall_di=asm.dynCall_di,dynCall_iid=Module.dynCall_iid=asm.dynCall_iid,dynCall_iii=Module.dynCall_iii=asm.dynCall_iii,dynCall_viiddi=Module.dynCall_viiddi=asm.dynCall_viiddi,dynCall_viiiiii=Module.dynCall_viiiiii=asm.dynCall_viiiiii,dynCall_dii=Module.dynCall_dii=asm.dynCall_dii,dynCall_i=Module.dynCall_i=asm.dynCall_i,dynCall_iiiiii=Module.dynCall_iiiiii=asm.dynCall_iiiiii,dynCall_viiid=Module.dynCall_viiid=asm.dynCall_viiid,dynCall_viififi=Module.dynCall_viififi=asm.dynCall_viififi,dynCall_viii=Module.dynCall_viii=asm.dynCall_viii,dynCall_v=Module.dynCall_v=asm.dynCall_v,dynCall_viid=Module.dynCall_viid=asm.dynCall_viid,dynCall_idd=Module.dynCall_idd=asm.dynCall_idd,dynCall_viiii=Module.dynCall_viiii=asm.dynCall_viiii;Runtime.stackAlloc=Module.stackAlloc,Runtime.stackSave=Module.stackSave,Runtime.stackRestore=Module.stackRestore,Runtime.establishStackSpace=Module.establishStackSpace,Runtime.setTempRet0=Module.setTempRet0,Runtime.getTempRet0=Module.getTempRet0,Module.asm=asm;function ExitStatus(t){this.name="ExitStatus",this.message="Program terminated with exit("+t+")",this.status=t}ExitStatus.prototype=new Error,ExitStatus.prototype.constructor=ExitStatus;var initialStackTop,preloadStartTime=null,calledMain=!1;dependenciesFulfilled=function t(){Module.calledRun||run(),Module.calledRun||(dependenciesFulfilled=t)},Module.callMain=Module.callMain=function t(e){e=e||[],ensureInitRuntime();var r=e.length+1;function s(){for(var p=0;p<3;p++)a.push(0)}var a=[allocate(intArrayFromString(Module.thisProgram),"i8",ALLOC_NORMAL)];s();for(var n=0;n0||(preRun(),runDependencies>0)||Module.calledRun)return;function e(){Module.calledRun||(Module.calledRun=!0,!ABORT&&(ensureInitRuntime(),preMain(),Module.onRuntimeInitialized&&Module.onRuntimeInitialized(),Module._main&&shouldRunNow&&Module.callMain(t),postRun()))}Module.setStatus?(Module.setStatus("Running..."),setTimeout(function(){setTimeout(function(){Module.setStatus("")},1),e()},1)):e()}Module.run=Module.run=run;function exit(t,e){e&&Module.noExitRuntime||(Module.noExitRuntime||(ABORT=!0,EXITSTATUS=t,STACKTOP=initialStackTop,exitRuntime(),Module.onExit&&Module.onExit(t)),ENVIRONMENT_IS_NODE&&process.exit(t),Module.quit(t,new ExitStatus(t)))}Module.exit=Module.exit=exit;var abortDecorators=[];function abort(t){Module.onAbort&&Module.onAbort(t),t!==void 0?(Module.print(t),Module.printErr(t),t=JSON.stringify(t)):t="",ABORT=!0,EXITSTATUS=1;var e=` If this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.`,r="abort("+t+") at "+stackTrace()+e;throw abortDecorators&&abortDecorators.forEach(function(s){r=s(r,t)}),r}if(Module.abort=Module.abort=abort,Module.preInit)for(typeof Module.preInit=="function"&&(Module.preInit=[Module.preInit]);Module.preInit.length>0;)Module.preInit.pop()();var shouldRunNow=!0;Module.noInitialRun&&(shouldRunNow=!1),run()})});var Fm=_((PKt,Rwe)=>{"use strict";var Ppt=Qwe(),xpt=Twe(),K9=!1,z9=null;xpt({},function(t,e){if(!K9){if(K9=!0,t)throw t;z9=e}});if(!K9)throw new Error("Failed to load the yoga module - it needed to be loaded synchronously, but didn't");Rwe.exports=Ppt(z9.bind,z9.lib)});var Z9=_((xKt,X9)=>{"use strict";var Fwe=t=>Number.isNaN(t)?!1:t>=4352&&(t<=4447||t===9001||t===9002||11904<=t&&t<=12871&&t!==12351||12880<=t&&t<=19903||19968<=t&&t<=42182||43360<=t&&t<=43388||44032<=t&&t<=55203||63744<=t&&t<=64255||65040<=t&&t<=65049||65072<=t&&t<=65131||65281<=t&&t<=65376||65504<=t&&t<=65510||110592<=t&&t<=110593||127488<=t&&t<=127569||131072<=t&&t<=262141);X9.exports=Fwe;X9.exports.default=Fwe});var Owe=_((kKt,Nwe)=>{"use strict";Nwe.exports=function(){return/\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F|\uD83D\uDC68(?:\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68\uD83C\uDFFB|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|[\u2695\u2696\u2708]\uFE0F|\uD83D[\uDC66\uDC67]|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708])\uFE0F|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C[\uDFFB-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)\uD83C\uDFFB|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB\uDFFC])|\uD83D\uDC69(?:\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB-\uDFFD])|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|(?:(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)\uFE0F|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\u200D[\u2640\u2642])|\uD83C\uDFF4\u200D\u2620)\uFE0F|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDF6\uD83C\uDDE6|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDB5\uDDB6\uDDBB\uDDD2-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5\uDEEB\uDEEC\uDEF4-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g}});var GS=_((QKt,$9)=>{"use strict";var kpt=dk(),Qpt=Z9(),Tpt=Owe(),Lwe=t=>{if(typeof t!="string"||t.length===0||(t=kpt(t),t.length===0))return 0;t=t.replace(Tpt()," ");let e=0;for(let r=0;r=127&&s<=159||s>=768&&s<=879||(s>65535&&r++,e+=Qpt(s)?2:1)}return e};$9.exports=Lwe;$9.exports.default=Lwe});var tW=_((TKt,eW)=>{"use strict";var Rpt=GS(),Mwe=t=>{let e=0;for(let r of t.split(` `))e=Math.max(e,Rpt(r));return e};eW.exports=Mwe;eW.exports.default=Mwe});var Uwe=_(qS=>{"use strict";var Fpt=qS&&qS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(qS,"__esModule",{value:!0});var Npt=Fpt(tW()),rW={};qS.default=t=>{if(t.length===0)return{width:0,height:0};if(rW[t])return rW[t];let e=Npt.default(t),r=t.split(` `).length;return rW[t]={width:e,height:r},{width:e,height:r}}});var _we=_(WS=>{"use strict";var Opt=WS&&WS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(WS,"__esModule",{value:!0});var bn=Opt(Fm()),Lpt=(t,e)=>{"position"in e&&t.setPositionType(e.position==="absolute"?bn.default.POSITION_TYPE_ABSOLUTE:bn.default.POSITION_TYPE_RELATIVE)},Mpt=(t,e)=>{"marginLeft"in e&&t.setMargin(bn.default.EDGE_START,e.marginLeft||0),"marginRight"in e&&t.setMargin(bn.default.EDGE_END,e.marginRight||0),"marginTop"in e&&t.setMargin(bn.default.EDGE_TOP,e.marginTop||0),"marginBottom"in e&&t.setMargin(bn.default.EDGE_BOTTOM,e.marginBottom||0)},Upt=(t,e)=>{"paddingLeft"in e&&t.setPadding(bn.default.EDGE_LEFT,e.paddingLeft||0),"paddingRight"in e&&t.setPadding(bn.default.EDGE_RIGHT,e.paddingRight||0),"paddingTop"in e&&t.setPadding(bn.default.EDGE_TOP,e.paddingTop||0),"paddingBottom"in e&&t.setPadding(bn.default.EDGE_BOTTOM,e.paddingBottom||0)},_pt=(t,e)=>{var r;"flexGrow"in e&&t.setFlexGrow((r=e.flexGrow)!==null&&r!==void 0?r:0),"flexShrink"in e&&t.setFlexShrink(typeof e.flexShrink=="number"?e.flexShrink:1),"flexDirection"in e&&(e.flexDirection==="row"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_ROW),e.flexDirection==="row-reverse"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_ROW_REVERSE),e.flexDirection==="column"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_COLUMN),e.flexDirection==="column-reverse"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_COLUMN_REVERSE)),"flexBasis"in e&&(typeof e.flexBasis=="number"?t.setFlexBasis(e.flexBasis):typeof e.flexBasis=="string"?t.setFlexBasisPercent(Number.parseInt(e.flexBasis,10)):t.setFlexBasis(NaN)),"alignItems"in e&&((e.alignItems==="stretch"||!e.alignItems)&&t.setAlignItems(bn.default.ALIGN_STRETCH),e.alignItems==="flex-start"&&t.setAlignItems(bn.default.ALIGN_FLEX_START),e.alignItems==="center"&&t.setAlignItems(bn.default.ALIGN_CENTER),e.alignItems==="flex-end"&&t.setAlignItems(bn.default.ALIGN_FLEX_END)),"alignSelf"in e&&((e.alignSelf==="auto"||!e.alignSelf)&&t.setAlignSelf(bn.default.ALIGN_AUTO),e.alignSelf==="flex-start"&&t.setAlignSelf(bn.default.ALIGN_FLEX_START),e.alignSelf==="center"&&t.setAlignSelf(bn.default.ALIGN_CENTER),e.alignSelf==="flex-end"&&t.setAlignSelf(bn.default.ALIGN_FLEX_END)),"justifyContent"in e&&((e.justifyContent==="flex-start"||!e.justifyContent)&&t.setJustifyContent(bn.default.JUSTIFY_FLEX_START),e.justifyContent==="center"&&t.setJustifyContent(bn.default.JUSTIFY_CENTER),e.justifyContent==="flex-end"&&t.setJustifyContent(bn.default.JUSTIFY_FLEX_END),e.justifyContent==="space-between"&&t.setJustifyContent(bn.default.JUSTIFY_SPACE_BETWEEN),e.justifyContent==="space-around"&&t.setJustifyContent(bn.default.JUSTIFY_SPACE_AROUND))},Hpt=(t,e)=>{var r,s;"width"in e&&(typeof e.width=="number"?t.setWidth(e.width):typeof e.width=="string"?t.setWidthPercent(Number.parseInt(e.width,10)):t.setWidthAuto()),"height"in e&&(typeof e.height=="number"?t.setHeight(e.height):typeof e.height=="string"?t.setHeightPercent(Number.parseInt(e.height,10)):t.setHeightAuto()),"minWidth"in e&&(typeof e.minWidth=="string"?t.setMinWidthPercent(Number.parseInt(e.minWidth,10)):t.setMinWidth((r=e.minWidth)!==null&&r!==void 0?r:0)),"minHeight"in e&&(typeof e.minHeight=="string"?t.setMinHeightPercent(Number.parseInt(e.minHeight,10)):t.setMinHeight((s=e.minHeight)!==null&&s!==void 0?s:0))},jpt=(t,e)=>{"display"in e&&t.setDisplay(e.display==="flex"?bn.default.DISPLAY_FLEX:bn.default.DISPLAY_NONE)},Gpt=(t,e)=>{if("borderStyle"in e){let r=typeof e.borderStyle=="string"?1:0;t.setBorder(bn.default.EDGE_TOP,r),t.setBorder(bn.default.EDGE_BOTTOM,r),t.setBorder(bn.default.EDGE_LEFT,r),t.setBorder(bn.default.EDGE_RIGHT,r)}};WS.default=(t,e={})=>{Lpt(t,e),Mpt(t,e),Upt(t,e),_pt(t,e),Hpt(t,e),jpt(t,e),Gpt(t,e)}});var Gwe=_((NKt,jwe)=>{"use strict";var YS=GS(),qpt=dk(),Wpt=sk(),iW=new Set(["\x1B","\x9B"]),Ypt=39,Hwe=t=>`${iW.values().next().value}[${t}m`,Vpt=t=>t.split(" ").map(e=>YS(e)),nW=(t,e,r)=>{let s=[...e],a=!1,n=YS(qpt(t[t.length-1]));for(let[c,f]of s.entries()){let p=YS(f);if(n+p<=r?t[t.length-1]+=f:(t.push(f),n=0),iW.has(f))a=!0;else if(a&&f==="m"){a=!1;continue}a||(n+=p,n===r&&c0&&t.length>1&&(t[t.length-2]+=t.pop())},Jpt=t=>{let e=t.split(" "),r=e.length;for(;r>0&&!(YS(e[r-1])>0);)r--;return r===e.length?t:e.slice(0,r).join(" ")+e.slice(r).join("")},Kpt=(t,e,r={})=>{if(r.trim!==!1&&t.trim()==="")return"";let s="",a="",n,c=Vpt(t),f=[""];for(let[p,h]of t.split(" ").entries()){r.trim!==!1&&(f[f.length-1]=f[f.length-1].trimLeft());let E=YS(f[f.length-1]);if(p!==0&&(E>=e&&(r.wordWrap===!1||r.trim===!1)&&(f.push(""),E=0),(E>0||r.trim===!1)&&(f[f.length-1]+=" ",E++)),r.hard&&c[p]>e){let C=e-E,S=1+Math.floor((c[p]-C-1)/e);Math.floor((c[p]-1)/e)e&&E>0&&c[p]>0){if(r.wordWrap===!1&&Ee&&r.wordWrap===!1){nW(f,h,e);continue}f[f.length-1]+=h}r.trim!==!1&&(f=f.map(Jpt)),s=f.join(` `);for(let[p,h]of[...s].entries()){if(a+=h,iW.has(h)){let C=parseFloat(/\d[^m]*/.exec(s.slice(p,p+4)));n=C===Ypt?null:C}let E=Wpt.codes.get(Number(n));n&&E&&(s[p+1]===` `?a+=Hwe(E):h===` `&&(a+=Hwe(n)))}return a};jwe.exports=(t,e,r)=>String(t).normalize().replace(/\r\n/g,` `).split(` `).map(s=>Kpt(s,e,r)).join(` `)});var Ywe=_((OKt,Wwe)=>{"use strict";var qwe="[\uD800-\uDBFF][\uDC00-\uDFFF]",zpt=t=>t&&t.exact?new RegExp(`^${qwe}$`):new RegExp(qwe,"g");Wwe.exports=zpt});var sW=_((LKt,zwe)=>{"use strict";var Xpt=Z9(),Zpt=Ywe(),Vwe=sk(),Kwe=["\x1B","\x9B"],NF=t=>`${Kwe[0]}[${t}m`,Jwe=(t,e,r)=>{let s=[];t=[...t];for(let a of t){let n=a;a.match(";")&&(a=a.split(";")[0][0]+"0");let c=Vwe.codes.get(parseInt(a,10));if(c){let f=t.indexOf(c.toString());f>=0?t.splice(f,1):s.push(NF(e?c:n))}else if(e){s.push(NF(0));break}else s.push(NF(n))}if(e&&(s=s.filter((a,n)=>s.indexOf(a)===n),r!==void 0)){let a=NF(Vwe.codes.get(parseInt(r,10)));s=s.reduce((n,c)=>c===a?[c,...n]:[...n,c],[])}return s.join("")};zwe.exports=(t,e,r)=>{let s=[...t.normalize()],a=[];r=typeof r=="number"?r:s.length;let n=!1,c,f=0,p="";for(let[h,E]of s.entries()){let C=!1;if(Kwe.includes(E)){let S=/\d[^m]*/.exec(t.slice(h,h+18));c=S&&S.length>0?S[0]:void 0,fe&&f<=r)p+=E;else if(f===e&&!n&&c!==void 0)p=Jwe(a);else if(f>=r){p+=Jwe(a,!0,c);break}}return p}});var Zwe=_((MKt,Xwe)=>{"use strict";var $0=sW(),$pt=GS();function OF(t,e,r){if(t.charAt(e)===" ")return e;for(let s=1;s<=3;s++)if(r){if(t.charAt(e+s)===" ")return e+s}else if(t.charAt(e-s)===" ")return e-s;return e}Xwe.exports=(t,e,r)=>{r={position:"end",preferTruncationOnSpace:!1,...r};let{position:s,space:a,preferTruncationOnSpace:n}=r,c="\u2026",f=1;if(typeof t!="string")throw new TypeError(`Expected \`input\` to be a string, got ${typeof t}`);if(typeof e!="number")throw new TypeError(`Expected \`columns\` to be a number, got ${typeof e}`);if(e<1)return"";if(e===1)return c;let p=$pt(t);if(p<=e)return t;if(s==="start"){if(n){let h=OF(t,p-e+1,!0);return c+$0(t,h,p).trim()}return a===!0&&(c+=" ",f=2),c+$0(t,p-e+f,p)}if(s==="middle"){a===!0&&(c=" "+c+" ",f=3);let h=Math.floor(e/2);if(n){let E=OF(t,h),C=OF(t,p-(e-h)+1,!0);return $0(t,0,E)+c+$0(t,C,p).trim()}return $0(t,0,h)+c+$0(t,p-(e-h)+f,p)}if(s==="end"){if(n){let h=OF(t,e-1);return $0(t,0,h)+c}return a===!0&&(c=" "+c,f=2),$0(t,0,e-f)+c}throw new Error(`Expected \`options.position\` to be either \`start\`, \`middle\` or \`end\`, got ${s}`)}});var aW=_(VS=>{"use strict";var $we=VS&&VS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(VS,"__esModule",{value:!0});var eht=$we(Gwe()),tht=$we(Zwe()),oW={};VS.default=(t,e,r)=>{let s=t+String(e)+String(r);if(oW[s])return oW[s];let a=t;if(r==="wrap"&&(a=eht.default(t,e,{trim:!1,hard:!0})),r.startsWith("truncate")){let n="end";r==="truncate-middle"&&(n="middle"),r==="truncate-start"&&(n="start"),a=tht.default(t,e,{position:n})}return oW[s]=a,a}});var cW=_(lW=>{"use strict";Object.defineProperty(lW,"__esModule",{value:!0});var e1e=t=>{let e="";if(t.childNodes.length>0)for(let r of t.childNodes){let s="";r.nodeName==="#text"?s=r.nodeValue:((r.nodeName==="ink-text"||r.nodeName==="ink-virtual-text")&&(s=e1e(r)),s.length>0&&typeof r.internal_transform=="function"&&(s=r.internal_transform(s))),e+=s}return e};lW.default=e1e});var uW=_(bi=>{"use strict";var JS=bi&&bi.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(bi,"__esModule",{value:!0});bi.setTextNodeValue=bi.createTextNode=bi.setStyle=bi.setAttribute=bi.removeChildNode=bi.insertBeforeNode=bi.appendChildNode=bi.createNode=bi.TEXT_NAME=void 0;var rht=JS(Fm()),t1e=JS(Uwe()),nht=JS(_we()),iht=JS(aW()),sht=JS(cW());bi.TEXT_NAME="#text";bi.createNode=t=>{var e;let r={nodeName:t,style:{},attributes:{},childNodes:[],parentNode:null,yogaNode:t==="ink-virtual-text"?void 0:rht.default.Node.create()};return t==="ink-text"&&((e=r.yogaNode)===null||e===void 0||e.setMeasureFunc(oht.bind(null,r))),r};bi.appendChildNode=(t,e)=>{var r;e.parentNode&&bi.removeChildNode(e.parentNode,e),e.parentNode=t,t.childNodes.push(e),e.yogaNode&&((r=t.yogaNode)===null||r===void 0||r.insertChild(e.yogaNode,t.yogaNode.getChildCount())),(t.nodeName==="ink-text"||t.nodeName==="ink-virtual-text")&&LF(t)};bi.insertBeforeNode=(t,e,r)=>{var s,a;e.parentNode&&bi.removeChildNode(e.parentNode,e),e.parentNode=t;let n=t.childNodes.indexOf(r);if(n>=0){t.childNodes.splice(n,0,e),e.yogaNode&&((s=t.yogaNode)===null||s===void 0||s.insertChild(e.yogaNode,n));return}t.childNodes.push(e),e.yogaNode&&((a=t.yogaNode)===null||a===void 0||a.insertChild(e.yogaNode,t.yogaNode.getChildCount())),(t.nodeName==="ink-text"||t.nodeName==="ink-virtual-text")&&LF(t)};bi.removeChildNode=(t,e)=>{var r,s;e.yogaNode&&((s=(r=e.parentNode)===null||r===void 0?void 0:r.yogaNode)===null||s===void 0||s.removeChild(e.yogaNode)),e.parentNode=null;let a=t.childNodes.indexOf(e);a>=0&&t.childNodes.splice(a,1),(t.nodeName==="ink-text"||t.nodeName==="ink-virtual-text")&&LF(t)};bi.setAttribute=(t,e,r)=>{t.attributes[e]=r};bi.setStyle=(t,e)=>{t.style=e,t.yogaNode&&nht.default(t.yogaNode,e)};bi.createTextNode=t=>{let e={nodeName:"#text",nodeValue:t,yogaNode:void 0,parentNode:null,style:{}};return bi.setTextNodeValue(e,t),e};var oht=function(t,e){var r,s;let a=t.nodeName==="#text"?t.nodeValue:sht.default(t),n=t1e.default(a);if(n.width<=e||n.width>=1&&e>0&&e<1)return n;let c=(s=(r=t.style)===null||r===void 0?void 0:r.textWrap)!==null&&s!==void 0?s:"wrap",f=iht.default(a,e,c);return t1e.default(f)},r1e=t=>{var e;if(!(!t||!t.parentNode))return(e=t.yogaNode)!==null&&e!==void 0?e:r1e(t.parentNode)},LF=t=>{let e=r1e(t);e?.markDirty()};bi.setTextNodeValue=(t,e)=>{typeof e!="string"&&(e=String(e)),t.nodeValue=e,LF(t)}});var a1e=_(KS=>{"use strict";var o1e=KS&&KS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(KS,"__esModule",{value:!0});var n1e=Y9(),aht=o1e(Swe()),i1e=o1e(Fm()),ea=uW(),s1e=t=>{t?.unsetMeasureFunc(),t?.freeRecursive()};KS.default=aht.default({schedulePassiveEffects:n1e.unstable_scheduleCallback,cancelPassiveEffects:n1e.unstable_cancelCallback,now:Date.now,getRootHostContext:()=>({isInsideText:!1}),prepareForCommit:()=>null,preparePortalMount:()=>null,clearContainer:()=>!1,shouldDeprioritizeSubtree:()=>!1,resetAfterCommit:t=>{if(t.isStaticDirty){t.isStaticDirty=!1,typeof t.onImmediateRender=="function"&&t.onImmediateRender();return}typeof t.onRender=="function"&&t.onRender()},getChildHostContext:(t,e)=>{let r=t.isInsideText,s=e==="ink-text"||e==="ink-virtual-text";return r===s?t:{isInsideText:s}},shouldSetTextContent:()=>!1,createInstance:(t,e,r,s)=>{if(s.isInsideText&&t==="ink-box")throw new Error(" can\u2019t be nested inside component");let a=t==="ink-text"&&s.isInsideText?"ink-virtual-text":t,n=ea.createNode(a);for(let[c,f]of Object.entries(e))c!=="children"&&(c==="style"?ea.setStyle(n,f):c==="internal_transform"?n.internal_transform=f:c==="internal_static"?n.internal_static=!0:ea.setAttribute(n,c,f));return n},createTextInstance:(t,e,r)=>{if(!r.isInsideText)throw new Error(`Text string "${t}" must be rendered inside component`);return ea.createTextNode(t)},resetTextContent:()=>{},hideTextInstance:t=>{ea.setTextNodeValue(t,"")},unhideTextInstance:(t,e)=>{ea.setTextNodeValue(t,e)},getPublicInstance:t=>t,hideInstance:t=>{var e;(e=t.yogaNode)===null||e===void 0||e.setDisplay(i1e.default.DISPLAY_NONE)},unhideInstance:t=>{var e;(e=t.yogaNode)===null||e===void 0||e.setDisplay(i1e.default.DISPLAY_FLEX)},appendInitialChild:ea.appendChildNode,appendChild:ea.appendChildNode,insertBefore:ea.insertBeforeNode,finalizeInitialChildren:(t,e,r,s)=>(t.internal_static&&(s.isStaticDirty=!0,s.staticNode=t),!1),supportsMutation:!0,appendChildToContainer:ea.appendChildNode,insertInContainerBefore:ea.insertBeforeNode,removeChildFromContainer:(t,e)=>{ea.removeChildNode(t,e),s1e(e.yogaNode)},prepareUpdate:(t,e,r,s,a)=>{t.internal_static&&(a.isStaticDirty=!0);let n={},c=Object.keys(s);for(let f of c)if(s[f]!==r[f]){if(f==="style"&&typeof s.style=="object"&&typeof r.style=="object"){let h=s.style,E=r.style,C=Object.keys(h);for(let S of C){if(S==="borderStyle"||S==="borderColor"){if(typeof n.style!="object"){let P={};n.style=P}n.style.borderStyle=h.borderStyle,n.style.borderColor=h.borderColor}if(h[S]!==E[S]){if(typeof n.style!="object"){let P={};n.style=P}n.style[S]=h[S]}}continue}n[f]=s[f]}return n},commitUpdate:(t,e)=>{for(let[r,s]of Object.entries(e))r!=="children"&&(r==="style"?ea.setStyle(t,s):r==="internal_transform"?t.internal_transform=s:r==="internal_static"?t.internal_static=!0:ea.setAttribute(t,r,s))},commitTextUpdate:(t,e,r)=>{ea.setTextNodeValue(t,r)},removeChild:(t,e)=>{ea.removeChildNode(t,e),s1e(e.yogaNode)}})});var c1e=_((GKt,l1e)=>{"use strict";l1e.exports=(t,e=1,r)=>{if(r={indent:" ",includeEmptyLines:!1,...r},typeof t!="string")throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof t}\``);if(typeof e!="number")throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof e}\``);if(typeof r.indent!="string")throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof r.indent}\``);if(e===0)return t;let s=r.includeEmptyLines?/^/gm:/^(?!\s*$)/gm;return t.replace(s,r.indent.repeat(e))}});var u1e=_(zS=>{"use strict";var lht=zS&&zS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(zS,"__esModule",{value:!0});var MF=lht(Fm());zS.default=t=>t.getComputedWidth()-t.getComputedPadding(MF.default.EDGE_LEFT)-t.getComputedPadding(MF.default.EDGE_RIGHT)-t.getComputedBorder(MF.default.EDGE_LEFT)-t.getComputedBorder(MF.default.EDGE_RIGHT)});var f1e=_((WKt,cht)=>{cht.exports={single:{topLeft:"\u250C",topRight:"\u2510",bottomRight:"\u2518",bottomLeft:"\u2514",vertical:"\u2502",horizontal:"\u2500"},double:{topLeft:"\u2554",topRight:"\u2557",bottomRight:"\u255D",bottomLeft:"\u255A",vertical:"\u2551",horizontal:"\u2550"},round:{topLeft:"\u256D",topRight:"\u256E",bottomRight:"\u256F",bottomLeft:"\u2570",vertical:"\u2502",horizontal:"\u2500"},bold:{topLeft:"\u250F",topRight:"\u2513",bottomRight:"\u251B",bottomLeft:"\u2517",vertical:"\u2503",horizontal:"\u2501"},singleDouble:{topLeft:"\u2553",topRight:"\u2556",bottomRight:"\u255C",bottomLeft:"\u2559",vertical:"\u2551",horizontal:"\u2500"},doubleSingle:{topLeft:"\u2552",topRight:"\u2555",bottomRight:"\u255B",bottomLeft:"\u2558",vertical:"\u2502",horizontal:"\u2550"},classic:{topLeft:"+",topRight:"+",bottomRight:"+",bottomLeft:"+",vertical:"|",horizontal:"-"}}});var p1e=_((YKt,fW)=>{"use strict";var A1e=f1e();fW.exports=A1e;fW.exports.default=A1e});var AW=_(ZS=>{"use strict";var uht=ZS&&ZS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(ZS,"__esModule",{value:!0});var XS=uht(TE()),fht=/^(rgb|hsl|hsv|hwb)\(\s?(\d+),\s?(\d+),\s?(\d+)\s?\)$/,Aht=/^(ansi|ansi256)\(\s?(\d+)\s?\)$/,UF=(t,e)=>e==="foreground"?t:"bg"+t[0].toUpperCase()+t.slice(1);ZS.default=(t,e,r)=>{if(!e)return t;if(e in XS.default){let a=UF(e,r);return XS.default[a](t)}if(e.startsWith("#")){let a=UF("hex",r);return XS.default[a](e)(t)}if(e.startsWith("ansi")){let a=Aht.exec(e);if(!a)return t;let n=UF(a[1],r),c=Number(a[2]);return XS.default[n](c)(t)}if(e.startsWith("rgb")||e.startsWith("hsl")||e.startsWith("hsv")||e.startsWith("hwb")){let a=fht.exec(e);if(!a)return t;let n=UF(a[1],r),c=Number(a[2]),f=Number(a[3]),p=Number(a[4]);return XS.default[n](c,f,p)(t)}return t}});var g1e=_($S=>{"use strict";var h1e=$S&&$S.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty($S,"__esModule",{value:!0});var pht=h1e(p1e()),pW=h1e(AW());$S.default=(t,e,r,s)=>{if(typeof r.style.borderStyle=="string"){let a=r.yogaNode.getComputedWidth(),n=r.yogaNode.getComputedHeight(),c=r.style.borderColor,f=pht.default[r.style.borderStyle],p=pW.default(f.topLeft+f.horizontal.repeat(a-2)+f.topRight,c,"foreground"),h=(pW.default(f.vertical,c,"foreground")+` `).repeat(n-2),E=pW.default(f.bottomLeft+f.horizontal.repeat(a-2)+f.bottomRight,c,"foreground");s.write(t,e,p,{transformers:[]}),s.write(t,e+1,h,{transformers:[]}),s.write(t+a-1,e+1,h,{transformers:[]}),s.write(t,e+n-1,E,{transformers:[]})}}});var m1e=_(eD=>{"use strict";var Nm=eD&&eD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(eD,"__esModule",{value:!0});var hht=Nm(Fm()),ght=Nm(tW()),dht=Nm(c1e()),mht=Nm(aW()),yht=Nm(u1e()),Eht=Nm(cW()),Iht=Nm(g1e()),Cht=(t,e)=>{var r;let s=(r=t.childNodes[0])===null||r===void 0?void 0:r.yogaNode;if(s){let a=s.getComputedLeft(),n=s.getComputedTop();e=` `.repeat(n)+dht.default(e,a)}return e},d1e=(t,e,r)=>{var s;let{offsetX:a=0,offsetY:n=0,transformers:c=[],skipStaticElements:f}=r;if(f&&t.internal_static)return;let{yogaNode:p}=t;if(p){if(p.getDisplay()===hht.default.DISPLAY_NONE)return;let h=a+p.getComputedLeft(),E=n+p.getComputedTop(),C=c;if(typeof t.internal_transform=="function"&&(C=[t.internal_transform,...c]),t.nodeName==="ink-text"){let S=Eht.default(t);if(S.length>0){let P=ght.default(S),I=yht.default(p);if(P>I){let R=(s=t.style.textWrap)!==null&&s!==void 0?s:"wrap";S=mht.default(S,I,R)}S=Cht(t,S),e.write(h,E,S,{transformers:C})}return}if(t.nodeName==="ink-box"&&Iht.default(h,E,t,e),t.nodeName==="ink-root"||t.nodeName==="ink-box")for(let S of t.childNodes)d1e(S,e,{offsetX:h,offsetY:E,transformers:C,skipStaticElements:f})}};eD.default=d1e});var I1e=_(tD=>{"use strict";var E1e=tD&&tD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(tD,"__esModule",{value:!0});var y1e=E1e(sW()),wht=E1e(GS()),hW=class{constructor(e){this.writes=[];let{width:r,height:s}=e;this.width=r,this.height=s}write(e,r,s,a){let{transformers:n}=a;s&&this.writes.push({x:e,y:r,text:s,transformers:n})}get(){let e=[];for(let s=0;ss.trimRight()).join(` `),height:e.length}}};tD.default=hW});var B1e=_(rD=>{"use strict";var gW=rD&&rD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(rD,"__esModule",{value:!0});var Bht=gW(Fm()),C1e=gW(m1e()),w1e=gW(I1e());rD.default=(t,e)=>{var r;if(t.yogaNode.setWidth(e),t.yogaNode){t.yogaNode.calculateLayout(void 0,void 0,Bht.default.DIRECTION_LTR);let s=new w1e.default({width:t.yogaNode.getComputedWidth(),height:t.yogaNode.getComputedHeight()});C1e.default(t,s,{skipStaticElements:!0});let a;!((r=t.staticNode)===null||r===void 0)&&r.yogaNode&&(a=new w1e.default({width:t.staticNode.yogaNode.getComputedWidth(),height:t.staticNode.yogaNode.getComputedHeight()}),C1e.default(t.staticNode,a,{skipStaticElements:!1}));let{output:n,height:c}=s.get();return{output:n,outputHeight:c,staticOutput:a?`${a.get().output} `:""}}return{output:"",outputHeight:0,staticOutput:""}}});var b1e=_((ZKt,D1e)=>{"use strict";var v1e=Ie("stream"),S1e=["assert","count","countReset","debug","dir","dirxml","error","group","groupCollapsed","groupEnd","info","log","table","time","timeEnd","timeLog","trace","warn"],dW={},vht=t=>{let e=new v1e.PassThrough,r=new v1e.PassThrough;e.write=a=>t("stdout",a),r.write=a=>t("stderr",a);let s=new console.Console(e,r);for(let a of S1e)dW[a]=console[a],console[a]=s[a];return()=>{for(let a of S1e)console[a]=dW[a];dW={}}};D1e.exports=vht});var yW=_(mW=>{"use strict";Object.defineProperty(mW,"__esModule",{value:!0});mW.default=new WeakMap});var IW=_(EW=>{"use strict";Object.defineProperty(EW,"__esModule",{value:!0});var Sht=hn(),P1e=Sht.createContext({exit:()=>{}});P1e.displayName="InternalAppContext";EW.default=P1e});var wW=_(CW=>{"use strict";Object.defineProperty(CW,"__esModule",{value:!0});var Dht=hn(),x1e=Dht.createContext({stdin:void 0,setRawMode:()=>{},isRawModeSupported:!1,internal_exitOnCtrlC:!0});x1e.displayName="InternalStdinContext";CW.default=x1e});var vW=_(BW=>{"use strict";Object.defineProperty(BW,"__esModule",{value:!0});var bht=hn(),k1e=bht.createContext({stdout:void 0,write:()=>{}});k1e.displayName="InternalStdoutContext";BW.default=k1e});var DW=_(SW=>{"use strict";Object.defineProperty(SW,"__esModule",{value:!0});var Pht=hn(),Q1e=Pht.createContext({stderr:void 0,write:()=>{}});Q1e.displayName="InternalStderrContext";SW.default=Q1e});var _F=_(bW=>{"use strict";Object.defineProperty(bW,"__esModule",{value:!0});var xht=hn(),T1e=xht.createContext({activeId:void 0,add:()=>{},remove:()=>{},activate:()=>{},deactivate:()=>{},enableFocus:()=>{},disableFocus:()=>{},focusNext:()=>{},focusPrevious:()=>{},focus:()=>{}});T1e.displayName="InternalFocusContext";bW.default=T1e});var F1e=_((szt,R1e)=>{"use strict";var kht=/[|\\{}()[\]^$+*?.-]/g;R1e.exports=t=>{if(typeof t!="string")throw new TypeError("Expected a string");return t.replace(kht,"\\$&")}});var M1e=_((ozt,L1e)=>{"use strict";var Qht=F1e(),Tht=typeof process=="object"&&process&&typeof process.cwd=="function"?process.cwd():".",O1e=[].concat(Ie("module").builtinModules,"bootstrap_node","node").map(t=>new RegExp(`(?:\\((?:node:)?${t}(?:\\.js)?:\\d+:\\d+\\)$|^\\s*at (?:node:)?${t}(?:\\.js)?:\\d+:\\d+$)`));O1e.push(/\((?:node:)?internal\/[^:]+:\d+:\d+\)$/,/\s*at (?:node:)?internal\/[^:]+:\d+:\d+$/,/\/\.node-spawn-wrap-\w+-\w+\/node:\d+:\d+\)?$/);var PW=class t{constructor(e){e={ignoredPackages:[],...e},"internals"in e||(e.internals=t.nodeInternals()),"cwd"in e||(e.cwd=Tht),this._cwd=e.cwd.replace(/\\/g,"/"),this._internals=[].concat(e.internals,Rht(e.ignoredPackages)),this._wrapCallSite=e.wrapCallSite||!1}static nodeInternals(){return[...O1e]}clean(e,r=0){r=" ".repeat(r),Array.isArray(e)||(e=e.split(` `)),!/^\s*at /.test(e[0])&&/^\s*at /.test(e[1])&&(e=e.slice(1));let s=!1,a=null,n=[];return e.forEach(c=>{if(c=c.replace(/\\/g,"/"),this._internals.some(p=>p.test(c)))return;let f=/^\s*at /.test(c);s?c=c.trimEnd().replace(/^(\s+)at /,"$1"):(c=c.trim(),f&&(c=c.slice(3))),c=c.replace(`${this._cwd}/`,""),c&&(f?(a&&(n.push(a),a=null),n.push(c)):(s=!0,a=c))}),n.map(c=>`${r}${c} `).join("")}captureString(e,r=this.captureString){typeof e=="function"&&(r=e,e=1/0);let{stackTraceLimit:s}=Error;e&&(Error.stackTraceLimit=e);let a={};Error.captureStackTrace(a,r);let{stack:n}=a;return Error.stackTraceLimit=s,this.clean(n)}capture(e,r=this.capture){typeof e=="function"&&(r=e,e=1/0);let{prepareStackTrace:s,stackTraceLimit:a}=Error;Error.prepareStackTrace=(f,p)=>this._wrapCallSite?p.map(this._wrapCallSite):p,e&&(Error.stackTraceLimit=e);let n={};Error.captureStackTrace(n,r);let{stack:c}=n;return Object.assign(Error,{prepareStackTrace:s,stackTraceLimit:a}),c}at(e=this.at){let[r]=this.capture(1,e);if(!r)return{};let s={line:r.getLineNumber(),column:r.getColumnNumber()};N1e(s,r.getFileName(),this._cwd),r.isConstructor()&&(s.constructor=!0),r.isEval()&&(s.evalOrigin=r.getEvalOrigin()),r.isNative()&&(s.native=!0);let a;try{a=r.getTypeName()}catch{}a&&a!=="Object"&&a!=="[object Object]"&&(s.type=a);let n=r.getFunctionName();n&&(s.function=n);let c=r.getMethodName();return c&&n!==c&&(s.method=c),s}parseLine(e){let r=e&&e.match(Fht);if(!r)return null;let s=r[1]==="new",a=r[2],n=r[3],c=r[4],f=Number(r[5]),p=Number(r[6]),h=r[7],E=r[8],C=r[9],S=r[10]==="native",P=r[11]===")",I,R={};if(E&&(R.line=Number(E)),C&&(R.column=Number(C)),P&&h){let N=0;for(let U=h.length-1;U>0;U--)if(h.charAt(U)===")")N++;else if(h.charAt(U)==="("&&h.charAt(U-1)===" "&&(N--,N===-1&&h.charAt(U-1)===" ")){let W=h.slice(0,U-1);h=h.slice(U+1),a+=` (${W}`;break}}if(a){let N=a.match(Nht);N&&(a=N[1],I=N[2])}return N1e(R,h,this._cwd),s&&(R.constructor=!0),n&&(R.evalOrigin=n,R.evalLine=f,R.evalColumn=p,R.evalFile=c&&c.replace(/\\/g,"/")),S&&(R.native=!0),a&&(R.function=a),I&&a!==I&&(R.method=I),R}};function N1e(t,e,r){e&&(e=e.replace(/\\/g,"/"),e.startsWith(`${r}/`)&&(e=e.slice(r.length+1)),t.file=e)}function Rht(t){if(t.length===0)return[];let e=t.map(r=>Qht(r));return new RegExp(`[/\\\\]node_modules[/\\\\](?:${e.join("|")})[/\\\\][^:]+:\\d+:\\d+`)}var Fht=new RegExp("^(?:\\s*at )?(?:(new) )?(?:(.*?) \\()?(?:eval at ([^ ]+) \\((.+?):(\\d+):(\\d+)\\), )?(?:(.+?):(\\d+):(\\d+)|(native))(\\)?)$"),Nht=/^(.*?) \[as (.*?)\]$/;L1e.exports=PW});var _1e=_((azt,U1e)=>{"use strict";U1e.exports=(t,e)=>t.replace(/^\t+/gm,r=>" ".repeat(r.length*(e||2)))});var j1e=_((lzt,H1e)=>{"use strict";var Oht=_1e(),Lht=(t,e)=>{let r=[],s=t-e,a=t+e;for(let n=s;n<=a;n++)r.push(n);return r};H1e.exports=(t,e,r)=>{if(typeof t!="string")throw new TypeError("Source code is missing.");if(!e||e<1)throw new TypeError("Line number must start from `1`.");if(t=Oht(t).split(/\r?\n/),!(e>t.length))return r={around:3,...r},Lht(e,r.around).filter(s=>t[s-1]!==void 0).map(s=>({line:s,value:t[s-1]}))}});var HF=_(rf=>{"use strict";var Mht=rf&&rf.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Uht=rf&&rf.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),_ht=rf&&rf.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&Mht(e,t,r);return Uht(e,t),e},Hht=rf&&rf.__rest||function(t,e){var r={};for(var s in t)Object.prototype.hasOwnProperty.call(t,s)&&e.indexOf(s)<0&&(r[s]=t[s]);if(t!=null&&typeof Object.getOwnPropertySymbols=="function")for(var a=0,s=Object.getOwnPropertySymbols(t);a{var{children:r}=t,s=Hht(t,["children"]);let a=Object.assign(Object.assign({},s),{marginLeft:s.marginLeft||s.marginX||s.margin||0,marginRight:s.marginRight||s.marginX||s.margin||0,marginTop:s.marginTop||s.marginY||s.margin||0,marginBottom:s.marginBottom||s.marginY||s.margin||0,paddingLeft:s.paddingLeft||s.paddingX||s.padding||0,paddingRight:s.paddingRight||s.paddingX||s.padding||0,paddingTop:s.paddingTop||s.paddingY||s.padding||0,paddingBottom:s.paddingBottom||s.paddingY||s.padding||0});return G1e.default.createElement("ink-box",{ref:e,style:a},r)});xW.displayName="Box";xW.defaultProps={flexDirection:"row",flexGrow:0,flexShrink:1};rf.default=xW});var TW=_(nD=>{"use strict";var kW=nD&&nD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(nD,"__esModule",{value:!0});var jht=kW(hn()),yw=kW(TE()),q1e=kW(AW()),QW=({color:t,backgroundColor:e,dimColor:r,bold:s,italic:a,underline:n,strikethrough:c,inverse:f,wrap:p,children:h})=>{if(h==null)return null;let E=C=>(r&&(C=yw.default.dim(C)),t&&(C=q1e.default(C,t,"foreground")),e&&(C=q1e.default(C,e,"background")),s&&(C=yw.default.bold(C)),a&&(C=yw.default.italic(C)),n&&(C=yw.default.underline(C)),c&&(C=yw.default.strikethrough(C)),f&&(C=yw.default.inverse(C)),C);return jht.default.createElement("ink-text",{style:{flexGrow:0,flexShrink:1,flexDirection:"row",textWrap:p},internal_transform:E},h)};QW.displayName="Text";QW.defaultProps={dimColor:!1,bold:!1,italic:!1,underline:!1,strikethrough:!1,wrap:"wrap"};nD.default=QW});var J1e=_(nf=>{"use strict";var Ght=nf&&nf.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),qht=nf&&nf.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Wht=nf&&nf.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&Ght(e,t,r);return qht(e,t),e},iD=nf&&nf.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(nf,"__esModule",{value:!0});var W1e=Wht(Ie("fs")),Qs=iD(hn()),Y1e=iD(M1e()),Yht=iD(j1e()),$p=iD(HF()),AA=iD(TW()),V1e=new Y1e.default({cwd:process.cwd(),internals:Y1e.default.nodeInternals()}),Vht=({error:t})=>{let e=t.stack?t.stack.split(` `).slice(1):void 0,r=e?V1e.parseLine(e[0]):void 0,s,a=0;if(r?.file&&r?.line&&W1e.existsSync(r.file)){let n=W1e.readFileSync(r.file,"utf8");if(s=Yht.default(n,r.line),s)for(let{line:c}of s)a=Math.max(a,String(c).length)}return Qs.default.createElement($p.default,{flexDirection:"column",padding:1},Qs.default.createElement($p.default,null,Qs.default.createElement(AA.default,{backgroundColor:"red",color:"white"}," ","ERROR"," "),Qs.default.createElement(AA.default,null," ",t.message)),r&&Qs.default.createElement($p.default,{marginTop:1},Qs.default.createElement(AA.default,{dimColor:!0},r.file,":",r.line,":",r.column)),r&&s&&Qs.default.createElement($p.default,{marginTop:1,flexDirection:"column"},s.map(({line:n,value:c})=>Qs.default.createElement($p.default,{key:n},Qs.default.createElement($p.default,{width:a+1},Qs.default.createElement(AA.default,{dimColor:n!==r.line,backgroundColor:n===r.line?"red":void 0,color:n===r.line?"white":void 0},String(n).padStart(a," "),":")),Qs.default.createElement(AA.default,{key:n,backgroundColor:n===r.line?"red":void 0,color:n===r.line?"white":void 0}," "+c)))),t.stack&&Qs.default.createElement($p.default,{marginTop:1,flexDirection:"column"},t.stack.split(` `).slice(1).map(n=>{let c=V1e.parseLine(n);return c?Qs.default.createElement($p.default,{key:n},Qs.default.createElement(AA.default,{dimColor:!0},"- "),Qs.default.createElement(AA.default,{dimColor:!0,bold:!0},c.function),Qs.default.createElement(AA.default,{dimColor:!0,color:"gray"}," ","(",c.file,":",c.line,":",c.column,")")):Qs.default.createElement($p.default,{key:n},Qs.default.createElement(AA.default,{dimColor:!0},"- "),Qs.default.createElement(AA.default,{dimColor:!0,bold:!0},n))})))};nf.default=Vht});var z1e=_(sf=>{"use strict";var Jht=sf&&sf.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Kht=sf&&sf.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),zht=sf&&sf.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&Jht(e,t,r);return Kht(e,t),e},Lm=sf&&sf.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(sf,"__esModule",{value:!0});var Om=zht(hn()),K1e=Lm(F9()),Xht=Lm(IW()),Zht=Lm(wW()),$ht=Lm(vW()),e0t=Lm(DW()),t0t=Lm(_F()),r0t=Lm(J1e()),n0t=" ",i0t="\x1B[Z",s0t="\x1B",jF=class extends Om.PureComponent{constructor(){super(...arguments),this.state={isFocusEnabled:!0,activeFocusId:void 0,focusables:[],error:void 0},this.rawModeEnabledCount=0,this.handleSetRawMode=e=>{let{stdin:r}=this.props;if(!this.isRawModeSupported())throw r===process.stdin?new Error(`Raw mode is not supported on the current process.stdin, which Ink uses as input stream by default. Read about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported`):new Error(`Raw mode is not supported on the stdin provided to Ink. Read about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported`);if(r.setEncoding("utf8"),e){this.rawModeEnabledCount===0&&(r.addListener("data",this.handleInput),r.resume(),r.setRawMode(!0)),this.rawModeEnabledCount++;return}--this.rawModeEnabledCount===0&&(r.setRawMode(!1),r.removeListener("data",this.handleInput),r.pause())},this.handleInput=e=>{e===""&&this.props.exitOnCtrlC&&this.handleExit(),e===s0t&&this.state.activeFocusId&&this.setState({activeFocusId:void 0}),this.state.isFocusEnabled&&this.state.focusables.length>0&&(e===n0t&&this.focusNext(),e===i0t&&this.focusPrevious())},this.handleExit=e=>{this.isRawModeSupported()&&this.handleSetRawMode(!1),this.props.onExit(e)},this.enableFocus=()=>{this.setState({isFocusEnabled:!0})},this.disableFocus=()=>{this.setState({isFocusEnabled:!1})},this.focus=e=>{this.setState(r=>r.focusables.some(a=>a?.id===e)?{activeFocusId:e}:r)},this.focusNext=()=>{this.setState(e=>{var r;let s=(r=e.focusables[0])===null||r===void 0?void 0:r.id;return{activeFocusId:this.findNextFocusable(e)||s}})},this.focusPrevious=()=>{this.setState(e=>{var r;let s=(r=e.focusables[e.focusables.length-1])===null||r===void 0?void 0:r.id;return{activeFocusId:this.findPreviousFocusable(e)||s}})},this.addFocusable=(e,{autoFocus:r})=>{this.setState(s=>{let a=s.activeFocusId;return!a&&r&&(a=e),{activeFocusId:a,focusables:[...s.focusables,{id:e,isActive:!0}]}})},this.removeFocusable=e=>{this.setState(r=>({activeFocusId:r.activeFocusId===e?void 0:r.activeFocusId,focusables:r.focusables.filter(s=>s.id!==e)}))},this.activateFocusable=e=>{this.setState(r=>({focusables:r.focusables.map(s=>s.id!==e?s:{id:e,isActive:!0})}))},this.deactivateFocusable=e=>{this.setState(r=>({activeFocusId:r.activeFocusId===e?void 0:r.activeFocusId,focusables:r.focusables.map(s=>s.id!==e?s:{id:e,isActive:!1})}))},this.findNextFocusable=e=>{var r;let s=e.focusables.findIndex(a=>a.id===e.activeFocusId);for(let a=s+1;a{var r;let s=e.focusables.findIndex(a=>a.id===e.activeFocusId);for(let a=s-1;a>=0;a--)if(!((r=e.focusables[a])===null||r===void 0)&&r.isActive)return e.focusables[a].id}}static getDerivedStateFromError(e){return{error:e}}isRawModeSupported(){return this.props.stdin.isTTY}render(){return Om.default.createElement(Xht.default.Provider,{value:{exit:this.handleExit}},Om.default.createElement(Zht.default.Provider,{value:{stdin:this.props.stdin,setRawMode:this.handleSetRawMode,isRawModeSupported:this.isRawModeSupported(),internal_exitOnCtrlC:this.props.exitOnCtrlC}},Om.default.createElement($ht.default.Provider,{value:{stdout:this.props.stdout,write:this.props.writeToStdout}},Om.default.createElement(e0t.default.Provider,{value:{stderr:this.props.stderr,write:this.props.writeToStderr}},Om.default.createElement(t0t.default.Provider,{value:{activeId:this.state.activeFocusId,add:this.addFocusable,remove:this.removeFocusable,activate:this.activateFocusable,deactivate:this.deactivateFocusable,enableFocus:this.enableFocus,disableFocus:this.disableFocus,focusNext:this.focusNext,focusPrevious:this.focusPrevious,focus:this.focus}},this.state.error?Om.default.createElement(r0t.default,{error:this.state.error}):this.props.children)))))}componentDidMount(){K1e.default.hide(this.props.stdout)}componentWillUnmount(){K1e.default.show(this.props.stdout),this.isRawModeSupported()&&this.handleSetRawMode(!1)}componentDidCatch(e){this.handleExit(e)}};sf.default=jF;jF.displayName="InternalApp"});var $1e=_(of=>{"use strict";var o0t=of&&of.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),a0t=of&&of.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),l0t=of&&of.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&o0t(e,t,r);return a0t(e,t),e},af=of&&of.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(of,"__esModule",{value:!0});var c0t=af(hn()),X1e=WCe(),u0t=af(awe()),f0t=af(x9()),A0t=af(pwe()),p0t=af(gwe()),RW=af(a1e()),h0t=af(B1e()),g0t=af(R9()),d0t=af(b1e()),m0t=l0t(uW()),y0t=af(yW()),E0t=af(z1e()),Ew=process.env.CI==="false"?!1:A0t.default,Z1e=()=>{},FW=class{constructor(e){this.resolveExitPromise=()=>{},this.rejectExitPromise=()=>{},this.unsubscribeExit=()=>{},this.onRender=()=>{if(this.isUnmounted)return;let{output:r,outputHeight:s,staticOutput:a}=h0t.default(this.rootNode,this.options.stdout.columns||80),n=a&&a!==` `;if(this.options.debug){n&&(this.fullStaticOutput+=a),this.options.stdout.write(this.fullStaticOutput+r);return}if(Ew){n&&this.options.stdout.write(a),this.lastOutput=r;return}if(n&&(this.fullStaticOutput+=a),s>=this.options.stdout.rows){this.options.stdout.write(f0t.default.clearTerminal+this.fullStaticOutput+r),this.lastOutput=r;return}n&&(this.log.clear(),this.options.stdout.write(a),this.log(r)),!n&&r!==this.lastOutput&&this.throttledLog(r),this.lastOutput=r},p0t.default(this),this.options=e,this.rootNode=m0t.createNode("ink-root"),this.rootNode.onRender=e.debug?this.onRender:X1e(this.onRender,32,{leading:!0,trailing:!0}),this.rootNode.onImmediateRender=this.onRender,this.log=u0t.default.create(e.stdout),this.throttledLog=e.debug?this.log:X1e(this.log,void 0,{leading:!0,trailing:!0}),this.isUnmounted=!1,this.lastOutput="",this.fullStaticOutput="",this.container=RW.default.createContainer(this.rootNode,0,!1,null),this.unsubscribeExit=g0t.default(this.unmount,{alwaysLast:!1}),e.patchConsole&&this.patchConsole(),Ew||(e.stdout.on("resize",this.onRender),this.unsubscribeResize=()=>{e.stdout.off("resize",this.onRender)})}render(e){let r=c0t.default.createElement(E0t.default,{stdin:this.options.stdin,stdout:this.options.stdout,stderr:this.options.stderr,writeToStdout:this.writeToStdout,writeToStderr:this.writeToStderr,exitOnCtrlC:this.options.exitOnCtrlC,onExit:this.unmount},e);RW.default.updateContainer(r,this.container,null,Z1e)}writeToStdout(e){if(!this.isUnmounted){if(this.options.debug){this.options.stdout.write(e+this.fullStaticOutput+this.lastOutput);return}if(Ew){this.options.stdout.write(e);return}this.log.clear(),this.options.stdout.write(e),this.log(this.lastOutput)}}writeToStderr(e){if(!this.isUnmounted){if(this.options.debug){this.options.stderr.write(e),this.options.stdout.write(this.fullStaticOutput+this.lastOutput);return}if(Ew){this.options.stderr.write(e);return}this.log.clear(),this.options.stderr.write(e),this.log(this.lastOutput)}}unmount(e){this.isUnmounted||(this.onRender(),this.unsubscribeExit(),typeof this.restoreConsole=="function"&&this.restoreConsole(),typeof this.unsubscribeResize=="function"&&this.unsubscribeResize(),Ew?this.options.stdout.write(this.lastOutput+` `):this.options.debug||this.log.done(),this.isUnmounted=!0,RW.default.updateContainer(null,this.container,null,Z1e),y0t.default.delete(this.options.stdout),e instanceof Error?this.rejectExitPromise(e):this.resolveExitPromise())}waitUntilExit(){return this.exitPromise||(this.exitPromise=new Promise((e,r)=>{this.resolveExitPromise=e,this.rejectExitPromise=r})),this.exitPromise}clear(){!Ew&&!this.options.debug&&this.log.clear()}patchConsole(){this.options.debug||(this.restoreConsole=d0t.default((e,r)=>{e==="stdout"&&this.writeToStdout(r),e==="stderr"&&(r.startsWith("The above error occurred")||this.writeToStderr(r))}))}};of.default=FW});var t2e=_(sD=>{"use strict";var e2e=sD&&sD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(sD,"__esModule",{value:!0});var I0t=e2e($1e()),GF=e2e(yW()),C0t=Ie("stream"),w0t=(t,e)=>{let r=Object.assign({stdout:process.stdout,stdin:process.stdin,stderr:process.stderr,debug:!1,exitOnCtrlC:!0,patchConsole:!0},B0t(e)),s=v0t(r.stdout,()=>new I0t.default(r));return s.render(t),{rerender:s.render,unmount:()=>s.unmount(),waitUntilExit:s.waitUntilExit,cleanup:()=>GF.default.delete(r.stdout),clear:s.clear}};sD.default=w0t;var B0t=(t={})=>t instanceof C0t.Stream?{stdout:t,stdin:process.stdin}:t,v0t=(t,e)=>{let r;return GF.default.has(t)?r=GF.default.get(t):(r=e(),GF.default.set(t,r)),r}});var n2e=_(eh=>{"use strict";var S0t=eh&&eh.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),D0t=eh&&eh.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),b0t=eh&&eh.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&S0t(e,t,r);return D0t(e,t),e};Object.defineProperty(eh,"__esModule",{value:!0});var oD=b0t(hn()),r2e=t=>{let{items:e,children:r,style:s}=t,[a,n]=oD.useState(0),c=oD.useMemo(()=>e.slice(a),[e,a]);oD.useLayoutEffect(()=>{n(e.length)},[e.length]);let f=c.map((h,E)=>r(h,a+E)),p=oD.useMemo(()=>Object.assign({position:"absolute",flexDirection:"column"},s),[s]);return oD.default.createElement("ink-box",{internal_static:!0,style:p},f)};r2e.displayName="Static";eh.default=r2e});var s2e=_(aD=>{"use strict";var P0t=aD&&aD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(aD,"__esModule",{value:!0});var x0t=P0t(hn()),i2e=({children:t,transform:e})=>t==null?null:x0t.default.createElement("ink-text",{style:{flexGrow:0,flexShrink:1,flexDirection:"row"},internal_transform:e},t);i2e.displayName="Transform";aD.default=i2e});var a2e=_(lD=>{"use strict";var k0t=lD&&lD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(lD,"__esModule",{value:!0});var Q0t=k0t(hn()),o2e=({count:t=1})=>Q0t.default.createElement("ink-text",null,` `.repeat(t));o2e.displayName="Newline";lD.default=o2e});var u2e=_(cD=>{"use strict";var l2e=cD&&cD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(cD,"__esModule",{value:!0});var T0t=l2e(hn()),R0t=l2e(HF()),c2e=()=>T0t.default.createElement(R0t.default,{flexGrow:1});c2e.displayName="Spacer";cD.default=c2e});var qF=_(uD=>{"use strict";var F0t=uD&&uD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(uD,"__esModule",{value:!0});var N0t=hn(),O0t=F0t(wW()),L0t=()=>N0t.useContext(O0t.default);uD.default=L0t});var A2e=_(fD=>{"use strict";var M0t=fD&&fD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(fD,"__esModule",{value:!0});var f2e=hn(),U0t=M0t(qF()),_0t=(t,e={})=>{let{stdin:r,setRawMode:s,internal_exitOnCtrlC:a}=U0t.default();f2e.useEffect(()=>{if(e.isActive!==!1)return s(!0),()=>{s(!1)}},[e.isActive,s]),f2e.useEffect(()=>{if(e.isActive===!1)return;let n=c=>{let f=String(c),p={upArrow:f==="\x1B[A",downArrow:f==="\x1B[B",leftArrow:f==="\x1B[D",rightArrow:f==="\x1B[C",pageDown:f==="\x1B[6~",pageUp:f==="\x1B[5~",return:f==="\r",escape:f==="\x1B",ctrl:!1,shift:!1,tab:f===" "||f==="\x1B[Z",backspace:f==="\b",delete:f==="\x7F"||f==="\x1B[3~",meta:!1};f<=""&&!p.return&&(f=String.fromCharCode(f.charCodeAt(0)+97-1),p.ctrl=!0),f.startsWith("\x1B")&&(f=f.slice(1),p.meta=!0);let h=f>="A"&&f<="Z",E=f>="\u0410"&&f<="\u042F";f.length===1&&(h||E)&&(p.shift=!0),p.tab&&f==="[Z"&&(p.shift=!0),(p.tab||p.backspace||p.delete)&&(f=""),(!(f==="c"&&p.ctrl)||!a)&&t(f,p)};return r?.on("data",n),()=>{r?.off("data",n)}},[e.isActive,r,a,t])};fD.default=_0t});var p2e=_(AD=>{"use strict";var H0t=AD&&AD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(AD,"__esModule",{value:!0});var j0t=hn(),G0t=H0t(IW()),q0t=()=>j0t.useContext(G0t.default);AD.default=q0t});var h2e=_(pD=>{"use strict";var W0t=pD&&pD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(pD,"__esModule",{value:!0});var Y0t=hn(),V0t=W0t(vW()),J0t=()=>Y0t.useContext(V0t.default);pD.default=J0t});var g2e=_(hD=>{"use strict";var K0t=hD&&hD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(hD,"__esModule",{value:!0});var z0t=hn(),X0t=K0t(DW()),Z0t=()=>z0t.useContext(X0t.default);hD.default=Z0t});var m2e=_(dD=>{"use strict";var d2e=dD&&dD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(dD,"__esModule",{value:!0});var gD=hn(),$0t=d2e(_F()),egt=d2e(qF()),tgt=({isActive:t=!0,autoFocus:e=!1,id:r}={})=>{let{isRawModeSupported:s,setRawMode:a}=egt.default(),{activeId:n,add:c,remove:f,activate:p,deactivate:h,focus:E}=gD.useContext($0t.default),C=gD.useMemo(()=>r??Math.random().toString().slice(2,7),[r]);return gD.useEffect(()=>(c(C,{autoFocus:e}),()=>{f(C)}),[C,e]),gD.useEffect(()=>{t?p(C):h(C)},[t,C]),gD.useEffect(()=>{if(!(!s||!t))return a(!0),()=>{a(!1)}},[t]),{isFocused:!!C&&n===C,focus:E}};dD.default=tgt});var y2e=_(mD=>{"use strict";var rgt=mD&&mD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(mD,"__esModule",{value:!0});var ngt=hn(),igt=rgt(_F()),sgt=()=>{let t=ngt.useContext(igt.default);return{enableFocus:t.enableFocus,disableFocus:t.disableFocus,focusNext:t.focusNext,focusPrevious:t.focusPrevious,focus:t.focus}};mD.default=sgt});var E2e=_(NW=>{"use strict";Object.defineProperty(NW,"__esModule",{value:!0});NW.default=t=>{var e,r,s,a;return{width:(r=(e=t.yogaNode)===null||e===void 0?void 0:e.getComputedWidth())!==null&&r!==void 0?r:0,height:(a=(s=t.yogaNode)===null||s===void 0?void 0:s.getComputedHeight())!==null&&a!==void 0?a:0}}});var Wc=_(mo=>{"use strict";Object.defineProperty(mo,"__esModule",{value:!0});var ogt=t2e();Object.defineProperty(mo,"render",{enumerable:!0,get:function(){return ogt.default}});var agt=HF();Object.defineProperty(mo,"Box",{enumerable:!0,get:function(){return agt.default}});var lgt=TW();Object.defineProperty(mo,"Text",{enumerable:!0,get:function(){return lgt.default}});var cgt=n2e();Object.defineProperty(mo,"Static",{enumerable:!0,get:function(){return cgt.default}});var ugt=s2e();Object.defineProperty(mo,"Transform",{enumerable:!0,get:function(){return ugt.default}});var fgt=a2e();Object.defineProperty(mo,"Newline",{enumerable:!0,get:function(){return fgt.default}});var Agt=u2e();Object.defineProperty(mo,"Spacer",{enumerable:!0,get:function(){return Agt.default}});var pgt=A2e();Object.defineProperty(mo,"useInput",{enumerable:!0,get:function(){return pgt.default}});var hgt=p2e();Object.defineProperty(mo,"useApp",{enumerable:!0,get:function(){return hgt.default}});var ggt=qF();Object.defineProperty(mo,"useStdin",{enumerable:!0,get:function(){return ggt.default}});var dgt=h2e();Object.defineProperty(mo,"useStdout",{enumerable:!0,get:function(){return dgt.default}});var mgt=g2e();Object.defineProperty(mo,"useStderr",{enumerable:!0,get:function(){return mgt.default}});var ygt=m2e();Object.defineProperty(mo,"useFocus",{enumerable:!0,get:function(){return ygt.default}});var Egt=y2e();Object.defineProperty(mo,"useFocusManager",{enumerable:!0,get:function(){return Egt.default}});var Igt=E2e();Object.defineProperty(mo,"measureElement",{enumerable:!0,get:function(){return Igt.default}})});var LW={};Vt(LW,{Gem:()=>OW});var I2e,Mm,OW,WF=Xe(()=>{I2e=ut(Wc()),Mm=ut(hn()),OW=(0,Mm.memo)(({active:t})=>{let e=(0,Mm.useMemo)(()=>t?"\u25C9":"\u25EF",[t]),r=(0,Mm.useMemo)(()=>t?"green":"yellow",[t]);return Mm.default.createElement(I2e.Text,{color:r},e)})});var w2e={};Vt(w2e,{useKeypress:()=>Um});function Um({active:t},e,r){let{stdin:s}=(0,C2e.useStdin)(),a=(0,YF.useCallback)((n,c)=>e(n,c),r);(0,YF.useEffect)(()=>{if(!(!t||!s))return s.on("keypress",a),()=>{s.off("keypress",a)}},[t,a,s])}var C2e,YF,yD=Xe(()=>{C2e=ut(Wc()),YF=ut(hn())});var v2e={};Vt(v2e,{FocusRequest:()=>B2e,useFocusRequest:()=>MW});var B2e,MW,UW=Xe(()=>{yD();B2e=(r=>(r.BEFORE="before",r.AFTER="after",r))(B2e||{}),MW=function({active:t},e,r){Um({active:t},(s,a)=>{a.name==="tab"&&(a.shift?e("before"):e("after"))},r)}});var S2e={};Vt(S2e,{useListInput:()=>ED});var ED,VF=Xe(()=>{yD();ED=function(t,e,{active:r,minus:s,plus:a,set:n,loop:c=!0}){Um({active:r},(f,p)=>{let h=e.indexOf(t);switch(p.name){case s:{let E=h-1;if(c){n(e[(e.length+E)%e.length]);return}if(E<0)return;n(e[E])}break;case a:{let E=h+1;if(c){n(e[E%e.length]);return}if(E>=e.length)return;n(e[E])}break}},[e,t,a,n,c])}});var JF={};Vt(JF,{ScrollableItems:()=>Cgt});var eg,dl,Cgt,KF=Xe(()=>{eg=ut(Wc()),dl=ut(hn());UW();VF();Cgt=({active:t=!0,children:e=[],radius:r=10,size:s=1,loop:a=!0,onFocusRequest:n,willReachEnd:c})=>{let f=N=>{if(N.key===null)throw new Error("Expected all children to have a key");return N.key},p=dl.default.Children.map(e,N=>f(N)),h=p[0],[E,C]=(0,dl.useState)(h),S=p.indexOf(E);(0,dl.useEffect)(()=>{p.includes(E)||C(h)},[e]),(0,dl.useEffect)(()=>{c&&S>=p.length-2&&c()},[S]),MW({active:t&&!!n},N=>{n?.(N)},[n]),ED(E,p,{active:t,minus:"up",plus:"down",set:C,loop:a});let P=S-r,I=S+r;I>p.length&&(P-=I-p.length,I=p.length),P<0&&(I+=-P,P=0),I>=p.length&&(I=p.length-1);let R=[];for(let N=P;N<=I;++N){let U=p[N],W=t&&U===E;R.push(dl.default.createElement(eg.Box,{key:U,height:s},dl.default.createElement(eg.Box,{marginLeft:1,marginRight:1},dl.default.createElement(eg.Text,null,W?dl.default.createElement(eg.Text,{color:"cyan",bold:!0},">"):" ")),dl.default.createElement(eg.Box,null,dl.default.cloneElement(e[N],{active:W}))))}return dl.default.createElement(eg.Box,{flexDirection:"column",width:"100%"},R)}});var D2e,th,b2e,_W,P2e,HW=Xe(()=>{D2e=ut(Wc()),th=ut(hn()),b2e=Ie("readline"),_W=th.default.createContext(null),P2e=({children:t})=>{let{stdin:e,setRawMode:r}=(0,D2e.useStdin)();(0,th.useEffect)(()=>{r&&r(!0),e&&(0,b2e.emitKeypressEvents)(e)},[e,r]);let[s,a]=(0,th.useState)(new Map),n=(0,th.useMemo)(()=>({getAll:()=>s,get:c=>s.get(c),set:(c,f)=>a(new Map([...s,[c,f]]))}),[s,a]);return th.default.createElement(_W.Provider,{value:n,children:t})}});var jW={};Vt(jW,{useMinistore:()=>wgt});function wgt(t,e){let r=(0,zF.useContext)(_W);if(r===null)throw new Error("Expected this hook to run with a ministore context attached");if(typeof t>"u")return r.getAll();let s=(0,zF.useCallback)(n=>{r.set(t,n)},[t,r.set]),a=r.get(t);return typeof a>"u"&&(a=e),[a,s]}var zF,GW=Xe(()=>{zF=ut(hn());HW()});var ZF={};Vt(ZF,{renderForm:()=>Bgt});async function Bgt(t,e,{stdin:r,stdout:s,stderr:a}){let n,c=p=>{let{exit:h}=(0,XF.useApp)();Um({active:!0},(E,C)=>{C.name==="return"&&(n=p,h())},[h,p])},{waitUntilExit:f}=(0,XF.render)(qW.default.createElement(P2e,null,qW.default.createElement(t,{...e,useSubmit:c})),{stdin:r,stdout:s,stderr:a});return await f(),n}var XF,qW,$F=Xe(()=>{XF=ut(Wc()),qW=ut(hn());HW();yD()});var T2e=_(ID=>{"use strict";Object.defineProperty(ID,"__esModule",{value:!0});ID.UncontrolledTextInput=void 0;var k2e=hn(),WW=hn(),x2e=Wc(),_m=TE(),Q2e=({value:t,placeholder:e="",focus:r=!0,mask:s,highlightPastedText:a=!1,showCursor:n=!0,onChange:c,onSubmit:f})=>{let[{cursorOffset:p,cursorWidth:h},E]=WW.useState({cursorOffset:(t||"").length,cursorWidth:0});WW.useEffect(()=>{E(R=>{if(!r||!n)return R;let N=t||"";return R.cursorOffset>N.length-1?{cursorOffset:N.length,cursorWidth:0}:R})},[t,r,n]);let C=a?h:0,S=s?s.repeat(t.length):t,P=S,I=e?_m.grey(e):void 0;if(n&&r){I=e.length>0?_m.inverse(e[0])+_m.grey(e.slice(1)):_m.inverse(" "),P=S.length>0?"":_m.inverse(" ");let R=0;for(let N of S)R>=p-C&&R<=p?P+=_m.inverse(N):P+=N,R++;S.length>0&&p===S.length&&(P+=_m.inverse(" "))}return x2e.useInput((R,N)=>{if(N.upArrow||N.downArrow||N.ctrl&&R==="c"||N.tab||N.shift&&N.tab)return;if(N.return){f&&f(t);return}let U=p,W=t,ee=0;N.leftArrow?n&&U--:N.rightArrow?n&&U++:N.backspace||N.delete?p>0&&(W=t.slice(0,p-1)+t.slice(p,t.length),U--):(W=t.slice(0,p)+R+t.slice(p,t.length),U+=R.length,R.length>1&&(ee=R.length)),p<0&&(U=0),p>t.length&&(U=t.length),E({cursorOffset:U,cursorWidth:ee}),W!==t&&c(W)},{isActive:r}),k2e.createElement(x2e.Text,null,e?S.length>0?P:I:P)};ID.default=Q2e;ID.UncontrolledTextInput=({initialValue:t="",...e})=>{let[r,s]=WW.useState(t);return k2e.createElement(Q2e,Object.assign({},e,{value:r,onChange:s}))}});var N2e={};Vt(N2e,{Pad:()=>YW});var R2e,F2e,YW,VW=Xe(()=>{R2e=ut(Wc()),F2e=ut(hn()),YW=({length:t,active:e})=>{if(t===0)return null;let r=t>1?` ${"-".repeat(t-1)}`:" ";return F2e.default.createElement(R2e.Text,{dimColor:!e},r)}});var O2e={};Vt(O2e,{ItemOptions:()=>vgt});var wD,tg,vgt,L2e=Xe(()=>{wD=ut(Wc()),tg=ut(hn());VF();WF();VW();vgt=function({active:t,skewer:e,options:r,value:s,onChange:a,sizes:n=[]}){let c=r.filter(({label:p})=>!!p).map(({value:p})=>p),f=r.findIndex(p=>p.value===s&&p.label!="");return ED(s,c,{active:t,minus:"left",plus:"right",set:a}),tg.default.createElement(tg.default.Fragment,null,r.map(({label:p},h)=>{let E=h===f,C=n[h]-1||0,S=p.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,""),P=Math.max(0,C-S.length-2);return p?tg.default.createElement(wD.Box,{key:p,width:C,marginLeft:1},tg.default.createElement(wD.Text,{wrap:"truncate"},tg.default.createElement(OW,{active:E})," ",p),e?tg.default.createElement(YW,{active:t,length:P}):null):tg.default.createElement(wD.Box,{key:`spacer-${h}`,width:C,marginLeft:1})}))}});var Z2e=_((AZt,X2e)=>{var iY;X2e.exports=()=>(typeof iY>"u"&&(iY=Ie("zlib").brotliDecompressSync(Buffer.from("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","base64")).toString()),iY)});var EBe=_((LZt,yBe)=>{var pY=Symbol("arg flag"),Yc=class t extends Error{constructor(e,r){super(e),this.name="ArgError",this.code=r,Object.setPrototypeOf(this,t.prototype)}};function UD(t,{argv:e=process.argv.slice(2),permissive:r=!1,stopAtPositional:s=!1}={}){if(!t)throw new Yc("argument specification object is required","ARG_CONFIG_NO_SPEC");let a={_:[]},n={},c={};for(let f of Object.keys(t)){if(!f)throw new Yc("argument key cannot be an empty string","ARG_CONFIG_EMPTY_KEY");if(f[0]!=="-")throw new Yc(`argument key must start with '-' but found: '${f}'`,"ARG_CONFIG_NONOPT_KEY");if(f.length===1)throw new Yc(`argument key must have a name; singular '-' keys are not allowed: ${f}`,"ARG_CONFIG_NONAME_KEY");if(typeof t[f]=="string"){n[f]=t[f];continue}let p=t[f],h=!1;if(Array.isArray(p)&&p.length===1&&typeof p[0]=="function"){let[E]=p;p=(C,S,P=[])=>(P.push(E(C,S,P[P.length-1])),P),h=E===Boolean||E[pY]===!0}else if(typeof p=="function")h=p===Boolean||p[pY]===!0;else throw new Yc(`type missing or not a function or valid array type: ${f}`,"ARG_CONFIG_VAD_TYPE");if(f[1]!=="-"&&f.length>2)throw new Yc(`short argument keys (with a single hyphen) must have only one character: ${f}`,"ARG_CONFIG_SHORTOPT_TOOLONG");c[f]=[p,h]}for(let f=0,p=e.length;f0){a._=a._.concat(e.slice(f));break}if(h==="--"){a._=a._.concat(e.slice(f+1));break}if(h.length>1&&h[0]==="-"){let E=h[1]==="-"||h.length===2?[h]:h.slice(1).split("").map(C=>`-${C}`);for(let C=0;C1&&e[f+1][0]==="-"&&!(e[f+1].match(/^-?\d*(\.(?=\d))?\d*$/)&&(N===Number||typeof BigInt<"u"&&N===BigInt))){let W=P===R?"":` (alias for ${R})`;throw new Yc(`option requires argument: ${P}${W}`,"ARG_MISSING_REQUIRED_LONGARG")}a[R]=N(e[f+1],R,a[R]),++f}else a[R]=N(I,R,a[R])}}else a._.push(h)}return a}UD.flag=t=>(t[pY]=!0,t);UD.COUNT=UD.flag((t,e,r)=>(r||0)+1);UD.ArgError=Yc;yBe.exports=UD});var bBe=_((p$t,DBe)=>{var mY;DBe.exports=()=>(typeof mY>"u"&&(mY=Ie("zlib").brotliDecompressSync(Buffer.from("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","base64")).toString()),mY)});var TBe=_((BY,vY)=>{(function(t){BY&&typeof BY=="object"&&typeof vY<"u"?vY.exports=t():typeof define=="function"&&define.amd?define([],t):typeof window<"u"?window.isWindows=t():typeof global<"u"?global.isWindows=t():typeof self<"u"?self.isWindows=t():this.isWindows=t()})(function(){"use strict";return function(){return process&&(process.platform==="win32"||/^(msys|cygwin)$/.test(process.env.OSTYPE))}})});var OBe=_((fer,NBe)=>{"use strict";SY.ifExists=Sdt;var Dw=Ie("util"),Vc=Ie("path"),RBe=TBe(),wdt=/^#!\s*(?:\/usr\/bin\/env)?\s*([^ \t]+)(.*)$/,Bdt={createPwshFile:!0,createCmdFile:RBe(),fs:Ie("fs")},vdt=new Map([[".js","node"],[".cjs","node"],[".mjs","node"],[".cmd","cmd"],[".bat","cmd"],[".ps1","pwsh"],[".sh","sh"]]);function FBe(t){let e={...Bdt,...t},r=e.fs;return e.fs_={chmod:r.chmod?Dw.promisify(r.chmod):async()=>{},mkdir:Dw.promisify(r.mkdir),readFile:Dw.promisify(r.readFile),stat:Dw.promisify(r.stat),unlink:Dw.promisify(r.unlink),writeFile:Dw.promisify(r.writeFile)},e}async function SY(t,e,r){let s=FBe(r);await s.fs_.stat(t),await bdt(t,e,s)}function Sdt(t,e,r){return SY(t,e,r).catch(()=>{})}function Ddt(t,e){return e.fs_.unlink(t).catch(()=>{})}async function bdt(t,e,r){let s=await Tdt(t,r);return await Pdt(e,r),xdt(t,e,s,r)}function Pdt(t,e){return e.fs_.mkdir(Vc.dirname(t),{recursive:!0})}function xdt(t,e,r,s){let a=FBe(s),n=[{generator:Ndt,extension:""}];return a.createCmdFile&&n.push({generator:Fdt,extension:".cmd"}),a.createPwshFile&&n.push({generator:Odt,extension:".ps1"}),Promise.all(n.map(c=>Rdt(t,e+c.extension,r,c.generator,a)))}function kdt(t,e){return Ddt(t,e)}function Qdt(t,e){return Ldt(t,e)}async function Tdt(t,e){let a=(await e.fs_.readFile(t,"utf8")).trim().split(/\r*\n/)[0].match(wdt);if(!a){let n=Vc.extname(t).toLowerCase();return{program:vdt.get(n)||null,additionalArgs:""}}return{program:a[1],additionalArgs:a[2]}}async function Rdt(t,e,r,s,a){let n=a.preserveSymlinks?"--preserve-symlinks":"",c=[r.additionalArgs,n].filter(f=>f).join(" ");return a=Object.assign({},a,{prog:r.program,args:c}),await kdt(e,a),await a.fs_.writeFile(e,s(t,e,a),"utf8"),Qdt(e,a)}function Fdt(t,e,r){let a=Vc.relative(Vc.dirname(e),t).split("/").join("\\"),n=Vc.isAbsolute(a)?`"${a}"`:`"%~dp0\\${a}"`,c,f=r.prog,p=r.args||"",h=DY(r.nodePath).win32;f?(c=`"%~dp0\\${f}.exe"`,a=n):(f=n,p="",a="");let E=r.progArgs?`${r.progArgs.join(" ")} `:"",C=h?`@SET NODE_PATH=${h}\r `:"";return c?C+=`@IF EXIST ${c} (\r ${c} ${p} ${a} ${E}%*\r ) ELSE (\r @SETLOCAL\r @SET PATHEXT=%PATHEXT:;.JS;=;%\r ${f} ${p} ${a} ${E}%*\r )\r `:C+=`@${f} ${p} ${a} ${E}%*\r `,C}function Ndt(t,e,r){let s=Vc.relative(Vc.dirname(e),t),a=r.prog&&r.prog.split("\\").join("/"),n;s=s.split("\\").join("/");let c=Vc.isAbsolute(s)?`"${s}"`:`"$basedir/${s}"`,f=r.args||"",p=DY(r.nodePath).posix;a?(n=`"$basedir/${r.prog}"`,s=c):(a=c,f="",s="");let h=r.progArgs?`${r.progArgs.join(" ")} `:"",E=`#!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')") case \`uname\` in *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;; esac `,C=r.nodePath?`export NODE_PATH="${p}" `:"";return n?E+=`${C}if [ -x ${n} ]; then exec ${n} ${f} ${s} ${h}"$@" else exec ${a} ${f} ${s} ${h}"$@" fi `:E+=`${C}${a} ${f} ${s} ${h}"$@" exit $? `,E}function Odt(t,e,r){let s=Vc.relative(Vc.dirname(e),t),a=r.prog&&r.prog.split("\\").join("/"),n=a&&`"${a}$exe"`,c;s=s.split("\\").join("/");let f=Vc.isAbsolute(s)?`"${s}"`:`"$basedir/${s}"`,p=r.args||"",h=DY(r.nodePath),E=h.win32,C=h.posix;n?(c=`"$basedir/${r.prog}$exe"`,s=f):(n=f,p="",s="");let S=r.progArgs?`${r.progArgs.join(" ")} `:"",P=`#!/usr/bin/env pwsh $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent $exe="" ${r.nodePath?`$env_node_path=$env:NODE_PATH $env:NODE_PATH="${E}" `:""}if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { # Fix case when both the Windows and Linux builds of Node # are installed in the same directory $exe=".exe" }`;return r.nodePath&&(P+=` else { $env:NODE_PATH="${C}" }`),c?P+=` $ret=0 if (Test-Path ${c}) { # Support pipeline input if ($MyInvocation.ExpectingInput) { $input | & ${c} ${p} ${s} ${S}$args } else { & ${c} ${p} ${s} ${S}$args } $ret=$LASTEXITCODE } else { # Support pipeline input if ($MyInvocation.ExpectingInput) { $input | & ${n} ${p} ${s} ${S}$args } else { & ${n} ${p} ${s} ${S}$args } $ret=$LASTEXITCODE } ${r.nodePath?`$env:NODE_PATH=$env_node_path `:""}exit $ret `:P+=` # Support pipeline input if ($MyInvocation.ExpectingInput) { $input | & ${n} ${p} ${s} ${S}$args } else { & ${n} ${p} ${s} ${S}$args } ${r.nodePath?`$env:NODE_PATH=$env_node_path `:""}exit $LASTEXITCODE `,P}function Ldt(t,e){return e.fs_.chmod(t,493)}function DY(t){if(!t)return{win32:"",posix:""};let e=typeof t=="string"?t.split(Vc.delimiter):Array.from(t),r={};for(let s=0;s`/mnt/${f.toLowerCase()}`):e[s];r.win32=r.win32?`${r.win32};${a}`:a,r.posix=r.posix?`${r.posix}:${n}`:n,r[s]={win32:a,posix:n}}return r}NBe.exports=SY});var _Y=_((_tr,tve)=>{tve.exports=Ie("stream")});var sve=_((Htr,ive)=>{"use strict";function rve(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(t);e&&(s=s.filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable})),r.push.apply(r,s)}return r}function mmt(t){for(var e=1;e0?this.tail.next=s:this.head=s,this.tail=s,++this.length}},{key:"unshift",value:function(r){var s={data:r,next:this.head};this.length===0&&(this.tail=s),this.head=s,++this.length}},{key:"shift",value:function(){if(this.length!==0){var r=this.head.data;return this.length===1?this.head=this.tail=null:this.head=this.head.next,--this.length,r}}},{key:"clear",value:function(){this.head=this.tail=null,this.length=0}},{key:"join",value:function(r){if(this.length===0)return"";for(var s=this.head,a=""+s.data;s=s.next;)a+=r+s.data;return a}},{key:"concat",value:function(r){if(this.length===0)return pN.alloc(0);for(var s=pN.allocUnsafe(r>>>0),a=this.head,n=0;a;)vmt(a.data,s,n),n+=a.data.length,a=a.next;return s}},{key:"consume",value:function(r,s){var a;return rc.length?c.length:r;if(f===c.length?n+=c:n+=c.slice(0,r),r-=f,r===0){f===c.length?(++a,s.next?this.head=s.next:this.head=this.tail=null):(this.head=s,s.data=c.slice(f));break}++a}return this.length-=a,n}},{key:"_getBuffer",value:function(r){var s=pN.allocUnsafe(r),a=this.head,n=1;for(a.data.copy(s),r-=a.data.length;a=a.next;){var c=a.data,f=r>c.length?c.length:r;if(c.copy(s,s.length-r,0,f),r-=f,r===0){f===c.length?(++n,a.next?this.head=a.next:this.head=this.tail=null):(this.head=a,a.data=c.slice(f));break}++n}return this.length-=n,s}},{key:Bmt,value:function(r,s){return HY(this,mmt({},s,{depth:0,customInspect:!1}))}}]),t}()});var GY=_((jtr,ave)=>{"use strict";function Smt(t,e){var r=this,s=this._readableState&&this._readableState.destroyed,a=this._writableState&&this._writableState.destroyed;return s||a?(e?e(t):t&&(this._writableState?this._writableState.errorEmitted||(this._writableState.errorEmitted=!0,process.nextTick(jY,this,t)):process.nextTick(jY,this,t)),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(t||null,function(n){!e&&n?r._writableState?r._writableState.errorEmitted?process.nextTick(hN,r):(r._writableState.errorEmitted=!0,process.nextTick(ove,r,n)):process.nextTick(ove,r,n):e?(process.nextTick(hN,r),e(n)):process.nextTick(hN,r)}),this)}function ove(t,e){jY(t,e),hN(t)}function hN(t){t._writableState&&!t._writableState.emitClose||t._readableState&&!t._readableState.emitClose||t.emit("close")}function Dmt(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finalCalled=!1,this._writableState.prefinished=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}function jY(t,e){t.emit("error",e)}function bmt(t,e){var r=t._readableState,s=t._writableState;r&&r.autoDestroy||s&&s.autoDestroy?t.destroy(e):t.emit("error",e)}ave.exports={destroy:Smt,undestroy:Dmt,errorOrDestroy:bmt}});var lg=_((Gtr,uve)=>{"use strict";var cve={};function Kc(t,e,r){r||(r=Error);function s(n,c,f){return typeof e=="string"?e:e(n,c,f)}class a extends r{constructor(c,f,p){super(s(c,f,p))}}a.prototype.name=r.name,a.prototype.code=t,cve[t]=a}function lve(t,e){if(Array.isArray(t)){let r=t.length;return t=t.map(s=>String(s)),r>2?`one of ${e} ${t.slice(0,r-1).join(", ")}, or `+t[r-1]:r===2?`one of ${e} ${t[0]} or ${t[1]}`:`of ${e} ${t[0]}`}else return`of ${e} ${String(t)}`}function Pmt(t,e,r){return t.substr(!r||r<0?0:+r,e.length)===e}function xmt(t,e,r){return(r===void 0||r>t.length)&&(r=t.length),t.substring(r-e.length,r)===e}function kmt(t,e,r){return typeof r!="number"&&(r=0),r+e.length>t.length?!1:t.indexOf(e,r)!==-1}Kc("ERR_INVALID_OPT_VALUE",function(t,e){return'The value "'+e+'" is invalid for option "'+t+'"'},TypeError);Kc("ERR_INVALID_ARG_TYPE",function(t,e,r){let s;typeof e=="string"&&Pmt(e,"not ")?(s="must not be",e=e.replace(/^not /,"")):s="must be";let a;if(xmt(t," argument"))a=`The ${t} ${s} ${lve(e,"type")}`;else{let n=kmt(t,".")?"property":"argument";a=`The "${t}" ${n} ${s} ${lve(e,"type")}`}return a+=`. Received type ${typeof r}`,a},TypeError);Kc("ERR_STREAM_PUSH_AFTER_EOF","stream.push() after EOF");Kc("ERR_METHOD_NOT_IMPLEMENTED",function(t){return"The "+t+" method is not implemented"});Kc("ERR_STREAM_PREMATURE_CLOSE","Premature close");Kc("ERR_STREAM_DESTROYED",function(t){return"Cannot call "+t+" after a stream was destroyed"});Kc("ERR_MULTIPLE_CALLBACK","Callback called multiple times");Kc("ERR_STREAM_CANNOT_PIPE","Cannot pipe, not readable");Kc("ERR_STREAM_WRITE_AFTER_END","write after end");Kc("ERR_STREAM_NULL_VALUES","May not write null values to stream",TypeError);Kc("ERR_UNKNOWN_ENCODING",function(t){return"Unknown encoding: "+t},TypeError);Kc("ERR_STREAM_UNSHIFT_AFTER_END_EVENT","stream.unshift() after end event");uve.exports.codes=cve});var qY=_((qtr,fve)=>{"use strict";var Qmt=lg().codes.ERR_INVALID_OPT_VALUE;function Tmt(t,e,r){return t.highWaterMark!=null?t.highWaterMark:e?t[r]:null}function Rmt(t,e,r,s){var a=Tmt(e,s,r);if(a!=null){if(!(isFinite(a)&&Math.floor(a)===a)||a<0){var n=s?r:"highWaterMark";throw new Qmt(n,a)}return Math.floor(a)}return t.objectMode?16:16*1024}fve.exports={getHighWaterMark:Rmt}});var Ave=_((Wtr,WY)=>{typeof Object.create=="function"?WY.exports=function(e,r){r&&(e.super_=r,e.prototype=Object.create(r.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:WY.exports=function(e,r){if(r){e.super_=r;var s=function(){};s.prototype=r.prototype,e.prototype=new s,e.prototype.constructor=e}}});var cg=_((Ytr,VY)=>{try{if(YY=Ie("util"),typeof YY.inherits!="function")throw"";VY.exports=YY.inherits}catch{VY.exports=Ave()}var YY});var hve=_((Vtr,pve)=>{pve.exports=Ie("util").deprecate});var zY=_((Jtr,Ive)=>{"use strict";Ive.exports=Vi;function dve(t){var e=this;this.next=null,this.entry=null,this.finish=function(){oyt(e,t)}}var Tw;Vi.WritableState=ZD;var Fmt={deprecate:hve()},mve=_Y(),dN=Ie("buffer").Buffer,Nmt=global.Uint8Array||function(){};function Omt(t){return dN.from(t)}function Lmt(t){return dN.isBuffer(t)||t instanceof Nmt}var KY=GY(),Mmt=qY(),Umt=Mmt.getHighWaterMark,ug=lg().codes,_mt=ug.ERR_INVALID_ARG_TYPE,Hmt=ug.ERR_METHOD_NOT_IMPLEMENTED,jmt=ug.ERR_MULTIPLE_CALLBACK,Gmt=ug.ERR_STREAM_CANNOT_PIPE,qmt=ug.ERR_STREAM_DESTROYED,Wmt=ug.ERR_STREAM_NULL_VALUES,Ymt=ug.ERR_STREAM_WRITE_AFTER_END,Vmt=ug.ERR_UNKNOWN_ENCODING,Rw=KY.errorOrDestroy;cg()(Vi,mve);function Jmt(){}function ZD(t,e,r){Tw=Tw||Ym(),t=t||{},typeof r!="boolean"&&(r=e instanceof Tw),this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.writableObjectMode),this.highWaterMark=Umt(this,t,"writableHighWaterMark",r),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var s=t.decodeStrings===!1;this.decodeStrings=!s,this.defaultEncoding=t.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(a){tyt(e,a)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.emitClose=t.emitClose!==!1,this.autoDestroy=!!t.autoDestroy,this.bufferedRequestCount=0,this.corkedRequestsFree=new dve(this)}ZD.prototype.getBuffer=function(){for(var e=this.bufferedRequest,r=[];e;)r.push(e),e=e.next;return r};(function(){try{Object.defineProperty(ZD.prototype,"buffer",{get:Fmt.deprecate(function(){return this.getBuffer()},"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch{}})();var gN;typeof Symbol=="function"&&Symbol.hasInstance&&typeof Function.prototype[Symbol.hasInstance]=="function"?(gN=Function.prototype[Symbol.hasInstance],Object.defineProperty(Vi,Symbol.hasInstance,{value:function(e){return gN.call(this,e)?!0:this!==Vi?!1:e&&e._writableState instanceof ZD}})):gN=function(e){return e instanceof this};function Vi(t){Tw=Tw||Ym();var e=this instanceof Tw;if(!e&&!gN.call(Vi,this))return new Vi(t);this._writableState=new ZD(t,this,e),this.writable=!0,t&&(typeof t.write=="function"&&(this._write=t.write),typeof t.writev=="function"&&(this._writev=t.writev),typeof t.destroy=="function"&&(this._destroy=t.destroy),typeof t.final=="function"&&(this._final=t.final)),mve.call(this)}Vi.prototype.pipe=function(){Rw(this,new Gmt)};function Kmt(t,e){var r=new Ymt;Rw(t,r),process.nextTick(e,r)}function zmt(t,e,r,s){var a;return r===null?a=new Wmt:typeof r!="string"&&!e.objectMode&&(a=new _mt("chunk",["string","Buffer"],r)),a?(Rw(t,a),process.nextTick(s,a),!1):!0}Vi.prototype.write=function(t,e,r){var s=this._writableState,a=!1,n=!s.objectMode&&Lmt(t);return n&&!dN.isBuffer(t)&&(t=Omt(t)),typeof e=="function"&&(r=e,e=null),n?e="buffer":e||(e=s.defaultEncoding),typeof r!="function"&&(r=Jmt),s.ending?Kmt(this,r):(n||zmt(this,s,t,r))&&(s.pendingcb++,a=Zmt(this,s,n,t,e,r)),a};Vi.prototype.cork=function(){this._writableState.corked++};Vi.prototype.uncork=function(){var t=this._writableState;t.corked&&(t.corked--,!t.writing&&!t.corked&&!t.bufferProcessing&&t.bufferedRequest&&yve(this,t))};Vi.prototype.setDefaultEncoding=function(e){if(typeof e=="string"&&(e=e.toLowerCase()),!(["hex","utf8","utf-8","ascii","binary","base64","ucs2","ucs-2","utf16le","utf-16le","raw"].indexOf((e+"").toLowerCase())>-1))throw new Vmt(e);return this._writableState.defaultEncoding=e,this};Object.defineProperty(Vi.prototype,"writableBuffer",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}});function Xmt(t,e,r){return!t.objectMode&&t.decodeStrings!==!1&&typeof e=="string"&&(e=dN.from(e,r)),e}Object.defineProperty(Vi.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}});function Zmt(t,e,r,s,a,n){if(!r){var c=Xmt(e,s,a);s!==c&&(r=!0,a="buffer",s=c)}var f=e.objectMode?1:s.length;e.length+=f;var p=e.length{"use strict";var ayt=Object.keys||function(t){var e=[];for(var r in t)e.push(r);return e};wve.exports=dA;var Cve=$Y(),ZY=zY();cg()(dA,Cve);for(XY=ayt(ZY.prototype),mN=0;mN{var EN=Ie("buffer"),ah=EN.Buffer;function Bve(t,e){for(var r in t)e[r]=t[r]}ah.from&&ah.alloc&&ah.allocUnsafe&&ah.allocUnsafeSlow?vve.exports=EN:(Bve(EN,eV),eV.Buffer=Fw);function Fw(t,e,r){return ah(t,e,r)}Bve(ah,Fw);Fw.from=function(t,e,r){if(typeof t=="number")throw new TypeError("Argument must not be a number");return ah(t,e,r)};Fw.alloc=function(t,e,r){if(typeof t!="number")throw new TypeError("Argument must be a number");var s=ah(t);return e!==void 0?typeof r=="string"?s.fill(e,r):s.fill(e):s.fill(0),s};Fw.allocUnsafe=function(t){if(typeof t!="number")throw new TypeError("Argument must be a number");return ah(t)};Fw.allocUnsafeSlow=function(t){if(typeof t!="number")throw new TypeError("Argument must be a number");return EN.SlowBuffer(t)}});var nV=_(bve=>{"use strict";var rV=Sve().Buffer,Dve=rV.isEncoding||function(t){switch(t=""+t,t&&t.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function uyt(t){if(!t)return"utf8";for(var e;;)switch(t){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return t;default:if(e)return;t=(""+t).toLowerCase(),e=!0}}function fyt(t){var e=uyt(t);if(typeof e!="string"&&(rV.isEncoding===Dve||!Dve(t)))throw new Error("Unknown encoding: "+t);return e||t}bve.StringDecoder=$D;function $D(t){this.encoding=fyt(t);var e;switch(this.encoding){case"utf16le":this.text=myt,this.end=yyt,e=4;break;case"utf8":this.fillLast=hyt,e=4;break;case"base64":this.text=Eyt,this.end=Iyt,e=3;break;default:this.write=Cyt,this.end=wyt;return}this.lastNeed=0,this.lastTotal=0,this.lastChar=rV.allocUnsafe(e)}$D.prototype.write=function(t){if(t.length===0)return"";var e,r;if(this.lastNeed){if(e=this.fillLast(t),e===void 0)return"";r=this.lastNeed,this.lastNeed=0}else r=0;return r>5===6?2:t>>4===14?3:t>>3===30?4:t>>6===2?-1:-2}function Ayt(t,e,r){var s=e.length-1;if(s=0?(a>0&&(t.lastNeed=a-1),a):--s=0?(a>0&&(t.lastNeed=a-2),a):--s=0?(a>0&&(a===2?a=0:t.lastNeed=a-3),a):0))}function pyt(t,e,r){if((e[0]&192)!==128)return t.lastNeed=0,"\uFFFD";if(t.lastNeed>1&&e.length>1){if((e[1]&192)!==128)return t.lastNeed=1,"\uFFFD";if(t.lastNeed>2&&e.length>2&&(e[2]&192)!==128)return t.lastNeed=2,"\uFFFD"}}function hyt(t){var e=this.lastTotal-this.lastNeed,r=pyt(this,t,e);if(r!==void 0)return r;if(this.lastNeed<=t.length)return t.copy(this.lastChar,e,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);t.copy(this.lastChar,e,0,t.length),this.lastNeed-=t.length}function gyt(t,e){var r=Ayt(this,t,e);if(!this.lastNeed)return t.toString("utf8",e);this.lastTotal=r;var s=t.length-(r-this.lastNeed);return t.copy(this.lastChar,0,s),t.toString("utf8",e,s)}function dyt(t){var e=t&&t.length?this.write(t):"";return this.lastNeed?e+"\uFFFD":e}function myt(t,e){if((t.length-e)%2===0){var r=t.toString("utf16le",e);if(r){var s=r.charCodeAt(r.length-1);if(s>=55296&&s<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1],r.slice(0,-1)}return r}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=t[t.length-1],t.toString("utf16le",e,t.length-1)}function yyt(t){var e=t&&t.length?this.write(t):"";if(this.lastNeed){var r=this.lastTotal-this.lastNeed;return e+this.lastChar.toString("utf16le",0,r)}return e}function Eyt(t,e){var r=(t.length-e)%3;return r===0?t.toString("base64",e):(this.lastNeed=3-r,this.lastTotal=3,r===1?this.lastChar[0]=t[t.length-1]:(this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1]),t.toString("base64",e,t.length-r))}function Iyt(t){var e=t&&t.length?this.write(t):"";return this.lastNeed?e+this.lastChar.toString("base64",0,3-this.lastNeed):e}function Cyt(t){return t.toString(this.encoding)}function wyt(t){return t&&t.length?this.write(t):""}});var IN=_((Xtr,kve)=>{"use strict";var Pve=lg().codes.ERR_STREAM_PREMATURE_CLOSE;function Byt(t){var e=!1;return function(){if(!e){e=!0;for(var r=arguments.length,s=new Array(r),a=0;a{"use strict";var CN;function fg(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}var Dyt=IN(),Ag=Symbol("lastResolve"),Vm=Symbol("lastReject"),eb=Symbol("error"),wN=Symbol("ended"),Jm=Symbol("lastPromise"),iV=Symbol("handlePromise"),Km=Symbol("stream");function pg(t,e){return{value:t,done:e}}function byt(t){var e=t[Ag];if(e!==null){var r=t[Km].read();r!==null&&(t[Jm]=null,t[Ag]=null,t[Vm]=null,e(pg(r,!1)))}}function Pyt(t){process.nextTick(byt,t)}function xyt(t,e){return function(r,s){t.then(function(){if(e[wN]){r(pg(void 0,!0));return}e[iV](r,s)},s)}}var kyt=Object.getPrototypeOf(function(){}),Qyt=Object.setPrototypeOf((CN={get stream(){return this[Km]},next:function(){var e=this,r=this[eb];if(r!==null)return Promise.reject(r);if(this[wN])return Promise.resolve(pg(void 0,!0));if(this[Km].destroyed)return new Promise(function(c,f){process.nextTick(function(){e[eb]?f(e[eb]):c(pg(void 0,!0))})});var s=this[Jm],a;if(s)a=new Promise(xyt(s,this));else{var n=this[Km].read();if(n!==null)return Promise.resolve(pg(n,!1));a=new Promise(this[iV])}return this[Jm]=a,a}},fg(CN,Symbol.asyncIterator,function(){return this}),fg(CN,"return",function(){var e=this;return new Promise(function(r,s){e[Km].destroy(null,function(a){if(a){s(a);return}r(pg(void 0,!0))})})}),CN),kyt),Tyt=function(e){var r,s=Object.create(Qyt,(r={},fg(r,Km,{value:e,writable:!0}),fg(r,Ag,{value:null,writable:!0}),fg(r,Vm,{value:null,writable:!0}),fg(r,eb,{value:null,writable:!0}),fg(r,wN,{value:e._readableState.endEmitted,writable:!0}),fg(r,iV,{value:function(n,c){var f=s[Km].read();f?(s[Jm]=null,s[Ag]=null,s[Vm]=null,n(pg(f,!1))):(s[Ag]=n,s[Vm]=c)},writable:!0}),r));return s[Jm]=null,Dyt(e,function(a){if(a&&a.code!=="ERR_STREAM_PREMATURE_CLOSE"){var n=s[Vm];n!==null&&(s[Jm]=null,s[Ag]=null,s[Vm]=null,n(a)),s[eb]=a;return}var c=s[Ag];c!==null&&(s[Jm]=null,s[Ag]=null,s[Vm]=null,c(pg(void 0,!0))),s[wN]=!0}),e.on("readable",Pyt.bind(null,s)),s};Qve.exports=Tyt});var Ove=_(($tr,Nve)=>{"use strict";function Rve(t,e,r,s,a,n,c){try{var f=t[n](c),p=f.value}catch(h){r(h);return}f.done?e(p):Promise.resolve(p).then(s,a)}function Ryt(t){return function(){var e=this,r=arguments;return new Promise(function(s,a){var n=t.apply(e,r);function c(p){Rve(n,s,a,c,f,"next",p)}function f(p){Rve(n,s,a,c,f,"throw",p)}c(void 0)})}}function Fve(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(t);e&&(s=s.filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable})),r.push.apply(r,s)}return r}function Fyt(t){for(var e=1;e{"use strict";Yve.exports=Pn;var Nw;Pn.ReadableState=_ve;var trr=Ie("events").EventEmitter,Uve=function(e,r){return e.listeners(r).length},rb=_Y(),BN=Ie("buffer").Buffer,Myt=global.Uint8Array||function(){};function Uyt(t){return BN.from(t)}function _yt(t){return BN.isBuffer(t)||t instanceof Myt}var sV=Ie("util"),cn;sV&&sV.debuglog?cn=sV.debuglog("stream"):cn=function(){};var Hyt=sve(),AV=GY(),jyt=qY(),Gyt=jyt.getHighWaterMark,vN=lg().codes,qyt=vN.ERR_INVALID_ARG_TYPE,Wyt=vN.ERR_STREAM_PUSH_AFTER_EOF,Yyt=vN.ERR_METHOD_NOT_IMPLEMENTED,Vyt=vN.ERR_STREAM_UNSHIFT_AFTER_END_EVENT,Ow,oV,aV;cg()(Pn,rb);var tb=AV.errorOrDestroy,lV=["error","close","destroy","pause","resume"];function Jyt(t,e,r){if(typeof t.prependListener=="function")return t.prependListener(e,r);!t._events||!t._events[e]?t.on(e,r):Array.isArray(t._events[e])?t._events[e].unshift(r):t._events[e]=[r,t._events[e]]}function _ve(t,e,r){Nw=Nw||Ym(),t=t||{},typeof r!="boolean"&&(r=e instanceof Nw),this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.readableObjectMode),this.highWaterMark=Gyt(this,t,"readableHighWaterMark",r),this.buffer=new Hyt,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.paused=!0,this.emitClose=t.emitClose!==!1,this.autoDestroy=!!t.autoDestroy,this.destroyed=!1,this.defaultEncoding=t.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,t.encoding&&(Ow||(Ow=nV().StringDecoder),this.decoder=new Ow(t.encoding),this.encoding=t.encoding)}function Pn(t){if(Nw=Nw||Ym(),!(this instanceof Pn))return new Pn(t);var e=this instanceof Nw;this._readableState=new _ve(t,this,e),this.readable=!0,t&&(typeof t.read=="function"&&(this._read=t.read),typeof t.destroy=="function"&&(this._destroy=t.destroy)),rb.call(this)}Object.defineProperty(Pn.prototype,"destroyed",{enumerable:!1,get:function(){return this._readableState===void 0?!1:this._readableState.destroyed},set:function(e){this._readableState&&(this._readableState.destroyed=e)}});Pn.prototype.destroy=AV.destroy;Pn.prototype._undestroy=AV.undestroy;Pn.prototype._destroy=function(t,e){e(t)};Pn.prototype.push=function(t,e){var r=this._readableState,s;return r.objectMode?s=!0:typeof t=="string"&&(e=e||r.defaultEncoding,e!==r.encoding&&(t=BN.from(t,e),e=""),s=!0),Hve(this,t,e,!1,s)};Pn.prototype.unshift=function(t){return Hve(this,t,null,!0,!1)};function Hve(t,e,r,s,a){cn("readableAddChunk",e);var n=t._readableState;if(e===null)n.reading=!1,Xyt(t,n);else{var c;if(a||(c=Kyt(n,e)),c)tb(t,c);else if(n.objectMode||e&&e.length>0)if(typeof e!="string"&&!n.objectMode&&Object.getPrototypeOf(e)!==BN.prototype&&(e=Uyt(e)),s)n.endEmitted?tb(t,new Vyt):cV(t,n,e,!0);else if(n.ended)tb(t,new Wyt);else{if(n.destroyed)return!1;n.reading=!1,n.decoder&&!r?(e=n.decoder.write(e),n.objectMode||e.length!==0?cV(t,n,e,!1):fV(t,n)):cV(t,n,e,!1)}else s||(n.reading=!1,fV(t,n))}return!n.ended&&(n.length=Lve?t=Lve:(t--,t|=t>>>1,t|=t>>>2,t|=t>>>4,t|=t>>>8,t|=t>>>16,t++),t}function Mve(t,e){return t<=0||e.length===0&&e.ended?0:e.objectMode?1:t!==t?e.flowing&&e.length?e.buffer.head.data.length:e.length:(t>e.highWaterMark&&(e.highWaterMark=zyt(t)),t<=e.length?t:e.ended?e.length:(e.needReadable=!0,0))}Pn.prototype.read=function(t){cn("read",t),t=parseInt(t,10);var e=this._readableState,r=t;if(t!==0&&(e.emittedReadable=!1),t===0&&e.needReadable&&((e.highWaterMark!==0?e.length>=e.highWaterMark:e.length>0)||e.ended))return cn("read: emitReadable",e.length,e.ended),e.length===0&&e.ended?uV(this):SN(this),null;if(t=Mve(t,e),t===0&&e.ended)return e.length===0&&uV(this),null;var s=e.needReadable;cn("need readable",s),(e.length===0||e.length-t0?a=qve(t,e):a=null,a===null?(e.needReadable=e.length<=e.highWaterMark,t=0):(e.length-=t,e.awaitDrain=0),e.length===0&&(e.ended||(e.needReadable=!0),r!==t&&e.ended&&uV(this)),a!==null&&this.emit("data",a),a};function Xyt(t,e){if(cn("onEofChunk"),!e.ended){if(e.decoder){var r=e.decoder.end();r&&r.length&&(e.buffer.push(r),e.length+=e.objectMode?1:r.length)}e.ended=!0,e.sync?SN(t):(e.needReadable=!1,e.emittedReadable||(e.emittedReadable=!0,jve(t)))}}function SN(t){var e=t._readableState;cn("emitReadable",e.needReadable,e.emittedReadable),e.needReadable=!1,e.emittedReadable||(cn("emitReadable",e.flowing),e.emittedReadable=!0,process.nextTick(jve,t))}function jve(t){var e=t._readableState;cn("emitReadable_",e.destroyed,e.length,e.ended),!e.destroyed&&(e.length||e.ended)&&(t.emit("readable"),e.emittedReadable=!1),e.needReadable=!e.flowing&&!e.ended&&e.length<=e.highWaterMark,pV(t)}function fV(t,e){e.readingMore||(e.readingMore=!0,process.nextTick(Zyt,t,e))}function Zyt(t,e){for(;!e.reading&&!e.ended&&(e.length1&&Wve(s.pipes,t)!==-1)&&!h&&(cn("false write response, pause",s.awaitDrain),s.awaitDrain++),r.pause())}function S(N){cn("onerror",N),R(),t.removeListener("error",S),Uve(t,"error")===0&&tb(t,N)}Jyt(t,"error",S);function P(){t.removeListener("finish",I),R()}t.once("close",P);function I(){cn("onfinish"),t.removeListener("close",P),R()}t.once("finish",I);function R(){cn("unpipe"),r.unpipe(t)}return t.emit("pipe",r),s.flowing||(cn("pipe resume"),r.resume()),t};function $yt(t){return function(){var r=t._readableState;cn("pipeOnDrain",r.awaitDrain),r.awaitDrain&&r.awaitDrain--,r.awaitDrain===0&&Uve(t,"data")&&(r.flowing=!0,pV(t))}}Pn.prototype.unpipe=function(t){var e=this._readableState,r={hasUnpiped:!1};if(e.pipesCount===0)return this;if(e.pipesCount===1)return t&&t!==e.pipes?this:(t||(t=e.pipes),e.pipes=null,e.pipesCount=0,e.flowing=!1,t&&t.emit("unpipe",this,r),this);if(!t){var s=e.pipes,a=e.pipesCount;e.pipes=null,e.pipesCount=0,e.flowing=!1;for(var n=0;n0,s.flowing!==!1&&this.resume()):t==="readable"&&!s.endEmitted&&!s.readableListening&&(s.readableListening=s.needReadable=!0,s.flowing=!1,s.emittedReadable=!1,cn("on readable",s.length,s.reading),s.length?SN(this):s.reading||process.nextTick(eEt,this)),r};Pn.prototype.addListener=Pn.prototype.on;Pn.prototype.removeListener=function(t,e){var r=rb.prototype.removeListener.call(this,t,e);return t==="readable"&&process.nextTick(Gve,this),r};Pn.prototype.removeAllListeners=function(t){var e=rb.prototype.removeAllListeners.apply(this,arguments);return(t==="readable"||t===void 0)&&process.nextTick(Gve,this),e};function Gve(t){var e=t._readableState;e.readableListening=t.listenerCount("readable")>0,e.resumeScheduled&&!e.paused?e.flowing=!0:t.listenerCount("data")>0&&t.resume()}function eEt(t){cn("readable nexttick read 0"),t.read(0)}Pn.prototype.resume=function(){var t=this._readableState;return t.flowing||(cn("resume"),t.flowing=!t.readableListening,tEt(this,t)),t.paused=!1,this};function tEt(t,e){e.resumeScheduled||(e.resumeScheduled=!0,process.nextTick(rEt,t,e))}function rEt(t,e){cn("resume",e.reading),e.reading||t.read(0),e.resumeScheduled=!1,t.emit("resume"),pV(t),e.flowing&&!e.reading&&t.read(0)}Pn.prototype.pause=function(){return cn("call pause flowing=%j",this._readableState.flowing),this._readableState.flowing!==!1&&(cn("pause"),this._readableState.flowing=!1,this.emit("pause")),this._readableState.paused=!0,this};function pV(t){var e=t._readableState;for(cn("flow",e.flowing);e.flowing&&t.read()!==null;);}Pn.prototype.wrap=function(t){var e=this,r=this._readableState,s=!1;t.on("end",function(){if(cn("wrapped end"),r.decoder&&!r.ended){var c=r.decoder.end();c&&c.length&&e.push(c)}e.push(null)}),t.on("data",function(c){if(cn("wrapped data"),r.decoder&&(c=r.decoder.write(c)),!(r.objectMode&&c==null)&&!(!r.objectMode&&(!c||!c.length))){var f=e.push(c);f||(s=!0,t.pause())}});for(var a in t)this[a]===void 0&&typeof t[a]=="function"&&(this[a]=function(f){return function(){return t[f].apply(t,arguments)}}(a));for(var n=0;n=e.length?(e.decoder?r=e.buffer.join(""):e.buffer.length===1?r=e.buffer.first():r=e.buffer.concat(e.length),e.buffer.clear()):r=e.buffer.consume(t,e.decoder),r}function uV(t){var e=t._readableState;cn("endReadable",e.endEmitted),e.endEmitted||(e.ended=!0,process.nextTick(nEt,e,t))}function nEt(t,e){if(cn("endReadableNT",t.endEmitted,t.length),!t.endEmitted&&t.length===0&&(t.endEmitted=!0,e.readable=!1,e.emit("end"),t.autoDestroy)){var r=e._writableState;(!r||r.autoDestroy&&r.finished)&&e.destroy()}}typeof Symbol=="function"&&(Pn.from=function(t,e){return aV===void 0&&(aV=Ove()),aV(Pn,t,e)});function Wve(t,e){for(var r=0,s=t.length;r{"use strict";Jve.exports=lh;var DN=lg().codes,iEt=DN.ERR_METHOD_NOT_IMPLEMENTED,sEt=DN.ERR_MULTIPLE_CALLBACK,oEt=DN.ERR_TRANSFORM_ALREADY_TRANSFORMING,aEt=DN.ERR_TRANSFORM_WITH_LENGTH_0,bN=Ym();cg()(lh,bN);function lEt(t,e){var r=this._transformState;r.transforming=!1;var s=r.writecb;if(s===null)return this.emit("error",new sEt);r.writechunk=null,r.writecb=null,e!=null&&this.push(e),s(t);var a=this._readableState;a.reading=!1,(a.needReadable||a.length{"use strict";zve.exports=nb;var Kve=hV();cg()(nb,Kve);function nb(t){if(!(this instanceof nb))return new nb(t);Kve.call(this,t)}nb.prototype._transform=function(t,e,r){r(null,t)}});var rSe=_((srr,tSe)=>{"use strict";var gV;function uEt(t){var e=!1;return function(){e||(e=!0,t.apply(void 0,arguments))}}var eSe=lg().codes,fEt=eSe.ERR_MISSING_ARGS,AEt=eSe.ERR_STREAM_DESTROYED;function Zve(t){if(t)throw t}function pEt(t){return t.setHeader&&typeof t.abort=="function"}function hEt(t,e,r,s){s=uEt(s);var a=!1;t.on("close",function(){a=!0}),gV===void 0&&(gV=IN()),gV(t,{readable:e,writable:r},function(c){if(c)return s(c);a=!0,s()});var n=!1;return function(c){if(!a&&!n){if(n=!0,pEt(t))return t.abort();if(typeof t.destroy=="function")return t.destroy();s(c||new AEt("pipe"))}}}function $ve(t){t()}function gEt(t,e){return t.pipe(e)}function dEt(t){return!t.length||typeof t[t.length-1]!="function"?Zve:t.pop()}function mEt(){for(var t=arguments.length,e=new Array(t),r=0;r0;return hEt(c,p,h,function(E){a||(a=E),E&&n.forEach($ve),!p&&(n.forEach($ve),s(a))})});return e.reduce(gEt)}tSe.exports=mEt});var Lw=_((zc,sb)=>{var ib=Ie("stream");process.env.READABLE_STREAM==="disable"&&ib?(sb.exports=ib.Readable,Object.assign(sb.exports,ib),sb.exports.Stream=ib):(zc=sb.exports=$Y(),zc.Stream=ib||zc,zc.Readable=zc,zc.Writable=zY(),zc.Duplex=Ym(),zc.Transform=hV(),zc.PassThrough=Xve(),zc.finished=IN(),zc.pipeline=rSe())});var sSe=_((orr,iSe)=>{"use strict";var{Buffer:cf}=Ie("buffer"),nSe=Symbol.for("BufferList");function Ci(t){if(!(this instanceof Ci))return new Ci(t);Ci._init.call(this,t)}Ci._init=function(e){Object.defineProperty(this,nSe,{value:!0}),this._bufs=[],this.length=0,e&&this.append(e)};Ci.prototype._new=function(e){return new Ci(e)};Ci.prototype._offset=function(e){if(e===0)return[0,0];let r=0;for(let s=0;sthis.length||e<0)return;let r=this._offset(e);return this._bufs[r[0]][r[1]]};Ci.prototype.slice=function(e,r){return typeof e=="number"&&e<0&&(e+=this.length),typeof r=="number"&&r<0&&(r+=this.length),this.copy(null,0,e,r)};Ci.prototype.copy=function(e,r,s,a){if((typeof s!="number"||s<0)&&(s=0),(typeof a!="number"||a>this.length)&&(a=this.length),s>=this.length||a<=0)return e||cf.alloc(0);let n=!!e,c=this._offset(s),f=a-s,p=f,h=n&&r||0,E=c[1];if(s===0&&a===this.length){if(!n)return this._bufs.length===1?this._bufs[0]:cf.concat(this._bufs,this.length);for(let C=0;CS)this._bufs[C].copy(e,h,E),h+=S;else{this._bufs[C].copy(e,h,E,E+p),h+=S;break}p-=S,E&&(E=0)}return e.length>h?e.slice(0,h):e};Ci.prototype.shallowSlice=function(e,r){if(e=e||0,r=typeof r!="number"?this.length:r,e<0&&(e+=this.length),r<0&&(r+=this.length),e===r)return this._new();let s=this._offset(e),a=this._offset(r),n=this._bufs.slice(s[0],a[0]+1);return a[1]===0?n.pop():n[n.length-1]=n[n.length-1].slice(0,a[1]),s[1]!==0&&(n[0]=n[0].slice(s[1])),this._new(n)};Ci.prototype.toString=function(e,r,s){return this.slice(r,s).toString(e)};Ci.prototype.consume=function(e){if(e=Math.trunc(e),Number.isNaN(e)||e<=0)return this;for(;this._bufs.length;)if(e>=this._bufs[0].length)e-=this._bufs[0].length,this.length-=this._bufs[0].length,this._bufs.shift();else{this._bufs[0]=this._bufs[0].slice(e),this.length-=e;break}return this};Ci.prototype.duplicate=function(){let e=this._new();for(let r=0;rthis.length?this.length:e;let s=this._offset(e),a=s[0],n=s[1];for(;a=t.length){let p=c.indexOf(t,n);if(p!==-1)return this._reverseOffset([a,p]);n=c.length-t.length+1}else{let p=this._reverseOffset([a,n]);if(this._match(p,t))return p;n++}n=0}return-1};Ci.prototype._match=function(t,e){if(this.length-t{"use strict";var dV=Lw().Duplex,yEt=cg(),ob=sSe();function ra(t){if(!(this instanceof ra))return new ra(t);if(typeof t=="function"){this._callback=t;let e=function(s){this._callback&&(this._callback(s),this._callback=null)}.bind(this);this.on("pipe",function(s){s.on("error",e)}),this.on("unpipe",function(s){s.removeListener("error",e)}),t=null}ob._init.call(this,t),dV.call(this)}yEt(ra,dV);Object.assign(ra.prototype,ob.prototype);ra.prototype._new=function(e){return new ra(e)};ra.prototype._write=function(e,r,s){this._appendBuffer(e),typeof s=="function"&&s()};ra.prototype._read=function(e){if(!this.length)return this.push(null);e=Math.min(e,this.length),this.push(this.slice(0,e)),this.consume(e)};ra.prototype.end=function(e){dV.prototype.end.call(this,e),this._callback&&(this._callback(null,this.slice()),this._callback=null)};ra.prototype._destroy=function(e,r){this._bufs.length=0,this.length=0,r(e)};ra.prototype._isBufferList=function(e){return e instanceof ra||e instanceof ob||ra.isBufferList(e)};ra.isBufferList=ob.isBufferList;PN.exports=ra;PN.exports.BufferListStream=ra;PN.exports.BufferList=ob});var EV=_(Uw=>{var EEt=Buffer.alloc,IEt="0000000000000000000",CEt="7777777777777777777",aSe=48,lSe=Buffer.from("ustar\0","binary"),wEt=Buffer.from("00","binary"),BEt=Buffer.from("ustar ","binary"),vEt=Buffer.from(" \0","binary"),SEt=parseInt("7777",8),ab=257,yV=263,DEt=function(t,e,r){return typeof t!="number"?r:(t=~~t,t>=e?e:t>=0||(t+=e,t>=0)?t:0)},bEt=function(t){switch(t){case 0:return"file";case 1:return"link";case 2:return"symlink";case 3:return"character-device";case 4:return"block-device";case 5:return"directory";case 6:return"fifo";case 7:return"contiguous-file";case 72:return"pax-header";case 55:return"pax-global-header";case 27:return"gnu-long-link-path";case 28:case 30:return"gnu-long-path"}return null},PEt=function(t){switch(t){case"file":return 0;case"link":return 1;case"symlink":return 2;case"character-device":return 3;case"block-device":return 4;case"directory":return 5;case"fifo":return 6;case"contiguous-file":return 7;case"pax-header":return 72}return 0},cSe=function(t,e,r,s){for(;re?CEt.slice(0,e)+" ":IEt.slice(0,e-t.length)+t+" "};function xEt(t){var e;if(t[0]===128)e=!0;else if(t[0]===255)e=!1;else return null;for(var r=[],s=t.length-1;s>0;s--){var a=t[s];e?r.push(a):r.push(255-a)}var n=0,c=r.length;for(s=0;s=Math.pow(10,r)&&r++,e+r+t};Uw.decodeLongPath=function(t,e){return Mw(t,0,t.length,e)};Uw.encodePax=function(t){var e="";t.name&&(e+=mV(" path="+t.name+` `)),t.linkname&&(e+=mV(" linkpath="+t.linkname+` `));var r=t.pax;if(r)for(var s in r)e+=mV(" "+s+"="+r[s]+` `);return Buffer.from(e)};Uw.decodePax=function(t){for(var e={};t.length;){for(var r=0;r100;){var a=r.indexOf("/");if(a===-1)return null;s+=s?"/"+r.slice(0,a):r.slice(0,a),r=r.slice(a+1)}return Buffer.byteLength(r)>100||Buffer.byteLength(s)>155||t.linkname&&Buffer.byteLength(t.linkname)>100?null:(e.write(r),e.write(hg(t.mode&SEt,6),100),e.write(hg(t.uid,6),108),e.write(hg(t.gid,6),116),e.write(hg(t.size,11),124),e.write(hg(t.mtime.getTime()/1e3|0,11),136),e[156]=aSe+PEt(t.type),t.linkname&&e.write(t.linkname,157),lSe.copy(e,ab),wEt.copy(e,yV),t.uname&&e.write(t.uname,265),t.gname&&e.write(t.gname,297),e.write(hg(t.devmajor||0,6),329),e.write(hg(t.devminor||0,6),337),s&&e.write(s,345),e.write(hg(uSe(e),6),148),e)};Uw.decode=function(t,e,r){var s=t[156]===0?0:t[156]-aSe,a=Mw(t,0,100,e),n=gg(t,100,8),c=gg(t,108,8),f=gg(t,116,8),p=gg(t,124,12),h=gg(t,136,12),E=bEt(s),C=t[157]===0?null:Mw(t,157,100,e),S=Mw(t,265,32),P=Mw(t,297,32),I=gg(t,329,8),R=gg(t,337,8),N=uSe(t);if(N===8*32)return null;if(N!==gg(t,148,8))throw new Error("Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?");if(lSe.compare(t,ab,ab+6)===0)t[345]&&(a=Mw(t,345,155,e)+"/"+a);else if(!(BEt.compare(t,ab,ab+6)===0&&vEt.compare(t,yV,yV+2)===0)){if(!r)throw new Error("Invalid tar header: unknown format.")}return s===0&&a&&a[a.length-1]==="/"&&(s=5),{name:a,mode:n,uid:c,gid:f,size:p,mtime:new Date(1e3*h),type:E,linkname:C,uname:S,gname:P,devmajor:I,devminor:R}}});var mSe=_((crr,dSe)=>{var ASe=Ie("util"),kEt=oSe(),lb=EV(),pSe=Lw().Writable,hSe=Lw().PassThrough,gSe=function(){},fSe=function(t){return t&=511,t&&512-t},QEt=function(t,e){var r=new xN(t,e);return r.end(),r},TEt=function(t,e){return e.path&&(t.name=e.path),e.linkpath&&(t.linkname=e.linkpath),e.size&&(t.size=parseInt(e.size,10)),t.pax=e,t},xN=function(t,e){this._parent=t,this.offset=e,hSe.call(this,{autoDestroy:!1})};ASe.inherits(xN,hSe);xN.prototype.destroy=function(t){this._parent.destroy(t)};var ch=function(t){if(!(this instanceof ch))return new ch(t);pSe.call(this,t),t=t||{},this._offset=0,this._buffer=kEt(),this._missing=0,this._partial=!1,this._onparse=gSe,this._header=null,this._stream=null,this._overflow=null,this._cb=null,this._locked=!1,this._destroyed=!1,this._pax=null,this._paxGlobal=null,this._gnuLongPath=null,this._gnuLongLinkPath=null;var e=this,r=e._buffer,s=function(){e._continue()},a=function(S){if(e._locked=!1,S)return e.destroy(S);e._stream||s()},n=function(){e._stream=null;var S=fSe(e._header.size);S?e._parse(S,c):e._parse(512,C),e._locked||s()},c=function(){e._buffer.consume(fSe(e._header.size)),e._parse(512,C),s()},f=function(){var S=e._header.size;e._paxGlobal=lb.decodePax(r.slice(0,S)),r.consume(S),n()},p=function(){var S=e._header.size;e._pax=lb.decodePax(r.slice(0,S)),e._paxGlobal&&(e._pax=Object.assign({},e._paxGlobal,e._pax)),r.consume(S),n()},h=function(){var S=e._header.size;this._gnuLongPath=lb.decodeLongPath(r.slice(0,S),t.filenameEncoding),r.consume(S),n()},E=function(){var S=e._header.size;this._gnuLongLinkPath=lb.decodeLongPath(r.slice(0,S),t.filenameEncoding),r.consume(S),n()},C=function(){var S=e._offset,P;try{P=e._header=lb.decode(r.slice(0,512),t.filenameEncoding,t.allowUnknownFormat)}catch(I){e.emit("error",I)}if(r.consume(512),!P){e._parse(512,C),s();return}if(P.type==="gnu-long-path"){e._parse(P.size,h),s();return}if(P.type==="gnu-long-link-path"){e._parse(P.size,E),s();return}if(P.type==="pax-global-header"){e._parse(P.size,f),s();return}if(P.type==="pax-header"){e._parse(P.size,p),s();return}if(e._gnuLongPath&&(P.name=e._gnuLongPath,e._gnuLongPath=null),e._gnuLongLinkPath&&(P.linkname=e._gnuLongLinkPath,e._gnuLongLinkPath=null),e._pax&&(e._header=P=TEt(P,e._pax),e._pax=null),e._locked=!0,!P.size||P.type==="directory"){e._parse(512,C),e.emit("entry",P,QEt(e,S),a);return}e._stream=new xN(e,S),e.emit("entry",P,e._stream,a),e._parse(P.size,n),s()};this._onheader=C,this._parse(512,C)};ASe.inherits(ch,pSe);ch.prototype.destroy=function(t){this._destroyed||(this._destroyed=!0,t&&this.emit("error",t),this.emit("close"),this._stream&&this._stream.emit("close"))};ch.prototype._parse=function(t,e){this._destroyed||(this._offset+=t,this._missing=t,e===this._onheader&&(this._partial=!1),this._onparse=e)};ch.prototype._continue=function(){if(!this._destroyed){var t=this._cb;this._cb=gSe,this._overflow?this._write(this._overflow,void 0,t):t()}};ch.prototype._write=function(t,e,r){if(!this._destroyed){var s=this._stream,a=this._buffer,n=this._missing;if(t.length&&(this._partial=!0),t.lengthn&&(c=t.slice(n),t=t.slice(0,n)),s?s.end(t):a.append(t),this._overflow=c,this._onparse()}};ch.prototype._final=function(t){if(this._partial)return this.destroy(new Error("Unexpected end of data"));t()};dSe.exports=ch});var ESe=_((urr,ySe)=>{ySe.exports=Ie("fs").constants||Ie("constants")});var vSe=_((frr,BSe)=>{var _w=ESe(),ISe=cH(),QN=cg(),REt=Buffer.alloc,CSe=Lw().Readable,Hw=Lw().Writable,FEt=Ie("string_decoder").StringDecoder,kN=EV(),NEt=parseInt("755",8),OEt=parseInt("644",8),wSe=REt(1024),CV=function(){},IV=function(t,e){e&=511,e&&t.push(wSe.slice(0,512-e))};function LEt(t){switch(t&_w.S_IFMT){case _w.S_IFBLK:return"block-device";case _w.S_IFCHR:return"character-device";case _w.S_IFDIR:return"directory";case _w.S_IFIFO:return"fifo";case _w.S_IFLNK:return"symlink"}return"file"}var TN=function(t){Hw.call(this),this.written=0,this._to=t,this._destroyed=!1};QN(TN,Hw);TN.prototype._write=function(t,e,r){if(this.written+=t.length,this._to.push(t))return r();this._to._drain=r};TN.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var RN=function(){Hw.call(this),this.linkname="",this._decoder=new FEt("utf-8"),this._destroyed=!1};QN(RN,Hw);RN.prototype._write=function(t,e,r){this.linkname+=this._decoder.write(t),r()};RN.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var ub=function(){Hw.call(this),this._destroyed=!1};QN(ub,Hw);ub.prototype._write=function(t,e,r){r(new Error("No body allowed for this entry"))};ub.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var mA=function(t){if(!(this instanceof mA))return new mA(t);CSe.call(this,t),this._drain=CV,this._finalized=!1,this._finalizing=!1,this._destroyed=!1,this._stream=null};QN(mA,CSe);mA.prototype.entry=function(t,e,r){if(this._stream)throw new Error("already piping an entry");if(!(this._finalized||this._destroyed)){typeof e=="function"&&(r=e,e=null),r||(r=CV);var s=this;if((!t.size||t.type==="symlink")&&(t.size=0),t.type||(t.type=LEt(t.mode)),t.mode||(t.mode=t.type==="directory"?NEt:OEt),t.uid||(t.uid=0),t.gid||(t.gid=0),t.mtime||(t.mtime=new Date),typeof e=="string"&&(e=Buffer.from(e)),Buffer.isBuffer(e)){t.size=e.length,this._encode(t);var a=this.push(e);return IV(s,t.size),a?process.nextTick(r):this._drain=r,new ub}if(t.type==="symlink"&&!t.linkname){var n=new RN;return ISe(n,function(f){if(f)return s.destroy(),r(f);t.linkname=n.linkname,s._encode(t),r()}),n}if(this._encode(t),t.type!=="file"&&t.type!=="contiguous-file")return process.nextTick(r),new ub;var c=new TN(this);return this._stream=c,ISe(c,function(f){if(s._stream=null,f)return s.destroy(),r(f);if(c.written!==t.size)return s.destroy(),r(new Error("size mismatch"));IV(s,t.size),s._finalizing&&s.finalize(),r()}),c}};mA.prototype.finalize=function(){if(this._stream){this._finalizing=!0;return}this._finalized||(this._finalized=!0,this.push(wSe),this.push(null))};mA.prototype.destroy=function(t){this._destroyed||(this._destroyed=!0,t&&this.emit("error",t),this.emit("close"),this._stream&&this._stream.destroy&&this._stream.destroy())};mA.prototype._encode=function(t){if(!t.pax){var e=kN.encode(t);if(e){this.push(e);return}}this._encodePax(t)};mA.prototype._encodePax=function(t){var e=kN.encodePax({name:t.name,linkname:t.linkname,pax:t.pax}),r={name:"PaxHeader",mode:t.mode,uid:t.uid,gid:t.gid,size:e.length,mtime:t.mtime,type:"pax-header",linkname:t.linkname&&"PaxHeader",uname:t.uname,gname:t.gname,devmajor:t.devmajor,devminor:t.devminor};this.push(kN.encode(r)),this.push(e),IV(this,e.length),r.size=t.size,r.type=t.type,this.push(kN.encode(r))};mA.prototype._read=function(t){var e=this._drain;this._drain=CV,e()};BSe.exports=mA});var SSe=_(wV=>{wV.extract=mSe();wV.pack=vSe()});var MSe=_(Ta=>{"use strict";var zEt=Ta&&Ta.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Ta,"__esModule",{value:!0});Ta.Minipass=Ta.isWritable=Ta.isReadable=Ta.isStream=void 0;var RSe=typeof process=="object"&&process?process:{stdout:null,stderr:null},FV=Ie("node:events"),LSe=zEt(Ie("node:stream")),XEt=Ie("node:string_decoder"),ZEt=t=>!!t&&typeof t=="object"&&(t instanceof jN||t instanceof LSe.default||(0,Ta.isReadable)(t)||(0,Ta.isWritable)(t));Ta.isStream=ZEt;var $Et=t=>!!t&&typeof t=="object"&&t instanceof FV.EventEmitter&&typeof t.pipe=="function"&&t.pipe!==LSe.default.Writable.prototype.pipe;Ta.isReadable=$Et;var eIt=t=>!!t&&typeof t=="object"&&t instanceof FV.EventEmitter&&typeof t.write=="function"&&typeof t.end=="function";Ta.isWritable=eIt;var uh=Symbol("EOF"),fh=Symbol("maybeEmitEnd"),dg=Symbol("emittedEnd"),ON=Symbol("emittingEnd"),fb=Symbol("emittedError"),LN=Symbol("closed"),FSe=Symbol("read"),MN=Symbol("flush"),NSe=Symbol("flushChunk"),uf=Symbol("encoding"),Gw=Symbol("decoder"),Ks=Symbol("flowing"),Ab=Symbol("paused"),qw=Symbol("resume"),zs=Symbol("buffer"),Qa=Symbol("pipes"),Xs=Symbol("bufferLength"),PV=Symbol("bufferPush"),UN=Symbol("bufferShift"),na=Symbol("objectMode"),ts=Symbol("destroyed"),xV=Symbol("error"),kV=Symbol("emitData"),OSe=Symbol("emitEnd"),QV=Symbol("emitEnd2"),EA=Symbol("async"),TV=Symbol("abort"),_N=Symbol("aborted"),pb=Symbol("signal"),zm=Symbol("dataListeners"),rc=Symbol("discarded"),hb=t=>Promise.resolve().then(t),tIt=t=>t(),rIt=t=>t==="end"||t==="finish"||t==="prefinish",nIt=t=>t instanceof ArrayBuffer||!!t&&typeof t=="object"&&t.constructor&&t.constructor.name==="ArrayBuffer"&&t.byteLength>=0,iIt=t=>!Buffer.isBuffer(t)&&ArrayBuffer.isView(t),HN=class{src;dest;opts;ondrain;constructor(e,r,s){this.src=e,this.dest=r,this.opts=s,this.ondrain=()=>e[qw](),this.dest.on("drain",this.ondrain)}unpipe(){this.dest.removeListener("drain",this.ondrain)}proxyErrors(e){}end(){this.unpipe(),this.opts.end&&this.dest.end()}},RV=class extends HN{unpipe(){this.src.removeListener("error",this.proxyErrors),super.unpipe()}constructor(e,r,s){super(e,r,s),this.proxyErrors=a=>r.emit("error",a),e.on("error",this.proxyErrors)}},sIt=t=>!!t.objectMode,oIt=t=>!t.objectMode&&!!t.encoding&&t.encoding!=="buffer",jN=class extends FV.EventEmitter{[Ks]=!1;[Ab]=!1;[Qa]=[];[zs]=[];[na];[uf];[EA];[Gw];[uh]=!1;[dg]=!1;[ON]=!1;[LN]=!1;[fb]=null;[Xs]=0;[ts]=!1;[pb];[_N]=!1;[zm]=0;[rc]=!1;writable=!0;readable=!0;constructor(...e){let r=e[0]||{};if(super(),r.objectMode&&typeof r.encoding=="string")throw new TypeError("Encoding and objectMode may not be used together");sIt(r)?(this[na]=!0,this[uf]=null):oIt(r)?(this[uf]=r.encoding,this[na]=!1):(this[na]=!1,this[uf]=null),this[EA]=!!r.async,this[Gw]=this[uf]?new XEt.StringDecoder(this[uf]):null,r&&r.debugExposeBuffer===!0&&Object.defineProperty(this,"buffer",{get:()=>this[zs]}),r&&r.debugExposePipes===!0&&Object.defineProperty(this,"pipes",{get:()=>this[Qa]});let{signal:s}=r;s&&(this[pb]=s,s.aborted?this[TV]():s.addEventListener("abort",()=>this[TV]()))}get bufferLength(){return this[Xs]}get encoding(){return this[uf]}set encoding(e){throw new Error("Encoding must be set at instantiation time")}setEncoding(e){throw new Error("Encoding must be set at instantiation time")}get objectMode(){return this[na]}set objectMode(e){throw new Error("objectMode must be set at instantiation time")}get async(){return this[EA]}set async(e){this[EA]=this[EA]||!!e}[TV](){this[_N]=!0,this.emit("abort",this[pb]?.reason),this.destroy(this[pb]?.reason)}get aborted(){return this[_N]}set aborted(e){}write(e,r,s){if(this[_N])return!1;if(this[uh])throw new Error("write after end");if(this[ts])return this.emit("error",Object.assign(new Error("Cannot call write after a stream was destroyed"),{code:"ERR_STREAM_DESTROYED"})),!0;typeof r=="function"&&(s=r,r="utf8"),r||(r="utf8");let a=this[EA]?hb:tIt;if(!this[na]&&!Buffer.isBuffer(e)){if(iIt(e))e=Buffer.from(e.buffer,e.byteOffset,e.byteLength);else if(nIt(e))e=Buffer.from(e);else if(typeof e!="string")throw new Error("Non-contiguous data written to non-objectMode stream")}return this[na]?(this[Ks]&&this[Xs]!==0&&this[MN](!0),this[Ks]?this.emit("data",e):this[PV](e),this[Xs]!==0&&this.emit("readable"),s&&a(s),this[Ks]):e.length?(typeof e=="string"&&!(r===this[uf]&&!this[Gw]?.lastNeed)&&(e=Buffer.from(e,r)),Buffer.isBuffer(e)&&this[uf]&&(e=this[Gw].write(e)),this[Ks]&&this[Xs]!==0&&this[MN](!0),this[Ks]?this.emit("data",e):this[PV](e),this[Xs]!==0&&this.emit("readable"),s&&a(s),this[Ks]):(this[Xs]!==0&&this.emit("readable"),s&&a(s),this[Ks])}read(e){if(this[ts])return null;if(this[rc]=!1,this[Xs]===0||e===0||e&&e>this[Xs])return this[fh](),null;this[na]&&(e=null),this[zs].length>1&&!this[na]&&(this[zs]=[this[uf]?this[zs].join(""):Buffer.concat(this[zs],this[Xs])]);let r=this[FSe](e||null,this[zs][0]);return this[fh](),r}[FSe](e,r){if(this[na])this[UN]();else{let s=r;e===s.length||e===null?this[UN]():typeof s=="string"?(this[zs][0]=s.slice(e),r=s.slice(0,e),this[Xs]-=e):(this[zs][0]=s.subarray(e),r=s.subarray(0,e),this[Xs]-=e)}return this.emit("data",r),!this[zs].length&&!this[uh]&&this.emit("drain"),r}end(e,r,s){return typeof e=="function"&&(s=e,e=void 0),typeof r=="function"&&(s=r,r="utf8"),e!==void 0&&this.write(e,r),s&&this.once("end",s),this[uh]=!0,this.writable=!1,(this[Ks]||!this[Ab])&&this[fh](),this}[qw](){this[ts]||(!this[zm]&&!this[Qa].length&&(this[rc]=!0),this[Ab]=!1,this[Ks]=!0,this.emit("resume"),this[zs].length?this[MN]():this[uh]?this[fh]():this.emit("drain"))}resume(){return this[qw]()}pause(){this[Ks]=!1,this[Ab]=!0,this[rc]=!1}get destroyed(){return this[ts]}get flowing(){return this[Ks]}get paused(){return this[Ab]}[PV](e){this[na]?this[Xs]+=1:this[Xs]+=e.length,this[zs].push(e)}[UN](){return this[na]?this[Xs]-=1:this[Xs]-=this[zs][0].length,this[zs].shift()}[MN](e=!1){do;while(this[NSe](this[UN]())&&this[zs].length);!e&&!this[zs].length&&!this[uh]&&this.emit("drain")}[NSe](e){return this.emit("data",e),this[Ks]}pipe(e,r){if(this[ts])return e;this[rc]=!1;let s=this[dg];return r=r||{},e===RSe.stdout||e===RSe.stderr?r.end=!1:r.end=r.end!==!1,r.proxyErrors=!!r.proxyErrors,s?r.end&&e.end():(this[Qa].push(r.proxyErrors?new RV(this,e,r):new HN(this,e,r)),this[EA]?hb(()=>this[qw]()):this[qw]()),e}unpipe(e){let r=this[Qa].find(s=>s.dest===e);r&&(this[Qa].length===1?(this[Ks]&&this[zm]===0&&(this[Ks]=!1),this[Qa]=[]):this[Qa].splice(this[Qa].indexOf(r),1),r.unpipe())}addListener(e,r){return this.on(e,r)}on(e,r){let s=super.on(e,r);if(e==="data")this[rc]=!1,this[zm]++,!this[Qa].length&&!this[Ks]&&this[qw]();else if(e==="readable"&&this[Xs]!==0)super.emit("readable");else if(rIt(e)&&this[dg])super.emit(e),this.removeAllListeners(e);else if(e==="error"&&this[fb]){let a=r;this[EA]?hb(()=>a.call(this,this[fb])):a.call(this,this[fb])}return s}removeListener(e,r){return this.off(e,r)}off(e,r){let s=super.off(e,r);return e==="data"&&(this[zm]=this.listeners("data").length,this[zm]===0&&!this[rc]&&!this[Qa].length&&(this[Ks]=!1)),s}removeAllListeners(e){let r=super.removeAllListeners(e);return(e==="data"||e===void 0)&&(this[zm]=0,!this[rc]&&!this[Qa].length&&(this[Ks]=!1)),r}get emittedEnd(){return this[dg]}[fh](){!this[ON]&&!this[dg]&&!this[ts]&&this[zs].length===0&&this[uh]&&(this[ON]=!0,this.emit("end"),this.emit("prefinish"),this.emit("finish"),this[LN]&&this.emit("close"),this[ON]=!1)}emit(e,...r){let s=r[0];if(e!=="error"&&e!=="close"&&e!==ts&&this[ts])return!1;if(e==="data")return!this[na]&&!s?!1:this[EA]?(hb(()=>this[kV](s)),!0):this[kV](s);if(e==="end")return this[OSe]();if(e==="close"){if(this[LN]=!0,!this[dg]&&!this[ts])return!1;let n=super.emit("close");return this.removeAllListeners("close"),n}else if(e==="error"){this[fb]=s,super.emit(xV,s);let n=!this[pb]||this.listeners("error").length?super.emit("error",s):!1;return this[fh](),n}else if(e==="resume"){let n=super.emit("resume");return this[fh](),n}else if(e==="finish"||e==="prefinish"){let n=super.emit(e);return this.removeAllListeners(e),n}let a=super.emit(e,...r);return this[fh](),a}[kV](e){for(let s of this[Qa])s.dest.write(e)===!1&&this.pause();let r=this[rc]?!1:super.emit("data",e);return this[fh](),r}[OSe](){return this[dg]?!1:(this[dg]=!0,this.readable=!1,this[EA]?(hb(()=>this[QV]()),!0):this[QV]())}[QV](){if(this[Gw]){let r=this[Gw].end();if(r){for(let s of this[Qa])s.dest.write(r);this[rc]||super.emit("data",r)}}for(let r of this[Qa])r.end();let e=super.emit("end");return this.removeAllListeners("end"),e}async collect(){let e=Object.assign([],{dataLength:0});this[na]||(e.dataLength=0);let r=this.promise();return this.on("data",s=>{e.push(s),this[na]||(e.dataLength+=s.length)}),await r,e}async concat(){if(this[na])throw new Error("cannot concat in objectMode");let e=await this.collect();return this[uf]?e.join(""):Buffer.concat(e,e.dataLength)}async promise(){return new Promise((e,r)=>{this.on(ts,()=>r(new Error("stream destroyed"))),this.on("error",s=>r(s)),this.on("end",()=>e())})}[Symbol.asyncIterator](){this[rc]=!1;let e=!1,r=async()=>(this.pause(),e=!0,{value:void 0,done:!0});return{next:()=>{if(e)return r();let a=this.read();if(a!==null)return Promise.resolve({done:!1,value:a});if(this[uh])return r();let n,c,f=C=>{this.off("data",p),this.off("end",h),this.off(ts,E),r(),c(C)},p=C=>{this.off("error",f),this.off("end",h),this.off(ts,E),this.pause(),n({value:C,done:!!this[uh]})},h=()=>{this.off("error",f),this.off("data",p),this.off(ts,E),r(),n({done:!0,value:void 0})},E=()=>f(new Error("stream destroyed"));return new Promise((C,S)=>{c=S,n=C,this.once(ts,E),this.once("error",f),this.once("end",h),this.once("data",p)})},throw:r,return:r,[Symbol.asyncIterator](){return this}}}[Symbol.iterator](){this[rc]=!1;let e=!1,r=()=>(this.pause(),this.off(xV,r),this.off(ts,r),this.off("end",r),e=!0,{done:!0,value:void 0}),s=()=>{if(e)return r();let a=this.read();return a===null?r():{done:!1,value:a}};return this.once("end",r),this.once(xV,r),this.once(ts,r),{next:s,throw:r,return:r,[Symbol.iterator](){return this}}}destroy(e){if(this[ts])return e?this.emit("error",e):this.emit(ts),this;this[ts]=!0,this[rc]=!0,this[zs].length=0,this[Xs]=0;let r=this;return typeof r.close=="function"&&!this[LN]&&r.close(),e?this.emit("error",e):this.emit(ts),this}static get isStream(){return Ta.isStream}};Ta.Minipass=jN});var HSe=_((Trr,IA)=>{"use strict";var db=Ie("crypto"),{Minipass:aIt}=MSe(),OV=["sha512","sha384","sha256"],MV=["sha512"],lIt=/^[a-z0-9+/]+(?:=?=?)$/i,cIt=/^([a-z0-9]+)-([^?]+)([?\S*]*)$/,uIt=/^([a-z0-9]+)-([A-Za-z0-9+/=]{44,88})(\?[\x21-\x7E]*)?$/,fIt=/^[\x21-\x7E]+$/,mb=t=>t?.length?`?${t.join("?")}`:"",LV=class extends aIt{#t;#r;#i;constructor(e){super(),this.size=0,this.opts=e,this.#e(),e?.algorithms?this.algorithms=[...e.algorithms]:this.algorithms=[...MV],this.algorithm!==null&&!this.algorithms.includes(this.algorithm)&&this.algorithms.push(this.algorithm),this.hashes=this.algorithms.map(db.createHash)}#e(){this.sri=this.opts?.integrity?nc(this.opts?.integrity,this.opts):null,this.expectedSize=this.opts?.size,this.sri?this.sri.isHash?(this.goodSri=!0,this.algorithm=this.sri.algorithm):(this.goodSri=!this.sri.isEmpty(),this.algorithm=this.sri.pickAlgorithm(this.opts)):this.algorithm=null,this.digests=this.goodSri?this.sri[this.algorithm]:null,this.optString=mb(this.opts?.options)}on(e,r){return e==="size"&&this.#r?r(this.#r):e==="integrity"&&this.#t?r(this.#t):e==="verified"&&this.#i?r(this.#i):super.on(e,r)}emit(e,r){return e==="end"&&this.#n(),super.emit(e,r)}write(e){return this.size+=e.length,this.hashes.forEach(r=>r.update(e)),super.write(e)}#n(){this.goodSri||this.#e();let e=nc(this.hashes.map((s,a)=>`${this.algorithms[a]}-${s.digest("base64")}${this.optString}`).join(" "),this.opts),r=this.goodSri&&e.match(this.sri,this.opts);if(typeof this.expectedSize=="number"&&this.size!==this.expectedSize){let s=new Error(`stream size mismatch when checking ${this.sri}. Wanted: ${this.expectedSize} Found: ${this.size}`);s.code="EBADSIZE",s.found=this.size,s.expected=this.expectedSize,s.sri=this.sri,this.emit("error",s)}else if(this.sri&&!r){let s=new Error(`${this.sri} integrity checksum failed when using ${this.algorithm}: wanted ${this.digests} but got ${e}. (${this.size} bytes)`);s.code="EINTEGRITY",s.found=e,s.expected=this.digests,s.algorithm=this.algorithm,s.sri=this.sri,this.emit("error",s)}else this.#r=this.size,this.emit("size",this.size),this.#t=e,this.emit("integrity",e),r&&(this.#i=r,this.emit("verified",r))}},Ah=class{get isHash(){return!0}constructor(e,r){let s=r?.strict;this.source=e.trim(),this.digest="",this.algorithm="",this.options=[];let a=this.source.match(s?uIt:cIt);if(!a||s&&!OV.includes(a[1]))return;this.algorithm=a[1],this.digest=a[2];let n=a[3];n&&(this.options=n.slice(1).split("?"))}hexDigest(){return this.digest&&Buffer.from(this.digest,"base64").toString("hex")}toJSON(){return this.toString()}match(e,r){let s=nc(e,r);if(!s)return!1;if(s.isIntegrity){let a=s.pickAlgorithm(r,[this.algorithm]);if(!a)return!1;let n=s[a].find(c=>c.digest===this.digest);return n||!1}return s.digest===this.digest?s:!1}toString(e){return e?.strict&&!(OV.includes(this.algorithm)&&this.digest.match(lIt)&&this.options.every(r=>r.match(fIt)))?"":`${this.algorithm}-${this.digest}${mb(this.options)}`}};function USe(t,e,r,s){let a=t!=="",n=!1,c="",f=s.length-1;for(let h=0;hs[a].find(c=>n.digest===c.digest)))throw new Error("hashes do not match, cannot update integrity")}else this[a]=s[a]}match(e,r){let s=nc(e,r);if(!s)return!1;let a=s.pickAlgorithm(r,Object.keys(this));return!!a&&this[a]&&s[a]&&this[a].find(n=>s[a].find(c=>n.digest===c.digest))||!1}pickAlgorithm(e,r){let s=e?.pickAlgorithm||EIt,a=Object.keys(this).filter(n=>r?.length?r.includes(n):!0);return a.length?a.reduce((n,c)=>s(n,c)||n):null}};IA.exports.parse=nc;function nc(t,e){if(!t)return null;if(typeof t=="string")return NV(t,e);if(t.algorithm&&t.digest){let r=new Xm;return r[t.algorithm]=[t],NV(gb(r,e),e)}else return NV(gb(t,e),e)}function NV(t,e){if(e?.single)return new Ah(t,e);let r=t.trim().split(/\s+/).reduce((s,a)=>{let n=new Ah(a,e);if(n.algorithm&&n.digest){let c=n.algorithm;s[c]||(s[c]=[]),s[c].push(n)}return s},new Xm);return r.isEmpty()?null:r}IA.exports.stringify=gb;function gb(t,e){return t.algorithm&&t.digest?Ah.prototype.toString.call(t,e):typeof t=="string"?gb(nc(t,e),e):Xm.prototype.toString.call(t,e)}IA.exports.fromHex=AIt;function AIt(t,e,r){let s=mb(r?.options);return nc(`${e}-${Buffer.from(t,"hex").toString("base64")}${s}`,r)}IA.exports.fromData=pIt;function pIt(t,e){let r=e?.algorithms||[...MV],s=mb(e?.options);return r.reduce((a,n)=>{let c=db.createHash(n).update(t).digest("base64"),f=new Ah(`${n}-${c}${s}`,e);if(f.algorithm&&f.digest){let p=f.algorithm;a[p]||(a[p]=[]),a[p].push(f)}return a},new Xm)}IA.exports.fromStream=hIt;function hIt(t,e){let r=UV(e);return new Promise((s,a)=>{t.pipe(r),t.on("error",a),r.on("error",a);let n;r.on("integrity",c=>{n=c}),r.on("end",()=>s(n)),r.resume()})}IA.exports.checkData=gIt;function gIt(t,e,r){if(e=nc(e,r),!e||!Object.keys(e).length){if(r?.error)throw Object.assign(new Error("No valid integrity hashes to check against"),{code:"EINTEGRITY"});return!1}let s=e.pickAlgorithm(r),a=db.createHash(s).update(t).digest("base64"),n=nc({algorithm:s,digest:a}),c=n.match(e,r);if(r=r||{},c||!r.error)return c;if(typeof r.size=="number"&&t.length!==r.size){let f=new Error(`data size mismatch when checking ${e}. Wanted: ${r.size} Found: ${t.length}`);throw f.code="EBADSIZE",f.found=t.length,f.expected=r.size,f.sri=e,f}else{let f=new Error(`Integrity checksum failed when using ${s}: Wanted ${e}, but got ${n}. (${t.length} bytes)`);throw f.code="EINTEGRITY",f.found=n,f.expected=e,f.algorithm=s,f.sri=e,f}}IA.exports.checkStream=dIt;function dIt(t,e,r){if(r=r||Object.create(null),r.integrity=e,e=nc(e,r),!e||!Object.keys(e).length)return Promise.reject(Object.assign(new Error("No valid integrity hashes to check against"),{code:"EINTEGRITY"}));let s=UV(r);return new Promise((a,n)=>{t.pipe(s),t.on("error",n),s.on("error",n);let c;s.on("verified",f=>{c=f}),s.on("end",()=>a(c)),s.resume()})}IA.exports.integrityStream=UV;function UV(t=Object.create(null)){return new LV(t)}IA.exports.create=mIt;function mIt(t){let e=t?.algorithms||[...MV],r=mb(t?.options),s=e.map(db.createHash);return{update:function(a,n){return s.forEach(c=>c.update(a,n)),this},digest:function(){return e.reduce((n,c)=>{let f=s.shift().digest("base64"),p=new Ah(`${c}-${f}${r}`,t);if(p.algorithm&&p.digest){let h=p.algorithm;n[h]||(n[h]=[]),n[h].push(p)}return n},new Xm)}}}var yIt=db.getHashes(),_Se=["md5","whirlpool","sha1","sha224","sha256","sha384","sha512","sha3","sha3-256","sha3-384","sha3-512","sha3_256","sha3_384","sha3_512"].filter(t=>yIt.includes(t));function EIt(t,e){return _Se.indexOf(t.toLowerCase())>=_Se.indexOf(e.toLowerCase())?t:e}});var _V=_(mg=>{"use strict";Object.defineProperty(mg,"__esModule",{value:!0});mg.Signature=mg.Envelope=void 0;mg.Envelope={fromJSON(t){return{payload:GN(t.payload)?Buffer.from(jSe(t.payload)):Buffer.alloc(0),payloadType:GN(t.payloadType)?globalThis.String(t.payloadType):"",signatures:globalThis.Array.isArray(t?.signatures)?t.signatures.map(e=>mg.Signature.fromJSON(e)):[]}},toJSON(t){let e={};return t.payload.length!==0&&(e.payload=GSe(t.payload)),t.payloadType!==""&&(e.payloadType=t.payloadType),t.signatures?.length&&(e.signatures=t.signatures.map(r=>mg.Signature.toJSON(r))),e}};mg.Signature={fromJSON(t){return{sig:GN(t.sig)?Buffer.from(jSe(t.sig)):Buffer.alloc(0),keyid:GN(t.keyid)?globalThis.String(t.keyid):""}},toJSON(t){let e={};return t.sig.length!==0&&(e.sig=GSe(t.sig)),t.keyid!==""&&(e.keyid=t.keyid),e}};function jSe(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function GSe(t){return globalThis.Buffer.from(t).toString("base64")}function GN(t){return t!=null}});var WSe=_(qN=>{"use strict";Object.defineProperty(qN,"__esModule",{value:!0});qN.Timestamp=void 0;qN.Timestamp={fromJSON(t){return{seconds:qSe(t.seconds)?globalThis.String(t.seconds):"0",nanos:qSe(t.nanos)?globalThis.Number(t.nanos):0}},toJSON(t){let e={};return t.seconds!=="0"&&(e.seconds=t.seconds),t.nanos!==0&&(e.nanos=Math.round(t.nanos)),e}};function qSe(t){return t!=null}});var Ww=_(Ur=>{"use strict";Object.defineProperty(Ur,"__esModule",{value:!0});Ur.TimeRange=Ur.X509CertificateChain=Ur.SubjectAlternativeName=Ur.X509Certificate=Ur.DistinguishedName=Ur.ObjectIdentifierValuePair=Ur.ObjectIdentifier=Ur.PublicKeyIdentifier=Ur.PublicKey=Ur.RFC3161SignedTimestamp=Ur.LogId=Ur.MessageSignature=Ur.HashOutput=Ur.SubjectAlternativeNameType=Ur.PublicKeyDetails=Ur.HashAlgorithm=void 0;Ur.hashAlgorithmFromJSON=VSe;Ur.hashAlgorithmToJSON=JSe;Ur.publicKeyDetailsFromJSON=KSe;Ur.publicKeyDetailsToJSON=zSe;Ur.subjectAlternativeNameTypeFromJSON=XSe;Ur.subjectAlternativeNameTypeToJSON=ZSe;var IIt=WSe(),yl;(function(t){t[t.HASH_ALGORITHM_UNSPECIFIED=0]="HASH_ALGORITHM_UNSPECIFIED",t[t.SHA2_256=1]="SHA2_256",t[t.SHA2_384=2]="SHA2_384",t[t.SHA2_512=3]="SHA2_512",t[t.SHA3_256=4]="SHA3_256",t[t.SHA3_384=5]="SHA3_384"})(yl||(Ur.HashAlgorithm=yl={}));function VSe(t){switch(t){case 0:case"HASH_ALGORITHM_UNSPECIFIED":return yl.HASH_ALGORITHM_UNSPECIFIED;case 1:case"SHA2_256":return yl.SHA2_256;case 2:case"SHA2_384":return yl.SHA2_384;case 3:case"SHA2_512":return yl.SHA2_512;case 4:case"SHA3_256":return yl.SHA3_256;case 5:case"SHA3_384":return yl.SHA3_384;default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum HashAlgorithm")}}function JSe(t){switch(t){case yl.HASH_ALGORITHM_UNSPECIFIED:return"HASH_ALGORITHM_UNSPECIFIED";case yl.SHA2_256:return"SHA2_256";case yl.SHA2_384:return"SHA2_384";case yl.SHA2_512:return"SHA2_512";case yl.SHA3_256:return"SHA3_256";case yl.SHA3_384:return"SHA3_384";default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum HashAlgorithm")}}var sn;(function(t){t[t.PUBLIC_KEY_DETAILS_UNSPECIFIED=0]="PUBLIC_KEY_DETAILS_UNSPECIFIED",t[t.PKCS1_RSA_PKCS1V5=1]="PKCS1_RSA_PKCS1V5",t[t.PKCS1_RSA_PSS=2]="PKCS1_RSA_PSS",t[t.PKIX_RSA_PKCS1V5=3]="PKIX_RSA_PKCS1V5",t[t.PKIX_RSA_PSS=4]="PKIX_RSA_PSS",t[t.PKIX_RSA_PKCS1V15_2048_SHA256=9]="PKIX_RSA_PKCS1V15_2048_SHA256",t[t.PKIX_RSA_PKCS1V15_3072_SHA256=10]="PKIX_RSA_PKCS1V15_3072_SHA256",t[t.PKIX_RSA_PKCS1V15_4096_SHA256=11]="PKIX_RSA_PKCS1V15_4096_SHA256",t[t.PKIX_RSA_PSS_2048_SHA256=16]="PKIX_RSA_PSS_2048_SHA256",t[t.PKIX_RSA_PSS_3072_SHA256=17]="PKIX_RSA_PSS_3072_SHA256",t[t.PKIX_RSA_PSS_4096_SHA256=18]="PKIX_RSA_PSS_4096_SHA256",t[t.PKIX_ECDSA_P256_HMAC_SHA_256=6]="PKIX_ECDSA_P256_HMAC_SHA_256",t[t.PKIX_ECDSA_P256_SHA_256=5]="PKIX_ECDSA_P256_SHA_256",t[t.PKIX_ECDSA_P384_SHA_384=12]="PKIX_ECDSA_P384_SHA_384",t[t.PKIX_ECDSA_P521_SHA_512=13]="PKIX_ECDSA_P521_SHA_512",t[t.PKIX_ED25519=7]="PKIX_ED25519",t[t.PKIX_ED25519_PH=8]="PKIX_ED25519_PH",t[t.LMS_SHA256=14]="LMS_SHA256",t[t.LMOTS_SHA256=15]="LMOTS_SHA256"})(sn||(Ur.PublicKeyDetails=sn={}));function KSe(t){switch(t){case 0:case"PUBLIC_KEY_DETAILS_UNSPECIFIED":return sn.PUBLIC_KEY_DETAILS_UNSPECIFIED;case 1:case"PKCS1_RSA_PKCS1V5":return sn.PKCS1_RSA_PKCS1V5;case 2:case"PKCS1_RSA_PSS":return sn.PKCS1_RSA_PSS;case 3:case"PKIX_RSA_PKCS1V5":return sn.PKIX_RSA_PKCS1V5;case 4:case"PKIX_RSA_PSS":return sn.PKIX_RSA_PSS;case 9:case"PKIX_RSA_PKCS1V15_2048_SHA256":return sn.PKIX_RSA_PKCS1V15_2048_SHA256;case 10:case"PKIX_RSA_PKCS1V15_3072_SHA256":return sn.PKIX_RSA_PKCS1V15_3072_SHA256;case 11:case"PKIX_RSA_PKCS1V15_4096_SHA256":return sn.PKIX_RSA_PKCS1V15_4096_SHA256;case 16:case"PKIX_RSA_PSS_2048_SHA256":return sn.PKIX_RSA_PSS_2048_SHA256;case 17:case"PKIX_RSA_PSS_3072_SHA256":return sn.PKIX_RSA_PSS_3072_SHA256;case 18:case"PKIX_RSA_PSS_4096_SHA256":return sn.PKIX_RSA_PSS_4096_SHA256;case 6:case"PKIX_ECDSA_P256_HMAC_SHA_256":return sn.PKIX_ECDSA_P256_HMAC_SHA_256;case 5:case"PKIX_ECDSA_P256_SHA_256":return sn.PKIX_ECDSA_P256_SHA_256;case 12:case"PKIX_ECDSA_P384_SHA_384":return sn.PKIX_ECDSA_P384_SHA_384;case 13:case"PKIX_ECDSA_P521_SHA_512":return sn.PKIX_ECDSA_P521_SHA_512;case 7:case"PKIX_ED25519":return sn.PKIX_ED25519;case 8:case"PKIX_ED25519_PH":return sn.PKIX_ED25519_PH;case 14:case"LMS_SHA256":return sn.LMS_SHA256;case 15:case"LMOTS_SHA256":return sn.LMOTS_SHA256;default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum PublicKeyDetails")}}function zSe(t){switch(t){case sn.PUBLIC_KEY_DETAILS_UNSPECIFIED:return"PUBLIC_KEY_DETAILS_UNSPECIFIED";case sn.PKCS1_RSA_PKCS1V5:return"PKCS1_RSA_PKCS1V5";case sn.PKCS1_RSA_PSS:return"PKCS1_RSA_PSS";case sn.PKIX_RSA_PKCS1V5:return"PKIX_RSA_PKCS1V5";case sn.PKIX_RSA_PSS:return"PKIX_RSA_PSS";case sn.PKIX_RSA_PKCS1V15_2048_SHA256:return"PKIX_RSA_PKCS1V15_2048_SHA256";case sn.PKIX_RSA_PKCS1V15_3072_SHA256:return"PKIX_RSA_PKCS1V15_3072_SHA256";case sn.PKIX_RSA_PKCS1V15_4096_SHA256:return"PKIX_RSA_PKCS1V15_4096_SHA256";case sn.PKIX_RSA_PSS_2048_SHA256:return"PKIX_RSA_PSS_2048_SHA256";case sn.PKIX_RSA_PSS_3072_SHA256:return"PKIX_RSA_PSS_3072_SHA256";case sn.PKIX_RSA_PSS_4096_SHA256:return"PKIX_RSA_PSS_4096_SHA256";case sn.PKIX_ECDSA_P256_HMAC_SHA_256:return"PKIX_ECDSA_P256_HMAC_SHA_256";case sn.PKIX_ECDSA_P256_SHA_256:return"PKIX_ECDSA_P256_SHA_256";case sn.PKIX_ECDSA_P384_SHA_384:return"PKIX_ECDSA_P384_SHA_384";case sn.PKIX_ECDSA_P521_SHA_512:return"PKIX_ECDSA_P521_SHA_512";case sn.PKIX_ED25519:return"PKIX_ED25519";case sn.PKIX_ED25519_PH:return"PKIX_ED25519_PH";case sn.LMS_SHA256:return"LMS_SHA256";case sn.LMOTS_SHA256:return"LMOTS_SHA256";default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum PublicKeyDetails")}}var CA;(function(t){t[t.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED=0]="SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED",t[t.EMAIL=1]="EMAIL",t[t.URI=2]="URI",t[t.OTHER_NAME=3]="OTHER_NAME"})(CA||(Ur.SubjectAlternativeNameType=CA={}));function XSe(t){switch(t){case 0:case"SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED":return CA.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED;case 1:case"EMAIL":return CA.EMAIL;case 2:case"URI":return CA.URI;case 3:case"OTHER_NAME":return CA.OTHER_NAME;default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum SubjectAlternativeNameType")}}function ZSe(t){switch(t){case CA.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED:return"SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED";case CA.EMAIL:return"EMAIL";case CA.URI:return"URI";case CA.OTHER_NAME:return"OTHER_NAME";default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum SubjectAlternativeNameType")}}Ur.HashOutput={fromJSON(t){return{algorithm:ds(t.algorithm)?VSe(t.algorithm):0,digest:ds(t.digest)?Buffer.from(Zm(t.digest)):Buffer.alloc(0)}},toJSON(t){let e={};return t.algorithm!==0&&(e.algorithm=JSe(t.algorithm)),t.digest.length!==0&&(e.digest=$m(t.digest)),e}};Ur.MessageSignature={fromJSON(t){return{messageDigest:ds(t.messageDigest)?Ur.HashOutput.fromJSON(t.messageDigest):void 0,signature:ds(t.signature)?Buffer.from(Zm(t.signature)):Buffer.alloc(0)}},toJSON(t){let e={};return t.messageDigest!==void 0&&(e.messageDigest=Ur.HashOutput.toJSON(t.messageDigest)),t.signature.length!==0&&(e.signature=$m(t.signature)),e}};Ur.LogId={fromJSON(t){return{keyId:ds(t.keyId)?Buffer.from(Zm(t.keyId)):Buffer.alloc(0)}},toJSON(t){let e={};return t.keyId.length!==0&&(e.keyId=$m(t.keyId)),e}};Ur.RFC3161SignedTimestamp={fromJSON(t){return{signedTimestamp:ds(t.signedTimestamp)?Buffer.from(Zm(t.signedTimestamp)):Buffer.alloc(0)}},toJSON(t){let e={};return t.signedTimestamp.length!==0&&(e.signedTimestamp=$m(t.signedTimestamp)),e}};Ur.PublicKey={fromJSON(t){return{rawBytes:ds(t.rawBytes)?Buffer.from(Zm(t.rawBytes)):void 0,keyDetails:ds(t.keyDetails)?KSe(t.keyDetails):0,validFor:ds(t.validFor)?Ur.TimeRange.fromJSON(t.validFor):void 0}},toJSON(t){let e={};return t.rawBytes!==void 0&&(e.rawBytes=$m(t.rawBytes)),t.keyDetails!==0&&(e.keyDetails=zSe(t.keyDetails)),t.validFor!==void 0&&(e.validFor=Ur.TimeRange.toJSON(t.validFor)),e}};Ur.PublicKeyIdentifier={fromJSON(t){return{hint:ds(t.hint)?globalThis.String(t.hint):""}},toJSON(t){let e={};return t.hint!==""&&(e.hint=t.hint),e}};Ur.ObjectIdentifier={fromJSON(t){return{id:globalThis.Array.isArray(t?.id)?t.id.map(e=>globalThis.Number(e)):[]}},toJSON(t){let e={};return t.id?.length&&(e.id=t.id.map(r=>Math.round(r))),e}};Ur.ObjectIdentifierValuePair={fromJSON(t){return{oid:ds(t.oid)?Ur.ObjectIdentifier.fromJSON(t.oid):void 0,value:ds(t.value)?Buffer.from(Zm(t.value)):Buffer.alloc(0)}},toJSON(t){let e={};return t.oid!==void 0&&(e.oid=Ur.ObjectIdentifier.toJSON(t.oid)),t.value.length!==0&&(e.value=$m(t.value)),e}};Ur.DistinguishedName={fromJSON(t){return{organization:ds(t.organization)?globalThis.String(t.organization):"",commonName:ds(t.commonName)?globalThis.String(t.commonName):""}},toJSON(t){let e={};return t.organization!==""&&(e.organization=t.organization),t.commonName!==""&&(e.commonName=t.commonName),e}};Ur.X509Certificate={fromJSON(t){return{rawBytes:ds(t.rawBytes)?Buffer.from(Zm(t.rawBytes)):Buffer.alloc(0)}},toJSON(t){let e={};return t.rawBytes.length!==0&&(e.rawBytes=$m(t.rawBytes)),e}};Ur.SubjectAlternativeName={fromJSON(t){return{type:ds(t.type)?XSe(t.type):0,identity:ds(t.regexp)?{$case:"regexp",regexp:globalThis.String(t.regexp)}:ds(t.value)?{$case:"value",value:globalThis.String(t.value)}:void 0}},toJSON(t){let e={};return t.type!==0&&(e.type=ZSe(t.type)),t.identity?.$case==="regexp"?e.regexp=t.identity.regexp:t.identity?.$case==="value"&&(e.value=t.identity.value),e}};Ur.X509CertificateChain={fromJSON(t){return{certificates:globalThis.Array.isArray(t?.certificates)?t.certificates.map(e=>Ur.X509Certificate.fromJSON(e)):[]}},toJSON(t){let e={};return t.certificates?.length&&(e.certificates=t.certificates.map(r=>Ur.X509Certificate.toJSON(r))),e}};Ur.TimeRange={fromJSON(t){return{start:ds(t.start)?YSe(t.start):void 0,end:ds(t.end)?YSe(t.end):void 0}},toJSON(t){let e={};return t.start!==void 0&&(e.start=t.start.toISOString()),t.end!==void 0&&(e.end=t.end.toISOString()),e}};function Zm(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function $m(t){return globalThis.Buffer.from(t).toString("base64")}function CIt(t){let e=(globalThis.Number(t.seconds)||0)*1e3;return e+=(t.nanos||0)/1e6,new globalThis.Date(e)}function YSe(t){return t instanceof globalThis.Date?t:typeof t=="string"?new globalThis.Date(t):CIt(IIt.Timestamp.fromJSON(t))}function ds(t){return t!=null}});var HV=_(ms=>{"use strict";Object.defineProperty(ms,"__esModule",{value:!0});ms.TransparencyLogEntry=ms.InclusionPromise=ms.InclusionProof=ms.Checkpoint=ms.KindVersion=void 0;var $Se=Ww();ms.KindVersion={fromJSON(t){return{kind:Ra(t.kind)?globalThis.String(t.kind):"",version:Ra(t.version)?globalThis.String(t.version):""}},toJSON(t){let e={};return t.kind!==""&&(e.kind=t.kind),t.version!==""&&(e.version=t.version),e}};ms.Checkpoint={fromJSON(t){return{envelope:Ra(t.envelope)?globalThis.String(t.envelope):""}},toJSON(t){let e={};return t.envelope!==""&&(e.envelope=t.envelope),e}};ms.InclusionProof={fromJSON(t){return{logIndex:Ra(t.logIndex)?globalThis.String(t.logIndex):"0",rootHash:Ra(t.rootHash)?Buffer.from(WN(t.rootHash)):Buffer.alloc(0),treeSize:Ra(t.treeSize)?globalThis.String(t.treeSize):"0",hashes:globalThis.Array.isArray(t?.hashes)?t.hashes.map(e=>Buffer.from(WN(e))):[],checkpoint:Ra(t.checkpoint)?ms.Checkpoint.fromJSON(t.checkpoint):void 0}},toJSON(t){let e={};return t.logIndex!=="0"&&(e.logIndex=t.logIndex),t.rootHash.length!==0&&(e.rootHash=YN(t.rootHash)),t.treeSize!=="0"&&(e.treeSize=t.treeSize),t.hashes?.length&&(e.hashes=t.hashes.map(r=>YN(r))),t.checkpoint!==void 0&&(e.checkpoint=ms.Checkpoint.toJSON(t.checkpoint)),e}};ms.InclusionPromise={fromJSON(t){return{signedEntryTimestamp:Ra(t.signedEntryTimestamp)?Buffer.from(WN(t.signedEntryTimestamp)):Buffer.alloc(0)}},toJSON(t){let e={};return t.signedEntryTimestamp.length!==0&&(e.signedEntryTimestamp=YN(t.signedEntryTimestamp)),e}};ms.TransparencyLogEntry={fromJSON(t){return{logIndex:Ra(t.logIndex)?globalThis.String(t.logIndex):"0",logId:Ra(t.logId)?$Se.LogId.fromJSON(t.logId):void 0,kindVersion:Ra(t.kindVersion)?ms.KindVersion.fromJSON(t.kindVersion):void 0,integratedTime:Ra(t.integratedTime)?globalThis.String(t.integratedTime):"0",inclusionPromise:Ra(t.inclusionPromise)?ms.InclusionPromise.fromJSON(t.inclusionPromise):void 0,inclusionProof:Ra(t.inclusionProof)?ms.InclusionProof.fromJSON(t.inclusionProof):void 0,canonicalizedBody:Ra(t.canonicalizedBody)?Buffer.from(WN(t.canonicalizedBody)):Buffer.alloc(0)}},toJSON(t){let e={};return t.logIndex!=="0"&&(e.logIndex=t.logIndex),t.logId!==void 0&&(e.logId=$Se.LogId.toJSON(t.logId)),t.kindVersion!==void 0&&(e.kindVersion=ms.KindVersion.toJSON(t.kindVersion)),t.integratedTime!=="0"&&(e.integratedTime=t.integratedTime),t.inclusionPromise!==void 0&&(e.inclusionPromise=ms.InclusionPromise.toJSON(t.inclusionPromise)),t.inclusionProof!==void 0&&(e.inclusionProof=ms.InclusionProof.toJSON(t.inclusionProof)),t.canonicalizedBody.length!==0&&(e.canonicalizedBody=YN(t.canonicalizedBody)),e}};function WN(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function YN(t){return globalThis.Buffer.from(t).toString("base64")}function Ra(t){return t!=null}});var jV=_(Xc=>{"use strict";Object.defineProperty(Xc,"__esModule",{value:!0});Xc.Bundle=Xc.VerificationMaterial=Xc.TimestampVerificationData=void 0;var eDe=_V(),wA=Ww(),tDe=HV();Xc.TimestampVerificationData={fromJSON(t){return{rfc3161Timestamps:globalThis.Array.isArray(t?.rfc3161Timestamps)?t.rfc3161Timestamps.map(e=>wA.RFC3161SignedTimestamp.fromJSON(e)):[]}},toJSON(t){let e={};return t.rfc3161Timestamps?.length&&(e.rfc3161Timestamps=t.rfc3161Timestamps.map(r=>wA.RFC3161SignedTimestamp.toJSON(r))),e}};Xc.VerificationMaterial={fromJSON(t){return{content:yg(t.publicKey)?{$case:"publicKey",publicKey:wA.PublicKeyIdentifier.fromJSON(t.publicKey)}:yg(t.x509CertificateChain)?{$case:"x509CertificateChain",x509CertificateChain:wA.X509CertificateChain.fromJSON(t.x509CertificateChain)}:yg(t.certificate)?{$case:"certificate",certificate:wA.X509Certificate.fromJSON(t.certificate)}:void 0,tlogEntries:globalThis.Array.isArray(t?.tlogEntries)?t.tlogEntries.map(e=>tDe.TransparencyLogEntry.fromJSON(e)):[],timestampVerificationData:yg(t.timestampVerificationData)?Xc.TimestampVerificationData.fromJSON(t.timestampVerificationData):void 0}},toJSON(t){let e={};return t.content?.$case==="publicKey"?e.publicKey=wA.PublicKeyIdentifier.toJSON(t.content.publicKey):t.content?.$case==="x509CertificateChain"?e.x509CertificateChain=wA.X509CertificateChain.toJSON(t.content.x509CertificateChain):t.content?.$case==="certificate"&&(e.certificate=wA.X509Certificate.toJSON(t.content.certificate)),t.tlogEntries?.length&&(e.tlogEntries=t.tlogEntries.map(r=>tDe.TransparencyLogEntry.toJSON(r))),t.timestampVerificationData!==void 0&&(e.timestampVerificationData=Xc.TimestampVerificationData.toJSON(t.timestampVerificationData)),e}};Xc.Bundle={fromJSON(t){return{mediaType:yg(t.mediaType)?globalThis.String(t.mediaType):"",verificationMaterial:yg(t.verificationMaterial)?Xc.VerificationMaterial.fromJSON(t.verificationMaterial):void 0,content:yg(t.messageSignature)?{$case:"messageSignature",messageSignature:wA.MessageSignature.fromJSON(t.messageSignature)}:yg(t.dsseEnvelope)?{$case:"dsseEnvelope",dsseEnvelope:eDe.Envelope.fromJSON(t.dsseEnvelope)}:void 0}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.verificationMaterial!==void 0&&(e.verificationMaterial=Xc.VerificationMaterial.toJSON(t.verificationMaterial)),t.content?.$case==="messageSignature"?e.messageSignature=wA.MessageSignature.toJSON(t.content.messageSignature):t.content?.$case==="dsseEnvelope"&&(e.dsseEnvelope=eDe.Envelope.toJSON(t.content.dsseEnvelope)),e}};function yg(t){return t!=null}});var GV=_(Ri=>{"use strict";Object.defineProperty(Ri,"__esModule",{value:!0});Ri.ClientTrustConfig=Ri.SigningConfig=Ri.TrustedRoot=Ri.CertificateAuthority=Ri.TransparencyLogInstance=void 0;var El=Ww();Ri.TransparencyLogInstance={fromJSON(t){return{baseUrl:ia(t.baseUrl)?globalThis.String(t.baseUrl):"",hashAlgorithm:ia(t.hashAlgorithm)?(0,El.hashAlgorithmFromJSON)(t.hashAlgorithm):0,publicKey:ia(t.publicKey)?El.PublicKey.fromJSON(t.publicKey):void 0,logId:ia(t.logId)?El.LogId.fromJSON(t.logId):void 0,checkpointKeyId:ia(t.checkpointKeyId)?El.LogId.fromJSON(t.checkpointKeyId):void 0}},toJSON(t){let e={};return t.baseUrl!==""&&(e.baseUrl=t.baseUrl),t.hashAlgorithm!==0&&(e.hashAlgorithm=(0,El.hashAlgorithmToJSON)(t.hashAlgorithm)),t.publicKey!==void 0&&(e.publicKey=El.PublicKey.toJSON(t.publicKey)),t.logId!==void 0&&(e.logId=El.LogId.toJSON(t.logId)),t.checkpointKeyId!==void 0&&(e.checkpointKeyId=El.LogId.toJSON(t.checkpointKeyId)),e}};Ri.CertificateAuthority={fromJSON(t){return{subject:ia(t.subject)?El.DistinguishedName.fromJSON(t.subject):void 0,uri:ia(t.uri)?globalThis.String(t.uri):"",certChain:ia(t.certChain)?El.X509CertificateChain.fromJSON(t.certChain):void 0,validFor:ia(t.validFor)?El.TimeRange.fromJSON(t.validFor):void 0}},toJSON(t){let e={};return t.subject!==void 0&&(e.subject=El.DistinguishedName.toJSON(t.subject)),t.uri!==""&&(e.uri=t.uri),t.certChain!==void 0&&(e.certChain=El.X509CertificateChain.toJSON(t.certChain)),t.validFor!==void 0&&(e.validFor=El.TimeRange.toJSON(t.validFor)),e}};Ri.TrustedRoot={fromJSON(t){return{mediaType:ia(t.mediaType)?globalThis.String(t.mediaType):"",tlogs:globalThis.Array.isArray(t?.tlogs)?t.tlogs.map(e=>Ri.TransparencyLogInstance.fromJSON(e)):[],certificateAuthorities:globalThis.Array.isArray(t?.certificateAuthorities)?t.certificateAuthorities.map(e=>Ri.CertificateAuthority.fromJSON(e)):[],ctlogs:globalThis.Array.isArray(t?.ctlogs)?t.ctlogs.map(e=>Ri.TransparencyLogInstance.fromJSON(e)):[],timestampAuthorities:globalThis.Array.isArray(t?.timestampAuthorities)?t.timestampAuthorities.map(e=>Ri.CertificateAuthority.fromJSON(e)):[]}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.tlogs?.length&&(e.tlogs=t.tlogs.map(r=>Ri.TransparencyLogInstance.toJSON(r))),t.certificateAuthorities?.length&&(e.certificateAuthorities=t.certificateAuthorities.map(r=>Ri.CertificateAuthority.toJSON(r))),t.ctlogs?.length&&(e.ctlogs=t.ctlogs.map(r=>Ri.TransparencyLogInstance.toJSON(r))),t.timestampAuthorities?.length&&(e.timestampAuthorities=t.timestampAuthorities.map(r=>Ri.CertificateAuthority.toJSON(r))),e}};Ri.SigningConfig={fromJSON(t){return{mediaType:ia(t.mediaType)?globalThis.String(t.mediaType):"",caUrl:ia(t.caUrl)?globalThis.String(t.caUrl):"",oidcUrl:ia(t.oidcUrl)?globalThis.String(t.oidcUrl):"",tlogUrls:globalThis.Array.isArray(t?.tlogUrls)?t.tlogUrls.map(e=>globalThis.String(e)):[],tsaUrls:globalThis.Array.isArray(t?.tsaUrls)?t.tsaUrls.map(e=>globalThis.String(e)):[]}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.caUrl!==""&&(e.caUrl=t.caUrl),t.oidcUrl!==""&&(e.oidcUrl=t.oidcUrl),t.tlogUrls?.length&&(e.tlogUrls=t.tlogUrls),t.tsaUrls?.length&&(e.tsaUrls=t.tsaUrls),e}};Ri.ClientTrustConfig={fromJSON(t){return{mediaType:ia(t.mediaType)?globalThis.String(t.mediaType):"",trustedRoot:ia(t.trustedRoot)?Ri.TrustedRoot.fromJSON(t.trustedRoot):void 0,signingConfig:ia(t.signingConfig)?Ri.SigningConfig.fromJSON(t.signingConfig):void 0}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.trustedRoot!==void 0&&(e.trustedRoot=Ri.TrustedRoot.toJSON(t.trustedRoot)),t.signingConfig!==void 0&&(e.signingConfig=Ri.SigningConfig.toJSON(t.signingConfig)),e}};function ia(t){return t!=null}});var iDe=_(Vr=>{"use strict";Object.defineProperty(Vr,"__esModule",{value:!0});Vr.Input=Vr.Artifact=Vr.ArtifactVerificationOptions_ObserverTimestampOptions=Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions=Vr.ArtifactVerificationOptions_TimestampAuthorityOptions=Vr.ArtifactVerificationOptions_CtlogOptions=Vr.ArtifactVerificationOptions_TlogOptions=Vr.ArtifactVerificationOptions=Vr.PublicKeyIdentities=Vr.CertificateIdentities=Vr.CertificateIdentity=void 0;var rDe=jV(),Eg=Ww(),nDe=GV();Vr.CertificateIdentity={fromJSON(t){return{issuer:gi(t.issuer)?globalThis.String(t.issuer):"",san:gi(t.san)?Eg.SubjectAlternativeName.fromJSON(t.san):void 0,oids:globalThis.Array.isArray(t?.oids)?t.oids.map(e=>Eg.ObjectIdentifierValuePair.fromJSON(e)):[]}},toJSON(t){let e={};return t.issuer!==""&&(e.issuer=t.issuer),t.san!==void 0&&(e.san=Eg.SubjectAlternativeName.toJSON(t.san)),t.oids?.length&&(e.oids=t.oids.map(r=>Eg.ObjectIdentifierValuePair.toJSON(r))),e}};Vr.CertificateIdentities={fromJSON(t){return{identities:globalThis.Array.isArray(t?.identities)?t.identities.map(e=>Vr.CertificateIdentity.fromJSON(e)):[]}},toJSON(t){let e={};return t.identities?.length&&(e.identities=t.identities.map(r=>Vr.CertificateIdentity.toJSON(r))),e}};Vr.PublicKeyIdentities={fromJSON(t){return{publicKeys:globalThis.Array.isArray(t?.publicKeys)?t.publicKeys.map(e=>Eg.PublicKey.fromJSON(e)):[]}},toJSON(t){let e={};return t.publicKeys?.length&&(e.publicKeys=t.publicKeys.map(r=>Eg.PublicKey.toJSON(r))),e}};Vr.ArtifactVerificationOptions={fromJSON(t){return{signers:gi(t.certificateIdentities)?{$case:"certificateIdentities",certificateIdentities:Vr.CertificateIdentities.fromJSON(t.certificateIdentities)}:gi(t.publicKeys)?{$case:"publicKeys",publicKeys:Vr.PublicKeyIdentities.fromJSON(t.publicKeys)}:void 0,tlogOptions:gi(t.tlogOptions)?Vr.ArtifactVerificationOptions_TlogOptions.fromJSON(t.tlogOptions):void 0,ctlogOptions:gi(t.ctlogOptions)?Vr.ArtifactVerificationOptions_CtlogOptions.fromJSON(t.ctlogOptions):void 0,tsaOptions:gi(t.tsaOptions)?Vr.ArtifactVerificationOptions_TimestampAuthorityOptions.fromJSON(t.tsaOptions):void 0,integratedTsOptions:gi(t.integratedTsOptions)?Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions.fromJSON(t.integratedTsOptions):void 0,observerOptions:gi(t.observerOptions)?Vr.ArtifactVerificationOptions_ObserverTimestampOptions.fromJSON(t.observerOptions):void 0}},toJSON(t){let e={};return t.signers?.$case==="certificateIdentities"?e.certificateIdentities=Vr.CertificateIdentities.toJSON(t.signers.certificateIdentities):t.signers?.$case==="publicKeys"&&(e.publicKeys=Vr.PublicKeyIdentities.toJSON(t.signers.publicKeys)),t.tlogOptions!==void 0&&(e.tlogOptions=Vr.ArtifactVerificationOptions_TlogOptions.toJSON(t.tlogOptions)),t.ctlogOptions!==void 0&&(e.ctlogOptions=Vr.ArtifactVerificationOptions_CtlogOptions.toJSON(t.ctlogOptions)),t.tsaOptions!==void 0&&(e.tsaOptions=Vr.ArtifactVerificationOptions_TimestampAuthorityOptions.toJSON(t.tsaOptions)),t.integratedTsOptions!==void 0&&(e.integratedTsOptions=Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions.toJSON(t.integratedTsOptions)),t.observerOptions!==void 0&&(e.observerOptions=Vr.ArtifactVerificationOptions_ObserverTimestampOptions.toJSON(t.observerOptions)),e}};Vr.ArtifactVerificationOptions_TlogOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,performOnlineVerification:gi(t.performOnlineVerification)?globalThis.Boolean(t.performOnlineVerification):!1,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.performOnlineVerification!==!1&&(e.performOnlineVerification=t.performOnlineVerification),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_CtlogOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_TimestampAuthorityOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_ObserverTimestampOptions={fromJSON(t){return{threshold:gi(t.threshold)?globalThis.Number(t.threshold):0,disable:gi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.Artifact={fromJSON(t){return{data:gi(t.artifactUri)?{$case:"artifactUri",artifactUri:globalThis.String(t.artifactUri)}:gi(t.artifact)?{$case:"artifact",artifact:Buffer.from(wIt(t.artifact))}:gi(t.artifactDigest)?{$case:"artifactDigest",artifactDigest:Eg.HashOutput.fromJSON(t.artifactDigest)}:void 0}},toJSON(t){let e={};return t.data?.$case==="artifactUri"?e.artifactUri=t.data.artifactUri:t.data?.$case==="artifact"?e.artifact=BIt(t.data.artifact):t.data?.$case==="artifactDigest"&&(e.artifactDigest=Eg.HashOutput.toJSON(t.data.artifactDigest)),e}};Vr.Input={fromJSON(t){return{artifactTrustRoot:gi(t.artifactTrustRoot)?nDe.TrustedRoot.fromJSON(t.artifactTrustRoot):void 0,artifactVerificationOptions:gi(t.artifactVerificationOptions)?Vr.ArtifactVerificationOptions.fromJSON(t.artifactVerificationOptions):void 0,bundle:gi(t.bundle)?rDe.Bundle.fromJSON(t.bundle):void 0,artifact:gi(t.artifact)?Vr.Artifact.fromJSON(t.artifact):void 0}},toJSON(t){let e={};return t.artifactTrustRoot!==void 0&&(e.artifactTrustRoot=nDe.TrustedRoot.toJSON(t.artifactTrustRoot)),t.artifactVerificationOptions!==void 0&&(e.artifactVerificationOptions=Vr.ArtifactVerificationOptions.toJSON(t.artifactVerificationOptions)),t.bundle!==void 0&&(e.bundle=rDe.Bundle.toJSON(t.bundle)),t.artifact!==void 0&&(e.artifact=Vr.Artifact.toJSON(t.artifact)),e}};function wIt(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function BIt(t){return globalThis.Buffer.from(t).toString("base64")}function gi(t){return t!=null}});var yb=_(Zc=>{"use strict";var vIt=Zc&&Zc.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Yw=Zc&&Zc.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&vIt(e,t,r)};Object.defineProperty(Zc,"__esModule",{value:!0});Yw(_V(),Zc);Yw(jV(),Zc);Yw(Ww(),Zc);Yw(HV(),Zc);Yw(GV(),Zc);Yw(iDe(),Zc)});var VN=_(Il=>{"use strict";Object.defineProperty(Il,"__esModule",{value:!0});Il.BUNDLE_V03_MEDIA_TYPE=Il.BUNDLE_V03_LEGACY_MEDIA_TYPE=Il.BUNDLE_V02_MEDIA_TYPE=Il.BUNDLE_V01_MEDIA_TYPE=void 0;Il.isBundleWithCertificateChain=SIt;Il.isBundleWithPublicKey=DIt;Il.isBundleWithMessageSignature=bIt;Il.isBundleWithDsseEnvelope=PIt;Il.BUNDLE_V01_MEDIA_TYPE="application/vnd.dev.sigstore.bundle+json;version=0.1";Il.BUNDLE_V02_MEDIA_TYPE="application/vnd.dev.sigstore.bundle+json;version=0.2";Il.BUNDLE_V03_LEGACY_MEDIA_TYPE="application/vnd.dev.sigstore.bundle+json;version=0.3";Il.BUNDLE_V03_MEDIA_TYPE="application/vnd.dev.sigstore.bundle.v0.3+json";function SIt(t){return t.verificationMaterial.content.$case==="x509CertificateChain"}function DIt(t){return t.verificationMaterial.content.$case==="publicKey"}function bIt(t){return t.content.$case==="messageSignature"}function PIt(t){return t.content.$case==="dsseEnvelope"}});var oDe=_(KN=>{"use strict";Object.defineProperty(KN,"__esModule",{value:!0});KN.toMessageSignatureBundle=kIt;KN.toDSSEBundle=QIt;var xIt=yb(),JN=VN();function kIt(t){return{mediaType:t.certificateChain?JN.BUNDLE_V02_MEDIA_TYPE:JN.BUNDLE_V03_MEDIA_TYPE,content:{$case:"messageSignature",messageSignature:{messageDigest:{algorithm:xIt.HashAlgorithm.SHA2_256,digest:t.digest},signature:t.signature}},verificationMaterial:sDe(t)}}function QIt(t){return{mediaType:t.certificateChain?JN.BUNDLE_V02_MEDIA_TYPE:JN.BUNDLE_V03_MEDIA_TYPE,content:{$case:"dsseEnvelope",dsseEnvelope:TIt(t)},verificationMaterial:sDe(t)}}function TIt(t){return{payloadType:t.artifactType,payload:t.artifact,signatures:[RIt(t)]}}function RIt(t){return{keyid:t.keyHint||"",sig:t.signature}}function sDe(t){return{content:FIt(t),tlogEntries:[],timestampVerificationData:{rfc3161Timestamps:[]}}}function FIt(t){return t.certificate?t.certificateChain?{$case:"x509CertificateChain",x509CertificateChain:{certificates:[{rawBytes:t.certificate}]}}:{$case:"certificate",certificate:{rawBytes:t.certificate}}:{$case:"publicKey",publicKey:{hint:t.keyHint||""}}}});var WV=_(zN=>{"use strict";Object.defineProperty(zN,"__esModule",{value:!0});zN.ValidationError=void 0;var qV=class extends Error{constructor(e,r){super(e),this.fields=r}};zN.ValidationError=qV});var YV=_(ey=>{"use strict";Object.defineProperty(ey,"__esModule",{value:!0});ey.assertBundle=NIt;ey.assertBundleV01=aDe;ey.isBundleV01=OIt;ey.assertBundleV02=LIt;ey.assertBundleLatest=MIt;var XN=WV();function NIt(t){let e=ZN(t);if(e.length>0)throw new XN.ValidationError("invalid bundle",e)}function aDe(t){let e=[];if(e.push(...ZN(t)),e.push(...UIt(t)),e.length>0)throw new XN.ValidationError("invalid v0.1 bundle",e)}function OIt(t){try{return aDe(t),!0}catch{return!1}}function LIt(t){let e=[];if(e.push(...ZN(t)),e.push(...lDe(t)),e.length>0)throw new XN.ValidationError("invalid v0.2 bundle",e)}function MIt(t){let e=[];if(e.push(...ZN(t)),e.push(...lDe(t)),e.push(..._It(t)),e.length>0)throw new XN.ValidationError("invalid bundle",e)}function ZN(t){let e=[];if((t.mediaType===void 0||!t.mediaType.match(/^application\/vnd\.dev\.sigstore\.bundle\+json;version=\d\.\d/)&&!t.mediaType.match(/^application\/vnd\.dev\.sigstore\.bundle\.v\d\.\d\+json/))&&e.push("mediaType"),t.content===void 0)e.push("content");else switch(t.content.$case){case"messageSignature":t.content.messageSignature.messageDigest===void 0?e.push("content.messageSignature.messageDigest"):t.content.messageSignature.messageDigest.digest.length===0&&e.push("content.messageSignature.messageDigest.digest"),t.content.messageSignature.signature.length===0&&e.push("content.messageSignature.signature");break;case"dsseEnvelope":t.content.dsseEnvelope.payload.length===0&&e.push("content.dsseEnvelope.payload"),t.content.dsseEnvelope.signatures.length!==1?e.push("content.dsseEnvelope.signatures"):t.content.dsseEnvelope.signatures[0].sig.length===0&&e.push("content.dsseEnvelope.signatures[0].sig");break}if(t.verificationMaterial===void 0)e.push("verificationMaterial");else{if(t.verificationMaterial.content===void 0)e.push("verificationMaterial.content");else switch(t.verificationMaterial.content.$case){case"x509CertificateChain":t.verificationMaterial.content.x509CertificateChain.certificates.length===0&&e.push("verificationMaterial.content.x509CertificateChain.certificates"),t.verificationMaterial.content.x509CertificateChain.certificates.forEach((r,s)=>{r.rawBytes.length===0&&e.push(`verificationMaterial.content.x509CertificateChain.certificates[${s}].rawBytes`)});break;case"certificate":t.verificationMaterial.content.certificate.rawBytes.length===0&&e.push("verificationMaterial.content.certificate.rawBytes");break}t.verificationMaterial.tlogEntries===void 0?e.push("verificationMaterial.tlogEntries"):t.verificationMaterial.tlogEntries.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.logId===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].logId`),r.kindVersion===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].kindVersion`)})}return e}function UIt(t){let e=[];return t.verificationMaterial&&t.verificationMaterial.tlogEntries?.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.inclusionPromise===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].inclusionPromise`)}),e}function lDe(t){let e=[];return t.verificationMaterial&&t.verificationMaterial.tlogEntries?.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.inclusionProof===void 0?e.push(`verificationMaterial.tlogEntries[${s}].inclusionProof`):r.inclusionProof.checkpoint===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].inclusionProof.checkpoint`)}),e}function _It(t){let e=[];return t.verificationMaterial?.content?.$case==="x509CertificateChain"&&e.push("verificationMaterial.content.$case"),e}});var uDe=_(BA=>{"use strict";Object.defineProperty(BA,"__esModule",{value:!0});BA.envelopeToJSON=BA.envelopeFromJSON=BA.bundleToJSON=BA.bundleFromJSON=void 0;var $N=yb(),cDe=VN(),VV=YV(),HIt=t=>{let e=$N.Bundle.fromJSON(t);switch(e.mediaType){case cDe.BUNDLE_V01_MEDIA_TYPE:(0,VV.assertBundleV01)(e);break;case cDe.BUNDLE_V02_MEDIA_TYPE:(0,VV.assertBundleV02)(e);break;default:(0,VV.assertBundleLatest)(e);break}return e};BA.bundleFromJSON=HIt;var jIt=t=>$N.Bundle.toJSON(t);BA.bundleToJSON=jIt;var GIt=t=>$N.Envelope.fromJSON(t);BA.envelopeFromJSON=GIt;var qIt=t=>$N.Envelope.toJSON(t);BA.envelopeToJSON=qIt});var Ib=_(Xr=>{"use strict";Object.defineProperty(Xr,"__esModule",{value:!0});Xr.isBundleV01=Xr.assertBundleV02=Xr.assertBundleV01=Xr.assertBundleLatest=Xr.assertBundle=Xr.envelopeToJSON=Xr.envelopeFromJSON=Xr.bundleToJSON=Xr.bundleFromJSON=Xr.ValidationError=Xr.isBundleWithPublicKey=Xr.isBundleWithMessageSignature=Xr.isBundleWithDsseEnvelope=Xr.isBundleWithCertificateChain=Xr.BUNDLE_V03_MEDIA_TYPE=Xr.BUNDLE_V03_LEGACY_MEDIA_TYPE=Xr.BUNDLE_V02_MEDIA_TYPE=Xr.BUNDLE_V01_MEDIA_TYPE=Xr.toMessageSignatureBundle=Xr.toDSSEBundle=void 0;var fDe=oDe();Object.defineProperty(Xr,"toDSSEBundle",{enumerable:!0,get:function(){return fDe.toDSSEBundle}});Object.defineProperty(Xr,"toMessageSignatureBundle",{enumerable:!0,get:function(){return fDe.toMessageSignatureBundle}});var Ig=VN();Object.defineProperty(Xr,"BUNDLE_V01_MEDIA_TYPE",{enumerable:!0,get:function(){return Ig.BUNDLE_V01_MEDIA_TYPE}});Object.defineProperty(Xr,"BUNDLE_V02_MEDIA_TYPE",{enumerable:!0,get:function(){return Ig.BUNDLE_V02_MEDIA_TYPE}});Object.defineProperty(Xr,"BUNDLE_V03_LEGACY_MEDIA_TYPE",{enumerable:!0,get:function(){return Ig.BUNDLE_V03_LEGACY_MEDIA_TYPE}});Object.defineProperty(Xr,"BUNDLE_V03_MEDIA_TYPE",{enumerable:!0,get:function(){return Ig.BUNDLE_V03_MEDIA_TYPE}});Object.defineProperty(Xr,"isBundleWithCertificateChain",{enumerable:!0,get:function(){return Ig.isBundleWithCertificateChain}});Object.defineProperty(Xr,"isBundleWithDsseEnvelope",{enumerable:!0,get:function(){return Ig.isBundleWithDsseEnvelope}});Object.defineProperty(Xr,"isBundleWithMessageSignature",{enumerable:!0,get:function(){return Ig.isBundleWithMessageSignature}});Object.defineProperty(Xr,"isBundleWithPublicKey",{enumerable:!0,get:function(){return Ig.isBundleWithPublicKey}});var WIt=WV();Object.defineProperty(Xr,"ValidationError",{enumerable:!0,get:function(){return WIt.ValidationError}});var eO=uDe();Object.defineProperty(Xr,"bundleFromJSON",{enumerable:!0,get:function(){return eO.bundleFromJSON}});Object.defineProperty(Xr,"bundleToJSON",{enumerable:!0,get:function(){return eO.bundleToJSON}});Object.defineProperty(Xr,"envelopeFromJSON",{enumerable:!0,get:function(){return eO.envelopeFromJSON}});Object.defineProperty(Xr,"envelopeToJSON",{enumerable:!0,get:function(){return eO.envelopeToJSON}});var Eb=YV();Object.defineProperty(Xr,"assertBundle",{enumerable:!0,get:function(){return Eb.assertBundle}});Object.defineProperty(Xr,"assertBundleLatest",{enumerable:!0,get:function(){return Eb.assertBundleLatest}});Object.defineProperty(Xr,"assertBundleV01",{enumerable:!0,get:function(){return Eb.assertBundleV01}});Object.defineProperty(Xr,"assertBundleV02",{enumerable:!0,get:function(){return Eb.assertBundleV02}});Object.defineProperty(Xr,"isBundleV01",{enumerable:!0,get:function(){return Eb.isBundleV01}})});var Cb=_(rO=>{"use strict";Object.defineProperty(rO,"__esModule",{value:!0});rO.ByteStream=void 0;var JV=class extends Error{},tO=class t{constructor(e){this.start=0,e?(this.buf=e,this.view=Buffer.from(e)):(this.buf=new ArrayBuffer(0),this.view=Buffer.from(this.buf))}get buffer(){return this.view.subarray(0,this.start)}get length(){return this.view.byteLength}get position(){return this.start}seek(e){this.start=e}slice(e,r){let s=e+r;if(s>this.length)throw new JV("request past end of buffer");return this.view.subarray(e,s)}appendChar(e){this.ensureCapacity(1),this.view[this.start]=e,this.start+=1}appendUint16(e){this.ensureCapacity(2);let r=new Uint16Array([e]),s=new Uint8Array(r.buffer);this.view[this.start]=s[1],this.view[this.start+1]=s[0],this.start+=2}appendUint24(e){this.ensureCapacity(3);let r=new Uint32Array([e]),s=new Uint8Array(r.buffer);this.view[this.start]=s[2],this.view[this.start+1]=s[1],this.view[this.start+2]=s[0],this.start+=3}appendView(e){this.ensureCapacity(e.length),this.view.set(e,this.start),this.start+=e.length}getBlock(e){if(e<=0)return Buffer.alloc(0);if(this.start+e>this.view.length)throw new Error("request past end of buffer");let r=this.view.subarray(this.start,this.start+e);return this.start+=e,r}getUint8(){return this.getBlock(1)[0]}getUint16(){let e=this.getBlock(2);return e[0]<<8|e[1]}ensureCapacity(e){if(this.start+e>this.view.byteLength){let r=t.BLOCK_SIZE+(e>t.BLOCK_SIZE?e:0);this.realloc(this.view.byteLength+r)}}realloc(e){let r=new ArrayBuffer(e),s=Buffer.from(r);s.set(this.view),this.buf=r,this.view=s}};rO.ByteStream=tO;tO.BLOCK_SIZE=1024});var nO=_(Vw=>{"use strict";Object.defineProperty(Vw,"__esModule",{value:!0});Vw.ASN1TypeError=Vw.ASN1ParseError=void 0;var KV=class extends Error{};Vw.ASN1ParseError=KV;var zV=class extends Error{};Vw.ASN1TypeError=zV});var pDe=_(iO=>{"use strict";Object.defineProperty(iO,"__esModule",{value:!0});iO.decodeLength=YIt;iO.encodeLength=VIt;var ADe=nO();function YIt(t){let e=t.getUint8();if(!(e&128))return e;let r=e&127;if(r>6)throw new ADe.ASN1ParseError("length exceeds 6 byte limit");let s=0;for(let a=0;a0n;)r.unshift(Number(e&255n)),e=e>>8n;return Buffer.from([128|r.length,...r])}});var gDe=_(Cg=>{"use strict";Object.defineProperty(Cg,"__esModule",{value:!0});Cg.parseInteger=zIt;Cg.parseStringASCII=hDe;Cg.parseTime=XIt;Cg.parseOID=ZIt;Cg.parseBoolean=$It;Cg.parseBitString=eCt;var JIt=/^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d{3})?Z$/,KIt=/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d{3})?Z$/;function zIt(t){let e=0,r=t.length,s=t[e],a=s>127,n=a?255:0;for(;s==n&&++e=50?1900:2e3,s[1]=a.toString()}return new Date(`${s[1]}-${s[2]}-${s[3]}T${s[4]}:${s[5]}:${s[6]}Z`)}function ZIt(t){let e=0,r=t.length,s=t[e++],a=Math.floor(s/40),n=s%40,c=`${a}.${n}`,f=0;for(;e=f;--p)a.push(c>>p&1)}return a}});var mDe=_(sO=>{"use strict";Object.defineProperty(sO,"__esModule",{value:!0});sO.ASN1Tag=void 0;var dDe=nO(),ty={BOOLEAN:1,INTEGER:2,BIT_STRING:3,OCTET_STRING:4,OBJECT_IDENTIFIER:6,SEQUENCE:16,SET:17,PRINTABLE_STRING:19,UTC_TIME:23,GENERALIZED_TIME:24},XV={UNIVERSAL:0,APPLICATION:1,CONTEXT_SPECIFIC:2,PRIVATE:3},ZV=class{constructor(e){if(this.number=e&31,this.constructed=(e&32)===32,this.class=e>>6,this.number===31)throw new dDe.ASN1ParseError("long form tags not supported");if(this.class===XV.UNIVERSAL&&this.number===0)throw new dDe.ASN1ParseError("unsupported tag 0x00")}isUniversal(){return this.class===XV.UNIVERSAL}isContextSpecific(e){let r=this.class===XV.CONTEXT_SPECIFIC;return e!==void 0?r&&this.number===e:r}isBoolean(){return this.isUniversal()&&this.number===ty.BOOLEAN}isInteger(){return this.isUniversal()&&this.number===ty.INTEGER}isBitString(){return this.isUniversal()&&this.number===ty.BIT_STRING}isOctetString(){return this.isUniversal()&&this.number===ty.OCTET_STRING}isOID(){return this.isUniversal()&&this.number===ty.OBJECT_IDENTIFIER}isUTCTime(){return this.isUniversal()&&this.number===ty.UTC_TIME}isGeneralizedTime(){return this.isUniversal()&&this.number===ty.GENERALIZED_TIME}toDER(){return this.number|(this.constructed?32:0)|this.class<<6}};sO.ASN1Tag=ZV});var CDe=_(aO=>{"use strict";Object.defineProperty(aO,"__esModule",{value:!0});aO.ASN1Obj=void 0;var $V=Cb(),ry=nO(),EDe=pDe(),Jw=gDe(),tCt=mDe(),oO=class{constructor(e,r,s){this.tag=e,this.value=r,this.subs=s}static parseBuffer(e){return IDe(new $V.ByteStream(e))}toDER(){let e=new $V.ByteStream;if(this.subs.length>0)for(let a of this.subs)e.appendView(a.toDER());else e.appendView(this.value);let r=e.buffer,s=new $V.ByteStream;return s.appendChar(this.tag.toDER()),s.appendView((0,EDe.encodeLength)(r.length)),s.appendView(r),s.buffer}toBoolean(){if(!this.tag.isBoolean())throw new ry.ASN1TypeError("not a boolean");return(0,Jw.parseBoolean)(this.value)}toInteger(){if(!this.tag.isInteger())throw new ry.ASN1TypeError("not an integer");return(0,Jw.parseInteger)(this.value)}toOID(){if(!this.tag.isOID())throw new ry.ASN1TypeError("not an OID");return(0,Jw.parseOID)(this.value)}toDate(){switch(!0){case this.tag.isUTCTime():return(0,Jw.parseTime)(this.value,!0);case this.tag.isGeneralizedTime():return(0,Jw.parseTime)(this.value,!1);default:throw new ry.ASN1TypeError("not a date")}}toBitString(){if(!this.tag.isBitString())throw new ry.ASN1TypeError("not a bit string");return(0,Jw.parseBitString)(this.value)}};aO.ASN1Obj=oO;function IDe(t){let e=new tCt.ASN1Tag(t.getUint8()),r=(0,EDe.decodeLength)(t),s=t.slice(t.position,r),a=t.position,n=[];if(e.constructed)n=yDe(t,r);else if(e.isOctetString())try{n=yDe(t,r)}catch{}return n.length===0&&t.seek(a+r),new oO(e,s,n)}function yDe(t,e){let r=t.position+e;if(r>t.length)throw new ry.ASN1ParseError("invalid length");let s=[];for(;t.position{"use strict";Object.defineProperty(lO,"__esModule",{value:!0});lO.ASN1Obj=void 0;var rCt=CDe();Object.defineProperty(lO,"ASN1Obj",{enumerable:!0,get:function(){return rCt.ASN1Obj}})});var Kw=_(wg=>{"use strict";var nCt=wg&&wg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(wg,"__esModule",{value:!0});wg.createPublicKey=iCt;wg.digest=sCt;wg.verify=oCt;wg.bufferEqual=aCt;var wb=nCt(Ie("crypto"));function iCt(t,e="spki"){return typeof t=="string"?wb.default.createPublicKey(t):wb.default.createPublicKey({key:t,format:"der",type:e})}function sCt(t,...e){let r=wb.default.createHash(t);for(let s of e)r.update(s);return r.digest()}function oCt(t,e,r,s){try{return wb.default.verify(s,t,e,r)}catch{return!1}}function aCt(t,e){try{return wb.default.timingSafeEqual(t,e)}catch{return!1}}});var wDe=_(e7=>{"use strict";Object.defineProperty(e7,"__esModule",{value:!0});e7.preAuthEncoding=cCt;var lCt="DSSEv1";function cCt(t,e){let r=[lCt,t.length,t,e.length,""].join(" ");return Buffer.concat([Buffer.from(r,"ascii"),e])}});var SDe=_(uO=>{"use strict";Object.defineProperty(uO,"__esModule",{value:!0});uO.base64Encode=uCt;uO.base64Decode=fCt;var BDe="base64",vDe="utf-8";function uCt(t){return Buffer.from(t,vDe).toString(BDe)}function fCt(t){return Buffer.from(t,BDe).toString(vDe)}});var DDe=_(r7=>{"use strict";Object.defineProperty(r7,"__esModule",{value:!0});r7.canonicalize=t7;function t7(t){let e="";if(t===null||typeof t!="object"||t.toJSON!=null)e+=JSON.stringify(t);else if(Array.isArray(t)){e+="[";let r=!0;t.forEach(s=>{r||(e+=","),r=!1,e+=t7(s)}),e+="]"}else{e+="{";let r=!0;Object.keys(t).sort().forEach(s=>{r||(e+=","),r=!1,e+=JSON.stringify(s),e+=":",e+=t7(t[s])}),e+="}"}return e}});var n7=_(fO=>{"use strict";Object.defineProperty(fO,"__esModule",{value:!0});fO.toDER=hCt;fO.fromDER=gCt;var ACt=/-----BEGIN (.*)-----/,pCt=/-----END (.*)-----/;function hCt(t){let e="";return t.split(` `).forEach(r=>{r.match(ACt)||r.match(pCt)||(e+=r)}),Buffer.from(e,"base64")}function gCt(t,e="CERTIFICATE"){let s=t.toString("base64").match(/.{1,64}/g)||"";return[`-----BEGIN ${e}-----`,...s,`-----END ${e}-----`].join(` `).concat(` `)}});var AO=_(zw=>{"use strict";Object.defineProperty(zw,"__esModule",{value:!0});zw.SHA2_HASH_ALGOS=zw.ECDSA_SIGNATURE_ALGOS=void 0;zw.ECDSA_SIGNATURE_ALGOS={"1.2.840.10045.4.3.1":"sha224","1.2.840.10045.4.3.2":"sha256","1.2.840.10045.4.3.3":"sha384","1.2.840.10045.4.3.4":"sha512"};zw.SHA2_HASH_ALGOS={"2.16.840.1.101.3.4.2.1":"sha256","2.16.840.1.101.3.4.2.2":"sha384","2.16.840.1.101.3.4.2.3":"sha512"}});var s7=_(pO=>{"use strict";Object.defineProperty(pO,"__esModule",{value:!0});pO.RFC3161TimestampVerificationError=void 0;var i7=class extends Error{};pO.RFC3161TimestampVerificationError=i7});var PDe=_(vA=>{"use strict";var dCt=vA&&vA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),mCt=vA&&vA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),yCt=vA&&vA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&dCt(e,t,r);return mCt(e,t),e};Object.defineProperty(vA,"__esModule",{value:!0});vA.TSTInfo=void 0;var bDe=yCt(Kw()),ECt=AO(),ICt=s7(),o7=class{constructor(e){this.root=e}get version(){return this.root.subs[0].toInteger()}get genTime(){return this.root.subs[4].toDate()}get messageImprintHashAlgorithm(){let e=this.messageImprintObj.subs[0].subs[0].toOID();return ECt.SHA2_HASH_ALGOS[e]}get messageImprintHashedMessage(){return this.messageImprintObj.subs[1].value}get raw(){return this.root.toDER()}verify(e){let r=bDe.digest(this.messageImprintHashAlgorithm,e);if(!bDe.bufferEqual(r,this.messageImprintHashedMessage))throw new ICt.RFC3161TimestampVerificationError("message imprint does not match artifact")}get messageImprintObj(){return this.root.subs[2]}};vA.TSTInfo=o7});var kDe=_(SA=>{"use strict";var CCt=SA&&SA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),wCt=SA&&SA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),BCt=SA&&SA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&CCt(e,t,r);return wCt(e,t),e};Object.defineProperty(SA,"__esModule",{value:!0});SA.RFC3161Timestamp=void 0;var vCt=cO(),a7=BCt(Kw()),xDe=AO(),Bb=s7(),SCt=PDe(),DCt="1.2.840.113549.1.7.2",bCt="1.2.840.113549.1.9.16.1.4",PCt="1.2.840.113549.1.9.4",l7=class t{constructor(e){this.root=e}static parse(e){let r=vCt.ASN1Obj.parseBuffer(e);return new t(r)}get status(){return this.pkiStatusInfoObj.subs[0].toInteger()}get contentType(){return this.contentTypeObj.toOID()}get eContentType(){return this.eContentTypeObj.toOID()}get signingTime(){return this.tstInfo.genTime}get signerIssuer(){return this.signerSidObj.subs[0].value}get signerSerialNumber(){return this.signerSidObj.subs[1].value}get signerDigestAlgorithm(){let e=this.signerDigestAlgorithmObj.subs[0].toOID();return xDe.SHA2_HASH_ALGOS[e]}get signatureAlgorithm(){let e=this.signatureAlgorithmObj.subs[0].toOID();return xDe.ECDSA_SIGNATURE_ALGOS[e]}get signatureValue(){return this.signatureValueObj.value}get tstInfo(){return new SCt.TSTInfo(this.eContentObj.subs[0].subs[0])}verify(e,r){if(!this.timeStampTokenObj)throw new Bb.RFC3161TimestampVerificationError("timeStampToken is missing");if(this.contentType!==DCt)throw new Bb.RFC3161TimestampVerificationError(`incorrect content type: ${this.contentType}`);if(this.eContentType!==bCt)throw new Bb.RFC3161TimestampVerificationError(`incorrect encapsulated content type: ${this.eContentType}`);this.tstInfo.verify(e),this.verifyMessageDigest(),this.verifySignature(r)}verifyMessageDigest(){let e=a7.digest(this.signerDigestAlgorithm,this.tstInfo.raw),r=this.messageDigestAttributeObj.subs[1].subs[0].value;if(!a7.bufferEqual(e,r))throw new Bb.RFC3161TimestampVerificationError("signed data does not match tstInfo")}verifySignature(e){let r=this.signedAttrsObj.toDER();if(r[0]=49,!a7.verify(r,e,this.signatureValue,this.signatureAlgorithm))throw new Bb.RFC3161TimestampVerificationError("signature verification failed")}get pkiStatusInfoObj(){return this.root.subs[0]}get timeStampTokenObj(){return this.root.subs[1]}get contentTypeObj(){return this.timeStampTokenObj.subs[0]}get signedDataObj(){return this.timeStampTokenObj.subs.find(r=>r.tag.isContextSpecific(0)).subs[0]}get encapContentInfoObj(){return this.signedDataObj.subs[2]}get signerInfosObj(){let e=this.signedDataObj;return e.subs[e.subs.length-1]}get signerInfoObj(){return this.signerInfosObj.subs[0]}get eContentTypeObj(){return this.encapContentInfoObj.subs[0]}get eContentObj(){return this.encapContentInfoObj.subs[1]}get signedAttrsObj(){return this.signerInfoObj.subs.find(r=>r.tag.isContextSpecific(0))}get messageDigestAttributeObj(){return this.signedAttrsObj.subs.find(r=>r.subs[0].tag.isOID()&&r.subs[0].toOID()===PCt)}get signerSidObj(){return this.signerInfoObj.subs[1]}get signerDigestAlgorithmObj(){return this.signerInfoObj.subs[2]}get signatureAlgorithmObj(){return this.signerInfoObj.subs[4]}get signatureValueObj(){return this.signerInfoObj.subs[5]}};SA.RFC3161Timestamp=l7});var QDe=_(hO=>{"use strict";Object.defineProperty(hO,"__esModule",{value:!0});hO.RFC3161Timestamp=void 0;var xCt=kDe();Object.defineProperty(hO,"RFC3161Timestamp",{enumerable:!0,get:function(){return xCt.RFC3161Timestamp}})});var RDe=_(DA=>{"use strict";var kCt=DA&&DA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),QCt=DA&&DA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),TCt=DA&&DA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&kCt(e,t,r);return QCt(e,t),e};Object.defineProperty(DA,"__esModule",{value:!0});DA.SignedCertificateTimestamp=void 0;var RCt=TCt(Kw()),TDe=Cb(),c7=class t{constructor(e){this.version=e.version,this.logID=e.logID,this.timestamp=e.timestamp,this.extensions=e.extensions,this.hashAlgorithm=e.hashAlgorithm,this.signatureAlgorithm=e.signatureAlgorithm,this.signature=e.signature}get datetime(){return new Date(Number(this.timestamp.readBigInt64BE()))}get algorithm(){switch(this.hashAlgorithm){case 0:return"none";case 1:return"md5";case 2:return"sha1";case 3:return"sha224";case 4:return"sha256";case 5:return"sha384";case 6:return"sha512";default:return"unknown"}}verify(e,r){let s=new TDe.ByteStream;return s.appendChar(this.version),s.appendChar(0),s.appendView(this.timestamp),s.appendUint16(1),s.appendView(e),s.appendUint16(this.extensions.byteLength),this.extensions.byteLength>0&&s.appendView(this.extensions),RCt.verify(s.buffer,r,this.signature,this.algorithm)}static parse(e){let r=new TDe.ByteStream(e),s=r.getUint8(),a=r.getBlock(32),n=r.getBlock(8),c=r.getUint16(),f=r.getBlock(c),p=r.getUint8(),h=r.getUint8(),E=r.getUint16(),C=r.getBlock(E);if(r.position!==e.length)throw new Error("SCT buffer length mismatch");return new t({version:s,logID:a,timestamp:n,extensions:f,hashAlgorithm:p,signatureAlgorithm:h,signature:C})}};DA.SignedCertificateTimestamp=c7});var d7=_(sa=>{"use strict";Object.defineProperty(sa,"__esModule",{value:!0});sa.X509SCTExtension=sa.X509SubjectKeyIDExtension=sa.X509AuthorityKeyIDExtension=sa.X509SubjectAlternativeNameExtension=sa.X509KeyUsageExtension=sa.X509BasicConstraintsExtension=sa.X509Extension=void 0;var FCt=Cb(),NCt=RDe(),ph=class{constructor(e){this.root=e}get oid(){return this.root.subs[0].toOID()}get critical(){return this.root.subs.length===3?this.root.subs[1].toBoolean():!1}get value(){return this.extnValueObj.value}get valueObj(){return this.extnValueObj}get extnValueObj(){return this.root.subs[this.root.subs.length-1]}};sa.X509Extension=ph;var u7=class extends ph{get isCA(){return this.sequence.subs[0]?.toBoolean()??!1}get pathLenConstraint(){return this.sequence.subs.length>1?this.sequence.subs[1].toInteger():void 0}get sequence(){return this.extnValueObj.subs[0]}};sa.X509BasicConstraintsExtension=u7;var f7=class extends ph{get digitalSignature(){return this.bitString[0]===1}get keyCertSign(){return this.bitString[5]===1}get crlSign(){return this.bitString[6]===1}get bitString(){return this.extnValueObj.subs[0].toBitString()}};sa.X509KeyUsageExtension=f7;var A7=class extends ph{get rfc822Name(){return this.findGeneralName(1)?.value.toString("ascii")}get uri(){return this.findGeneralName(6)?.value.toString("ascii")}otherName(e){let r=this.findGeneralName(0);return r===void 0||r.subs[0].toOID()!==e?void 0:r.subs[1].subs[0].value.toString("ascii")}findGeneralName(e){return this.generalNames.find(r=>r.tag.isContextSpecific(e))}get generalNames(){return this.extnValueObj.subs[0].subs}};sa.X509SubjectAlternativeNameExtension=A7;var p7=class extends ph{get keyIdentifier(){return this.findSequenceMember(0)?.value}findSequenceMember(e){return this.sequence.subs.find(r=>r.tag.isContextSpecific(e))}get sequence(){return this.extnValueObj.subs[0]}};sa.X509AuthorityKeyIDExtension=p7;var h7=class extends ph{get keyIdentifier(){return this.extnValueObj.subs[0].value}};sa.X509SubjectKeyIDExtension=h7;var g7=class extends ph{constructor(e){super(e)}get signedCertificateTimestamps(){let e=this.extnValueObj.subs[0].value,r=new FCt.ByteStream(e),s=r.getUint16()+2,a=[];for(;r.position{"use strict";var OCt=ic&&ic.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),LCt=ic&&ic.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),NDe=ic&&ic.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&OCt(e,t,r);return LCt(e,t),e};Object.defineProperty(ic,"__esModule",{value:!0});ic.X509Certificate=ic.EXTENSION_OID_SCT=void 0;var MCt=cO(),FDe=NDe(Kw()),UCt=AO(),_Ct=NDe(n7()),ny=d7(),HCt="2.5.29.14",jCt="2.5.29.15",GCt="2.5.29.17",qCt="2.5.29.19",WCt="2.5.29.35";ic.EXTENSION_OID_SCT="1.3.6.1.4.1.11129.2.4.2";var m7=class t{constructor(e){this.root=e}static parse(e){let r=typeof e=="string"?_Ct.toDER(e):e,s=MCt.ASN1Obj.parseBuffer(r);return new t(s)}get tbsCertificate(){return this.tbsCertificateObj}get version(){return`v${(this.versionObj.subs[0].toInteger()+BigInt(1)).toString()}`}get serialNumber(){return this.serialNumberObj.value}get notBefore(){return this.validityObj.subs[0].toDate()}get notAfter(){return this.validityObj.subs[1].toDate()}get issuer(){return this.issuerObj.value}get subject(){return this.subjectObj.value}get publicKey(){return this.subjectPublicKeyInfoObj.toDER()}get signatureAlgorithm(){let e=this.signatureAlgorithmObj.subs[0].toOID();return UCt.ECDSA_SIGNATURE_ALGOS[e]}get signatureValue(){return this.signatureValueObj.value.subarray(1)}get subjectAltName(){let e=this.extSubjectAltName;return e?.uri||e?.rfc822Name}get extensions(){return this.extensionsObj?.subs[0]?.subs||[]}get extKeyUsage(){let e=this.findExtension(jCt);return e?new ny.X509KeyUsageExtension(e):void 0}get extBasicConstraints(){let e=this.findExtension(qCt);return e?new ny.X509BasicConstraintsExtension(e):void 0}get extSubjectAltName(){let e=this.findExtension(GCt);return e?new ny.X509SubjectAlternativeNameExtension(e):void 0}get extAuthorityKeyID(){let e=this.findExtension(WCt);return e?new ny.X509AuthorityKeyIDExtension(e):void 0}get extSubjectKeyID(){let e=this.findExtension(HCt);return e?new ny.X509SubjectKeyIDExtension(e):void 0}get extSCT(){let e=this.findExtension(ic.EXTENSION_OID_SCT);return e?new ny.X509SCTExtension(e):void 0}get isCA(){let e=this.extBasicConstraints?.isCA||!1;return this.extKeyUsage?e&&this.extKeyUsage.keyCertSign:e}extension(e){let r=this.findExtension(e);return r?new ny.X509Extension(r):void 0}verify(e){let r=e?.publicKey||this.publicKey,s=FDe.createPublicKey(r);return FDe.verify(this.tbsCertificate.toDER(),s,this.signatureValue,this.signatureAlgorithm)}validForDate(e){return this.notBefore<=e&&e<=this.notAfter}equals(e){return this.root.toDER().equals(e.root.toDER())}clone(){let e=this.root.toDER(),r=Buffer.alloc(e.length);return e.copy(r),t.parse(r)}findExtension(e){return this.extensions.find(r=>r.subs[0].toOID()===e)}get tbsCertificateObj(){return this.root.subs[0]}get signatureAlgorithmObj(){return this.root.subs[1]}get signatureValueObj(){return this.root.subs[2]}get versionObj(){return this.tbsCertificateObj.subs[0]}get serialNumberObj(){return this.tbsCertificateObj.subs[1]}get issuerObj(){return this.tbsCertificateObj.subs[3]}get validityObj(){return this.tbsCertificateObj.subs[4]}get subjectObj(){return this.tbsCertificateObj.subs[5]}get subjectPublicKeyInfoObj(){return this.tbsCertificateObj.subs[6]}get extensionsObj(){return this.tbsCertificateObj.subs.find(e=>e.tag.isContextSpecific(3))}};ic.X509Certificate=m7});var MDe=_(Bg=>{"use strict";Object.defineProperty(Bg,"__esModule",{value:!0});Bg.X509SCTExtension=Bg.X509Certificate=Bg.EXTENSION_OID_SCT=void 0;var LDe=ODe();Object.defineProperty(Bg,"EXTENSION_OID_SCT",{enumerable:!0,get:function(){return LDe.EXTENSION_OID_SCT}});Object.defineProperty(Bg,"X509Certificate",{enumerable:!0,get:function(){return LDe.X509Certificate}});var YCt=d7();Object.defineProperty(Bg,"X509SCTExtension",{enumerable:!0,get:function(){return YCt.X509SCTExtension}})});var Cl=_(Jn=>{"use strict";var VCt=Jn&&Jn.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),JCt=Jn&&Jn.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),vb=Jn&&Jn.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&VCt(e,t,r);return JCt(e,t),e};Object.defineProperty(Jn,"__esModule",{value:!0});Jn.X509SCTExtension=Jn.X509Certificate=Jn.EXTENSION_OID_SCT=Jn.ByteStream=Jn.RFC3161Timestamp=Jn.pem=Jn.json=Jn.encoding=Jn.dsse=Jn.crypto=Jn.ASN1Obj=void 0;var KCt=cO();Object.defineProperty(Jn,"ASN1Obj",{enumerable:!0,get:function(){return KCt.ASN1Obj}});Jn.crypto=vb(Kw());Jn.dsse=vb(wDe());Jn.encoding=vb(SDe());Jn.json=vb(DDe());Jn.pem=vb(n7());var zCt=QDe();Object.defineProperty(Jn,"RFC3161Timestamp",{enumerable:!0,get:function(){return zCt.RFC3161Timestamp}});var XCt=Cb();Object.defineProperty(Jn,"ByteStream",{enumerable:!0,get:function(){return XCt.ByteStream}});var y7=MDe();Object.defineProperty(Jn,"EXTENSION_OID_SCT",{enumerable:!0,get:function(){return y7.EXTENSION_OID_SCT}});Object.defineProperty(Jn,"X509Certificate",{enumerable:!0,get:function(){return y7.X509Certificate}});Object.defineProperty(Jn,"X509SCTExtension",{enumerable:!0,get:function(){return y7.X509SCTExtension}})});var UDe=_(E7=>{"use strict";Object.defineProperty(E7,"__esModule",{value:!0});E7.extractJWTSubject=$Ct;var ZCt=Cl();function $Ct(t){let e=t.split(".",3),r=JSON.parse(ZCt.encoding.base64Decode(e[1]));switch(r.iss){case"https://accounts.google.com":case"https://oauth2.sigstore.dev/auth":return r.email;default:return r.sub}}});var _De=_((dnr,ewt)=>{ewt.exports={name:"@sigstore/sign",version:"3.1.0",description:"Sigstore signing library",main:"dist/index.js",types:"dist/index.d.ts",scripts:{clean:"shx rm -rf dist *.tsbuildinfo",build:"tsc --build",test:"jest"},files:["dist"],author:"bdehamer@github.com",license:"Apache-2.0",repository:{type:"git",url:"git+https://github.com/sigstore/sigstore-js.git"},bugs:{url:"https://github.com/sigstore/sigstore-js/issues"},homepage:"https://github.com/sigstore/sigstore-js/tree/main/packages/sign#readme",publishConfig:{provenance:!0},devDependencies:{"@sigstore/jest":"^0.0.0","@sigstore/mock":"^0.10.0","@sigstore/rekor-types":"^3.0.0","@types/make-fetch-happen":"^10.0.4","@types/promise-retry":"^1.1.6"},dependencies:{"@sigstore/bundle":"^3.1.0","@sigstore/core":"^2.0.0","@sigstore/protobuf-specs":"^0.4.0","make-fetch-happen":"^14.0.2","proc-log":"^5.0.0","promise-retry":"^2.0.1"},engines:{node:"^18.17.0 || >=20.5.0"}}});var jDe=_(Xw=>{"use strict";var twt=Xw&&Xw.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Xw,"__esModule",{value:!0});Xw.getUserAgent=void 0;var HDe=twt(Ie("os")),rwt=()=>{let t=_De().version,e=process.version,r=HDe.default.platform(),s=HDe.default.arch();return`sigstore-js/${t} (Node ${e}) (${r}/${s})`};Xw.getUserAgent=rwt});var vg=_(Ji=>{"use strict";var nwt=Ji&&Ji.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),iwt=Ji&&Ji.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),GDe=Ji&&Ji.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;a{"use strict";Object.defineProperty(gO,"__esModule",{value:!0});gO.BaseBundleBuilder=void 0;var I7=class{constructor(e){this.signer=e.signer,this.witnesses=e.witnesses}async create(e){let r=await this.prepare(e).then(f=>this.signer.sign(f)),s=await this.package(e,r),a=await Promise.all(this.witnesses.map(f=>f.testify(s.content,swt(r.key)))),n=[],c=[];return a.forEach(({tlogEntries:f,rfc3161Timestamps:p})=>{n.push(...f??[]),c.push(...p??[])}),s.verificationMaterial.tlogEntries=n,s.verificationMaterial.timestampVerificationData={rfc3161Timestamps:c},s}async prepare(e){return e.data}};gO.BaseBundleBuilder=I7;function swt(t){switch(t.$case){case"publicKey":return t.publicKey;case"x509Certificate":return t.certificate}}});var B7=_(bA=>{"use strict";var owt=bA&&bA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),awt=bA&&bA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),lwt=bA&&bA.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;a{"use strict";Object.defineProperty(dO,"__esModule",{value:!0});dO.DSSEBundleBuilder=void 0;var fwt=vg(),Awt=C7(),pwt=B7(),v7=class extends Awt.BaseBundleBuilder{constructor(e){super(e),this.certificateChain=e.certificateChain??!1}async prepare(e){let r=WDe(e);return fwt.dsse.preAuthEncoding(r.type,r.data)}async package(e,r){return(0,pwt.toDSSEBundle)(WDe(e),r,this.certificateChain)}};dO.DSSEBundleBuilder=v7;function WDe(t){return{...t,type:t.type??""}}});var VDe=_(mO=>{"use strict";Object.defineProperty(mO,"__esModule",{value:!0});mO.MessageSignatureBundleBuilder=void 0;var hwt=C7(),gwt=B7(),S7=class extends hwt.BaseBundleBuilder{constructor(e){super(e)}async package(e,r){return(0,gwt.toMessageSignatureBundle)(e,r)}};mO.MessageSignatureBundleBuilder=S7});var JDe=_(Zw=>{"use strict";Object.defineProperty(Zw,"__esModule",{value:!0});Zw.MessageSignatureBundleBuilder=Zw.DSSEBundleBuilder=void 0;var dwt=YDe();Object.defineProperty(Zw,"DSSEBundleBuilder",{enumerable:!0,get:function(){return dwt.DSSEBundleBuilder}});var mwt=VDe();Object.defineProperty(Zw,"MessageSignatureBundleBuilder",{enumerable:!0,get:function(){return mwt.MessageSignatureBundleBuilder}})});var EO=_(yO=>{"use strict";Object.defineProperty(yO,"__esModule",{value:!0});yO.HTTPError=void 0;var D7=class extends Error{constructor({status:e,message:r,location:s}){super(`(${e}) ${r}`),this.statusCode=e,this.location=s}};yO.HTTPError=D7});var $w=_(Db=>{"use strict";Object.defineProperty(Db,"__esModule",{value:!0});Db.InternalError=void 0;Db.internalError=Ewt;var ywt=EO(),IO=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.name=this.constructor.name,this.cause=s,this.code=e}};Db.InternalError=IO;function Ewt(t,e,r){throw t instanceof ywt.HTTPError&&(r+=` - ${t.message}`),new IO({code:e,message:r,cause:t})}});var CO=_((Dnr,KDe)=>{KDe.exports=fetch});var zDe=_(e1=>{"use strict";var Iwt=e1&&e1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e1,"__esModule",{value:!0});e1.CIContextProvider=void 0;var Cwt=Iwt(CO()),wwt=[Bwt,vwt],b7=class{constructor(e="sigstore"){this.audience=e}async getToken(){return Promise.any(wwt.map(e=>e(this.audience))).catch(()=>Promise.reject("CI: no tokens available"))}};e1.CIContextProvider=b7;async function Bwt(t){if(!process.env.ACTIONS_ID_TOKEN_REQUEST_URL||!process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN)return Promise.reject("no token available");let e=new URL(process.env.ACTIONS_ID_TOKEN_REQUEST_URL);return e.searchParams.append("audience",t),(await(0,Cwt.default)(e.href,{retry:2,headers:{Accept:"application/json",Authorization:`Bearer ${process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN}`}})).json().then(s=>s.value)}async function vwt(){return process.env.SIGSTORE_ID_TOKEN?process.env.SIGSTORE_ID_TOKEN:Promise.reject("no token available")}});var XDe=_(wO=>{"use strict";Object.defineProperty(wO,"__esModule",{value:!0});wO.CIContextProvider=void 0;var Swt=zDe();Object.defineProperty(wO,"CIContextProvider",{enumerable:!0,get:function(){return Swt.CIContextProvider}})});var $De=_((xnr,ZDe)=>{var Dwt=Symbol("proc-log.meta");ZDe.exports={META:Dwt,output:{LEVELS:["standard","error","buffer","flush"],KEYS:{standard:"standard",error:"error",buffer:"buffer",flush:"flush"},standard:function(...t){return process.emit("output","standard",...t)},error:function(...t){return process.emit("output","error",...t)},buffer:function(...t){return process.emit("output","buffer",...t)},flush:function(...t){return process.emit("output","flush",...t)}},log:{LEVELS:["notice","error","warn","info","verbose","http","silly","timing","pause","resume"],KEYS:{notice:"notice",error:"error",warn:"warn",info:"info",verbose:"verbose",http:"http",silly:"silly",timing:"timing",pause:"pause",resume:"resume"},error:function(...t){return process.emit("log","error",...t)},notice:function(...t){return process.emit("log","notice",...t)},warn:function(...t){return process.emit("log","warn",...t)},info:function(...t){return process.emit("log","info",...t)},verbose:function(...t){return process.emit("log","verbose",...t)},http:function(...t){return process.emit("log","http",...t)},silly:function(...t){return process.emit("log","silly",...t)},timing:function(...t){return process.emit("log","timing",...t)},pause:function(){return process.emit("log","pause")},resume:function(){return process.emit("log","resume")}},time:{LEVELS:["start","end"],KEYS:{start:"start",end:"end"},start:function(t,e){process.emit("time","start",t);function r(){return process.emit("time","end",t)}if(typeof e=="function"){let s=e();return s&&s.finally?s.finally(r):(r(),s)}return r},end:function(t){return process.emit("time","end",t)}},input:{LEVELS:["start","end","read"],KEYS:{start:"start",end:"end",read:"read"},start:function(t){process.emit("input","start");function e(){return process.emit("input","end")}if(typeof t=="function"){let r=t();return r&&r.finally?r.finally(e):(e(),r)}return e},end:function(){return process.emit("input","end")},read:function(...t){let e,r,s=new Promise((a,n)=>{e=a,r=n});return process.emit("input","read",e,r,...t),s}}}});var rbe=_((knr,tbe)=>{"use strict";function ebe(t,e){for(let r in e)Object.defineProperty(t,r,{value:e[r],enumerable:!0,configurable:!0});return t}function bwt(t,e,r){if(!t||typeof t=="string")throw new TypeError("Please pass an Error to err-code");r||(r={}),typeof e=="object"&&(r=e,e=void 0),e!=null&&(r.code=e);try{return ebe(t,r)}catch{r.message=t.message,r.stack=t.stack;let a=function(){};return a.prototype=Object.create(Object.getPrototypeOf(t)),ebe(new a,r)}}tbe.exports=bwt});var ibe=_((Qnr,nbe)=>{function $c(t,e){typeof e=="boolean"&&(e={forever:e}),this._originalTimeouts=JSON.parse(JSON.stringify(t)),this._timeouts=t,this._options=e||{},this._maxRetryTime=e&&e.maxRetryTime||1/0,this._fn=null,this._errors=[],this._attempts=1,this._operationTimeout=null,this._operationTimeoutCb=null,this._timeout=null,this._operationStart=null,this._options.forever&&(this._cachedTimeouts=this._timeouts.slice(0))}nbe.exports=$c;$c.prototype.reset=function(){this._attempts=1,this._timeouts=this._originalTimeouts};$c.prototype.stop=function(){this._timeout&&clearTimeout(this._timeout),this._timeouts=[],this._cachedTimeouts=null};$c.prototype.retry=function(t){if(this._timeout&&clearTimeout(this._timeout),!t)return!1;var e=new Date().getTime();if(t&&e-this._operationStart>=this._maxRetryTime)return this._errors.unshift(new Error("RetryOperation timeout occurred")),!1;this._errors.push(t);var r=this._timeouts.shift();if(r===void 0)if(this._cachedTimeouts)this._errors.splice(this._errors.length-1,this._errors.length),this._timeouts=this._cachedTimeouts.slice(0),r=this._timeouts.shift();else return!1;var s=this,a=setTimeout(function(){s._attempts++,s._operationTimeoutCb&&(s._timeout=setTimeout(function(){s._operationTimeoutCb(s._attempts)},s._operationTimeout),s._options.unref&&s._timeout.unref()),s._fn(s._attempts)},r);return this._options.unref&&a.unref(),!0};$c.prototype.attempt=function(t,e){this._fn=t,e&&(e.timeout&&(this._operationTimeout=e.timeout),e.cb&&(this._operationTimeoutCb=e.cb));var r=this;this._operationTimeoutCb&&(this._timeout=setTimeout(function(){r._operationTimeoutCb()},r._operationTimeout)),this._operationStart=new Date().getTime(),this._fn(this._attempts)};$c.prototype.try=function(t){console.log("Using RetryOperation.try() is deprecated"),this.attempt(t)};$c.prototype.start=function(t){console.log("Using RetryOperation.start() is deprecated"),this.attempt(t)};$c.prototype.start=$c.prototype.try;$c.prototype.errors=function(){return this._errors};$c.prototype.attempts=function(){return this._attempts};$c.prototype.mainError=function(){if(this._errors.length===0)return null;for(var t={},e=null,r=0,s=0;s=r&&(e=a,r=c)}return e}});var sbe=_(iy=>{var Pwt=ibe();iy.operation=function(t){var e=iy.timeouts(t);return new Pwt(e,{forever:t&&t.forever,unref:t&&t.unref,maxRetryTime:t&&t.maxRetryTime})};iy.timeouts=function(t){if(t instanceof Array)return[].concat(t);var e={retries:10,factor:2,minTimeout:1*1e3,maxTimeout:1/0,randomize:!1};for(var r in t)e[r]=t[r];if(e.minTimeout>e.maxTimeout)throw new Error("minTimeout is greater than maxTimeout");for(var s=[],a=0;a{obe.exports=sbe()});var ube=_((Fnr,cbe)=>{"use strict";var xwt=rbe(),kwt=abe(),Qwt=Object.prototype.hasOwnProperty;function lbe(t){return t&&t.code==="EPROMISERETRY"&&Qwt.call(t,"retried")}function Twt(t,e){var r,s;return typeof t=="object"&&typeof e=="function"&&(r=e,e=t,t=r),s=kwt.operation(e),new Promise(function(a,n){s.attempt(function(c){Promise.resolve().then(function(){return t(function(f){throw lbe(f)&&(f=f.retried),xwt(new Error("Retrying"),"EPROMISERETRY",{retried:f})},c)}).then(a,function(f){lbe(f)&&(f=f.retried,s.retry(f||new Error))||n(f)})})})}cbe.exports=Twt});var BO=_(bb=>{"use strict";var Abe=bb&&bb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(bb,"__esModule",{value:!0});bb.fetchWithRetry=qwt;var Rwt=Ie("http2"),Fwt=Abe(CO()),fbe=$De(),Nwt=Abe(ube()),Owt=vg(),Lwt=EO(),{HTTP2_HEADER_LOCATION:Mwt,HTTP2_HEADER_CONTENT_TYPE:Uwt,HTTP2_HEADER_USER_AGENT:_wt,HTTP_STATUS_INTERNAL_SERVER_ERROR:Hwt,HTTP_STATUS_TOO_MANY_REQUESTS:jwt,HTTP_STATUS_REQUEST_TIMEOUT:Gwt}=Rwt.constants;async function qwt(t,e){return(0,Nwt.default)(async(r,s)=>{let a=e.method||"POST",n={[_wt]:Owt.ua.getUserAgent(),...e.headers},c=await(0,Fwt.default)(t,{method:a,headers:n,body:e.body,timeout:e.timeout,retry:!1}).catch(f=>(fbe.log.http("fetch",`${a} ${t} attempt ${s} failed with ${f}`),r(f)));if(c.ok)return c;{let f=await Wwt(c);if(fbe.log.http("fetch",`${a} ${t} attempt ${s} failed with ${c.status}`),Ywt(c.status))return r(f);throw f}},Vwt(e.retry))}var Wwt=async t=>{let e=t.statusText,r=t.headers.get(Mwt)||void 0;if(t.headers.get(Uwt)?.includes("application/json"))try{e=(await t.json()).message||e}catch{}return new Lwt.HTTPError({status:t.status,message:e,location:r})},Ywt=t=>[Gwt,jwt].includes(t)||t>=Hwt,Vwt=t=>typeof t=="boolean"?{retries:t?1:0}:typeof t=="number"?{retries:t}:{retries:0,...t}});var pbe=_(vO=>{"use strict";Object.defineProperty(vO,"__esModule",{value:!0});vO.Fulcio=void 0;var Jwt=BO(),P7=class{constructor(e){this.options=e}async createSigningCertificate(e){let{baseURL:r,retry:s,timeout:a}=this.options,n=`${r}/api/v2/signingCert`;return(await(0,Jwt.fetchWithRetry)(n,{headers:{"Content-Type":"application/json"},body:JSON.stringify(e),timeout:a,retry:s})).json()}};vO.Fulcio=P7});var hbe=_(SO=>{"use strict";Object.defineProperty(SO,"__esModule",{value:!0});SO.CAClient=void 0;var Kwt=$w(),zwt=pbe(),x7=class{constructor(e){this.fulcio=new zwt.Fulcio({baseURL:e.fulcioBaseURL,retry:e.retry,timeout:e.timeout})}async createSigningCertificate(e,r,s){let a=Xwt(e,r,s);try{let n=await this.fulcio.createSigningCertificate(a);return(n.signedCertificateEmbeddedSct?n.signedCertificateEmbeddedSct:n.signedCertificateDetachedSct).chain.certificates}catch(n){(0,Kwt.internalError)(n,"CA_CREATE_SIGNING_CERTIFICATE_ERROR","error creating signing certificate")}}};SO.CAClient=x7;function Xwt(t,e,r){return{credentials:{oidcIdentityToken:t},publicKeyRequest:{publicKey:{algorithm:"ECDSA",content:e},proofOfPossession:r.toString("base64")}}}});var dbe=_(t1=>{"use strict";var Zwt=t1&&t1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(t1,"__esModule",{value:!0});t1.EphemeralSigner=void 0;var gbe=Zwt(Ie("crypto")),$wt="ec",e1t="P-256",k7=class{constructor(){this.keypair=gbe.default.generateKeyPairSync($wt,{namedCurve:e1t})}async sign(e){let r=gbe.default.sign(null,e,this.keypair.privateKey),s=this.keypair.publicKey.export({format:"pem",type:"spki"}).toString("ascii");return{signature:r,key:{$case:"publicKey",publicKey:s}}}};t1.EphemeralSigner=k7});var mbe=_(sy=>{"use strict";Object.defineProperty(sy,"__esModule",{value:!0});sy.FulcioSigner=sy.DEFAULT_FULCIO_URL=void 0;var Q7=$w(),t1t=vg(),r1t=hbe(),n1t=dbe();sy.DEFAULT_FULCIO_URL="https://fulcio.sigstore.dev";var T7=class{constructor(e){this.ca=new r1t.CAClient({...e,fulcioBaseURL:e.fulcioBaseURL||sy.DEFAULT_FULCIO_URL}),this.identityProvider=e.identityProvider,this.keyHolder=e.keyHolder||new n1t.EphemeralSigner}async sign(e){let r=await this.getIdentityToken(),s;try{s=t1t.oidc.extractJWTSubject(r)}catch(f){throw new Q7.InternalError({code:"IDENTITY_TOKEN_PARSE_ERROR",message:`invalid identity token: ${r}`,cause:f})}let a=await this.keyHolder.sign(Buffer.from(s));if(a.key.$case!=="publicKey")throw new Q7.InternalError({code:"CA_CREATE_SIGNING_CERTIFICATE_ERROR",message:"unexpected format for signing key"});let n=await this.ca.createSigningCertificate(r,a.key.publicKey,a.signature);return{signature:(await this.keyHolder.sign(e)).signature,key:{$case:"x509Certificate",certificate:n[0]}}}async getIdentityToken(){try{return await this.identityProvider.getToken()}catch(e){throw new Q7.InternalError({code:"IDENTITY_TOKEN_READ_ERROR",message:"error retrieving identity token",cause:e})}}};sy.FulcioSigner=T7});var Ebe=_(r1=>{"use strict";Object.defineProperty(r1,"__esModule",{value:!0});r1.FulcioSigner=r1.DEFAULT_FULCIO_URL=void 0;var ybe=mbe();Object.defineProperty(r1,"DEFAULT_FULCIO_URL",{enumerable:!0,get:function(){return ybe.DEFAULT_FULCIO_URL}});Object.defineProperty(r1,"FulcioSigner",{enumerable:!0,get:function(){return ybe.FulcioSigner}})});var wbe=_(DO=>{"use strict";Object.defineProperty(DO,"__esModule",{value:!0});DO.Rekor=void 0;var Ibe=BO(),R7=class{constructor(e){this.options=e}async createEntry(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/log/entries`,f=await(await(0,Ibe.fetchWithRetry)(n,{headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(e),timeout:s,retry:a})).json();return Cbe(f)}async getEntry(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/log/entries/${e}`,f=await(await(0,Ibe.fetchWithRetry)(n,{method:"GET",headers:{Accept:"application/json"},timeout:s,retry:a})).json();return Cbe(f)}};DO.Rekor=R7;function Cbe(t){let e=Object.entries(t);if(e.length!=1)throw new Error("Received multiple entries in Rekor response");let[r,s]=e[0];return{...s,uuid:r}}});var vbe=_(bO=>{"use strict";Object.defineProperty(bO,"__esModule",{value:!0});bO.TLogClient=void 0;var Bbe=$w(),i1t=EO(),s1t=wbe(),F7=class{constructor(e){this.fetchOnConflict=e.fetchOnConflict??!1,this.rekor=new s1t.Rekor({baseURL:e.rekorBaseURL,retry:e.retry,timeout:e.timeout})}async createEntry(e){let r;try{r=await this.rekor.createEntry(e)}catch(s){if(o1t(s)&&this.fetchOnConflict){let a=s.location.split("/").pop()||"";try{r=await this.rekor.getEntry(a)}catch(n){(0,Bbe.internalError)(n,"TLOG_FETCH_ENTRY_ERROR","error fetching tlog entry")}}else(0,Bbe.internalError)(s,"TLOG_CREATE_ENTRY_ERROR","error creating tlog entry")}return r}};bO.TLogClient=F7;function o1t(t){return t instanceof i1t.HTTPError&&t.statusCode===409&&t.location!==void 0}});var Sbe=_(N7=>{"use strict";Object.defineProperty(N7,"__esModule",{value:!0});N7.toProposedEntry=l1t;var a1t=Ib(),Sg=vg(),Pb="sha256";function l1t(t,e,r="dsse"){switch(t.$case){case"dsseEnvelope":return r==="intoto"?f1t(t.dsseEnvelope,e):u1t(t.dsseEnvelope,e);case"messageSignature":return c1t(t.messageSignature,e)}}function c1t(t,e){let r=t.messageDigest.digest.toString("hex"),s=t.signature.toString("base64"),a=Sg.encoding.base64Encode(e);return{apiVersion:"0.0.1",kind:"hashedrekord",spec:{data:{hash:{algorithm:Pb,value:r}},signature:{content:s,publicKey:{content:a}}}}}function u1t(t,e){let r=JSON.stringify((0,a1t.envelopeToJSON)(t)),s=Sg.encoding.base64Encode(e);return{apiVersion:"0.0.1",kind:"dsse",spec:{proposedContent:{envelope:r,verifiers:[s]}}}}function f1t(t,e){let r=Sg.crypto.digest(Pb,t.payload).toString("hex"),s=A1t(t,e),a=Sg.encoding.base64Encode(t.payload.toString("base64")),n=Sg.encoding.base64Encode(t.signatures[0].sig.toString("base64")),c=t.signatures[0].keyid,f=Sg.encoding.base64Encode(e),p={payloadType:t.payloadType,payload:a,signatures:[{sig:n,publicKey:f}]};return c.length>0&&(p.signatures[0].keyid=c),{apiVersion:"0.0.2",kind:"intoto",spec:{content:{envelope:p,hash:{algorithm:Pb,value:s},payloadHash:{algorithm:Pb,value:r}}}}}function A1t(t,e){let r={payloadType:t.payloadType,payload:t.payload.toString("base64"),signatures:[{sig:t.signatures[0].sig.toString("base64"),publicKey:e}]};return t.signatures[0].keyid.length>0&&(r.signatures[0].keyid=t.signatures[0].keyid),Sg.crypto.digest(Pb,Sg.json.canonicalize(r)).toString("hex")}});var Dbe=_(oy=>{"use strict";Object.defineProperty(oy,"__esModule",{value:!0});oy.RekorWitness=oy.DEFAULT_REKOR_URL=void 0;var p1t=vg(),h1t=vbe(),g1t=Sbe();oy.DEFAULT_REKOR_URL="https://rekor.sigstore.dev";var O7=class{constructor(e){this.entryType=e.entryType,this.tlog=new h1t.TLogClient({...e,rekorBaseURL:e.rekorBaseURL||oy.DEFAULT_REKOR_URL})}async testify(e,r){let s=(0,g1t.toProposedEntry)(e,r,this.entryType),a=await this.tlog.createEntry(s);return d1t(a)}};oy.RekorWitness=O7;function d1t(t){let e=Buffer.from(t.logID,"hex"),r=p1t.encoding.base64Decode(t.body),s=JSON.parse(r),a=t?.verification?.signedEntryTimestamp?m1t(t.verification.signedEntryTimestamp):void 0,n=t?.verification?.inclusionProof?y1t(t.verification.inclusionProof):void 0;return{tlogEntries:[{logIndex:t.logIndex.toString(),logId:{keyId:e},integratedTime:t.integratedTime.toString(),kindVersion:{kind:s.kind,version:s.apiVersion},inclusionPromise:a,inclusionProof:n,canonicalizedBody:Buffer.from(t.body,"base64")}]}}function m1t(t){return{signedEntryTimestamp:Buffer.from(t,"base64")}}function y1t(t){return{logIndex:t.logIndex.toString(),treeSize:t.treeSize.toString(),rootHash:Buffer.from(t.rootHash,"hex"),hashes:t.hashes.map(e=>Buffer.from(e,"hex")),checkpoint:{envelope:t.checkpoint}}}});var bbe=_(PO=>{"use strict";Object.defineProperty(PO,"__esModule",{value:!0});PO.TimestampAuthority=void 0;var E1t=BO(),L7=class{constructor(e){this.options=e}async createTimestamp(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/timestamp`;return(await(0,E1t.fetchWithRetry)(n,{headers:{"Content-Type":"application/json"},body:JSON.stringify(e),timeout:s,retry:a})).buffer()}};PO.TimestampAuthority=L7});var xbe=_(xO=>{"use strict";Object.defineProperty(xO,"__esModule",{value:!0});xO.TSAClient=void 0;var I1t=$w(),C1t=bbe(),w1t=vg(),Pbe="sha256",M7=class{constructor(e){this.tsa=new C1t.TimestampAuthority({baseURL:e.tsaBaseURL,retry:e.retry,timeout:e.timeout})}async createTimestamp(e){let r={artifactHash:w1t.crypto.digest(Pbe,e).toString("base64"),hashAlgorithm:Pbe};try{return await this.tsa.createTimestamp(r)}catch(s){(0,I1t.internalError)(s,"TSA_CREATE_TIMESTAMP_ERROR","error creating timestamp")}}};xO.TSAClient=M7});var kbe=_(kO=>{"use strict";Object.defineProperty(kO,"__esModule",{value:!0});kO.TSAWitness=void 0;var B1t=xbe(),U7=class{constructor(e){this.tsa=new B1t.TSAClient({tsaBaseURL:e.tsaBaseURL,retry:e.retry,timeout:e.timeout})}async testify(e){let r=v1t(e);return{rfc3161Timestamps:[{signedTimestamp:await this.tsa.createTimestamp(r)}]}}};kO.TSAWitness=U7;function v1t(t){switch(t.$case){case"dsseEnvelope":return t.dsseEnvelope.signatures[0].sig;case"messageSignature":return t.messageSignature.signature}}});var Tbe=_(Dg=>{"use strict";Object.defineProperty(Dg,"__esModule",{value:!0});Dg.TSAWitness=Dg.RekorWitness=Dg.DEFAULT_REKOR_URL=void 0;var Qbe=Dbe();Object.defineProperty(Dg,"DEFAULT_REKOR_URL",{enumerable:!0,get:function(){return Qbe.DEFAULT_REKOR_URL}});Object.defineProperty(Dg,"RekorWitness",{enumerable:!0,get:function(){return Qbe.RekorWitness}});var S1t=kbe();Object.defineProperty(Dg,"TSAWitness",{enumerable:!0,get:function(){return S1t.TSAWitness}})});var H7=_(ys=>{"use strict";Object.defineProperty(ys,"__esModule",{value:!0});ys.TSAWitness=ys.RekorWitness=ys.DEFAULT_REKOR_URL=ys.FulcioSigner=ys.DEFAULT_FULCIO_URL=ys.CIContextProvider=ys.InternalError=ys.MessageSignatureBundleBuilder=ys.DSSEBundleBuilder=void 0;var Rbe=JDe();Object.defineProperty(ys,"DSSEBundleBuilder",{enumerable:!0,get:function(){return Rbe.DSSEBundleBuilder}});Object.defineProperty(ys,"MessageSignatureBundleBuilder",{enumerable:!0,get:function(){return Rbe.MessageSignatureBundleBuilder}});var D1t=$w();Object.defineProperty(ys,"InternalError",{enumerable:!0,get:function(){return D1t.InternalError}});var b1t=XDe();Object.defineProperty(ys,"CIContextProvider",{enumerable:!0,get:function(){return b1t.CIContextProvider}});var Fbe=Ebe();Object.defineProperty(ys,"DEFAULT_FULCIO_URL",{enumerable:!0,get:function(){return Fbe.DEFAULT_FULCIO_URL}});Object.defineProperty(ys,"FulcioSigner",{enumerable:!0,get:function(){return Fbe.FulcioSigner}});var _7=Tbe();Object.defineProperty(ys,"DEFAULT_REKOR_URL",{enumerable:!0,get:function(){return _7.DEFAULT_REKOR_URL}});Object.defineProperty(ys,"RekorWitness",{enumerable:!0,get:function(){return _7.RekorWitness}});Object.defineProperty(ys,"TSAWitness",{enumerable:!0,get:function(){return _7.TSAWitness}})});var Obe=_(xb=>{"use strict";var Nbe=xb&&xb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(xb,"__esModule",{value:!0});xb.appDataPath=x1t;var P1t=Nbe(Ie("os")),n1=Nbe(Ie("path"));function x1t(t){let e=P1t.default.homedir();switch(process.platform){case"darwin":{let r=n1.default.join(e,"Library","Application Support");return n1.default.join(r,t)}case"win32":{let r=process.env.LOCALAPPDATA||n1.default.join(e,"AppData","Local");return n1.default.join(r,t,"Data")}default:{let r=process.env.XDG_DATA_HOME||n1.default.join(e,".local","share");return n1.default.join(r,t)}}}});var PA=_(wl=>{"use strict";Object.defineProperty(wl,"__esModule",{value:!0});wl.UnsupportedAlgorithmError=wl.CryptoError=wl.LengthOrHashMismatchError=wl.UnsignedMetadataError=wl.RepositoryError=wl.ValueError=void 0;var j7=class extends Error{};wl.ValueError=j7;var kb=class extends Error{};wl.RepositoryError=kb;var G7=class extends kb{};wl.UnsignedMetadataError=G7;var q7=class extends kb{};wl.LengthOrHashMismatchError=q7;var QO=class extends Error{};wl.CryptoError=QO;var W7=class extends QO{};wl.UnsupportedAlgorithmError=W7});var Mbe=_(bg=>{"use strict";Object.defineProperty(bg,"__esModule",{value:!0});bg.isDefined=k1t;bg.isObject=Lbe;bg.isStringArray=Q1t;bg.isObjectArray=T1t;bg.isStringRecord=R1t;bg.isObjectRecord=F1t;function k1t(t){return t!==void 0}function Lbe(t){return typeof t=="object"&&t!==null}function Q1t(t){return Array.isArray(t)&&t.every(e=>typeof e=="string")}function T1t(t){return Array.isArray(t)&&t.every(Lbe)}function R1t(t){return typeof t=="object"&&t!==null&&Object.keys(t).every(e=>typeof e=="string")&&Object.values(t).every(e=>typeof e=="string")}function F1t(t){return typeof t=="object"&&t!==null&&Object.keys(t).every(e=>typeof e=="string")&&Object.values(t).every(e=>typeof e=="object"&&e!==null)}});var V7=_(($nr,Hbe)=>{var Ube=",",N1t=":",O1t="[",L1t="]",M1t="{",U1t="}";function Y7(t){let e=[];if(typeof t=="string")e.push(_be(t));else if(typeof t=="boolean")e.push(JSON.stringify(t));else if(Number.isInteger(t))e.push(JSON.stringify(t));else if(t===null)e.push(JSON.stringify(t));else if(Array.isArray(t)){e.push(O1t);let r=!0;t.forEach(s=>{r||e.push(Ube),r=!1,e.push(Y7(s))}),e.push(L1t)}else if(typeof t=="object"){e.push(M1t);let r=!0;Object.keys(t).sort().forEach(s=>{r||e.push(Ube),r=!1,e.push(_be(s)),e.push(N1t),e.push(Y7(t[s]))}),e.push(U1t)}else throw new TypeError("cannot encode "+t.toString());return e.join("")}function _be(t){return'"'+t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')+'"'}Hbe.exports={canonicalize:Y7}});var jbe=_(i1=>{"use strict";var _1t=i1&&i1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(i1,"__esModule",{value:!0});i1.verifySignature=void 0;var H1t=V7(),j1t=_1t(Ie("crypto")),G1t=(t,e,r)=>{let s=Buffer.from((0,H1t.canonicalize)(t));return j1t.default.verify(void 0,s,e,Buffer.from(r,"hex"))};i1.verifySignature=G1t});var ff=_(eu=>{"use strict";var q1t=eu&&eu.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),W1t=eu&&eu.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Gbe=eu&&eu.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&q1t(e,t,r);return W1t(e,t),e};Object.defineProperty(eu,"__esModule",{value:!0});eu.crypto=eu.guard=void 0;eu.guard=Gbe(Mbe());eu.crypto=Gbe(jbe())});var ay=_(hh=>{"use strict";var Y1t=hh&&hh.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(hh,"__esModule",{value:!0});hh.Signed=hh.MetadataKind=void 0;hh.isMetadataKind=J1t;var V1t=Y1t(Ie("util")),Qb=PA(),J7=ff(),qbe=["1","0","31"],K7;(function(t){t.Root="root",t.Timestamp="timestamp",t.Snapshot="snapshot",t.Targets="targets"})(K7||(hh.MetadataKind=K7={}));function J1t(t){return typeof t=="string"&&Object.values(K7).includes(t)}var z7=class t{constructor(e){this.specVersion=e.specVersion||qbe.join(".");let r=this.specVersion.split(".");if(!(r.length===2||r.length===3)||!r.every(s=>K1t(s)))throw new Qb.ValueError("Failed to parse specVersion");if(r[0]!=qbe[0])throw new Qb.ValueError("Unsupported specVersion");this.expires=e.expires,this.version=e.version,this.unrecognizedFields=e.unrecognizedFields||{}}equals(e){return e instanceof t?this.specVersion===e.specVersion&&this.expires===e.expires&&this.version===e.version&&V1t.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}isExpired(e){return e||(e=new Date),e>=new Date(this.expires)}static commonFieldsFromJSON(e){let{spec_version:r,expires:s,version:a,...n}=e;if(J7.guard.isDefined(r)){if(typeof r!="string")throw new TypeError("spec_version must be a string")}else throw new Qb.ValueError("spec_version is not defined");if(J7.guard.isDefined(s)){if(typeof s!="string")throw new TypeError("expires must be a string")}else throw new Qb.ValueError("expires is not defined");if(J7.guard.isDefined(a)){if(typeof a!="number")throw new TypeError("version must be a number")}else throw new Qb.ValueError("version is not defined");return{specVersion:r,expires:s,version:a,unrecognizedFields:n}}};hh.Signed=z7;function K1t(t){return!isNaN(Number(t))}});var Tb=_(xg=>{"use strict";var Wbe=xg&&xg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(xg,"__esModule",{value:!0});xg.TargetFile=xg.MetaFile=void 0;var Ybe=Wbe(Ie("crypto")),RO=Wbe(Ie("util")),Pg=PA(),TO=ff(),X7=class t{constructor(e){if(e.version<=0)throw new Pg.ValueError("Metafile version must be at least 1");e.length!==void 0&&Vbe(e.length),this.version=e.version,this.length=e.length,this.hashes=e.hashes,this.unrecognizedFields=e.unrecognizedFields||{}}equals(e){return e instanceof t?this.version===e.version&&this.length===e.length&&RO.default.isDeepStrictEqual(this.hashes,e.hashes)&&RO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}verify(e){if(this.length!==void 0&&e.length!==this.length)throw new Pg.LengthOrHashMismatchError(`Expected length ${this.length} but got ${e.length}`);this.hashes&&Object.entries(this.hashes).forEach(([r,s])=>{let a;try{a=Ybe.default.createHash(r)}catch{throw new Pg.LengthOrHashMismatchError(`Hash algorithm ${r} not supported`)}let n=a.update(e).digest("hex");if(n!==s)throw new Pg.LengthOrHashMismatchError(`Expected hash ${s} but got ${n}`)})}toJSON(){let e={version:this.version,...this.unrecognizedFields};return this.length!==void 0&&(e.length=this.length),this.hashes&&(e.hashes=this.hashes),e}static fromJSON(e){let{version:r,length:s,hashes:a,...n}=e;if(typeof r!="number")throw new TypeError("version must be a number");if(TO.guard.isDefined(s)&&typeof s!="number")throw new TypeError("length must be a number");if(TO.guard.isDefined(a)&&!TO.guard.isStringRecord(a))throw new TypeError("hashes must be string keys and values");return new t({version:r,length:s,hashes:a,unrecognizedFields:n})}};xg.MetaFile=X7;var Z7=class t{constructor(e){Vbe(e.length),this.length=e.length,this.path=e.path,this.hashes=e.hashes,this.unrecognizedFields=e.unrecognizedFields||{}}get custom(){let e=this.unrecognizedFields.custom;return!e||Array.isArray(e)||typeof e!="object"?{}:e}equals(e){return e instanceof t?this.length===e.length&&this.path===e.path&&RO.default.isDeepStrictEqual(this.hashes,e.hashes)&&RO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}async verify(e){let r=0,s=Object.keys(this.hashes).reduce((a,n)=>{try{a[n]=Ybe.default.createHash(n)}catch{throw new Pg.LengthOrHashMismatchError(`Hash algorithm ${n} not supported`)}return a},{});for await(let a of e)r+=a.length,Object.values(s).forEach(n=>{n.update(a)});if(r!==this.length)throw new Pg.LengthOrHashMismatchError(`Expected length ${this.length} but got ${r}`);Object.entries(s).forEach(([a,n])=>{let c=this.hashes[a],f=n.digest("hex");if(f!==c)throw new Pg.LengthOrHashMismatchError(`Expected hash ${c} but got ${f}`)})}toJSON(){return{length:this.length,hashes:this.hashes,...this.unrecognizedFields}}static fromJSON(e,r){let{length:s,hashes:a,...n}=r;if(typeof s!="number")throw new TypeError("length must be a number");if(!TO.guard.isStringRecord(a))throw new TypeError("hashes must have string keys and values");return new t({length:s,path:e,hashes:a,unrecognizedFields:n})}};xg.TargetFile=Z7;function Vbe(t){if(t<0)throw new Pg.ValueError("Length must be at least 0")}});var Jbe=_($7=>{"use strict";Object.defineProperty($7,"__esModule",{value:!0});$7.encodeOIDString=X1t;var z1t=6;function X1t(t){let e=t.split("."),r=parseInt(e[0],10)*40+parseInt(e[1],10),s=[];e.slice(2).forEach(n=>{let c=Z1t(parseInt(n,10));s.push(...c)});let a=Buffer.from([r,...s]);return Buffer.from([z1t,a.length,...a])}function Z1t(t){let e=[],r=0;for(;t>0;)e.unshift(t&127|r),t>>=7,r=128;return e}});var Zbe=_(Fb=>{"use strict";var $1t=Fb&&Fb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Fb,"__esModule",{value:!0});Fb.getPublicKey=n2t;var s1=$1t(Ie("crypto")),Rb=PA(),eJ=Jbe(),FO=48,Kbe=3,zbe=0,e2t="1.3.101.112",t2t="1.2.840.10045.2.1",r2t="1.2.840.10045.3.1.7",tJ="-----BEGIN PUBLIC KEY-----";function n2t(t){switch(t.keyType){case"rsa":return i2t(t);case"ed25519":return s2t(t);case"ecdsa":case"ecdsa-sha2-nistp256":case"ecdsa-sha2-nistp384":return o2t(t);default:throw new Rb.UnsupportedAlgorithmError(`Unsupported key type: ${t.keyType}`)}}function i2t(t){if(!t.keyVal.startsWith(tJ))throw new Rb.CryptoError("Invalid key format");let e=s1.default.createPublicKey(t.keyVal);switch(t.scheme){case"rsassa-pss-sha256":return{key:e,padding:s1.default.constants.RSA_PKCS1_PSS_PADDING};default:throw new Rb.UnsupportedAlgorithmError(`Unsupported RSA scheme: ${t.scheme}`)}}function s2t(t){let e;if(t.keyVal.startsWith(tJ))e=s1.default.createPublicKey(t.keyVal);else{if(!Xbe(t.keyVal))throw new Rb.CryptoError("Invalid key format");e=s1.default.createPublicKey({key:a2t.hexToDER(t.keyVal),format:"der",type:"spki"})}return{key:e}}function o2t(t){let e;if(t.keyVal.startsWith(tJ))e=s1.default.createPublicKey(t.keyVal);else{if(!Xbe(t.keyVal))throw new Rb.CryptoError("Invalid key format");e=s1.default.createPublicKey({key:l2t.hexToDER(t.keyVal),format:"der",type:"spki"})}return{key:e}}var a2t={hexToDER:t=>{let e=Buffer.from(t,"hex"),r=(0,eJ.encodeOIDString)(e2t),s=Buffer.concat([Buffer.concat([Buffer.from([FO]),Buffer.from([r.length]),r]),Buffer.concat([Buffer.from([Kbe]),Buffer.from([e.length+1]),Buffer.from([zbe]),e])]);return Buffer.concat([Buffer.from([FO]),Buffer.from([s.length]),s])}},l2t={hexToDER:t=>{let e=Buffer.from(t,"hex"),r=Buffer.concat([Buffer.from([Kbe]),Buffer.from([e.length+1]),Buffer.from([zbe]),e]),s=Buffer.concat([(0,eJ.encodeOIDString)(t2t),(0,eJ.encodeOIDString)(r2t)]),a=Buffer.concat([Buffer.from([FO]),Buffer.from([s.length]),s]);return Buffer.concat([Buffer.from([FO]),Buffer.from([a.length+r.length]),a,r])}},Xbe=t=>/^[0-9a-fA-F]+$/.test(t)});var NO=_(o1=>{"use strict";var c2t=o1&&o1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(o1,"__esModule",{value:!0});o1.Key=void 0;var $be=c2t(Ie("util")),Nb=PA(),ePe=ff(),u2t=Zbe(),rJ=class t{constructor(e){let{keyID:r,keyType:s,scheme:a,keyVal:n,unrecognizedFields:c}=e;this.keyID=r,this.keyType=s,this.scheme=a,this.keyVal=n,this.unrecognizedFields=c||{}}verifySignature(e){let r=e.signatures[this.keyID];if(!r)throw new Nb.UnsignedMetadataError("no signature for key found in metadata");if(!this.keyVal.public)throw new Nb.UnsignedMetadataError("no public key found");let s=(0,u2t.getPublicKey)({keyType:this.keyType,scheme:this.scheme,keyVal:this.keyVal.public}),a=e.signed.toJSON();try{if(!ePe.crypto.verifySignature(a,s,r.sig))throw new Nb.UnsignedMetadataError(`failed to verify ${this.keyID} signature`)}catch(n){throw n instanceof Nb.UnsignedMetadataError?n:new Nb.UnsignedMetadataError(`failed to verify ${this.keyID} signature`)}}equals(e){return e instanceof t?this.keyID===e.keyID&&this.keyType===e.keyType&&this.scheme===e.scheme&&$be.default.isDeepStrictEqual(this.keyVal,e.keyVal)&&$be.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}toJSON(){return{keytype:this.keyType,scheme:this.scheme,keyval:this.keyVal,...this.unrecognizedFields}}static fromJSON(e,r){let{keytype:s,scheme:a,keyval:n,...c}=r;if(typeof s!="string")throw new TypeError("keytype must be a string");if(typeof a!="string")throw new TypeError("scheme must be a string");if(!ePe.guard.isStringRecord(n))throw new TypeError("keyval must be a string record");return new t({keyID:e,keyType:s,scheme:a,keyVal:n,unrecognizedFields:c})}};o1.Key=rJ});var sPe=_((air,iPe)=>{"use strict";iPe.exports=rPe;function rPe(t,e,r){t instanceof RegExp&&(t=tPe(t,r)),e instanceof RegExp&&(e=tPe(e,r));var s=nPe(t,e,r);return s&&{start:s[0],end:s[1],pre:r.slice(0,s[0]),body:r.slice(s[0]+t.length,s[1]),post:r.slice(s[1]+e.length)}}function tPe(t,e){var r=e.match(t);return r?r[0]:null}rPe.range=nPe;function nPe(t,e,r){var s,a,n,c,f,p=r.indexOf(t),h=r.indexOf(e,p+1),E=p;if(p>=0&&h>0){for(s=[],n=r.length;E>=0&&!f;)E==p?(s.push(E),p=r.indexOf(t,E+1)):s.length==1?f=[s.pop(),h]:(a=s.pop(),a=0?p:h;s.length&&(f=[n,c])}return f}});var pPe=_((lir,APe)=>{var oPe=sPe();APe.exports=p2t;var aPe="\0SLASH"+Math.random()+"\0",lPe="\0OPEN"+Math.random()+"\0",iJ="\0CLOSE"+Math.random()+"\0",cPe="\0COMMA"+Math.random()+"\0",uPe="\0PERIOD"+Math.random()+"\0";function nJ(t){return parseInt(t,10)==t?parseInt(t,10):t.charCodeAt(0)}function f2t(t){return t.split("\\\\").join(aPe).split("\\{").join(lPe).split("\\}").join(iJ).split("\\,").join(cPe).split("\\.").join(uPe)}function A2t(t){return t.split(aPe).join("\\").split(lPe).join("{").split(iJ).join("}").split(cPe).join(",").split(uPe).join(".")}function fPe(t){if(!t)return[""];var e=[],r=oPe("{","}",t);if(!r)return t.split(",");var s=r.pre,a=r.body,n=r.post,c=s.split(",");c[c.length-1]+="{"+a+"}";var f=fPe(n);return n.length&&(c[c.length-1]+=f.shift(),c.push.apply(c,f)),e.push.apply(e,c),e}function p2t(t){return t?(t.substr(0,2)==="{}"&&(t="\\{\\}"+t.substr(2)),Ob(f2t(t),!0).map(A2t)):[]}function h2t(t){return"{"+t+"}"}function g2t(t){return/^-?0\d/.test(t)}function d2t(t,e){return t<=e}function m2t(t,e){return t>=e}function Ob(t,e){var r=[],s=oPe("{","}",t);if(!s)return[t];var a=s.pre,n=s.post.length?Ob(s.post,!1):[""];if(/\$$/.test(s.pre))for(var c=0;c=0;if(!E&&!C)return s.post.match(/,.*\}/)?(t=s.pre+"{"+s.body+iJ+s.post,Ob(t)):[t];var S;if(E)S=s.body.split(/\.\./);else if(S=fPe(s.body),S.length===1&&(S=Ob(S[0],!1).map(h2t),S.length===1))return n.map(function(Ce){return s.pre+S[0]+Ce});var P;if(E){var I=nJ(S[0]),R=nJ(S[1]),N=Math.max(S[0].length,S[1].length),U=S.length==3?Math.abs(nJ(S[2])):1,W=d2t,ee=R0){var pe=new Array(me+1).join("0");ue<0?le="-"+pe+le.slice(1):le=pe+le}}P.push(le)}}else{P=[];for(var Be=0;Be{"use strict";Object.defineProperty(OO,"__esModule",{value:!0});OO.assertValidPattern=void 0;var y2t=1024*64,E2t=t=>{if(typeof t!="string")throw new TypeError("invalid pattern");if(t.length>y2t)throw new TypeError("pattern is too long")};OO.assertValidPattern=E2t});var dPe=_(LO=>{"use strict";Object.defineProperty(LO,"__esModule",{value:!0});LO.parseClass=void 0;var I2t={"[:alnum:]":["\\p{L}\\p{Nl}\\p{Nd}",!0],"[:alpha:]":["\\p{L}\\p{Nl}",!0],"[:ascii:]":["\\x00-\\x7f",!1],"[:blank:]":["\\p{Zs}\\t",!0],"[:cntrl:]":["\\p{Cc}",!0],"[:digit:]":["\\p{Nd}",!0],"[:graph:]":["\\p{Z}\\p{C}",!0,!0],"[:lower:]":["\\p{Ll}",!0],"[:print:]":["\\p{C}",!0],"[:punct:]":["\\p{P}",!0],"[:space:]":["\\p{Z}\\t\\r\\n\\v\\f",!0],"[:upper:]":["\\p{Lu}",!0],"[:word:]":["\\p{L}\\p{Nl}\\p{Nd}\\p{Pc}",!0],"[:xdigit:]":["A-Fa-f0-9",!1]},Lb=t=>t.replace(/[[\]\\-]/g,"\\$&"),C2t=t=>t.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),gPe=t=>t.join(""),w2t=(t,e)=>{let r=e;if(t.charAt(r)!=="[")throw new Error("not in a brace expression");let s=[],a=[],n=r+1,c=!1,f=!1,p=!1,h=!1,E=r,C="";e:for(;nC?s.push(Lb(C)+"-"+Lb(R)):R===C&&s.push(Lb(R)),C="",n++;continue}if(t.startsWith("-]",n+1)){s.push(Lb(R+"-")),n+=2;continue}if(t.startsWith("-",n+1)){C=R,n+=2;continue}s.push(Lb(R)),n++}if(E{"use strict";Object.defineProperty(MO,"__esModule",{value:!0});MO.unescape=void 0;var B2t=(t,{windowsPathsNoEscape:e=!1}={})=>e?t.replace(/\[([^\/\\])\]/g,"$1"):t.replace(/((?!\\).|^)\[([^\/\\])\]/g,"$1$2").replace(/\\([^\/])/g,"$1");MO.unescape=B2t});var aJ=_(jO=>{"use strict";Object.defineProperty(jO,"__esModule",{value:!0});jO.AST=void 0;var v2t=dPe(),_O=UO(),S2t=new Set(["!","?","+","*","@"]),mPe=t=>S2t.has(t),D2t="(?!(?:^|/)\\.\\.?(?:$|/))",HO="(?!\\.)",b2t=new Set(["[","."]),P2t=new Set(["..","."]),x2t=new Set("().*{}+?[]^$\\!"),k2t=t=>t.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),oJ="[^/]",yPe=oJ+"*?",EPe=oJ+"+?",sJ=class t{type;#t;#r;#i=!1;#e=[];#n;#o;#l;#a=!1;#s;#c;#f=!1;constructor(e,r,s={}){this.type=e,e&&(this.#r=!0),this.#n=r,this.#t=this.#n?this.#n.#t:this,this.#s=this.#t===this?s:this.#t.#s,this.#l=this.#t===this?[]:this.#t.#l,e==="!"&&!this.#t.#a&&this.#l.push(this),this.#o=this.#n?this.#n.#e.length:0}get hasMagic(){if(this.#r!==void 0)return this.#r;for(let e of this.#e)if(typeof e!="string"&&(e.type||e.hasMagic))return this.#r=!0;return this.#r}toString(){return this.#c!==void 0?this.#c:this.type?this.#c=this.type+"("+this.#e.map(e=>String(e)).join("|")+")":this.#c=this.#e.map(e=>String(e)).join("")}#p(){if(this!==this.#t)throw new Error("should only call on root");if(this.#a)return this;this.toString(),this.#a=!0;let e;for(;e=this.#l.pop();){if(e.type!=="!")continue;let r=e,s=r.#n;for(;s;){for(let a=r.#o+1;!s.type&&atypeof r=="string"?r:r.toJSON()):[this.type,...this.#e.map(r=>r.toJSON())];return this.isStart()&&!this.type&&e.unshift([]),this.isEnd()&&(this===this.#t||this.#t.#a&&this.#n?.type==="!")&&e.push({}),e}isStart(){if(this.#t===this)return!0;if(!this.#n?.isStart())return!1;if(this.#o===0)return!0;let e=this.#n;for(let r=0;r{let[I,R,N,U]=typeof P=="string"?t.#h(P,this.#r,p):P.toRegExpSource(e);return this.#r=this.#r||N,this.#i=this.#i||U,I}).join(""),E="";if(this.isStart()&&typeof this.#e[0]=="string"&&!(this.#e.length===1&&P2t.has(this.#e[0]))){let I=b2t,R=r&&I.has(h.charAt(0))||h.startsWith("\\.")&&I.has(h.charAt(2))||h.startsWith("\\.\\.")&&I.has(h.charAt(4)),N=!r&&!e&&I.has(h.charAt(0));E=R?D2t:N?HO:""}let C="";return this.isEnd()&&this.#t.#a&&this.#n?.type==="!"&&(C="(?:$|\\/)"),[E+h+C,(0,_O.unescape)(h),this.#r=!!this.#r,this.#i]}let s=this.type==="*"||this.type==="+",a=this.type==="!"?"(?:(?!(?:":"(?:",n=this.#A(r);if(this.isStart()&&this.isEnd()&&!n&&this.type!=="!"){let p=this.toString();return this.#e=[p],this.type=null,this.#r=void 0,[p,(0,_O.unescape)(this.toString()),!1,!1]}let c=!s||e||r||!HO?"":this.#A(!0);c===n&&(c=""),c&&(n=`(?:${n})(?:${c})*?`);let f="";if(this.type==="!"&&this.#f)f=(this.isStart()&&!r?HO:"")+EPe;else{let p=this.type==="!"?"))"+(this.isStart()&&!r&&!e?HO:"")+yPe+")":this.type==="@"?")":this.type==="?"?")?":this.type==="+"&&c?")":this.type==="*"&&c?")?":`)${this.type}`;f=a+n+p}return[f,(0,_O.unescape)(n),this.#r=!!this.#r,this.#i]}#A(e){return this.#e.map(r=>{if(typeof r=="string")throw new Error("string type in extglob ast??");let[s,a,n,c]=r.toRegExpSource(e);return this.#i=this.#i||c,s}).filter(r=>!(this.isStart()&&this.isEnd())||!!r).join("|")}static#h(e,r,s=!1){let a=!1,n="",c=!1;for(let f=0;f{"use strict";Object.defineProperty(GO,"__esModule",{value:!0});GO.escape=void 0;var Q2t=(t,{windowsPathsNoEscape:e=!1}={})=>e?t.replace(/[?*()[\]]/g,"[$&]"):t.replace(/[?*()[\]\\]/g,"\\$&");GO.escape=Q2t});var DPe=_(pr=>{"use strict";var T2t=pr&&pr.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(pr,"__esModule",{value:!0});pr.unescape=pr.escape=pr.AST=pr.Minimatch=pr.match=pr.makeRe=pr.braceExpand=pr.defaults=pr.filter=pr.GLOBSTAR=pr.sep=pr.minimatch=void 0;var R2t=T2t(pPe()),qO=hPe(),wPe=aJ(),F2t=lJ(),N2t=UO(),O2t=(t,e,r={})=>((0,qO.assertValidPattern)(e),!r.nocomment&&e.charAt(0)==="#"?!1:new ly(e,r).match(t));pr.minimatch=O2t;var L2t=/^\*+([^+@!?\*\[\(]*)$/,M2t=t=>e=>!e.startsWith(".")&&e.endsWith(t),U2t=t=>e=>e.endsWith(t),_2t=t=>(t=t.toLowerCase(),e=>!e.startsWith(".")&&e.toLowerCase().endsWith(t)),H2t=t=>(t=t.toLowerCase(),e=>e.toLowerCase().endsWith(t)),j2t=/^\*+\.\*+$/,G2t=t=>!t.startsWith(".")&&t.includes("."),q2t=t=>t!=="."&&t!==".."&&t.includes("."),W2t=/^\.\*+$/,Y2t=t=>t!=="."&&t!==".."&&t.startsWith("."),V2t=/^\*+$/,J2t=t=>t.length!==0&&!t.startsWith("."),K2t=t=>t.length!==0&&t!=="."&&t!=="..",z2t=/^\?+([^+@!?\*\[\(]*)?$/,X2t=([t,e=""])=>{let r=BPe([t]);return e?(e=e.toLowerCase(),s=>r(s)&&s.toLowerCase().endsWith(e)):r},Z2t=([t,e=""])=>{let r=vPe([t]);return e?(e=e.toLowerCase(),s=>r(s)&&s.toLowerCase().endsWith(e)):r},$2t=([t,e=""])=>{let r=vPe([t]);return e?s=>r(s)&&s.endsWith(e):r},eBt=([t,e=""])=>{let r=BPe([t]);return e?s=>r(s)&&s.endsWith(e):r},BPe=([t])=>{let e=t.length;return r=>r.length===e&&!r.startsWith(".")},vPe=([t])=>{let e=t.length;return r=>r.length===e&&r!=="."&&r!==".."},SPe=typeof process=="object"&&process?typeof process.env=="object"&&process.env&&process.env.__MINIMATCH_TESTING_PLATFORM__||process.platform:"posix",IPe={win32:{sep:"\\"},posix:{sep:"/"}};pr.sep=SPe==="win32"?IPe.win32.sep:IPe.posix.sep;pr.minimatch.sep=pr.sep;pr.GLOBSTAR=Symbol("globstar **");pr.minimatch.GLOBSTAR=pr.GLOBSTAR;var tBt="[^/]",rBt=tBt+"*?",nBt="(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?",iBt="(?:(?!(?:\\/|^)\\.).)*?",sBt=(t,e={})=>r=>(0,pr.minimatch)(r,t,e);pr.filter=sBt;pr.minimatch.filter=pr.filter;var tu=(t,e={})=>Object.assign({},t,e),oBt=t=>{if(!t||typeof t!="object"||!Object.keys(t).length)return pr.minimatch;let e=pr.minimatch;return Object.assign((s,a,n={})=>e(s,a,tu(t,n)),{Minimatch:class extends e.Minimatch{constructor(a,n={}){super(a,tu(t,n))}static defaults(a){return e.defaults(tu(t,a)).Minimatch}},AST:class extends e.AST{constructor(a,n,c={}){super(a,n,tu(t,c))}static fromGlob(a,n={}){return e.AST.fromGlob(a,tu(t,n))}},unescape:(s,a={})=>e.unescape(s,tu(t,a)),escape:(s,a={})=>e.escape(s,tu(t,a)),filter:(s,a={})=>e.filter(s,tu(t,a)),defaults:s=>e.defaults(tu(t,s)),makeRe:(s,a={})=>e.makeRe(s,tu(t,a)),braceExpand:(s,a={})=>e.braceExpand(s,tu(t,a)),match:(s,a,n={})=>e.match(s,a,tu(t,n)),sep:e.sep,GLOBSTAR:pr.GLOBSTAR})};pr.defaults=oBt;pr.minimatch.defaults=pr.defaults;var aBt=(t,e={})=>((0,qO.assertValidPattern)(t),e.nobrace||!/\{(?:(?!\{).)*\}/.test(t)?[t]:(0,R2t.default)(t));pr.braceExpand=aBt;pr.minimatch.braceExpand=pr.braceExpand;var lBt=(t,e={})=>new ly(t,e).makeRe();pr.makeRe=lBt;pr.minimatch.makeRe=pr.makeRe;var cBt=(t,e,r={})=>{let s=new ly(e,r);return t=t.filter(a=>s.match(a)),s.options.nonull&&!t.length&&t.push(e),t};pr.match=cBt;pr.minimatch.match=pr.match;var CPe=/[?*]|[+@!]\(.*?\)|\[|\]/,uBt=t=>t.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),ly=class{options;set;pattern;windowsPathsNoEscape;nonegate;negate;comment;empty;preserveMultipleSlashes;partial;globSet;globParts;nocase;isWindows;platform;windowsNoMagicRoot;regexp;constructor(e,r={}){(0,qO.assertValidPattern)(e),r=r||{},this.options=r,this.pattern=e,this.platform=r.platform||SPe,this.isWindows=this.platform==="win32",this.windowsPathsNoEscape=!!r.windowsPathsNoEscape||r.allowWindowsEscape===!1,this.windowsPathsNoEscape&&(this.pattern=this.pattern.replace(/\\/g,"/")),this.preserveMultipleSlashes=!!r.preserveMultipleSlashes,this.regexp=null,this.negate=!1,this.nonegate=!!r.nonegate,this.comment=!1,this.empty=!1,this.partial=!!r.partial,this.nocase=!!this.options.nocase,this.windowsNoMagicRoot=r.windowsNoMagicRoot!==void 0?r.windowsNoMagicRoot:!!(this.isWindows&&this.nocase),this.globSet=[],this.globParts=[],this.set=[],this.make()}hasMagic(){if(this.options.magicalBraces&&this.set.length>1)return!0;for(let e of this.set)for(let r of e)if(typeof r!="string")return!0;return!1}debug(...e){}make(){let e=this.pattern,r=this.options;if(!r.nocomment&&e.charAt(0)==="#"){this.comment=!0;return}if(!e){this.empty=!0;return}this.parseNegate(),this.globSet=[...new Set(this.braceExpand())],r.debug&&(this.debug=(...n)=>console.error(...n)),this.debug(this.pattern,this.globSet);let s=this.globSet.map(n=>this.slashSplit(n));this.globParts=this.preprocess(s),this.debug(this.pattern,this.globParts);let a=this.globParts.map((n,c,f)=>{if(this.isWindows&&this.windowsNoMagicRoot){let p=n[0]===""&&n[1]===""&&(n[2]==="?"||!CPe.test(n[2]))&&!CPe.test(n[3]),h=/^[a-z]:/i.test(n[0]);if(p)return[...n.slice(0,4),...n.slice(4).map(E=>this.parse(E))];if(h)return[n[0],...n.slice(1).map(E=>this.parse(E))]}return n.map(p=>this.parse(p))});if(this.debug(this.pattern,a),this.set=a.filter(n=>n.indexOf(!1)===-1),this.isWindows)for(let n=0;n=2?(e=this.firstPhasePreProcess(e),e=this.secondPhasePreProcess(e)):r>=1?e=this.levelOneOptimize(e):e=this.adjascentGlobstarOptimize(e),e}adjascentGlobstarOptimize(e){return e.map(r=>{let s=-1;for(;(s=r.indexOf("**",s+1))!==-1;){let a=s;for(;r[a+1]==="**";)a++;a!==s&&r.splice(s,a-s)}return r})}levelOneOptimize(e){return e.map(r=>(r=r.reduce((s,a)=>{let n=s[s.length-1];return a==="**"&&n==="**"?s:a===".."&&n&&n!==".."&&n!=="."&&n!=="**"?(s.pop(),s):(s.push(a),s)},[]),r.length===0?[""]:r))}levelTwoFileOptimize(e){Array.isArray(e)||(e=this.slashSplit(e));let r=!1;do{if(r=!1,!this.preserveMultipleSlashes){for(let a=1;aa&&s.splice(a+1,c-a);let f=s[a+1],p=s[a+2],h=s[a+3];if(f!==".."||!p||p==="."||p===".."||!h||h==="."||h==="..")continue;r=!0,s.splice(a,1);let E=s.slice(0);E[a]="**",e.push(E),a--}if(!this.preserveMultipleSlashes){for(let c=1;cr.length)}partsMatch(e,r,s=!1){let a=0,n=0,c=[],f="";for(;aee?r=r.slice(ie):ee>ie&&(e=e.slice(ee)))}}let{optimizationLevel:n=1}=this.options;n>=2&&(e=this.levelTwoFileOptimize(e)),this.debug("matchOne",this,{file:e,pattern:r}),this.debug("matchOne",e.length,r.length);for(var c=0,f=0,p=e.length,h=r.length;c>> no match, partial?`,e,S,r,P),S===p))}let R;if(typeof E=="string"?(R=C===E,this.debug("string match",E,C,R)):(R=E.test(C),this.debug("pattern match",E,C,R)),!R)return!1}if(c===p&&f===h)return!0;if(c===p)return s;if(f===h)return c===p-1&&e[c]==="";throw new Error("wtf?")}braceExpand(){return(0,pr.braceExpand)(this.pattern,this.options)}parse(e){(0,qO.assertValidPattern)(e);let r=this.options;if(e==="**")return pr.GLOBSTAR;if(e==="")return"";let s,a=null;(s=e.match(V2t))?a=r.dot?K2t:J2t:(s=e.match(L2t))?a=(r.nocase?r.dot?H2t:_2t:r.dot?U2t:M2t)(s[1]):(s=e.match(z2t))?a=(r.nocase?r.dot?Z2t:X2t:r.dot?$2t:eBt)(s):(s=e.match(j2t))?a=r.dot?q2t:G2t:(s=e.match(W2t))&&(a=Y2t);let n=wPe.AST.fromGlob(e,this.options).toMMPattern();return a&&typeof n=="object"&&Reflect.defineProperty(n,"test",{value:a}),n}makeRe(){if(this.regexp||this.regexp===!1)return this.regexp;let e=this.set;if(!e.length)return this.regexp=!1,this.regexp;let r=this.options,s=r.noglobstar?rBt:r.dot?nBt:iBt,a=new Set(r.nocase?["i"]:[]),n=e.map(p=>{let h=p.map(E=>{if(E instanceof RegExp)for(let C of E.flags.split(""))a.add(C);return typeof E=="string"?uBt(E):E===pr.GLOBSTAR?pr.GLOBSTAR:E._src});return h.forEach((E,C)=>{let S=h[C+1],P=h[C-1];E!==pr.GLOBSTAR||P===pr.GLOBSTAR||(P===void 0?S!==void 0&&S!==pr.GLOBSTAR?h[C+1]="(?:\\/|"+s+"\\/)?"+S:h[C]=s:S===void 0?h[C-1]=P+"(?:\\/|"+s+")?":S!==pr.GLOBSTAR&&(h[C-1]=P+"(?:\\/|\\/"+s+"\\/)"+S,h[C+1]=pr.GLOBSTAR))}),h.filter(E=>E!==pr.GLOBSTAR).join("/")}).join("|"),[c,f]=e.length>1?["(?:",")"]:["",""];n="^"+c+n+f+"$",this.negate&&(n="^(?!"+n+").+$");try{this.regexp=new RegExp(n,[...a].join(""))}catch{this.regexp=!1}return this.regexp}slashSplit(e){return this.preserveMultipleSlashes?e.split("/"):this.isWindows&&/^\/\/[^\/]+/.test(e)?["",...e.split(/\/+/)]:e.split(/\/+/)}match(e,r=this.partial){if(this.debug("match",e,this.pattern),this.comment)return!1;if(this.empty)return e==="";if(e==="/"&&r)return!0;let s=this.options;this.isWindows&&(e=e.split("\\").join("/"));let a=this.slashSplit(e);this.debug(this.pattern,"split",a);let n=this.set;this.debug(this.pattern,"set",n);let c=a[a.length-1];if(!c)for(let f=a.length-2;!c&&f>=0;f--)c=a[f];for(let f=0;f{"use strict";var bPe=ru&&ru.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(ru,"__esModule",{value:!0});ru.SuccinctRoles=ru.DelegatedRole=ru.Role=ru.TOP_LEVEL_ROLE_NAMES=void 0;var PPe=bPe(Ie("crypto")),hBt=DPe(),WO=bPe(Ie("util")),YO=PA(),cy=ff();ru.TOP_LEVEL_ROLE_NAMES=["root","targets","snapshot","timestamp"];var Mb=class t{constructor(e){let{keyIDs:r,threshold:s,unrecognizedFields:a}=e;if(gBt(r))throw new YO.ValueError("duplicate key IDs found");if(s<1)throw new YO.ValueError("threshold must be at least 1");this.keyIDs=r,this.threshold=s,this.unrecognizedFields=a||{}}equals(e){return e instanceof t?this.threshold===e.threshold&&WO.default.isDeepStrictEqual(this.keyIDs,e.keyIDs)&&WO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}toJSON(){return{keyids:this.keyIDs,threshold:this.threshold,...this.unrecognizedFields}}static fromJSON(e){let{keyids:r,threshold:s,...a}=e;if(!cy.guard.isStringArray(r))throw new TypeError("keyids must be an array");if(typeof s!="number")throw new TypeError("threshold must be a number");return new t({keyIDs:r,threshold:s,unrecognizedFields:a})}};ru.Role=Mb;function gBt(t){return new Set(t).size!==t.length}var cJ=class t extends Mb{constructor(e){super(e);let{name:r,terminating:s,paths:a,pathHashPrefixes:n}=e;if(this.name=r,this.terminating=s,e.paths&&e.pathHashPrefixes)throw new YO.ValueError("paths and pathHashPrefixes are mutually exclusive");this.paths=a,this.pathHashPrefixes=n}equals(e){return e instanceof t?super.equals(e)&&this.name===e.name&&this.terminating===e.terminating&&WO.default.isDeepStrictEqual(this.paths,e.paths)&&WO.default.isDeepStrictEqual(this.pathHashPrefixes,e.pathHashPrefixes):!1}isDelegatedPath(e){if(this.paths)return this.paths.some(r=>mBt(e,r));if(this.pathHashPrefixes){let s=PPe.default.createHash("sha256").update(e).digest("hex");return this.pathHashPrefixes.some(a=>s.startsWith(a))}return!1}toJSON(){let e={...super.toJSON(),name:this.name,terminating:this.terminating};return this.paths&&(e.paths=this.paths),this.pathHashPrefixes&&(e.path_hash_prefixes=this.pathHashPrefixes),e}static fromJSON(e){let{keyids:r,threshold:s,name:a,terminating:n,paths:c,path_hash_prefixes:f,...p}=e;if(!cy.guard.isStringArray(r))throw new TypeError("keyids must be an array of strings");if(typeof s!="number")throw new TypeError("threshold must be a number");if(typeof a!="string")throw new TypeError("name must be a string");if(typeof n!="boolean")throw new TypeError("terminating must be a boolean");if(cy.guard.isDefined(c)&&!cy.guard.isStringArray(c))throw new TypeError("paths must be an array of strings");if(cy.guard.isDefined(f)&&!cy.guard.isStringArray(f))throw new TypeError("path_hash_prefixes must be an array of strings");return new t({keyIDs:r,threshold:s,name:a,terminating:n,paths:c,pathHashPrefixes:f,unrecognizedFields:p})}};ru.DelegatedRole=cJ;var dBt=(t,e)=>t.map((r,s)=>[r,e[s]]);function mBt(t,e){let r=t.split("/"),s=e.split("/");return s.length!=r.length?!1:dBt(r,s).every(([a,n])=>(0,hBt.minimatch)(a,n))}var uJ=class t extends Mb{constructor(e){super(e);let{bitLength:r,namePrefix:s}=e;if(r<=0||r>32)throw new YO.ValueError("bitLength must be between 1 and 32");this.bitLength=r,this.namePrefix=s,this.numberOfBins=Math.pow(2,r),this.suffixLen=(this.numberOfBins-1).toString(16).length}equals(e){return e instanceof t?super.equals(e)&&this.bitLength===e.bitLength&&this.namePrefix===e.namePrefix:!1}getRoleForTarget(e){let a=PPe.default.createHash("sha256").update(e).digest().subarray(0,4),n=32-this.bitLength,f=(a.readUInt32BE()>>>n).toString(16).padStart(this.suffixLen,"0");return`${this.namePrefix}-${f}`}*getRoles(){for(let e=0;e{"use strict";var yBt=a1&&a1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(a1,"__esModule",{value:!0});a1.Root=void 0;var xPe=yBt(Ie("util")),AJ=ay(),kPe=PA(),EBt=NO(),VO=fJ(),JO=ff(),pJ=class t extends AJ.Signed{constructor(e){if(super(e),this.type=AJ.MetadataKind.Root,this.keys=e.keys||{},this.consistentSnapshot=e.consistentSnapshot??!0,!e.roles)this.roles=VO.TOP_LEVEL_ROLE_NAMES.reduce((r,s)=>({...r,[s]:new VO.Role({keyIDs:[],threshold:1})}),{});else{let r=new Set(Object.keys(e.roles));if(!VO.TOP_LEVEL_ROLE_NAMES.every(s=>r.has(s)))throw new kPe.ValueError("missing top-level role");this.roles=e.roles}}addKey(e,r){if(!this.roles[r])throw new kPe.ValueError(`role ${r} does not exist`);this.roles[r].keyIDs.includes(e.keyID)||this.roles[r].keyIDs.push(e.keyID),this.keys[e.keyID]=e}equals(e){return e instanceof t?super.equals(e)&&this.consistentSnapshot===e.consistentSnapshot&&xPe.default.isDeepStrictEqual(this.keys,e.keys)&&xPe.default.isDeepStrictEqual(this.roles,e.roles):!1}toJSON(){return{_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,keys:IBt(this.keys),roles:CBt(this.roles),consistent_snapshot:this.consistentSnapshot,...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=AJ.Signed.commonFieldsFromJSON(e),{keys:a,roles:n,consistent_snapshot:c,...f}=r;if(typeof c!="boolean")throw new TypeError("consistent_snapshot must be a boolean");return new t({...s,keys:wBt(a),roles:BBt(n),consistentSnapshot:c,unrecognizedFields:f})}};a1.Root=pJ;function IBt(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function CBt(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function wBt(t){let e;if(JO.guard.isDefined(t)){if(!JO.guard.isObjectRecord(t))throw new TypeError("keys must be an object");e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:EBt.Key.fromJSON(s,a)}),{})}return e}function BBt(t){let e;if(JO.guard.isDefined(t)){if(!JO.guard.isObjectRecord(t))throw new TypeError("roles must be an object");e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:VO.Role.fromJSON(a)}),{})}return e}});var dJ=_(KO=>{"use strict";Object.defineProperty(KO,"__esModule",{value:!0});KO.Signature=void 0;var gJ=class t{constructor(e){let{keyID:r,sig:s}=e;this.keyID=r,this.sig=s}toJSON(){return{keyid:this.keyID,sig:this.sig}}static fromJSON(e){let{keyid:r,sig:s}=e;if(typeof r!="string")throw new TypeError("keyid must be a string");if(typeof s!="string")throw new TypeError("sig must be a string");return new t({keyID:r,sig:s})}};KO.Signature=gJ});var EJ=_(l1=>{"use strict";var vBt=l1&&l1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(l1,"__esModule",{value:!0});l1.Snapshot=void 0;var SBt=vBt(Ie("util")),mJ=ay(),TPe=Tb(),QPe=ff(),yJ=class t extends mJ.Signed{constructor(e){super(e),this.type=mJ.MetadataKind.Snapshot,this.meta=e.meta||{"targets.json":new TPe.MetaFile({version:1})}}equals(e){return e instanceof t?super.equals(e)&&SBt.default.isDeepStrictEqual(this.meta,e.meta):!1}toJSON(){return{_type:this.type,meta:DBt(this.meta),spec_version:this.specVersion,version:this.version,expires:this.expires,...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=mJ.Signed.commonFieldsFromJSON(e),{meta:a,...n}=r;return new t({...s,meta:bBt(a),unrecognizedFields:n})}};l1.Snapshot=yJ;function DBt(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function bBt(t){let e;if(QPe.guard.isDefined(t))if(QPe.guard.isObjectRecord(t))e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:TPe.MetaFile.fromJSON(a)}),{});else throw new TypeError("meta field is malformed");return e}});var RPe=_(c1=>{"use strict";var PBt=c1&&c1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(c1,"__esModule",{value:!0});c1.Delegations=void 0;var zO=PBt(Ie("util")),xBt=PA(),kBt=NO(),IJ=fJ(),XO=ff(),CJ=class t{constructor(e){if(this.keys=e.keys,this.unrecognizedFields=e.unrecognizedFields||{},e.roles&&Object.keys(e.roles).some(r=>IJ.TOP_LEVEL_ROLE_NAMES.includes(r)))throw new xBt.ValueError("Delegated role name conflicts with top-level role name");this.succinctRoles=e.succinctRoles,this.roles=e.roles}equals(e){return e instanceof t?zO.default.isDeepStrictEqual(this.keys,e.keys)&&zO.default.isDeepStrictEqual(this.roles,e.roles)&&zO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields)&&zO.default.isDeepStrictEqual(this.succinctRoles,e.succinctRoles):!1}*rolesForTarget(e){if(this.roles)for(let r of Object.values(this.roles))r.isDelegatedPath(e)&&(yield{role:r.name,terminating:r.terminating});else this.succinctRoles&&(yield{role:this.succinctRoles.getRoleForTarget(e),terminating:!0})}toJSON(){let e={keys:QBt(this.keys),...this.unrecognizedFields};return this.roles?e.roles=TBt(this.roles):this.succinctRoles&&(e.succinct_roles=this.succinctRoles.toJSON()),e}static fromJSON(e){let{keys:r,roles:s,succinct_roles:a,...n}=e,c;return XO.guard.isObject(a)&&(c=IJ.SuccinctRoles.fromJSON(a)),new t({keys:RBt(r),roles:FBt(s),unrecognizedFields:n,succinctRoles:c})}};c1.Delegations=CJ;function QBt(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function TBt(t){return Object.values(t).map(e=>e.toJSON())}function RBt(t){if(!XO.guard.isObjectRecord(t))throw new TypeError("keys is malformed");return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:kBt.Key.fromJSON(r,s)}),{})}function FBt(t){let e;if(XO.guard.isDefined(t)){if(!XO.guard.isObjectArray(t))throw new TypeError("roles is malformed");e=t.reduce((r,s)=>{let a=IJ.DelegatedRole.fromJSON(s);return{...r,[a.name]:a}},{})}return e}});var vJ=_(u1=>{"use strict";var NBt=u1&&u1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(u1,"__esModule",{value:!0});u1.Targets=void 0;var FPe=NBt(Ie("util")),wJ=ay(),OBt=RPe(),LBt=Tb(),ZO=ff(),BJ=class t extends wJ.Signed{constructor(e){super(e),this.type=wJ.MetadataKind.Targets,this.targets=e.targets||{},this.delegations=e.delegations}addTarget(e){this.targets[e.path]=e}equals(e){return e instanceof t?super.equals(e)&&FPe.default.isDeepStrictEqual(this.targets,e.targets)&&FPe.default.isDeepStrictEqual(this.delegations,e.delegations):!1}toJSON(){let e={_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,targets:MBt(this.targets),...this.unrecognizedFields};return this.delegations&&(e.delegations=this.delegations.toJSON()),e}static fromJSON(e){let{unrecognizedFields:r,...s}=wJ.Signed.commonFieldsFromJSON(e),{targets:a,delegations:n,...c}=r;return new t({...s,targets:UBt(a),delegations:_Bt(n),unrecognizedFields:c})}};u1.Targets=BJ;function MBt(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function UBt(t){let e;if(ZO.guard.isDefined(t))if(ZO.guard.isObjectRecord(t))e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:LBt.TargetFile.fromJSON(s,a)}),{});else throw new TypeError("targets must be an object");return e}function _Bt(t){let e;if(ZO.guard.isDefined(t))if(ZO.guard.isObject(t))e=OBt.Delegations.fromJSON(t);else throw new TypeError("delegations must be an object");return e}});var PJ=_($O=>{"use strict";Object.defineProperty($O,"__esModule",{value:!0});$O.Timestamp=void 0;var SJ=ay(),NPe=Tb(),DJ=ff(),bJ=class t extends SJ.Signed{constructor(e){super(e),this.type=SJ.MetadataKind.Timestamp,this.snapshotMeta=e.snapshotMeta||new NPe.MetaFile({version:1})}equals(e){return e instanceof t?super.equals(e)&&this.snapshotMeta.equals(e.snapshotMeta):!1}toJSON(){return{_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,meta:{"snapshot.json":this.snapshotMeta.toJSON()},...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=SJ.Signed.commonFieldsFromJSON(e),{meta:a,...n}=r;return new t({...s,snapshotMeta:HBt(a),unrecognizedFields:n})}};$O.Timestamp=bJ;function HBt(t){let e;if(DJ.guard.isDefined(t)){let r=t["snapshot.json"];if(!DJ.guard.isDefined(r)||!DJ.guard.isObject(r))throw new TypeError("missing snapshot.json in meta");e=NPe.MetaFile.fromJSON(r)}return e}});var LPe=_(A1=>{"use strict";var jBt=A1&&A1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(A1,"__esModule",{value:!0});A1.Metadata=void 0;var GBt=V7(),OPe=jBt(Ie("util")),f1=ay(),Ub=PA(),qBt=hJ(),WBt=dJ(),YBt=EJ(),VBt=vJ(),JBt=PJ(),xJ=ff(),kJ=class t{constructor(e,r,s){this.signed=e,this.signatures=r||{},this.unrecognizedFields=s||{}}sign(e,r=!0){let s=Buffer.from((0,GBt.canonicalize)(this.signed.toJSON())),a=e(s);r||(this.signatures={}),this.signatures[a.keyID]=a}verifyDelegate(e,r){let s,a={};switch(this.signed.type){case f1.MetadataKind.Root:a=this.signed.keys,s=this.signed.roles[e];break;case f1.MetadataKind.Targets:if(!this.signed.delegations)throw new Ub.ValueError(`No delegations found for ${e}`);a=this.signed.delegations.keys,this.signed.delegations.roles?s=this.signed.delegations.roles[e]:this.signed.delegations.succinctRoles&&this.signed.delegations.succinctRoles.isDelegatedRole(e)&&(s=this.signed.delegations.succinctRoles);break;default:throw new TypeError("invalid metadata type")}if(!s)throw new Ub.ValueError(`no delegation found for ${e}`);let n=new Set;if(s.keyIDs.forEach(c=>{let f=a[c];if(f)try{f.verifySignature(r),n.add(f.keyID)}catch{}}),n.sizer.toJSON()),signed:this.signed.toJSON(),...this.unrecognizedFields}}static fromJSON(e,r){let{signed:s,signatures:a,...n}=r;if(!xJ.guard.isDefined(s)||!xJ.guard.isObject(s))throw new TypeError("signed is not defined");if(e!==s._type)throw new Ub.ValueError(`expected '${e}', got ${s._type}`);if(!xJ.guard.isObjectArray(a))throw new TypeError("signatures is not an array");let c;switch(e){case f1.MetadataKind.Root:c=qBt.Root.fromJSON(s);break;case f1.MetadataKind.Timestamp:c=JBt.Timestamp.fromJSON(s);break;case f1.MetadataKind.Snapshot:c=YBt.Snapshot.fromJSON(s);break;case f1.MetadataKind.Targets:c=VBt.Targets.fromJSON(s);break;default:throw new TypeError("invalid metadata type")}let f={};return a.forEach(p=>{let h=WBt.Signature.fromJSON(p);if(f[h.keyID])throw new Ub.ValueError(`multiple signatures found for keyid: ${h.keyID}`);f[h.keyID]=h}),new t(c,f,n)}};A1.Metadata=kJ});var eL=_(Fi=>{"use strict";Object.defineProperty(Fi,"__esModule",{value:!0});Fi.Timestamp=Fi.Targets=Fi.Snapshot=Fi.Signature=Fi.Root=Fi.Metadata=Fi.Key=Fi.TargetFile=Fi.MetaFile=Fi.ValueError=Fi.MetadataKind=void 0;var KBt=ay();Object.defineProperty(Fi,"MetadataKind",{enumerable:!0,get:function(){return KBt.MetadataKind}});var zBt=PA();Object.defineProperty(Fi,"ValueError",{enumerable:!0,get:function(){return zBt.ValueError}});var MPe=Tb();Object.defineProperty(Fi,"MetaFile",{enumerable:!0,get:function(){return MPe.MetaFile}});Object.defineProperty(Fi,"TargetFile",{enumerable:!0,get:function(){return MPe.TargetFile}});var XBt=NO();Object.defineProperty(Fi,"Key",{enumerable:!0,get:function(){return XBt.Key}});var ZBt=LPe();Object.defineProperty(Fi,"Metadata",{enumerable:!0,get:function(){return ZBt.Metadata}});var $Bt=hJ();Object.defineProperty(Fi,"Root",{enumerable:!0,get:function(){return $Bt.Root}});var evt=dJ();Object.defineProperty(Fi,"Signature",{enumerable:!0,get:function(){return evt.Signature}});var tvt=EJ();Object.defineProperty(Fi,"Snapshot",{enumerable:!0,get:function(){return tvt.Snapshot}});var rvt=vJ();Object.defineProperty(Fi,"Targets",{enumerable:!0,get:function(){return rvt.Targets}});var nvt=PJ();Object.defineProperty(Fi,"Timestamp",{enumerable:!0,get:function(){return nvt.Timestamp}})});var _Pe=_((Dir,UPe)=>{var p1=1e3,h1=p1*60,g1=h1*60,uy=g1*24,ivt=uy*7,svt=uy*365.25;UPe.exports=function(t,e){e=e||{};var r=typeof t;if(r==="string"&&t.length>0)return ovt(t);if(r==="number"&&isFinite(t))return e.long?lvt(t):avt(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))};function ovt(t){if(t=String(t),!(t.length>100)){var e=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(t);if(e){var r=parseFloat(e[1]),s=(e[2]||"ms").toLowerCase();switch(s){case"years":case"year":case"yrs":case"yr":case"y":return r*svt;case"weeks":case"week":case"w":return r*ivt;case"days":case"day":case"d":return r*uy;case"hours":case"hour":case"hrs":case"hr":case"h":return r*g1;case"minutes":case"minute":case"mins":case"min":case"m":return r*h1;case"seconds":case"second":case"secs":case"sec":case"s":return r*p1;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r;default:return}}}}function avt(t){var e=Math.abs(t);return e>=uy?Math.round(t/uy)+"d":e>=g1?Math.round(t/g1)+"h":e>=h1?Math.round(t/h1)+"m":e>=p1?Math.round(t/p1)+"s":t+"ms"}function lvt(t){var e=Math.abs(t);return e>=uy?tL(t,e,uy,"day"):e>=g1?tL(t,e,g1,"hour"):e>=h1?tL(t,e,h1,"minute"):e>=p1?tL(t,e,p1,"second"):t+" ms"}function tL(t,e,r,s){var a=e>=r*1.5;return Math.round(t/r)+" "+s+(a?"s":"")}});var QJ=_((bir,HPe)=>{function cvt(t){r.debug=r,r.default=r,r.coerce=p,r.disable=c,r.enable=a,r.enabled=f,r.humanize=_Pe(),r.destroy=h,Object.keys(t).forEach(E=>{r[E]=t[E]}),r.names=[],r.skips=[],r.formatters={};function e(E){let C=0;for(let S=0;S{if(le==="%%")return"%";ie++;let pe=r.formatters[me];if(typeof pe=="function"){let Be=N[ie];le=pe.call(U,Be),N.splice(ie,1),ie--}return le}),r.formatArgs.call(U,N),(U.log||r.log).apply(U,N)}return R.namespace=E,R.useColors=r.useColors(),R.color=r.selectColor(E),R.extend=s,R.destroy=r.destroy,Object.defineProperty(R,"enabled",{enumerable:!0,configurable:!1,get:()=>S!==null?S:(P!==r.namespaces&&(P=r.namespaces,I=r.enabled(E)),I),set:N=>{S=N}}),typeof r.init=="function"&&r.init(R),R}function s(E,C){let S=r(this.namespace+(typeof C>"u"?":":C)+E);return S.log=this.log,S}function a(E){r.save(E),r.namespaces=E,r.names=[],r.skips=[];let C=(typeof E=="string"?E:"").trim().replace(" ",",").split(",").filter(Boolean);for(let S of C)S[0]==="-"?r.skips.push(S.slice(1)):r.names.push(S)}function n(E,C){let S=0,P=0,I=-1,R=0;for(;S"-"+C)].join(",");return r.enable(""),E}function f(E){for(let C of r.skips)if(n(E,C))return!1;for(let C of r.names)if(n(E,C))return!0;return!1}function p(E){return E instanceof Error?E.stack||E.message:E}function h(){console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.")}return r.enable(r.load()),r}HPe.exports=cvt});var jPe=_((sc,rL)=>{sc.formatArgs=fvt;sc.save=Avt;sc.load=pvt;sc.useColors=uvt;sc.storage=hvt();sc.destroy=(()=>{let t=!1;return()=>{t||(t=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})();sc.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"];function uvt(){if(typeof window<"u"&&window.process&&(window.process.type==="renderer"||window.process.__nwjs))return!0;if(typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))return!1;let t;return typeof document<"u"&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||typeof window<"u"&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||typeof navigator<"u"&&navigator.userAgent&&(t=navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/))&&parseInt(t[1],10)>=31||typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)}function fvt(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+rL.exports.humanize(this.diff),!this.useColors)return;let e="color: "+this.color;t.splice(1,0,e,"color: inherit");let r=0,s=0;t[0].replace(/%[a-zA-Z%]/g,a=>{a!=="%%"&&(r++,a==="%c"&&(s=r))}),t.splice(s,0,e)}sc.log=console.debug||console.log||(()=>{});function Avt(t){try{t?sc.storage.setItem("debug",t):sc.storage.removeItem("debug")}catch{}}function pvt(){let t;try{t=sc.storage.getItem("debug")}catch{}return!t&&typeof process<"u"&&"env"in process&&(t=process.env.DEBUG),t}function hvt(){try{return localStorage}catch{}}rL.exports=QJ()(sc);var{formatters:gvt}=rL.exports;gvt.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}});var qPe=_((Zs,iL)=>{var dvt=Ie("tty"),nL=Ie("util");Zs.init=Bvt;Zs.log=Ivt;Zs.formatArgs=yvt;Zs.save=Cvt;Zs.load=wvt;Zs.useColors=mvt;Zs.destroy=nL.deprecate(()=>{},"Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");Zs.colors=[6,2,3,4,5,1];try{let t=Ie("supports-color");t&&(t.stderr||t).level>=2&&(Zs.colors=[20,21,26,27,32,33,38,39,40,41,42,43,44,45,56,57,62,63,68,69,74,75,76,77,78,79,80,81,92,93,98,99,112,113,128,129,134,135,148,149,160,161,162,163,164,165,166,167,168,169,170,171,172,173,178,179,184,185,196,197,198,199,200,201,202,203,204,205,206,207,208,209,214,215,220,221])}catch{}Zs.inspectOpts=Object.keys(process.env).filter(t=>/^debug_/i.test(t)).reduce((t,e)=>{let r=e.substring(6).toLowerCase().replace(/_([a-z])/g,(a,n)=>n.toUpperCase()),s=process.env[e];return/^(yes|on|true|enabled)$/i.test(s)?s=!0:/^(no|off|false|disabled)$/i.test(s)?s=!1:s==="null"?s=null:s=Number(s),t[r]=s,t},{});function mvt(){return"colors"in Zs.inspectOpts?!!Zs.inspectOpts.colors:dvt.isatty(process.stderr.fd)}function yvt(t){let{namespace:e,useColors:r}=this;if(r){let s=this.color,a="\x1B[3"+(s<8?s:"8;5;"+s),n=` ${a};1m${e} \x1B[0m`;t[0]=n+t[0].split(` `).join(` `+n),t.push(a+"m+"+iL.exports.humanize(this.diff)+"\x1B[0m")}else t[0]=Evt()+e+" "+t[0]}function Evt(){return Zs.inspectOpts.hideDate?"":new Date().toISOString()+" "}function Ivt(...t){return process.stderr.write(nL.formatWithOptions(Zs.inspectOpts,...t)+` `)}function Cvt(t){t?process.env.DEBUG=t:delete process.env.DEBUG}function wvt(){return process.env.DEBUG}function Bvt(t){t.inspectOpts={};let e=Object.keys(Zs.inspectOpts);for(let r=0;re.trim()).join(" ")};GPe.O=function(t){return this.inspectOpts.colors=this.useColors,nL.inspect(t,this.inspectOpts)}});var RJ=_((Pir,TJ)=>{typeof process>"u"||process.type==="renderer"||process.browser===!0||process.__nwjs?TJ.exports=jPe():TJ.exports=qPe()});var oL=_(Ki=>{"use strict";Object.defineProperty(Ki,"__esModule",{value:!0});Ki.DownloadHTTPError=Ki.DownloadLengthMismatchError=Ki.DownloadError=Ki.ExpiredMetadataError=Ki.EqualVersionError=Ki.BadVersionError=Ki.RepositoryError=Ki.PersistError=Ki.RuntimeError=Ki.ValueError=void 0;var FJ=class extends Error{};Ki.ValueError=FJ;var NJ=class extends Error{};Ki.RuntimeError=NJ;var OJ=class extends Error{};Ki.PersistError=OJ;var _b=class extends Error{};Ki.RepositoryError=_b;var sL=class extends _b{};Ki.BadVersionError=sL;var LJ=class extends sL{};Ki.EqualVersionError=LJ;var MJ=class extends _b{};Ki.ExpiredMetadataError=MJ;var Hb=class extends Error{};Ki.DownloadError=Hb;var UJ=class extends Hb{};Ki.DownloadLengthMismatchError=UJ;var _J=class extends Hb{constructor(e,r){super(e),this.statusCode=r}};Ki.DownloadHTTPError=_J});var YPe=_(d1=>{"use strict";var jJ=d1&&d1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(d1,"__esModule",{value:!0});d1.withTempFile=void 0;var HJ=jJ(Ie("fs/promises")),vvt=jJ(Ie("os")),WPe=jJ(Ie("path")),Svt=async t=>Dvt(async e=>t(WPe.default.join(e,"tempfile")));d1.withTempFile=Svt;var Dvt=async t=>{let e=await HJ.default.realpath(vvt.default.tmpdir()),r=await HJ.default.mkdtemp(e+WPe.default.sep);try{return await t(r)}finally{await HJ.default.rm(r,{force:!0,recursive:!0,maxRetries:3})}}});var qJ=_(kg=>{"use strict";var lL=kg&&kg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(kg,"__esModule",{value:!0});kg.DefaultFetcher=kg.BaseFetcher=void 0;var bvt=lL(RJ()),VPe=lL(Ie("fs")),Pvt=lL(CO()),xvt=lL(Ie("util")),JPe=oL(),kvt=YPe(),Qvt=(0,bvt.default)("tuf:fetch"),aL=class{async downloadFile(e,r,s){return(0,kvt.withTempFile)(async a=>{let n=await this.fetch(e),c=0,f=VPe.default.createWriteStream(a);try{for await(let p of n){let h=Buffer.from(p);if(c+=h.length,c>r)throw new JPe.DownloadLengthMismatchError("Max length reached");await Tvt(f,h)}}finally{await xvt.default.promisify(f.close).bind(f)()}return s(a)})}async downloadBytes(e,r){return this.downloadFile(e,r,async s=>{let a=VPe.default.createReadStream(s),n=[];for await(let c of a)n.push(c);return Buffer.concat(n)})}};kg.BaseFetcher=aL;var GJ=class extends aL{constructor(e={}){super(),this.timeout=e.timeout,this.retry=e.retry}async fetch(e){Qvt("GET %s",e);let r=await(0,Pvt.default)(e,{timeout:this.timeout,retry:this.retry});if(!r.ok||!r?.body)throw new JPe.DownloadHTTPError("Failed to download",r.status);return r.body}};kg.DefaultFetcher=GJ;var Tvt=async(t,e)=>new Promise((r,s)=>{t.write(e,a=>{a&&s(a),r(!0)})})});var KPe=_(cL=>{"use strict";Object.defineProperty(cL,"__esModule",{value:!0});cL.defaultConfig=void 0;cL.defaultConfig={maxRootRotations:256,maxDelegations:32,rootMaxLength:512e3,timestampMaxLength:16384,snapshotMaxLength:2e6,targetsMaxLength:5e6,prefixTargetsWithHash:!0,fetchTimeout:1e5,fetchRetries:void 0,fetchRetry:2}});var zPe=_(uL=>{"use strict";Object.defineProperty(uL,"__esModule",{value:!0});uL.TrustedMetadataStore=void 0;var Es=eL(),Hi=oL(),WJ=class{constructor(e){this.trustedSet={},this.referenceTime=new Date,this.loadTrustedRoot(e)}get root(){if(!this.trustedSet.root)throw new ReferenceError("No trusted root metadata");return this.trustedSet.root}get timestamp(){return this.trustedSet.timestamp}get snapshot(){return this.trustedSet.snapshot}get targets(){return this.trustedSet.targets}getRole(e){return this.trustedSet[e]}updateRoot(e){let r=JSON.parse(e.toString("utf8")),s=Es.Metadata.fromJSON(Es.MetadataKind.Root,r);if(s.signed.type!=Es.MetadataKind.Root)throw new Hi.RepositoryError(`Expected 'root', got ${s.signed.type}`);if(this.root.verifyDelegate(Es.MetadataKind.Root,s),s.signed.version!=this.root.signed.version+1)throw new Hi.BadVersionError(`Expected version ${this.root.signed.version+1}, got ${s.signed.version}`);return s.verifyDelegate(Es.MetadataKind.Root,s),this.trustedSet.root=s,s}updateTimestamp(e){if(this.snapshot)throw new Hi.RuntimeError("Cannot update timestamp after snapshot");if(this.root.signed.isExpired(this.referenceTime))throw new Hi.ExpiredMetadataError("Final root.json is expired");let r=JSON.parse(e.toString("utf8")),s=Es.Metadata.fromJSON(Es.MetadataKind.Timestamp,r);if(s.signed.type!=Es.MetadataKind.Timestamp)throw new Hi.RepositoryError(`Expected 'timestamp', got ${s.signed.type}`);if(this.root.verifyDelegate(Es.MetadataKind.Timestamp,s),this.timestamp){if(s.signed.version{let p=n.signed.meta[c];if(!p)throw new Hi.RepositoryError(`Missing file ${c} in new snapshot`);if(p.version{"use strict";Object.defineProperty(YJ,"__esModule",{value:!0});YJ.join=Fvt;var Rvt=Ie("url");function Fvt(t,e){return new Rvt.URL(Nvt(t)+Ovt(e)).toString()}function Nvt(t){return t.endsWith("/")?t:t+"/"}function Ovt(t){return t.startsWith("/")?t.slice(1):t}});var ZPe=_(nu=>{"use strict";var Lvt=nu&&nu.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Mvt=nu&&nu.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),KJ=nu&&nu.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&Lvt(e,t,r);return Mvt(e,t),e},Uvt=nu&&nu.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(nu,"__esModule",{value:!0});nu.Updater=void 0;var xA=eL(),_vt=Uvt(RJ()),m1=KJ(Ie("fs")),fL=KJ(Ie("path")),Hvt=KPe(),fy=oL(),jvt=qJ(),Gvt=zPe(),jb=KJ(XPe()),VJ=(0,_vt.default)("tuf:cache"),JJ=class{constructor(e){let{metadataDir:r,metadataBaseUrl:s,targetDir:a,targetBaseUrl:n,fetcher:c,config:f}=e;this.dir=r,this.metadataBaseUrl=s,this.targetDir=a,this.targetBaseUrl=n,this.forceCache=e.forceCache??!1;let p=this.loadLocalMetadata(xA.MetadataKind.Root);this.trustedSet=new Gvt.TrustedMetadataStore(p),this.config={...Hvt.defaultConfig,...f},this.fetcher=c||new jvt.DefaultFetcher({timeout:this.config.fetchTimeout,retry:this.config.fetchRetries??this.config.fetchRetry})}async refresh(){if(this.forceCache)try{await this.loadTimestamp({checkRemote:!1})}catch{await this.loadRoot(),await this.loadTimestamp()}else await this.loadRoot(),await this.loadTimestamp();await this.loadSnapshot(),await this.loadTargets(xA.MetadataKind.Targets,xA.MetadataKind.Root)}async getTargetInfo(e){return this.trustedSet.targets||await this.refresh(),this.preorderDepthFirstWalk(e)}async downloadTarget(e,r,s){let a=r||this.generateTargetPath(e);if(!s){if(!this.targetBaseUrl)throw new fy.ValueError("Target base URL not set");s=this.targetBaseUrl}let n=e.path;if(this.trustedSet.root.signed.consistentSnapshot&&this.config.prefixTargetsWithHash){let p=Object.values(e.hashes),{dir:h,base:E}=fL.parse(n),C=`${p[0]}.${E}`;n=h?`${h}/${C}`:C}let f=jb.join(s,n);return await this.fetcher.downloadFile(f,e.length,async p=>{await e.verify(m1.createReadStream(p)),VJ("WRITE %s",a),m1.copyFileSync(p,a)}),a}async findCachedTarget(e,r){r||(r=this.generateTargetPath(e));try{if(m1.existsSync(r))return await e.verify(m1.createReadStream(r)),r}catch{return}}loadLocalMetadata(e){let r=fL.join(this.dir,`${e}.json`);return VJ("READ %s",r),m1.readFileSync(r)}async loadRoot(){let r=this.trustedSet.root.signed.version+1,s=r+this.config.maxRootRotations;for(let a=r;a0;){let{roleName:a,parentRoleName:n}=r.pop();if(s.has(a))continue;let c=(await this.loadTargets(a,n))?.signed;if(!c)continue;let f=c.targets?.[e];if(f)return f;if(s.add(a),c.delegations){let p=[],h=c.delegations.rolesForTarget(e);for(let{role:E,terminating:C}of h)if(p.push({roleName:E,parentRoleName:a}),C){r.splice(0);break}p.reverse(),r.push(...p)}}}generateTargetPath(e){if(!this.targetDir)throw new fy.ValueError("Target directory not set");let r=encodeURIComponent(e.path);return fL.join(this.targetDir,r)}persistMetadata(e,r){let s=encodeURIComponent(e);try{let a=fL.join(this.dir,`${s}.json`);VJ("WRITE %s",a),m1.writeFileSync(a,r.toString("utf8"))}catch(a){throw new fy.PersistError(`Failed to persist metadata ${s} error: ${a}`)}}};nu.Updater=JJ});var $Pe=_(Qg=>{"use strict";Object.defineProperty(Qg,"__esModule",{value:!0});Qg.Updater=Qg.BaseFetcher=Qg.TargetFile=void 0;var qvt=eL();Object.defineProperty(Qg,"TargetFile",{enumerable:!0,get:function(){return qvt.TargetFile}});var Wvt=qJ();Object.defineProperty(Qg,"BaseFetcher",{enumerable:!0,get:function(){return Wvt.BaseFetcher}});var Yvt=ZPe();Object.defineProperty(Qg,"Updater",{enumerable:!0,get:function(){return Yvt.Updater}})});var XJ=_(AL=>{"use strict";Object.defineProperty(AL,"__esModule",{value:!0});AL.TUFError=void 0;var zJ=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.code=e,this.cause=s,this.name=this.constructor.name}};AL.TUFError=zJ});var exe=_(Gb=>{"use strict";var Vvt=Gb&&Gb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Gb,"__esModule",{value:!0});Gb.readTarget=Kvt;var Jvt=Vvt(Ie("fs")),pL=XJ();async function Kvt(t,e){let r=await zvt(t,e);return new Promise((s,a)=>{Jvt.default.readFile(r,"utf-8",(n,c)=>{n?a(new pL.TUFError({code:"TUF_READ_TARGET_ERROR",message:`error reading target ${r}`,cause:n})):s(c)})})}async function zvt(t,e){let r;try{r=await t.getTargetInfo(e)}catch(a){throw new pL.TUFError({code:"TUF_REFRESH_METADATA_ERROR",message:"error refreshing TUF metadata",cause:a})}if(!r)throw new pL.TUFError({code:"TUF_FIND_TARGET_ERROR",message:`target ${e} not found`});let s=await t.findCachedTarget(r);if(!s)try{s=await t.downloadTarget(r)}catch(a){throw new pL.TUFError({code:"TUF_DOWNLOAD_TARGET_ERROR",message:`error downloading target ${s}`,cause:a})}return s}});var txe=_((Uir,Xvt)=>{Xvt.exports={"https://tuf-repo-cdn.sigstore.dev":{"root.json":"ewogInNpZ25hdHVyZXMiOiBbCiAgewogICAia2V5aWQiOiAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyIsCiAgICJzaWciOiAiMzA0NjAyMjEwMDhhYjFmNmYxN2Q0ZjllNmQ3ZGNmMWM4ODkxMmI2YjUzY2MxMDM4ODY0NGFlMWYwOWJjMzdhMDgyY2QwNjAwM2UwMjIxMDBlMTQ1ZWY0YzdiNzgyZDRlODEwN2I1MzQzN2U2NjlkMDQ3Njg5MmNlOTk5OTAzYWUzM2QxNDQ0ODM2Njk5NmU3IgogIH0sCiAgewogICAia2V5aWQiOiAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICJzaWciOiAiMzA0NTAyMjEwMGM3NjhiMmY4NmRhOTk1NjkwMTljMTYwYTA4MWRhNTRhZTM2YzM0YzBhMzEyMGQzY2I2OWI1M2I3ZDExMzc1OGUwMjIwNGY2NzE1MThmNjE3YjIwZDQ2NTM3ZmFlNmMzYjYzYmFlODkxM2Y0ZjE5NjIxNTYxMDVjYzRmMDE5YWMzNWM2YSIKICB9LAogIHsKICAgImtleWlkIjogIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAic2lnIjogIjMwNDUwMjIxMDBiNDQzNGU2OTk1ZDM2OGQyM2U3NDc1OWFjZDBjYjkwMTNjODNhNWQzNTExZjBmOTk3ZWM1NGM0NTZhZTQzNTBhMDIyMDE1YjBlMjY1ZDE4MmQyYjYxZGM3NGUxNTVkOThiM2MzZmJlNTY0YmEwNTI4NmFhMTRjOGRmMDJjOWI3NTY1MTYiCiAgfSwKICB7CiAgICJrZXlpZCI6ICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgInNpZyI6ICIzMDQ1MDIyMTAwODJjNTg0MTFkOTg5ZWI5Zjg2MTQxMDg1N2Q0MjM4MTU5MGVjOTQyNGRiZGFhNTFlNzhlZDEzNTE1NDMxOTA0ZTAyMjAxMTgxODVkYTZhNmMyOTQ3MTMxYzE3Nzk3ZTJiYjc2MjBjZTI2ZTVmMzAxZDFjZWFjNWYyYTdlNThmOWRjZjJlIgogIH0sCiAgewogICAia2V5aWQiOiAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIsCiAgICJzaWciOiAiMzA0NjAyMjEwMGM3ODUxMzg1NGNhZTljMzJlYWE2Yjg4ZTE4OTEyZjQ4MDA2YzI3NTdhMjU4ZjkxNzMxMmNhYmE3NTk0OGViOWUwMjIxMDBkOWUxYjRjZTBhZGZlOWZkMmUyMTQ4ZDdmYTI3YTJmNDBiYTExMjJiZDY5ZGE3NjEyZDhkMTc3NmIwMTNjOTFkIgogIH0sCiAgewogICAia2V5aWQiOiAiZmRmYTgzYTA3YjVhODM1ODliODdkZWQ0MWY3N2YzOWQyMzJhZDkxZjdjY2U1Mjg2OGRhY2QwNmJhMDg5ODQ5ZiIsCiAgICJzaWciOiAiMzA0NTAyMjA1NjQ4M2EyZDVkOWVhOWNlYzZlMTFlYWRmYjMzYzQ4NGI2MTQyOThmYWNhMTVhY2YxYzQzMWIxMWVkN2Y3MzRjMDIyMTAwZDBjMWQ3MjZhZjkyYTg3ZTRlNjY0NTljYTVhZGYzOGEwNWI0NGUxZjk0MzE4NDIzZjk1NGJhZThiY2E1YmIyZSIKICB9LAogIHsKICAgImtleWlkIjogImUyZjU5YWNiOTQ4ODUxOTQwN2UxOGNiZmM5MzI5NTEwYmUwM2MwNGFjYTk5MjlkMmYwMzAxMzQzZmVjODU1MjMiLAogICAic2lnIjogIjMwNDYwMjIxMDBkMDA0ZGU4ODAyNGMzMmRjNTY1M2E5ZjQ4NDNjZmM1MjE1NDI3MDQ4YWQ5NjAwZDJjZjljOTY5ZTZlZGZmM2QyMDIyMTAwZDllYmI3OThmNWZjNjZhZjEwODk5ZGVjZTAxNGE4NjI4Y2NmM2M1NDAyY2Q0YTQyNzAyMDc0NzJmOGY2ZTcxMiIKICB9LAogIHsKICAgImtleWlkIjogIjNjMzQ0YWEwNjhmZDRjYzRlODdkYzUwYjYxMmMwMjQzMWZiYzc3MWU5NTAwMzk5MzY4M2EyYjBiZjI2MGNmMGUiLAogICAic2lnIjogIjMwNDYwMjIxMDBiN2IwOTk5NmM0NWNhMmQ0YjA1NjAzZTU2YmFlZmEyOTcxOGEwYjcxMTQ3Y2Y4YzZlNjYzNDliYWE2MTQ3N2RmMDIyMTAwYzRkYTgwYzcxN2I0ZmE3YmJhMGZkNWM3MmRhOGEwNDk5MzU4YjAxMzU4YjIzMDlmNDFkMTQ1NmVhMWU3ZTFkOSIKICB9LAogIHsKICAgImtleWlkIjogImVjODE2Njk3MzRlMDE3OTk2YzViODVmM2QwMmMzZGUxZGQ0NjM3YTE1MjAxOWZlMWFmMTI1ZDJmOTM2OGI5NWUiLAogICAic2lnIjogIjMwNDYwMjIxMDBiZTk3ODJjMzA3NDRlNDExYTgyZmE4NWI1MTM4ZDYwMWNlMTQ4YmMxOTI1OGFlYzY0ZTdlYzI0NDc4ZjM4ODEyMDIyMTAwY2FlZjYzZGNhZjFhNGI5YTUwMGQzYmQwZTNmMTY0ZWMxOGYxYjYzZDdhOTQ2MGQ5YWNhYjEwNjZkYjBmMDE2ZCIKICB9LAogIHsKICAgImtleWlkIjogIjFlMWQ2NWNlOThiMTBhZGRhZDQ3NjRmZWJmN2RkYTJkMDQzNmIzZDNhMzg5MzU3OWMwZGRkYWVhMjBlNTQ4NDkiLAogICAic2lnIjogIjMwNDUwMjIwNzQ2ZWMzZjg1MzRjZTU1NTMxZDBkMDFmZjY0OTY0ZWY0NDBkMWU3ZDJjNGMxNDI0MDliOGU5NzY5ZjFhZGE2ZjAyMjEwMGUzYjkyOWZjZDkzZWExOGZlYWEwODI1ODg3YTcyMTA0ODk4NzlhNjY3ODBjMDdhODNmNGJkNDZlMmYwOWFiM2IiCiAgfQogXSwKICJzaWduZWQiOiB7CiAgIl90eXBlIjogInJvb3QiLAogICJjb25zaXN0ZW50X3NuYXBzaG90IjogdHJ1ZSwKICAiZXhwaXJlcyI6ICIyMDI1LTAyLTE5VDA4OjA0OjMyWiIsCiAgImtleXMiOiB7CiAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFekJ6Vk9tSENQb2pNVkxTSTM2NFdpaVY4TlByRFxuNklnUnhWbGlza3ovdit5M0pFUjVtY1ZHY09ObGlEY1dNQzVKMmxmSG1qUE5QaGI0SDd4bThMemZTQT09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBzYW50aWFnb3RvcnJlcyIKICAgfSwKICAgIjYxNjQzODM4MTI1YjQ0MGI0MGRiNjk0MmY1Y2I1YTMxYzBkYzA0MzY4MzE2ZWIyYWFhNThiOTU5MDRhNTgyMjIiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVpbmlrU3NBUW1Za05lSDVlWXEvQ25JekxhYWNPXG54bFNhYXdRRE93cUt5L3RDcXhxNXh4UFNKYzIxSzRXSWhzOUd5T2tLZnp1ZVkzR0lMemNNSlo0Y1d3PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGJvYmNhbGxhd2F5IgogICB9LAogICAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXk4WEtzbWhCWURJOEpjMEd3ekJ4ZUtheDBjbTVcblNUS0VVNjVIUEZ1blVuNDFzVDhwaTBGak00SWtIei9ZVW13bUxVTzBXdDdseGhqNkJrTElLNHFZQXc9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAZGxvcmVuYyIKICAgfSwKICAgIjcyNDdmMGRiYWQ4NWIxNDdlMTg2M2JhZGU3NjEyNDNjYzc4NWRjYjdhYTQxMGU3MTA1ZGQzZDJiNjFhMzZkMmMiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVXUmlHcjUraiszSjVTc0grWnRyNW5FMkgyd083XG5CVituTzNzOTNnTGNhMThxVE96SFkxb1d5QUdEeWtNU3NHVFVCU3Q5RCtBbjBLZktzRDJtZlNNNDJRPT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2ktb25saW5lLXVyaSI6ICJnY3BrbXM6Ly9wcm9qZWN0cy9zaWdzdG9yZS1yb290LXNpZ25pbmcvbG9jYXRpb25zL2dsb2JhbC9rZXlSaW5ncy9yb290L2NyeXB0b0tleXMvdGltZXN0YW1wIgogICB9LAogICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTBnaHJoOTJMdzFZcjNpZEdWNVdxQ3RNREI4Q3hcbitEOGhkQzR3MlpMTklwbFZSb1ZHTHNrWWEzZ2hlTXlPamlKOGtQaTE1YVEyLy83UCtvajdVdkpQR3c9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAam9zaHVhZ2wiCiAgIH0sCiAgICJlNzFhNTRkNTQzODM1YmE4NmFkYWQ5NDYwMzc5Yzc2NDFmYjg3MjZkMTY0ZWE3NjY4MDFhMWM1MjJhYmE3ZWEyIjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFRVhzejNTWlhGYjhqTVY0Mmo2cEpseWpialI4S1xuTjNCd29jZXhxNkxNSWI1cXNXS09RdkxOMTZOVWVmTGM0SHN3T291bVJzVlZhYWpTcFFTNmZvYmtSdz09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBtbm02NzgiCiAgIH0KICB9LAogICJyb2xlcyI6IHsKICAgInJvb3QiOiB7CiAgICAia2V5aWRzIjogWwogICAgICI2ZjI2MDA4OWQ1OTIzZGFmMjAxNjZjYTY1N2M1NDNhZjYxODM0NmFiOTcxODg0YTk5OTYyYjAxOTg4YmJlMGMzIiwKICAgICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICAgIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIKICAgIF0sCiAgICAidGhyZXNob2xkIjogMwogICB9LAogICAic25hcHNob3QiOiB7CiAgICAia2V5aWRzIjogWwogICAgICI3MjQ3ZjBkYmFkODViMTQ3ZTE4NjNiYWRlNzYxMjQzY2M3ODVkY2I3YWE0MTBlNzEwNWRkM2QyYjYxYTM2ZDJjIgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAxLAogICAgIngtdHVmLW9uLWNpLWV4cGlyeS1wZXJpb2QiOiAzNjUwLAogICAgIngtdHVmLW9uLWNpLXNpZ25pbmctcGVyaW9kIjogMzY1CiAgIH0sCiAgICJ0YXJnZXRzIjogewogICAgImtleWlkcyI6IFsKICAgICAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyIsCiAgICAgImU3MWE1NGQ1NDM4MzViYTg2YWRhZDk0NjAzNzljNzY0MWZiODcyNmQxNjRlYTc2NjgwMWExYzUyMmFiYTdlYTIiLAogICAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IiwKICAgICAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDMKICAgfSwKICAgInRpbWVzdGFtcCI6IHsKICAgICJrZXlpZHMiOiBbCiAgICAgIjcyNDdmMGRiYWQ4NWIxNDdlMTg2M2JhZGU3NjEyNDNjYzc4NWRjYjdhYTQxMGU3MTA1ZGQzZDJiNjFhMzZkMmMiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDEsCiAgICAieC10dWYtb24tY2ktZXhwaXJ5LXBlcmlvZCI6IDcsCiAgICAieC10dWYtb24tY2ktc2lnbmluZy1wZXJpb2QiOiA0CiAgIH0KICB9LAogICJzcGVjX3ZlcnNpb24iOiAiMS4wIiwKICAidmVyc2lvbiI6IDEwLAogICJ4LXR1Zi1vbi1jaS1leHBpcnktcGVyaW9kIjogMTgyLAogICJ4LXR1Zi1vbi1jaS1zaWduaW5nLXBlcmlvZCI6IDMxCiB9Cn0=",targets:{"trusted_root.json":"ewogICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRldi5zaWdzdG9yZS50cnVzdGVkcm9vdCtqc29uO3ZlcnNpb249MC4xIiwKICAidGxvZ3MiOiBbCiAgICB7CiAgICAgICJiYXNlVXJsIjogImh0dHBzOi8vcmVrb3Iuc2lnc3RvcmUuZGV2IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUyRzJZKzJ0YWJkVFY1QmNHaUJJeDBhOWZBRndya0JibUxTR3RrczRMM3FYNnlZWTB6dWZCbmhDOFVyL2l5NTVHaFdQLzlBL2JZMkxoQzMwTTkrUll0dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDEtMTJUMTE6NTM6MjcuMDAwWiIKICAgICAgICB9CiAgICAgIH0sCiAgICAgICJsb2dJZCI6IHsKICAgICAgICAia2V5SWQiOiAid05JOWF0UUdseitWV2ZPNkxSeWdINFFVZlkvOFc0UkZ3aVQ1aTVXUmdCMD0iCiAgICAgIH0KICAgIH0KICBdLAogICJjZXJ0aWZpY2F0ZUF1dGhvcml0aWVzIjogWwogICAgewogICAgICAic3ViamVjdCI6IHsKICAgICAgICAib3JnYW5pemF0aW9uIjogInNpZ3N0b3JlLmRldiIsCiAgICAgICAgImNvbW1vbk5hbWUiOiAic2lnc3RvcmUiCiAgICAgIH0sCiAgICAgICJ1cmkiOiAiaHR0cHM6Ly9mdWxjaW8uc2lnc3RvcmUuZGV2IiwKICAgICAgImNlcnRDaGFpbiI6IHsKICAgICAgICAiY2VydGlmaWNhdGVzIjogWwogICAgICAgICAgewogICAgICAgICAgICAicmF3Qnl0ZXMiOiAiTUlJQitEQ0NBWDZnQXdJQkFnSVROVmtEWm9DaW9mUERzeTdkZm02Z2VMYnVoekFLQmdncWhrak9QUVFEQXpBcU1SVXdFd1lEVlFRS0V3eHphV2R6ZEc5eVpTNWtaWFl4RVRBUEJnTlZCQU1UQ0hOcFozTjBiM0psTUI0WERUSXhNRE13TnpBek1qQXlPVm9YRFRNeE1ESXlNekF6TWpBeU9Wb3dLakVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1SRXdEd1lEVlFRREV3aHphV2R6ZEc5eVpUQjJNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWlBMklBQkxTeUE3SWk1aytwTk84WkVXWTB5bGVtV0Rvd09rTmEza0wrR1pFNVo1R1dlaEw5L0E5YlJOQTNSYnJzWjVpMEpjYXN0YVJMN1NwNWZwL2pENWR4cWMvVWRUVm5sdlMxNmFuKzJZZnN3ZS9RdUxvbFJVQ3JjT0UyKzJpQTUrdHpkNk5tTUdRd0RnWURWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFFd0hRWURWUjBPQkJZRUZNakZIUUJCbWlRcE1sRWs2dzJ1U3UxS0J0UHNNQjhHQTFVZEl3UVlNQmFBRk1qRkhRQkJtaVFwTWxFazZ3MnVTdTFLQnRQc01Bb0dDQ3FHU000OUJBTURBMmdBTUdVQ01IOGxpV0pmTXVpNnZYWEJoakRnWTRNd3NsbU4vVEp4VmUvODNXckZvbXdtTmYwNTZ5MVg0OEY5YzRtM2Ezb3pYQUl4QUtqUmF5NS9hai9qc0tLR0lrbVFhdGpJOHV1cEhyLytDeEZ2YUpXbXBZcU5rTERHUlUrOW9yemg1aEkyUnJjdWFRPT0iCiAgICAgICAgICB9CiAgICAgICAgXQogICAgICB9LAogICAgICAidmFsaWRGb3IiOiB7CiAgICAgICAgInN0YXJ0IjogIjIwMjEtMDMtMDdUMDM6MjA6MjkuMDAwWiIsCiAgICAgICAgImVuZCI6ICIyMDIyLTEyLTMxVDIzOjU5OjU5Ljk5OVoiCiAgICAgIH0KICAgIH0sCiAgICB7CiAgICAgICJzdWJqZWN0IjogewogICAgICAgICJvcmdhbml6YXRpb24iOiAic2lnc3RvcmUuZGV2IiwKICAgICAgICAiY29tbW9uTmFtZSI6ICJzaWdzdG9yZSIKICAgICAgfSwKICAgICAgInVyaSI6ICJodHRwczovL2Z1bGNpby5zaWdzdG9yZS5kZXYiLAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlDR2pDQ0FhR2dBd0lCQWdJVUFMblZpVmZuVTBickphc21Sa0hybi9VbmZhUXdDZ1lJS29aSXpqMEVBd013S2pFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUkV3RHdZRFZRUURFd2h6YVdkemRHOXlaVEFlRncweU1qQTBNVE15TURBMk1UVmFGdzB6TVRFd01EVXhNelUyTlRoYU1EY3hGVEFUQmdOVkJBb1RESE5wWjNOMGIzSmxMbVJsZGpFZU1Cd0dBMVVFQXhNVmMybG5jM1J2Y21VdGFXNTBaWEp0WldScFlYUmxNSFl3RUFZSEtvWkl6ajBDQVFZRks0RUVBQ0lEWWdBRThSVlMveXNIK05PdnVEWnlQSVp0aWxnVUY5TmxhcllwQWQ5SFAxdkJCSDFVNUNWNzdMU1M3czBaaUg0bkU3SHY3cHRTNkx2dlIvU1RrNzk4TFZnTXpMbEo0SGVJZkYzdEhTYWV4TGNZcFNBU3Ixa1MwTi9SZ0JKei85aldDaVhubzNzd2VUQU9CZ05WSFE4QkFmOEVCQU1DQVFZd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdFZ1lEVlIwVEFRSC9CQWd3QmdFQi93SUJBREFkQmdOVkhRNEVGZ1FVMzlQcHoxWWtFWmI1cU5qcEtGV2l4aTRZWkQ4d0h3WURWUjBqQkJnd0ZvQVVXTUFlWDVGRnBXYXBlc3lRb1pNaTBDckZ4Zm93Q2dZSUtvWkl6ajBFQXdNRFp3QXdaQUl3UENzUUs0RFlpWllEUElhRGk1SEZLbmZ4WHg2QVNTVm1FUmZzeW5ZQmlYMlg2U0pSblpVODQvOURaZG5GdnZ4bUFqQk90NlFwQmxjNEovMER4dmtUQ3FwY2x2emlMNkJDQ1BuamRsSUIzUHUzQnhzUG15Z1VZN0lpMnpiZENkbGlpb3c9IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgInJhd0J5dGVzIjogIk1JSUI5ekNDQVh5Z0F3SUJBZ0lVQUxaTkFQRmR4SFB3amVEbG9Ed3lZQ2hBTy80d0NnWUlLb1pJemowRUF3TXdLakVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1SRXdEd1lEVlFRREV3aHphV2R6ZEc5eVpUQWVGdzB5TVRFd01EY3hNelUyTlRsYUZ3MHpNVEV3TURVeE16VTJOVGhhTUNveEZUQVRCZ05WQkFvVERITnBaM04wYjNKbExtUmxkakVSTUE4R0ExVUVBeE1JYzJsbmMzUnZjbVV3ZGpBUUJnY3Foa2pPUFFJQkJnVXJnUVFBSWdOaUFBVDdYZUZUNHJiM1BRR3dTNElhanRMazMvT2xucGdhbmdhQmNsWXBzWUJyNWkrNHluQjA3Y2ViM0xQME9JT1pkeGV4WDY5YzVpVnV5SlJRK0h6MDV5aStVRjN1QldBbEhwaVM1c2gwK0gyR0hFN1NYcmsxRUM1bTFUcjE5TDlnZzkyall6QmhNQTRHQTFVZER3RUIvd1FFQXdJQkJqQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCUll3QjVma1VXbFpxbDZ6SkNoa3lMUUtzWEYrakFmQmdOVkhTTUVHREFXZ0JSWXdCNWZrVVdsWnFsNnpKQ2hreUxRS3NYRitqQUtCZ2dxaGtqT1BRUURBd05wQURCbUFqRUFqMW5IZVhacCsxM05XQk5hK0VEc0RQOEcxV1dnMXRDTVdQL1dIUHFwYVZvMGpoc3dlTkZaZ1NzMGVFN3dZSTRxQWpFQTJXQjlvdDk4c0lrb0YzdlpZZGQzL1Z0V0I1YjlUTk1lYTdJeC9zdEo1VGZjTExlQUJMRTRCTkpPc1E0dm5CSEoiCiAgICAgICAgICB9CiAgICAgICAgXQogICAgICB9LAogICAgICAidmFsaWRGb3IiOiB7CiAgICAgICAgInN0YXJ0IjogIjIwMjItMDQtMTNUMjA6MDY6MTUuMDAwWiIKICAgICAgfQogICAgfQogIF0sCiAgImN0bG9ncyI6IFsKICAgIHsKICAgICAgImJhc2VVcmwiOiAiaHR0cHM6Ly9jdGZlLnNpZ3N0b3JlLmRldi90ZXN0IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUViZndSK1JKdWRYc2NnUkJScEtYMVhGRHkzUHl1ZER4ei9TZm5SaTFmVDhla3BmQmQyTzF1b3o3anIzWjhuS3p4QTY5RVVRK2VGQ0ZJM3pldWJQV1U3dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDMtMTRUMDA6MDA6MDAuMDAwWiIsCiAgICAgICAgICAiZW5kIjogIjIwMjItMTAtMzFUMjM6NTk6NTkuOTk5WiIKICAgICAgICB9CiAgICAgIH0sCiAgICAgICJsb2dJZCI6IHsKICAgICAgICAia2V5SWQiOiAiQ0dDUzhDaFMvMmhGMGRGcko0U2NSV2NZckJZOXd6alNiZWE4SWdZMmIzST0iCiAgICAgIH0KICAgIH0sCiAgICB7CiAgICAgICJiYXNlVXJsIjogImh0dHBzOi8vY3RmZS5zaWdzdG9yZS5kZXYvMjAyMiIsCiAgICAgICJoYXNoQWxnb3JpdGhtIjogIlNIQTJfMjU2IiwKICAgICAgInB1YmxpY0tleSI6IHsKICAgICAgICAicmF3Qnl0ZXMiOiAiTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFaVBTbEZpMENtRlRmRWpDVXFGOUh1Q0VjWVhOS0FhWWFsSUptQlo4eXllelBqVHFoeHJLQnBNbmFvY1Z0TEpCSTFlTTN1WG5RelFHQUpkSjRnczlGeXc9PSIsCiAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAgICJzdGFydCI6ICIyMDIyLTEwLTIwVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgfQogICAgICB9LAogICAgICAibG9nSWQiOiB7CiAgICAgICAgImtleUlkIjogIjNUMHdhc2JIRVRKakdSNGNtV2MzQXFKS1hyamVQSzMvaDRweWdDOHA3bzQ9IgogICAgICB9CiAgICB9CiAgXSwKICAidGltZXN0YW1wQXV0aG9yaXRpZXMiOiBbCiAgICB7CiAgICAgICJzdWJqZWN0IjogewogICAgICAgICJvcmdhbml6YXRpb24iOiAiR2l0SHViLCBJbmMuIiwKICAgICAgICAiY29tbW9uTmFtZSI6ICJJbnRlcm5hbCBTZXJ2aWNlcyBSb290IgogICAgICB9LAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlCM0RDQ0FXS2dBd0lCQWdJVWNoa05zSDM2WGEwNGIxTHFJYytxcjlEVmVjTXdDZ1lJS29aSXpqMEVBd013TWpFVk1CTUdBMVVFQ2hNTVIybDBTSFZpTENCSmJtTXVNUmt3RndZRFZRUURFeEJVVTBFZ2FXNTBaWEp0WldScFlYUmxNQjRYRFRJek1EUXhOREF3TURBd01Gb1hEVEkwTURReE16QXdNREF3TUZvd01qRVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVJrd0Z3WURWUVFERXhCVVUwRWdWR2x0WlhOMFlXMXdhVzVuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFVUQ1Wk5iU3FZTWQ2cjhxcE9PRVg5aWJHblpUOUdzdVhPaHIvZjhVOUZKdWdCR0V4S1lwNDBPVUxTMGVyalpXN3hWOXhWNTJObkpmNU9lRHE0ZTVaS3FOV01GUXdEZ1lEVlIwUEFRSC9CQVFEQWdlQU1CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUlNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqQkJnd0ZvQVVhVzFSdWRPZ1Z0MGxlcVkwV0tZYnVQcjQ3d0F3Q2dZSUtvWkl6ajBFQXdNRGFBQXdaUUl3YlVIOUh2RDRlakNaSk9XUW5xQWxrcVVSbGx2dTlNOCtWcUxiaVJLK3pTZlpDWndzaWxqUm44TVFRUlNrWEVFNUFqRUFnK1Z4cXRvamZWZnU4RGh6emhDeDlHS0VUYkpIYjE5aVY3Mm1NS1ViREFGbXpaNmJROGI1NFpiOHRpZHk1YVdlIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgInJhd0J5dGVzIjogIk1JSUNFRENDQVpXZ0F3SUJBZ0lVWDhaTzVRWFA3dk40ZE1RNWU5c1UzbnViOE9nd0NnWUlLb1pJemowRUF3TXdPREVWTUJNR0ExVUVDaE1NUjJsMFNIVmlMQ0JKYm1NdU1SOHdIUVlEVlFRREV4WkpiblJsY201aGJDQlRaWEoyYVdObGN5QlNiMjkwTUI0WERUSXpNRFF4TkRBd01EQXdNRm9YRFRJNE1EUXhNakF3TURBd01Gb3dNakVWTUJNR0ExVUVDaE1NUjJsMFNIVmlMQ0JKYm1NdU1Sa3dGd1lEVlFRREV4QlVVMEVnYVc1MFpYSnRaV1JwWVhSbE1IWXdFQVlIS29aSXpqMENBUVlGSzRFRUFDSURZZ0FFdk1MWS9kVFZidklKWUFOQXVzekV3Sm5RRTFsbGZ0eW55TUtJTWhoNDhIbXFiVnI1eWd5YnpzTFJMVktiQldPZFoyMWFlSnorZ1ppeXRaZXRxY3lGOVdsRVI1TkVNZjZKVjdaTm9qUXB4SHE0UkhHb0dTY2VRdi9xdlRpWnhFREtvMll3WkRBT0JnTlZIUThCQWY4RUJBTUNBUVl3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBZEJnTlZIUTRFRmdRVWFXMVJ1ZE9nVnQwbGVxWTBXS1lidVByNDd3QXdId1lEVlIwakJCZ3dGb0FVOU5ZWWxvYm5BRzRjMC9xanh5SC9scS93eitRd0NnWUlLb1pJemowRUF3TURhUUF3WmdJeEFLMUIxODV5Z0NySVlGbElzM0dqc3dqbndTTUc2TFk4d29MVmRha0tEWnhWYThmOGNxTXMxRGhjeEowKzA5dzk1UUl4QU8rdEJ6Wms3dmpVSjlpSmdENFI2WldUeFFXS3FObTc0ak85OW8rbzlzdjRGSS9TWlRaVEZ5TW4wSUpFSGRObXlBPT0iCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAicmF3Qnl0ZXMiOiAiTUlJQjlEQ0NBWHFnQXdJQkFnSVVhL0pBa2RVaks0SlV3c3F0YWlSSkdXaHFMU293Q2dZSUtvWkl6ajBFQXdNd09ERVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVI4d0hRWURWUVFERXhaSmJuUmxjbTVoYkNCVFpYSjJhV05sY3lCU2IyOTBNQjRYRFRJek1EUXhOREF3TURBd01Gb1hEVE16TURReE1UQXdNREF3TUZvd09ERVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVI4d0hRWURWUVFERXhaSmJuUmxjbTVoYkNCVFpYSjJhV05sY3lCU2IyOTBNSFl3RUFZSEtvWkl6ajBDQVFZRks0RUVBQ0lEWWdBRWY5akZBWHh6NGt4NjhBSFJNT2tGQmhmbERjTVR2emFYejR4L0ZDY1hqSi8xcUVLb24vcVBJR25hVVJza0R0eU5iTkRPcGVKVERERnF0NDhpTVBybnpweDZJWndxZW1mVUpONHhCRVpmemErcFl0L2l5b2QrOXRacjIwUlJXU3YvbzBVd1F6QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0VnWURWUjBUQVFIL0JBZ3dCZ0VCL3dJQkFqQWRCZ05WSFE0RUZnUVU5TllZbG9ibkFHNGMwL3FqeHlIL2xxL3d6K1F3Q2dZSUtvWkl6ajBFQXdNRGFBQXdaUUl4QUxaTFo4QmdSWHpLeExNTU45VklsTytlNGhyQm5OQmdGN3R6N0hucm93djJOZXRaRXJJQUNLRnltQmx2V0R2dE1BSXdaTytraTZzc1ExYnNabzk4TzhtRUFmMk5aN2lpQ2dERFUwVndqZWNvNnp5ZWgwekJUczkvN2dWNkFITlE1M3hEIgogICAgICAgICAgfQogICAgICAgIF0KICAgICAgfSwKICAgICAgInZhbGlkRm9yIjogewogICAgICAgICJzdGFydCI6ICIyMDIzLTA0LTE0VDAwOjAwOjAwLjAwMFoiCiAgICAgIH0KICAgIH0KICBdCn0K","registry.npmjs.org%2Fkeys.json":"ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OmpsM2J3c3d1ODBQampva0NnaDBvMnc1YzJVNExoUUFFNTdnajljejFrekEiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTFPbGIzek1BRkZ4WEtIaUlrUU81Y0ozWWhsNWk2VVBwK0lodXRlQkpidUhjQTVVb2dLbzBFV3RsV3dXNktTYUtvVE5FWUw3SmxDUWlWbmtoQmt0VWdnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIxOTk5LTAxLTAxVDAwOjAwOjAwLjAwMFoiLAogICAgICAgICAgICAgICAgICAgICJlbmQiOiAiMjAyNS0wMS0yOVQwMDowMDowMC4wMDBaIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJrZXlJZCI6ICJTSEEyNTY6amwzYndzd3U4MFBqam9rQ2doMG8ydzVjMlU0TGhRQUU1N2dqOWN6MWt6QSIsCiAgICAgICAgICAgICJrZXlVc2FnZSI6ICJucG06YXR0ZXN0YXRpb25zIiwKICAgICAgICAgICAgInB1YmxpY0tleSI6IHsKICAgICAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUxT2xiM3pNQUZGeFhLSGlJa1FPNWNKM1lobDVpNlVQcCtJaHV0ZUJKYnVIY0E1VW9nS28wRVd0bFd3VzZLU2FLb1RORVlMN0psQ1FpVm5raEJrdFVnZz09IiwKICAgICAgICAgICAgICAgICJrZXlEZXRhaWxzIjogIlBLSVhfRUNEU0FfUDI1Nl9TSEFfMjU2IiwKICAgICAgICAgICAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAiMjAyMi0xMi0wMVQwMDowMDowMC4wMDBaIiwKICAgICAgICAgICAgICAgICAgICAiZW5kIjogIjIwMjUtMDEtMjlUMDA6MDA6MDAuMDAwWiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OkRoUTh3UjVBUEJ2RkhMRi8rVGMrQVl2UE9kVHBjSURxT2h4c0JIUndDN1UiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgImtleUlkIjogIlNIQTI1NjpEaFE4d1I1QVBCdkZITEYvK1RjK0FZdlBPZFRwY0lEcU9oeHNCSFJ3QzdVIiwKICAgICAgICAgICAgImtleVVzYWdlIjogIm5wbTphdHRlc3RhdGlvbnMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K"}}}});var nxe=_(y1=>{"use strict";var rxe=y1&&y1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(y1,"__esModule",{value:!0});y1.TUFClient=void 0;var Tg=rxe(Ie("fs")),qb=rxe(Ie("path")),Zvt=$Pe(),$vt=hL(),eSt=exe(),$J="targets",ZJ=class{constructor(e){let r=new URL(e.mirrorURL),s=encodeURIComponent(r.host+r.pathname.replace(/\/$/,"")),a=qb.default.join(e.cachePath,s);tSt(a),rSt({cachePath:a,mirrorURL:e.mirrorURL,tufRootPath:e.rootPath,forceInit:e.forceInit}),this.updater=nSt({mirrorURL:e.mirrorURL,cachePath:a,forceCache:e.forceCache,retry:e.retry,timeout:e.timeout})}async refresh(){return this.updater.refresh()}getTarget(e){return(0,eSt.readTarget)(this.updater,e)}};y1.TUFClient=ZJ;function tSt(t){let e=qb.default.join(t,$J);Tg.default.existsSync(t)||Tg.default.mkdirSync(t,{recursive:!0}),Tg.default.existsSync(e)||Tg.default.mkdirSync(e)}function rSt({cachePath:t,mirrorURL:e,tufRootPath:r,forceInit:s}){let a=qb.default.join(t,"root.json");if(!Tg.default.existsSync(a)||s)if(r)Tg.default.copyFileSync(r,a);else{let c=txe()[e];if(!c)throw new $vt.TUFError({code:"TUF_INIT_CACHE_ERROR",message:`No root.json found for mirror: ${e}`});Tg.default.writeFileSync(a,Buffer.from(c["root.json"],"base64")),Object.entries(c.targets).forEach(([f,p])=>{Tg.default.writeFileSync(qb.default.join(t,$J,f),Buffer.from(p,"base64"))})}}function nSt(t){let e={fetchTimeout:t.timeout,fetchRetry:t.retry};return new Zvt.Updater({metadataBaseUrl:t.mirrorURL,targetBaseUrl:`${t.mirrorURL}/targets`,metadataDir:t.cachePath,targetDir:qb.default.join(t.cachePath,$J),forceCache:t.forceCache,config:e})}});var hL=_(gh=>{"use strict";Object.defineProperty(gh,"__esModule",{value:!0});gh.TUFError=gh.DEFAULT_MIRROR_URL=void 0;gh.getTrustedRoot=fSt;gh.initTUF=ASt;var iSt=yb(),sSt=Obe(),oSt=nxe();gh.DEFAULT_MIRROR_URL="https://tuf-repo-cdn.sigstore.dev";var aSt="sigstore-js",lSt={retries:2},cSt=5e3,uSt="trusted_root.json";async function fSt(t={}){let r=await ixe(t).getTarget(uSt);return iSt.TrustedRoot.fromJSON(JSON.parse(r))}async function ASt(t={}){let e=ixe(t);return e.refresh().then(()=>e)}function ixe(t){return new oSt.TUFClient({cachePath:t.cachePath||(0,sSt.appDataPath)(aSt),rootPath:t.rootPath,mirrorURL:t.mirrorURL||gh.DEFAULT_MIRROR_URL,retry:t.retry??lSt,timeout:t.timeout??cSt,forceCache:t.forceCache??!1,forceInit:t.forceInit??t.force??!1})}var pSt=XJ();Object.defineProperty(gh,"TUFError",{enumerable:!0,get:function(){return pSt.TUFError}})});var sxe=_(gL=>{"use strict";Object.defineProperty(gL,"__esModule",{value:!0});gL.DSSESignatureContent=void 0;var Wb=Cl(),eK=class{constructor(e){this.env=e}compareDigest(e){return Wb.crypto.bufferEqual(e,Wb.crypto.digest("sha256",this.env.payload))}compareSignature(e){return Wb.crypto.bufferEqual(e,this.signature)}verifySignature(e){return Wb.crypto.verify(this.preAuthEncoding,e,this.signature)}get signature(){return this.env.signatures.length>0?this.env.signatures[0].sig:Buffer.from("")}get preAuthEncoding(){return Wb.dsse.preAuthEncoding(this.env.payloadType,this.env.payload)}};gL.DSSESignatureContent=eK});var oxe=_(dL=>{"use strict";Object.defineProperty(dL,"__esModule",{value:!0});dL.MessageSignatureContent=void 0;var tK=Cl(),rK=class{constructor(e,r){this.signature=e.signature,this.messageDigest=e.messageDigest.digest,this.artifact=r}compareSignature(e){return tK.crypto.bufferEqual(e,this.signature)}compareDigest(e){return tK.crypto.bufferEqual(e,this.messageDigest)}verifySignature(e){return tK.crypto.verify(this.artifact,e,this.signature)}};dL.MessageSignatureContent=rK});var lxe=_(mL=>{"use strict";Object.defineProperty(mL,"__esModule",{value:!0});mL.toSignedEntity=dSt;mL.signatureContent=axe;var nK=Cl(),hSt=sxe(),gSt=oxe();function dSt(t,e){let{tlogEntries:r,timestampVerificationData:s}=t.verificationMaterial,a=[];for(let n of r)a.push({$case:"transparency-log",tlogEntry:n});for(let n of s?.rfc3161Timestamps??[])a.push({$case:"timestamp-authority",timestamp:nK.RFC3161Timestamp.parse(n.signedTimestamp)});return{signature:axe(t,e),key:mSt(t),tlogEntries:r,timestamps:a}}function axe(t,e){switch(t.content.$case){case"dsseEnvelope":return new hSt.DSSESignatureContent(t.content.dsseEnvelope);case"messageSignature":return new gSt.MessageSignatureContent(t.content.messageSignature,e)}}function mSt(t){switch(t.verificationMaterial.content.$case){case"publicKey":return{$case:"public-key",hint:t.verificationMaterial.content.publicKey.hint};case"x509CertificateChain":return{$case:"certificate",certificate:nK.X509Certificate.parse(t.verificationMaterial.content.x509CertificateChain.certificates[0].rawBytes)};case"certificate":return{$case:"certificate",certificate:nK.X509Certificate.parse(t.verificationMaterial.content.certificate.rawBytes)}}}});var Eo=_(E1=>{"use strict";Object.defineProperty(E1,"__esModule",{value:!0});E1.PolicyError=E1.VerificationError=void 0;var yL=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.code=e,this.cause=s,this.name=this.constructor.name}},iK=class extends yL{};E1.VerificationError=iK;var sK=class extends yL{};E1.PolicyError=sK});var cxe=_(EL=>{"use strict";Object.defineProperty(EL,"__esModule",{value:!0});EL.filterCertAuthorities=ySt;EL.filterTLogAuthorities=ESt;function ySt(t,e){return t.filter(r=>r.validFor.start<=e.start&&r.validFor.end>=e.end)}function ESt(t,e){return t.filter(r=>e.logID&&!r.logID.equals(e.logID)?!1:r.validFor.start<=e.targetDate&&e.targetDate<=r.validFor.end)}});var py=_(Ay=>{"use strict";Object.defineProperty(Ay,"__esModule",{value:!0});Ay.filterTLogAuthorities=Ay.filterCertAuthorities=void 0;Ay.toTrustMaterial=CSt;var oK=Cl(),Yb=yb(),ISt=Eo(),aK=new Date(0),lK=new Date(864e13),Axe=cxe();Object.defineProperty(Ay,"filterCertAuthorities",{enumerable:!0,get:function(){return Axe.filterCertAuthorities}});Object.defineProperty(Ay,"filterTLogAuthorities",{enumerable:!0,get:function(){return Axe.filterTLogAuthorities}});function CSt(t,e){let r=typeof e=="function"?e:wSt(e);return{certificateAuthorities:t.certificateAuthorities.map(fxe),timestampAuthorities:t.timestampAuthorities.map(fxe),tlogs:t.tlogs.map(uxe),ctlogs:t.ctlogs.map(uxe),publicKey:r}}function uxe(t){let e=t.publicKey.keyDetails,r=e===Yb.PublicKeyDetails.PKCS1_RSA_PKCS1V5||e===Yb.PublicKeyDetails.PKIX_RSA_PKCS1V5||e===Yb.PublicKeyDetails.PKIX_RSA_PKCS1V15_2048_SHA256||e===Yb.PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256||e===Yb.PublicKeyDetails.PKIX_RSA_PKCS1V15_4096_SHA256?"pkcs1":"spki";return{logID:t.logId.keyId,publicKey:oK.crypto.createPublicKey(t.publicKey.rawBytes,r),validFor:{start:t.publicKey.validFor?.start||aK,end:t.publicKey.validFor?.end||lK}}}function fxe(t){return{certChain:t.certChain.certificates.map(e=>oK.X509Certificate.parse(e.rawBytes)),validFor:{start:t.validFor?.start||aK,end:t.validFor?.end||lK}}}function wSt(t){return e=>{let r=(t||{})[e];if(!r)throw new ISt.VerificationError({code:"PUBLIC_KEY_ERROR",message:`key not found: ${e}`});return{publicKey:oK.crypto.createPublicKey(r.rawBytes),validFor:s=>(r.validFor?.start||aK)<=s&&(r.validFor?.end||lK)>=s}}}});var cK=_(Vb=>{"use strict";Object.defineProperty(Vb,"__esModule",{value:!0});Vb.CertificateChainVerifier=void 0;Vb.verifyCertificateChain=vSt;var hy=Eo(),BSt=py();function vSt(t,e){let r=(0,BSt.filterCertAuthorities)(e,{start:t.notBefore,end:t.notAfter}),s;for(let a of r)try{return new IL({trustedCerts:a.certChain,untrustedCert:t}).verify()}catch(n){s=n}throw new hy.VerificationError({code:"CERTIFICATE_ERROR",message:"Failed to verify certificate chain",cause:s})}var IL=class{constructor(e){this.untrustedCert=e.untrustedCert,this.trustedCerts=e.trustedCerts,this.localCerts=SSt([...e.trustedCerts,e.untrustedCert])}verify(){let e=this.sort();return this.checkPath(e),e}sort(){let e=this.untrustedCert,r=this.buildPaths(e);if(r=r.filter(a=>a.some(n=>this.trustedCerts.includes(n))),r.length===0)throw new hy.VerificationError({code:"CERTIFICATE_ERROR",message:"no trusted certificate path found"});let s=r.reduce((a,n)=>a.length{if(s&&a.extSubjectKeyID){a.extSubjectKeyID.keyIdentifier.equals(s)&&r.push(a);return}a.subject.equals(e.issuer)&&r.push(a)}),r=r.filter(a=>{try{return e.verify(a)}catch{return!1}}),r)}checkPath(e){if(e.length<1)throw new hy.VerificationError({code:"CERTIFICATE_ERROR",message:"certificate chain must contain at least one certificate"});if(!e.slice(1).every(s=>s.isCA))throw new hy.VerificationError({code:"CERTIFICATE_ERROR",message:"intermediate certificate is not a CA"});for(let s=e.length-2;s>=0;s--)if(!e[s].issuer.equals(e[s+1].subject))throw new hy.VerificationError({code:"CERTIFICATE_ERROR",message:"incorrect certificate name chaining"});for(let s=0;s{"use strict";Object.defineProperty(uK,"__esModule",{value:!0});uK.verifySCTs=PSt;var CL=Cl(),DSt=Eo(),bSt=py();function PSt(t,e,r){let s,a=t.clone();for(let p=0;p{if(!(0,bSt.filterTLogAuthorities)(r,{logID:p.logID,targetDate:p.datetime}).some(C=>p.verify(n.buffer,C.publicKey)))throw new DSt.VerificationError({code:"CERTIFICATE_ERROR",message:"SCT verification failed"});return p.logID})}});var gxe=_(wL=>{"use strict";Object.defineProperty(wL,"__esModule",{value:!0});wL.verifyPublicKey=FSt;wL.verifyCertificate=NSt;var xSt=Cl(),hxe=Eo(),kSt=cK(),QSt=pxe(),TSt="1.3.6.1.4.1.57264.1.1",RSt="1.3.6.1.4.1.57264.1.8";function FSt(t,e,r){let s=r.publicKey(t);return e.forEach(a=>{if(!s.validFor(a))throw new hxe.VerificationError({code:"PUBLIC_KEY_ERROR",message:`Public key is not valid for timestamp: ${a.toISOString()}`})}),{key:s.publicKey}}function NSt(t,e,r){let s=(0,kSt.verifyCertificateChain)(t,r.certificateAuthorities);if(!e.every(n=>s.every(c=>c.validForDate(n))))throw new hxe.VerificationError({code:"CERTIFICATE_ERROR",message:"certificate is not valid or expired at the specified date"});return{scts:(0,QSt.verifySCTs)(s[0],s[1],r.ctlogs),signer:OSt(s[0])}}function OSt(t){let e,r=t.extension(RSt);r?e=r.valueObj.subs?.[0]?.value.toString("ascii"):e=t.extension(TSt)?.value.toString("ascii");let s={extensions:{issuer:e},subjectAlternativeName:t.subjectAltName};return{key:xSt.crypto.createPublicKey(t.publicKey),identity:s}}});var mxe=_(BL=>{"use strict";Object.defineProperty(BL,"__esModule",{value:!0});BL.verifySubjectAlternativeName=LSt;BL.verifyExtensions=MSt;var dxe=Eo();function LSt(t,e){if(e===void 0||!e.match(t))throw new dxe.PolicyError({code:"UNTRUSTED_SIGNER_ERROR",message:`certificate identity error - expected ${t}, got ${e}`})}function MSt(t,e={}){let r;for(r in t)if(e[r]!==t[r])throw new dxe.PolicyError({code:"UNTRUSTED_SIGNER_ERROR",message:`invalid certificate extension - expected ${r}=${t[r]}, got ${r}=${e[r]}`})}});var yxe=_(gK=>{"use strict";Object.defineProperty(gK,"__esModule",{value:!0});gK.verifyCheckpoint=HSt;var AK=Cl(),I1=Eo(),USt=py(),fK=` `,_St=/\u2014 (\S+) (\S+)\n/g;function HSt(t,e){let r=(0,USt.filterTLogAuthorities)(e,{targetDate:new Date(Number(t.integratedTime)*1e3)}),s=t.inclusionProof,a=pK.fromString(s.checkpoint.envelope),n=hK.fromString(a.note);if(!jSt(a,r))throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"invalid checkpoint signature"});if(!AK.crypto.bufferEqual(n.logHash,s.rootHash))throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"root hash mismatch"})}function jSt(t,e){let r=Buffer.from(t.note,"utf-8");return t.signatures.every(s=>{let a=e.find(n=>AK.crypto.bufferEqual(n.logID.subarray(0,4),s.keyHint));return a?AK.crypto.verify(r,a.publicKey,s.signature):!1})}var pK=class t{constructor(e,r){this.note=e,this.signatures=r}static fromString(e){if(!e.includes(fK))throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"missing checkpoint separator"});let r=e.indexOf(fK),s=e.slice(0,r+1),n=e.slice(r+fK.length).matchAll(_St),c=Array.from(n,f=>{let[,p,h]=f,E=Buffer.from(h,"base64");if(E.length<5)throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"malformed checkpoint signature"});return{name:p,keyHint:E.subarray(0,4),signature:E.subarray(4)}});if(c.length===0)throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"no signatures found in checkpoint"});return new t(s,c)}},hK=class t{constructor(e,r,s,a){this.origin=e,this.logSize=r,this.logHash=s,this.rest=a}static fromString(e){let r=e.trimEnd().split(` `);if(r.length<3)throw new I1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"too few lines in checkpoint header"});let s=r[0],a=BigInt(r[1]),n=Buffer.from(r[2],"base64"),c=r.slice(3);return new t(s,a,n,c)}}});var Exe=_(EK=>{"use strict";Object.defineProperty(EK,"__esModule",{value:!0});EK.verifyMerkleInclusion=WSt;var yK=Cl(),dK=Eo(),GSt=Buffer.from([0]),qSt=Buffer.from([1]);function WSt(t){let e=t.inclusionProof,r=BigInt(e.logIndex),s=BigInt(e.treeSize);if(r<0n||r>=s)throw new dK.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:`invalid index: ${r}`});let{inner:a,border:n}=YSt(r,s);if(e.hashes.length!==a+n)throw new dK.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"invalid hash count"});let c=e.hashes.slice(0,a),f=e.hashes.slice(a),p=ZSt(t.canonicalizedBody),h=JSt(VSt(p,c,r),f);if(!yK.crypto.bufferEqual(h,e.rootHash))throw new dK.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"calculated root hash does not match inclusion proof"})}function YSt(t,e){let r=KSt(t,e),s=zSt(t>>BigInt(r));return{inner:r,border:s}}function VSt(t,e,r){return e.reduce((s,a,n)=>r>>BigInt(n)&BigInt(1)?mK(a,s):mK(s,a),t)}function JSt(t,e){return e.reduce((r,s)=>mK(s,r),t)}function KSt(t,e){return XSt(t^e-BigInt(1))}function zSt(t){return t.toString(2).split("1").length-1}function XSt(t){return t===0n?0:t.toString(2).length}function mK(t,e){return yK.crypto.digest("sha256",qSt,t,e)}function ZSt(t){return yK.crypto.digest("sha256",GSt,t)}});var Cxe=_(IK=>{"use strict";Object.defineProperty(IK,"__esModule",{value:!0});IK.verifyTLogSET=tDt;var Ixe=Cl(),$St=Eo(),eDt=py();function tDt(t,e){if(!(0,eDt.filterTLogAuthorities)(e,{logID:t.logId.keyId,targetDate:new Date(Number(t.integratedTime)*1e3)}).some(a=>{let n=rDt(t),c=Buffer.from(Ixe.json.canonicalize(n),"utf8"),f=t.inclusionPromise.signedEntryTimestamp;return Ixe.crypto.verify(c,a.publicKey,f)}))throw new $St.VerificationError({code:"TLOG_INCLUSION_PROMISE_ERROR",message:"inclusion promise could not be verified"})}function rDt(t){let{integratedTime:e,logIndex:r,logId:s,canonicalizedBody:a}=t;return{body:a.toString("base64"),integratedTime:Number(e),logIndex:Number(r),logID:s.keyId.toString("hex")}}});var wxe=_(BK=>{"use strict";Object.defineProperty(BK,"__esModule",{value:!0});BK.verifyRFC3161Timestamp=sDt;var CK=Cl(),wK=Eo(),nDt=cK(),iDt=py();function sDt(t,e,r){let s=t.signingTime;if(r=(0,iDt.filterCertAuthorities)(r,{start:s,end:s}),r=aDt(r,{serialNumber:t.signerSerialNumber,issuer:t.signerIssuer}),!r.some(n=>{try{return oDt(t,e,n),!0}catch{return!1}}))throw new wK.VerificationError({code:"TIMESTAMP_ERROR",message:"timestamp could not be verified"})}function oDt(t,e,r){let[s,...a]=r.certChain,n=CK.crypto.createPublicKey(s.publicKey),c=t.signingTime;try{new nDt.CertificateChainVerifier({untrustedCert:s,trustedCerts:a}).verify()}catch{throw new wK.VerificationError({code:"TIMESTAMP_ERROR",message:"invalid certificate chain"})}if(!r.certChain.every(p=>p.validForDate(c)))throw new wK.VerificationError({code:"TIMESTAMP_ERROR",message:"timestamp was signed with an expired certificate"});t.verify(e,n)}function aDt(t,e){return t.filter(r=>r.certChain.length>0&&CK.crypto.bufferEqual(r.certChain[0].serialNumber,e.serialNumber)&&CK.crypto.bufferEqual(r.certChain[0].issuer,e.issuer))}});var Bxe=_(vL=>{"use strict";Object.defineProperty(vL,"__esModule",{value:!0});vL.verifyTSATimestamp=pDt;vL.verifyTLogTimestamp=hDt;var lDt=Eo(),cDt=yxe(),uDt=Exe(),fDt=Cxe(),ADt=wxe();function pDt(t,e,r){return(0,ADt.verifyRFC3161Timestamp)(t,e,r),{type:"timestamp-authority",logID:t.signerSerialNumber,timestamp:t.signingTime}}function hDt(t,e){let r=!1;if(gDt(t)&&((0,fDt.verifyTLogSET)(t,e),r=!0),dDt(t)&&((0,uDt.verifyMerkleInclusion)(t),(0,cDt.verifyCheckpoint)(t,e),r=!0),!r)throw new lDt.VerificationError({code:"TLOG_MISSING_INCLUSION_ERROR",message:"inclusion could not be verified"});return{type:"transparency-log",logID:t.logId.keyId,timestamp:new Date(Number(t.integratedTime)*1e3)}}function gDt(t){return t.inclusionPromise!==void 0}function dDt(t){return t.inclusionProof!==void 0}});var vxe=_(vK=>{"use strict";Object.defineProperty(vK,"__esModule",{value:!0});vK.verifyDSSETLogBody=mDt;var SL=Eo();function mDt(t,e){switch(t.apiVersion){case"0.0.1":return yDt(t,e);default:throw new SL.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported dsse version: ${t.apiVersion}`})}}function yDt(t,e){if(t.spec.signatures?.length!==1)throw new SL.VerificationError({code:"TLOG_BODY_ERROR",message:"signature count mismatch"});let r=t.spec.signatures[0].signature;if(!e.compareSignature(Buffer.from(r,"base64")))throw new SL.VerificationError({code:"TLOG_BODY_ERROR",message:"tlog entry signature mismatch"});let s=t.spec.payloadHash?.value||"";if(!e.compareDigest(Buffer.from(s,"hex")))throw new SL.VerificationError({code:"TLOG_BODY_ERROR",message:"DSSE payload hash mismatch"})}});var Sxe=_(DK=>{"use strict";Object.defineProperty(DK,"__esModule",{value:!0});DK.verifyHashedRekordTLogBody=EDt;var SK=Eo();function EDt(t,e){switch(t.apiVersion){case"0.0.1":return IDt(t,e);default:throw new SK.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported hashedrekord version: ${t.apiVersion}`})}}function IDt(t,e){let r=t.spec.signature.content||"";if(!e.compareSignature(Buffer.from(r,"base64")))throw new SK.VerificationError({code:"TLOG_BODY_ERROR",message:"signature mismatch"});let s=t.spec.data.hash?.value||"";if(!e.compareDigest(Buffer.from(s,"hex")))throw new SK.VerificationError({code:"TLOG_BODY_ERROR",message:"digest mismatch"})}});var Dxe=_(bK=>{"use strict";Object.defineProperty(bK,"__esModule",{value:!0});bK.verifyIntotoTLogBody=CDt;var DL=Eo();function CDt(t,e){switch(t.apiVersion){case"0.0.2":return wDt(t,e);default:throw new DL.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported intoto version: ${t.apiVersion}`})}}function wDt(t,e){if(t.spec.content.envelope.signatures?.length!==1)throw new DL.VerificationError({code:"TLOG_BODY_ERROR",message:"signature count mismatch"});let r=BDt(t.spec.content.envelope.signatures[0].sig);if(!e.compareSignature(Buffer.from(r,"base64")))throw new DL.VerificationError({code:"TLOG_BODY_ERROR",message:"tlog entry signature mismatch"});let s=t.spec.content.payloadHash?.value||"";if(!e.compareDigest(Buffer.from(s,"hex")))throw new DL.VerificationError({code:"TLOG_BODY_ERROR",message:"DSSE payload hash mismatch"})}function BDt(t){return Buffer.from(t,"base64").toString("utf-8")}});var Pxe=_(PK=>{"use strict";Object.defineProperty(PK,"__esModule",{value:!0});PK.verifyTLogBody=bDt;var bxe=Eo(),vDt=vxe(),SDt=Sxe(),DDt=Dxe();function bDt(t,e){let{kind:r,version:s}=t.kindVersion,a=JSON.parse(t.canonicalizedBody.toString("utf8"));if(r!==a.kind||s!==a.apiVersion)throw new bxe.VerificationError({code:"TLOG_BODY_ERROR",message:`kind/version mismatch - expected: ${r}/${s}, received: ${a.kind}/${a.apiVersion}`});switch(a.kind){case"dsse":return(0,vDt.verifyDSSETLogBody)(a,e);case"intoto":return(0,DDt.verifyIntotoTLogBody)(a,e);case"hashedrekord":return(0,SDt.verifyHashedRekordTLogBody)(a,e);default:throw new bxe.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported kind: ${r}`})}}});var Rxe=_(bL=>{"use strict";Object.defineProperty(bL,"__esModule",{value:!0});bL.Verifier=void 0;var PDt=Ie("util"),C1=Eo(),xxe=gxe(),kxe=mxe(),Qxe=Bxe(),xDt=Pxe(),xK=class{constructor(e,r={}){this.trustMaterial=e,this.options={ctlogThreshold:r.ctlogThreshold??1,tlogThreshold:r.tlogThreshold??1,tsaThreshold:r.tsaThreshold??0}}verify(e,r){let s=this.verifyTimestamps(e),a=this.verifySigningKey(e,s);return this.verifyTLogs(e),this.verifySignature(e,a),r&&this.verifyPolicy(r,a.identity||{}),a}verifyTimestamps(e){let r=0,s=0,a=e.timestamps.map(n=>{switch(n.$case){case"timestamp-authority":return s++,(0,Qxe.verifyTSATimestamp)(n.timestamp,e.signature.signature,this.trustMaterial.timestampAuthorities);case"transparency-log":return r++,(0,Qxe.verifyTLogTimestamp)(n.tlogEntry,this.trustMaterial.tlogs)}});if(Txe(a))throw new C1.VerificationError({code:"TIMESTAMP_ERROR",message:"duplicate timestamp"});if(rn.timestamp)}verifySigningKey({key:e},r){switch(e.$case){case"public-key":return(0,xxe.verifyPublicKey)(e.hint,r,this.trustMaterial);case"certificate":{let s=(0,xxe.verifyCertificate)(e.certificate,r,this.trustMaterial);if(Txe(s.scts))throw new C1.VerificationError({code:"CERTIFICATE_ERROR",message:"duplicate SCT"});if(s.scts.length(0,xDt.verifyTLogBody)(s,e))}verifySignature(e,r){if(!e.signature.verifySignature(r.key))throw new C1.VerificationError({code:"SIGNATURE_ERROR",message:"signature verification failed"})}verifyPolicy(e,r){e.subjectAlternativeName&&(0,kxe.verifySubjectAlternativeName)(e.subjectAlternativeName,r.subjectAlternativeName),e.extensions&&(0,kxe.verifyExtensions)(e.extensions,r.extensions)}};bL.Verifier=xK;function Txe(t){for(let e=0;e{"use strict";Object.defineProperty(iu,"__esModule",{value:!0});iu.Verifier=iu.toTrustMaterial=iu.VerificationError=iu.PolicyError=iu.toSignedEntity=void 0;var kDt=lxe();Object.defineProperty(iu,"toSignedEntity",{enumerable:!0,get:function(){return kDt.toSignedEntity}});var Fxe=Eo();Object.defineProperty(iu,"PolicyError",{enumerable:!0,get:function(){return Fxe.PolicyError}});Object.defineProperty(iu,"VerificationError",{enumerable:!0,get:function(){return Fxe.VerificationError}});var QDt=py();Object.defineProperty(iu,"toTrustMaterial",{enumerable:!0,get:function(){return QDt.toTrustMaterial}});var TDt=Rxe();Object.defineProperty(iu,"Verifier",{enumerable:!0,get:function(){return TDt.Verifier}})});var Nxe=_(Fa=>{"use strict";Object.defineProperty(Fa,"__esModule",{value:!0});Fa.DEFAULT_TIMEOUT=Fa.DEFAULT_RETRY=void 0;Fa.createBundleBuilder=NDt;Fa.createKeyFinder=ODt;Fa.createVerificationPolicy=LDt;var RDt=Cl(),w1=H7(),FDt=PL();Fa.DEFAULT_RETRY={retries:2};Fa.DEFAULT_TIMEOUT=5e3;function NDt(t,e){let r={signer:MDt(e),witnesses:_Dt(e)};switch(t){case"messageSignature":return new w1.MessageSignatureBundleBuilder(r);case"dsseEnvelope":return new w1.DSSEBundleBuilder({...r,certificateChain:e.legacyCompatibility})}}function ODt(t){return e=>{let r=t(e);if(!r)throw new FDt.VerificationError({code:"PUBLIC_KEY_ERROR",message:`key not found: ${e}`});return{publicKey:RDt.crypto.createPublicKey(r),validFor:()=>!0}}}function LDt(t){let e={},r=t.certificateIdentityEmail||t.certificateIdentityURI;return r&&(e.subjectAlternativeName=r),t.certificateIssuer&&(e.extensions={issuer:t.certificateIssuer}),e}function MDt(t){return new w1.FulcioSigner({fulcioBaseURL:t.fulcioURL,identityProvider:t.identityProvider||UDt(t),retry:t.retry??Fa.DEFAULT_RETRY,timeout:t.timeout??Fa.DEFAULT_TIMEOUT})}function UDt(t){let e=t.identityToken;return e?{getToken:()=>Promise.resolve(e)}:new w1.CIContextProvider("sigstore")}function _Dt(t){let e=[];return HDt(t)&&e.push(new w1.RekorWitness({rekorBaseURL:t.rekorURL,entryType:t.legacyCompatibility?"intoto":"dsse",fetchOnConflict:!1,retry:t.retry??Fa.DEFAULT_RETRY,timeout:t.timeout??Fa.DEFAULT_TIMEOUT})),jDt(t)&&e.push(new w1.TSAWitness({tsaBaseURL:t.tsaServerURL,retry:t.retry??Fa.DEFAULT_RETRY,timeout:t.timeout??Fa.DEFAULT_TIMEOUT})),e}function HDt(t){return t.tlogUpload!==!1}function jDt(t){return t.tsaServerURL!==void 0}});var Mxe=_(su=>{"use strict";var GDt=su&&su.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),qDt=su&&su.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Oxe=su&&su.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;aa.verify(t,s))}async function Lxe(t={}){let e=await WDt.getTrustedRoot({mirrorURL:t.tufMirrorURL,rootPath:t.tufRootPath,cachePath:t.tufCachePath,forceCache:t.tufForceCache,retry:t.retry??B1.DEFAULT_RETRY,timeout:t.timeout??B1.DEFAULT_TIMEOUT}),r=t.keySelector?B1.createKeyFinder(t.keySelector):void 0,s=(0,kK.toTrustMaterial)(e,r),a={ctlogThreshold:t.ctLogThreshold,tlogThreshold:t.tlogThreshold},n=new kK.Verifier(s,a),c=B1.createVerificationPolicy(t);return{verify:(f,p)=>{let h=(0,QK.bundleFromJSON)(f),E=(0,kK.toSignedEntity)(h,p);n.verify(E,c)}}}});var _xe=_(Ni=>{"use strict";Object.defineProperty(Ni,"__esModule",{value:!0});Ni.verify=Ni.sign=Ni.createVerifier=Ni.attest=Ni.VerificationError=Ni.PolicyError=Ni.TUFError=Ni.InternalError=Ni.DEFAULT_REKOR_URL=Ni.DEFAULT_FULCIO_URL=Ni.ValidationError=void 0;var KDt=Ib();Object.defineProperty(Ni,"ValidationError",{enumerable:!0,get:function(){return KDt.ValidationError}});var TK=H7();Object.defineProperty(Ni,"DEFAULT_FULCIO_URL",{enumerable:!0,get:function(){return TK.DEFAULT_FULCIO_URL}});Object.defineProperty(Ni,"DEFAULT_REKOR_URL",{enumerable:!0,get:function(){return TK.DEFAULT_REKOR_URL}});Object.defineProperty(Ni,"InternalError",{enumerable:!0,get:function(){return TK.InternalError}});var zDt=hL();Object.defineProperty(Ni,"TUFError",{enumerable:!0,get:function(){return zDt.TUFError}});var Uxe=PL();Object.defineProperty(Ni,"PolicyError",{enumerable:!0,get:function(){return Uxe.PolicyError}});Object.defineProperty(Ni,"VerificationError",{enumerable:!0,get:function(){return Uxe.VerificationError}});var xL=Mxe();Object.defineProperty(Ni,"attest",{enumerable:!0,get:function(){return xL.attest}});Object.defineProperty(Ni,"createVerifier",{enumerable:!0,get:function(){return xL.createVerifier}});Object.defineProperty(Ni,"sign",{enumerable:!0,get:function(){return xL.sign}});Object.defineProperty(Ni,"verify",{enumerable:!0,get:function(){return xL.verify}})});Dt();Ge();Dt();var dke=Ie("child_process"),mke=ut(Fd());Yt();var $I=new Map([]);var Gv={};Vt(Gv,{BaseCommand:()=>ft,WorkspaceRequiredError:()=>ar,getCli:()=>bde,getDynamicLibs:()=>Dde,getPluginConfiguration:()=>tC,openWorkspace:()=>eC,pluginCommands:()=>$I,runExit:()=>VR});Yt();var ft=class extends ot{constructor(){super(...arguments);this.cwd=ge.String("--cwd",{hidden:!0})}validateAndExecute(){if(typeof this.cwd<"u")throw new nt("The --cwd option is ambiguous when used anywhere else than the very first parameter provided in the command line, before even the command path");return super.validateAndExecute()}};Ge();Dt();Yt();var ar=class extends nt{constructor(e,r){let s=J.relative(e,r),a=J.join(e,Ut.fileName);super(`This command can only be run from within a workspace of your project (${s} isn't a workspace of ${a}).`)}};Ge();Dt();eA();wc();pv();Yt();var yat=ut(Ai());Ul();var Dde=()=>new Map([["@yarnpkg/cli",Gv],["@yarnpkg/core",jv],["@yarnpkg/fslib",_2],["@yarnpkg/libzip",fv],["@yarnpkg/parsers",J2],["@yarnpkg/shell",mv],["clipanion",oB],["semver",yat],["typanion",Ea]]);Ge();async function eC(t,e){let{project:r,workspace:s}=await Tt.find(t,e);if(!s)throw new ar(r.cwd,e);return s}Ge();Dt();eA();wc();pv();Yt();var IPt=ut(Ai());Ul();var hq={};Vt(hq,{AddCommand:()=>sC,BinCommand:()=>oC,CacheCleanCommand:()=>aC,ClipanionCommand:()=>pC,ConfigCommand:()=>fC,ConfigGetCommand:()=>lC,ConfigSetCommand:()=>cC,ConfigUnsetCommand:()=>uC,DedupeCommand:()=>AC,EntryCommand:()=>gC,ExecCommand:()=>mC,ExplainCommand:()=>IC,ExplainPeerRequirementsCommand:()=>yC,HelpCommand:()=>hC,InfoCommand:()=>CC,LinkCommand:()=>BC,NodeCommand:()=>vC,PluginCheckCommand:()=>SC,PluginImportCommand:()=>PC,PluginImportSourcesCommand:()=>xC,PluginListCommand:()=>DC,PluginRemoveCommand:()=>kC,PluginRuntimeCommand:()=>QC,RebuildCommand:()=>TC,RemoveCommand:()=>RC,RunCommand:()=>NC,RunIndexCommand:()=>FC,SetResolutionCommand:()=>OC,SetVersionCommand:()=>EC,SetVersionSourcesCommand:()=>bC,UnlinkCommand:()=>LC,UpCommand:()=>MC,VersionCommand:()=>dC,WhyCommand:()=>UC,WorkspaceCommand:()=>qC,WorkspacesListCommand:()=>GC,YarnCommand:()=>wC,dedupeUtils:()=>rF,default:()=>Tct,suggestUtils:()=>Xu});var zye=ut(Fd());Ge();Ge();Ge();Yt();var hye=ut(Vv());Ul();var Xu={};Vt(Xu,{Modifier:()=>W5,Strategy:()=>eF,Target:()=>Jv,WorkspaceModifier:()=>cye,applyModifier:()=>Mlt,extractDescriptorFromPath:()=>Y5,extractRangeModifier:()=>uye,fetchDescriptorFrom:()=>V5,findProjectDescriptors:()=>pye,getModifier:()=>Kv,getSuggestedDescriptors:()=>zv,makeWorkspaceDescriptor:()=>Aye,toWorkspaceModifier:()=>fye});Ge();Ge();Dt();var q5=ut(Ai()),Olt="workspace:",Jv=(s=>(s.REGULAR="dependencies",s.DEVELOPMENT="devDependencies",s.PEER="peerDependencies",s))(Jv||{}),W5=(s=>(s.CARET="^",s.TILDE="~",s.EXACT="",s))(W5||{}),cye=(s=>(s.CARET="^",s.TILDE="~",s.EXACT="*",s))(cye||{}),eF=(n=>(n.KEEP="keep",n.REUSE="reuse",n.PROJECT="project",n.LATEST="latest",n.CACHE="cache",n))(eF||{});function Kv(t,e){return t.exact?"":t.caret?"^":t.tilde?"~":e.configuration.get("defaultSemverRangePrefix")}var Llt=/^([\^~]?)[0-9]+(?:\.[0-9]+){0,2}(?:-\S+)?$/;function uye(t,{project:e}){let r=t.match(Llt);return r?r[1]:e.configuration.get("defaultSemverRangePrefix")}function Mlt(t,e){let{protocol:r,source:s,params:a,selector:n}=G.parseRange(t.range);return q5.default.valid(n)&&(n=`${e}${t.range}`),G.makeDescriptor(t,G.makeRange({protocol:r,source:s,params:a,selector:n}))}function fye(t){switch(t){case"^":return"^";case"~":return"~";case"":return"*";default:throw new Error(`Assertion failed: Unknown modifier: "${t}"`)}}function Aye(t,e){return G.makeDescriptor(t.anchoredDescriptor,`${Olt}${fye(e)}`)}async function pye(t,{project:e,target:r}){let s=new Map,a=n=>{let c=s.get(n.descriptorHash);return c||s.set(n.descriptorHash,c={descriptor:n,locators:[]}),c};for(let n of e.workspaces)if(r==="peerDependencies"){let c=n.manifest.peerDependencies.get(t.identHash);c!==void 0&&a(c).locators.push(n.anchoredLocator)}else{let c=n.manifest.dependencies.get(t.identHash),f=n.manifest.devDependencies.get(t.identHash);r==="devDependencies"?f!==void 0?a(f).locators.push(n.anchoredLocator):c!==void 0&&a(c).locators.push(n.anchoredLocator):c!==void 0?a(c).locators.push(n.anchoredLocator):f!==void 0&&a(f).locators.push(n.anchoredLocator)}return s}async function Y5(t,{cwd:e,workspace:r}){return await _lt(async s=>{J.isAbsolute(t)||(t=J.relative(r.cwd,J.resolve(e,t)),t.match(/^\.{0,2}\//)||(t=`./${t}`));let{project:a}=r,n=await V5(G.makeIdent(null,"archive"),t,{project:r.project,cache:s,workspace:r});if(!n)throw new Error("Assertion failed: The descriptor should have been found");let c=new ki,f=a.configuration.makeResolver(),p=a.configuration.makeFetcher(),h={checksums:a.storedChecksums,project:a,cache:s,fetcher:p,report:c,resolver:f},E=f.bindDescriptor(n,r.anchoredLocator,h),C=G.convertDescriptorToLocator(E),S=await p.fetch(C,h),P=await Ut.find(S.prefixPath,{baseFs:S.packageFs});if(!P.name)throw new Error("Target path doesn't have a name");return G.makeDescriptor(P.name,t)})}function Ult(t){if(t.range==="unknown")return{type:"resolve",range:"latest"};if(Fr.validRange(t.range))return{type:"fixed",range:t.range};if(Mp.test(t.range))return{type:"resolve",range:t.range};let e=t.range.match(/^(?:jsr:|npm:)(.*)/);if(!e)return{type:"fixed",range:t.range};let[,r]=e,s=`${G.stringifyIdent(t)}@`;return r.startsWith(s)&&(r=r.slice(s.length)),Fr.validRange(r)?{type:"fixed",range:t.range}:Mp.test(r)?{type:"resolve",range:t.range}:{type:"fixed",range:t.range}}async function zv(t,{project:e,workspace:r,cache:s,target:a,fixed:n,modifier:c,strategies:f,maxResults:p=1/0}){if(!(p>=0))throw new Error(`Invalid maxResults (${p})`);let h=!n||t.range==="unknown"?Ult(t):{type:"fixed",range:t.range};if(h.type==="fixed")return{suggestions:[{descriptor:t,name:`Use ${G.prettyDescriptor(e.configuration,t)}`,reason:"(unambiguous explicit request)"}],rejections:[]};let E=typeof r<"u"&&r!==null&&r.manifest[a].get(t.identHash)||null,C=[],S=[],P=async I=>{try{await I()}catch(R){S.push(R)}};for(let I of f){if(C.length>=p)break;switch(I){case"keep":await P(async()=>{E&&C.push({descriptor:E,name:`Keep ${G.prettyDescriptor(e.configuration,E)}`,reason:"(no changes)"})});break;case"reuse":await P(async()=>{for(let{descriptor:R,locators:N}of(await pye(t,{project:e,target:a})).values()){if(N.length===1&&N[0].locatorHash===r.anchoredLocator.locatorHash&&f.includes("keep"))continue;let U=`(originally used by ${G.prettyLocator(e.configuration,N[0])}`;U+=N.length>1?` and ${N.length-1} other${N.length>2?"s":""})`:")",C.push({descriptor:R,name:`Reuse ${G.prettyDescriptor(e.configuration,R)}`,reason:U})}});break;case"cache":await P(async()=>{for(let R of e.storedDescriptors.values())R.identHash===t.identHash&&C.push({descriptor:R,name:`Reuse ${G.prettyDescriptor(e.configuration,R)}`,reason:"(already used somewhere in the lockfile)"})});break;case"project":await P(async()=>{if(r.manifest.name!==null&&t.identHash===r.manifest.name.identHash)return;let R=e.tryWorkspaceByIdent(t);if(R===null)return;let N=Aye(R,c);C.push({descriptor:N,name:`Attach ${G.prettyDescriptor(e.configuration,N)}`,reason:`(local workspace at ${he.pretty(e.configuration,R.relativeCwd,he.Type.PATH)})`})});break;case"latest":{let R=e.configuration.get("enableNetwork"),N=e.configuration.get("enableOfflineMode");await P(async()=>{if(a==="peerDependencies")C.push({descriptor:G.makeDescriptor(t,"*"),name:"Use *",reason:"(catch-all peer dependency pattern)"});else if(!R&&!N)C.push({descriptor:null,name:"Resolve from latest",reason:he.pretty(e.configuration,"(unavailable because enableNetwork is toggled off)","grey")});else{let U=await V5(t,h.range,{project:e,cache:s,workspace:r,modifier:c});U&&C.push({descriptor:U,name:`Use ${G.prettyDescriptor(e.configuration,U)}`,reason:`(resolved from ${N?"the cache":"latest"})`})}})}break}}return{suggestions:C.slice(0,p),rejections:S.slice(0,p)}}async function V5(t,e,{project:r,cache:s,workspace:a,preserveModifier:n=!0,modifier:c}){let f=r.configuration.normalizeDependency(G.makeDescriptor(t,e)),p=new ki,h=r.configuration.makeFetcher(),E=r.configuration.makeResolver(),C={project:r,fetcher:h,cache:s,checksums:r.storedChecksums,report:p,cacheOptions:{skipIntegrityCheck:!0}},S={...C,resolver:E,fetchOptions:C},P=E.bindDescriptor(f,a.anchoredLocator,S),I=await E.getCandidates(P,{},S);if(I.length===0)return null;let R=I[0],{protocol:N,source:U,params:W,selector:ee}=G.parseRange(G.convertToManifestRange(R.reference));if(N===r.configuration.get("defaultProtocol")&&(N=null),q5.default.valid(ee)){let ie=ee;if(typeof c<"u")ee=c+ee;else if(n!==!1){let me=typeof n=="string"?n:f.range;ee=uye(me,{project:r})+ee}let ue=G.makeDescriptor(R,G.makeRange({protocol:N,source:U,params:W,selector:ee}));(await E.getCandidates(r.configuration.normalizeDependency(ue),{},S)).length!==1&&(ee=ie)}return G.makeDescriptor(R,G.makeRange({protocol:N,source:U,params:W,selector:ee}))}async function _lt(t){return await ce.mktempPromise(async e=>{let r=ze.create(e);return r.useWithSource(e,{enableMirror:!1,compressionLevel:0},e,{overwrite:!0}),await t(new Kr(e,{configuration:r,check:!1,immutable:!1}))})}var sC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.fixed=ge.Boolean("-F,--fixed",!1,{description:"Store dependency tags as-is instead of resolving them"});this.exact=ge.Boolean("-E,--exact",!1,{description:"Don't use any semver modifier on the resolved range"});this.tilde=ge.Boolean("-T,--tilde",!1,{description:"Use the `~` semver modifier on the resolved range"});this.caret=ge.Boolean("-C,--caret",!1,{description:"Use the `^` semver modifier on the resolved range"});this.dev=ge.Boolean("-D,--dev",!1,{description:"Add a package as a dev dependency"});this.peer=ge.Boolean("-P,--peer",!1,{description:"Add a package as a peer dependency"});this.optional=ge.Boolean("-O,--optional",!1,{description:"Add / upgrade a package to an optional regular / peer dependency"});this.preferDev=ge.Boolean("--prefer-dev",!1,{description:"Add / upgrade a package to a dev dependency"});this.interactive=ge.Boolean("-i,--interactive",{description:"Reuse the specified package from other workspaces in the project"});this.cached=ge.Boolean("--cached",!1,{description:"Reuse the highest version already used somewhere within the project"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:fo($l)});this.silent=ge.Boolean("--silent",{hidden:!0});this.packages=ge.Rest()}static{this.paths=[["add"]]}static{this.usage=ot.Usage({description:"add dependencies to the project",details:"\n This command adds a package to the package.json for the nearest workspace.\n\n - If it didn't exist before, the package will by default be added to the regular `dependencies` field, but this behavior can be overriden thanks to the `-D,--dev` flag (which will cause the dependency to be added to the `devDependencies` field instead) and the `-P,--peer` flag (which will do the same but for `peerDependencies`).\n\n - If the package was already listed in your dependencies, it will by default be upgraded whether it's part of your `dependencies` or `devDependencies` (it won't ever update `peerDependencies`, though).\n\n - If set, the `--prefer-dev` flag will operate as a more flexible `-D,--dev` in that it will add the package to your `devDependencies` if it isn't already listed in either `dependencies` or `devDependencies`, but it will also happily upgrade your `dependencies` if that's what you already use (whereas `-D,--dev` would throw an exception).\n\n - If set, the `-O,--optional` flag will add the package to the `optionalDependencies` field and, in combination with the `-P,--peer` flag, it will add the package as an optional peer dependency. If the package was already listed in your `dependencies`, it will be upgraded to `optionalDependencies`. If the package was already listed in your `peerDependencies`, in combination with the `-P,--peer` flag, it will be upgraded to an optional peer dependency: `\"peerDependenciesMeta\": { \"\": { \"optional\": true } }`\n\n - If the added package doesn't specify a range at all its `latest` tag will be resolved and the returned version will be used to generate a new semver range (using the `^` modifier by default unless otherwise configured via the `defaultSemverRangePrefix` configuration, or the `~` modifier if `-T,--tilde` is specified, or no modifier at all if `-E,--exact` is specified). Two exceptions to this rule: the first one is that if the package is a workspace then its local version will be used, and the second one is that if you use `-P,--peer` the default range will be `*` and won't be resolved at all.\n\n - If the added package specifies a range (such as `^1.0.0`, `latest`, or `rc`), Yarn will add this range as-is in the resulting package.json entry (in particular, tags such as `rc` will be encoded as-is rather than being converted into a semver range).\n\n If the `--cached` option is used, Yarn will preferably reuse the highest version already used somewhere within the project, even if through a transitive dependency.\n\n If the `-i,--interactive` option is used (or if the `preferInteractive` settings is toggled on) the command will first try to check whether other workspaces in the project use the specified package and, if so, will offer to reuse them.\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n\n For a compilation of all the supported protocols, please consult the dedicated page from our website: https://yarnpkg.com/protocols.\n ",examples:[["Add a regular package to the current workspace","$0 add lodash"],["Add a specific version for a package to the current workspace","$0 add lodash@1.2.3"],["Add a package from a GitHub repository (the master branch) to the current workspace using a URL","$0 add lodash@https://github.com/lodash/lodash"],["Add a package from a GitHub repository (the master branch) to the current workspace using the GitHub protocol","$0 add lodash@github:lodash/lodash"],["Add a package from a GitHub repository (the master branch) to the current workspace using the GitHub protocol (shorthand)","$0 add lodash@lodash/lodash"],["Add a package from a specific branch of a GitHub repository to the current workspace using the GitHub protocol (shorthand)","$0 add lodash-es@lodash/lodash#es"],["Add a local package (gzipped tarball format) to the current workspace","$0 add local-package-name@file:../path/to/local-package-name-v0.1.2.tgz"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.fixed,f=r.isInteractive({interactive:this.interactive,stdout:this.context.stdout}),p=f||r.get("preferReuse"),h=Kv(this,s),E=[p?"reuse":void 0,"project",this.cached?"cache":void 0,"latest"].filter(W=>typeof W<"u"),C=f?1/0:1,S=W=>{let ee=G.tryParseDescriptor(W.slice(4));return ee?ee.range==="unknown"?G.makeDescriptor(ee,`jsr:${G.stringifyIdent(ee)}@latest`):G.makeDescriptor(ee,`jsr:${ee.range}`):null},P=await Promise.all(this.packages.map(async W=>{let ee=W.match(/^\.{0,2}\//)?await Y5(W,{cwd:this.context.cwd,workspace:a}):W.startsWith("jsr:")?S(W):G.tryParseDescriptor(W),ie=W.match(/^(https?:|git@github)/);if(ie)throw new nt(`It seems you are trying to add a package using a ${he.pretty(r,`${ie[0]}...`,he.Type.RANGE)} url; we now require package names to be explicitly specified. Try running the command again with the package name prefixed: ${he.pretty(r,"yarn add",he.Type.CODE)} ${he.pretty(r,G.makeDescriptor(G.makeIdent(null,"my-package"),`${ie[0]}...`),he.Type.DESCRIPTOR)}`);if(!ee)throw new nt(`The ${he.pretty(r,W,he.Type.CODE)} string didn't match the required format (package-name@range). Did you perhaps forget to explicitly reference the package name?`);let ue=Hlt(a,ee,{dev:this.dev,peer:this.peer,preferDev:this.preferDev,optional:this.optional});return await Promise.all(ue.map(async me=>{let pe=await zv(ee,{project:s,workspace:a,cache:n,fixed:c,target:me,modifier:h,strategies:E,maxResults:C});return{request:ee,suggestedDescriptors:pe,target:me}}))})).then(W=>W.flat()),I=await lA.start({configuration:r,stdout:this.context.stdout,suggestInstall:!1},async W=>{for(let{request:ee,suggestedDescriptors:{suggestions:ie,rejections:ue}}of P)if(ie.filter(me=>me.descriptor!==null).length===0){let[me]=ue;if(typeof me>"u")throw new Error("Assertion failed: Expected an error to have been set");s.configuration.get("enableNetwork")?W.reportError(27,`${G.prettyDescriptor(r,ee)} can't be resolved to a satisfying range`):W.reportError(27,`${G.prettyDescriptor(r,ee)} can't be resolved to a satisfying range (note: network resolution has been disabled)`),W.reportSeparator(),W.reportExceptionOnce(me)}});if(I.hasErrors())return I.exitCode();let R=!1,N=[],U=[];for(let{suggestedDescriptors:{suggestions:W},target:ee}of P){let ie,ue=W.filter(Be=>Be.descriptor!==null),le=ue[0].descriptor,me=ue.every(Be=>G.areDescriptorsEqual(Be.descriptor,le));ue.length===1||me?ie=le:(R=!0,{answer:ie}=await(0,hye.prompt)({type:"select",name:"answer",message:"Which range do you want to use?",choices:W.map(({descriptor:Be,name:Ce,reason:g})=>Be?{name:Ce,hint:g,descriptor:Be}:{name:Ce,hint:g,disabled:!0}),onCancel:()=>process.exit(130),result(Be){return this.find(Be,"descriptor")},stdin:this.context.stdin,stdout:this.context.stdout}));let pe=a.manifest[ee].get(ie.identHash);(typeof pe>"u"||pe.descriptorHash!==ie.descriptorHash)&&(a.manifest[ee].set(ie.identHash,ie),this.optional&&(ee==="dependencies"?a.manifest.ensureDependencyMeta({...ie,range:"unknown"}).optional=!0:ee==="peerDependencies"&&(a.manifest.ensurePeerDependencyMeta({...ie,range:"unknown"}).optional=!0)),typeof pe>"u"?N.push([a,ee,ie,E]):U.push([a,ee,pe,ie]))}return await r.triggerMultipleHooks(W=>W.afterWorkspaceDependencyAddition,N),await r.triggerMultipleHooks(W=>W.afterWorkspaceDependencyReplacement,U),R&&this.context.stdout.write(` `),await s.installWithNewReport({json:this.json,stdout:this.context.stdout,quiet:this.context.quiet},{cache:n,mode:this.mode})}};function Hlt(t,e,{dev:r,peer:s,preferDev:a,optional:n}){let c=t.manifest.dependencies.has(e.identHash),f=t.manifest.devDependencies.has(e.identHash),p=t.manifest.peerDependencies.has(e.identHash);if((r||s)&&c)throw new nt(`Package "${G.prettyIdent(t.project.configuration,e)}" is already listed as a regular dependency - remove the -D,-P flags or remove it from your dependencies first`);if(!r&&!s&&p)throw new nt(`Package "${G.prettyIdent(t.project.configuration,e)}" is already listed as a peer dependency - use either of -D or -P, or remove it from your peer dependencies first`);if(n&&f)throw new nt(`Package "${G.prettyIdent(t.project.configuration,e)}" is already listed as a dev dependency - remove the -O flag or remove it from your dev dependencies first`);if(n&&!s&&p)throw new nt(`Package "${G.prettyIdent(t.project.configuration,e)}" is already listed as a peer dependency - remove the -O flag or add the -P flag or remove it from your peer dependencies first`);if((r||a)&&n)throw new nt(`Package "${G.prettyIdent(t.project.configuration,e)}" cannot simultaneously be a dev dependency and an optional dependency`);let h=[];return s&&h.push("peerDependencies"),(r||a)&&h.push("devDependencies"),n&&h.push("dependencies"),h.length>0?h:f?["devDependencies"]:p?["peerDependencies"]:["dependencies"]}Ge();Ge();Yt();var oC=class extends ft{constructor(){super(...arguments);this.verbose=ge.Boolean("-v,--verbose",!1,{description:"Print both the binary name and the locator of the package that provides the binary"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.name=ge.String({required:!1})}static{this.paths=[["bin"]]}static{this.usage=ot.Usage({description:"get the path to a binary script",details:` When used without arguments, this command will print the list of all the binaries available in the current workspace. Adding the \`-v,--verbose\` flag will cause the output to contain both the binary name and the locator of the package that provides the binary. When an argument is specified, this command will just print the path to the binary on the standard output and exit. Note that the reported path may be stored within a zip archive. `,examples:[["List all the available binaries","$0 bin"],["Print the path to a specific binary","$0 bin eslint"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,locator:a}=await Tt.find(r,this.context.cwd);if(await s.restoreInstallState(),this.name){let f=(await In.getPackageAccessibleBinaries(a,{project:s})).get(this.name);if(!f)throw new nt(`Couldn't find a binary named "${this.name}" for package "${G.prettyLocator(r,a)}"`);let[,p]=f;return this.context.stdout.write(`${p} `),0}return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async c=>{let f=await In.getPackageAccessibleBinaries(a,{project:s}),h=Array.from(f.keys()).reduce((E,C)=>Math.max(E,C.length),0);for(let[E,[C,S]]of f)c.reportJson({name:E,source:G.stringifyIdent(C),path:S});if(this.verbose)for(let[E,[C]]of f)c.reportInfo(null,`${E.padEnd(h," ")} ${G.prettyLocator(r,C)}`);else for(let E of f.keys())c.reportInfo(null,E)})).exitCode()}};Ge();Dt();Yt();var aC=class extends ft{constructor(){super(...arguments);this.mirror=ge.Boolean("--mirror",!1,{description:"Remove the global cache files instead of the local cache files"});this.all=ge.Boolean("--all",!1,{description:"Remove both the global cache files and the local cache files of the current project"})}static{this.paths=[["cache","clean"],["cache","clear"]]}static{this.usage=ot.Usage({description:"remove the shared cache files",details:` This command will remove all the files from the cache. `,examples:[["Remove all the local archives","$0 cache clean"],["Remove all the archives stored in the ~/.yarn directory","$0 cache clean --mirror"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(!r.get("enableCacheClean"))throw new nt("Cache cleaning is currently disabled. To enable it, set `enableCacheClean: true` in your configuration file. Note: Cache cleaning is typically not required and should be avoided when using Zero-Installs.");let s=await Kr.find(r);return(await Ot.start({configuration:r,stdout:this.context.stdout},async()=>{let n=(this.all||this.mirror)&&s.mirrorCwd!==null,c=!this.mirror;n&&(await ce.removePromise(s.mirrorCwd),await r.triggerHook(f=>f.cleanGlobalArtifacts,r)),c&&await ce.removePromise(s.cwd)})).exitCode()}};Ge();Yt();ql();var J5=Ie("util"),lC=class extends ft{constructor(){super(...arguments);this.why=ge.Boolean("--why",!1,{description:"Print the explanation for why a setting has its value"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.unsafe=ge.Boolean("--no-redacted",!1,{description:"Don't redact secrets (such as tokens) from the output"});this.name=ge.String()}static{this.paths=[["config","get"]]}static{this.usage=ot.Usage({description:"read a configuration settings",details:` This command will print a configuration setting. Secrets (such as tokens) will be redacted from the output by default. If this behavior isn't desired, set the \`--no-redacted\` to get the untransformed value. `,examples:[["Print a simple configuration setting","yarn config get yarnPath"],["Print a complex configuration setting","yarn config get packageExtensions"],["Print a nested field from the configuration",`yarn config get 'npmScopes["my-company"].npmRegistryServer'`],["Print a token from the configuration","yarn config get npmAuthToken --no-redacted"],["Print a configuration setting as JSON","yarn config get packageExtensions --json"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=this.name.replace(/[.[].*$/,""),a=this.name.replace(/^[^.[]*/,"");if(typeof r.settings.get(s)>"u")throw new nt(`Couldn't find a configuration settings named "${s}"`);let c=r.getSpecial(s,{hideSecrets:!this.unsafe,getNativePaths:!0}),f=je.convertMapsToIndexableObjects(c),p=a?va(f,a):f,h=await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async E=>{E.reportJson(p)});if(!this.json){if(typeof p=="string")return this.context.stdout.write(`${p} `),h.exitCode();J5.inspect.styles.name="cyan",this.context.stdout.write(`${(0,J5.inspect)(p,{depth:1/0,colors:r.get("enableColors"),compact:!1})} `)}return h.exitCode()}};Ge();Yt();ql();var K5=Ie("util"),cC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Set complex configuration settings to JSON values"});this.home=ge.Boolean("-H,--home",!1,{description:"Update the home configuration instead of the project configuration"});this.name=ge.String();this.value=ge.String()}static{this.paths=[["config","set"]]}static{this.usage=ot.Usage({description:"change a configuration settings",details:` This command will set a configuration setting. When used without the \`--json\` flag, it can only set a simple configuration setting (a string, a number, or a boolean). When used with the \`--json\` flag, it can set both simple and complex configuration settings, including Arrays and Objects. `,examples:[["Set a simple configuration setting (a string, a number, or a boolean)","yarn config set initScope myScope"],["Set a simple configuration setting (a string, a number, or a boolean) using the `--json` flag",'yarn config set initScope --json \\"myScope\\"'],["Set a complex configuration setting (an Array) using the `--json` flag",`yarn config set unsafeHttpWhitelist --json '["*.example.com", "example.com"]'`],["Set a complex configuration setting (an Object) using the `--json` flag",`yarn config set packageExtensions --json '{ "@babel/parser@*": { "dependencies": { "@babel/types": "*" } } }'`],["Set a nested configuration setting",'yarn config set npmScopes.company.npmRegistryServer "https://npm.example.com"'],["Set a nested configuration setting using indexed access for non-simple keys",`yarn config set 'npmRegistries["//npm.example.com"].npmAuthToken' "ffffffff-ffff-ffff-ffff-ffffffffffff"`]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=()=>{if(!r.projectCwd)throw new nt("This command must be run from within a project folder");return r.projectCwd},a=this.name.replace(/[.[].*$/,""),n=this.name.replace(/^[^.[]*\.?/,"");if(typeof r.settings.get(a)>"u")throw new nt(`Couldn't find a configuration settings named "${a}"`);if(a==="enableStrictSettings")throw new nt("This setting only affects the file it's in, and thus cannot be set from the CLI");let f=this.json?JSON.parse(this.value):this.value;await(this.home?I=>ze.updateHomeConfiguration(I):I=>ze.updateConfiguration(s(),I))(I=>{if(n){let R=f0(I);return Jd(R,this.name,f),R}else return{...I,[a]:f}});let E=(await ze.find(this.context.cwd,this.context.plugins)).getSpecial(a,{hideSecrets:!0,getNativePaths:!0}),C=je.convertMapsToIndexableObjects(E),S=n?va(C,n):C;return(await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout},async I=>{K5.inspect.styles.name="cyan",I.reportInfo(0,`Successfully set ${this.name} to ${(0,K5.inspect)(S,{depth:1/0,colors:r.get("enableColors"),compact:!1})}`)})).exitCode()}};Ge();Yt();ql();var uC=class extends ft{constructor(){super(...arguments);this.home=ge.Boolean("-H,--home",!1,{description:"Update the home configuration instead of the project configuration"});this.name=ge.String()}static{this.paths=[["config","unset"]]}static{this.usage=ot.Usage({description:"unset a configuration setting",details:` This command will unset a configuration setting. `,examples:[["Unset a simple configuration setting","yarn config unset initScope"],["Unset a complex configuration setting","yarn config unset packageExtensions"],["Unset a nested configuration setting","yarn config unset npmScopes.company.npmRegistryServer"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=()=>{if(!r.projectCwd)throw new nt("This command must be run from within a project folder");return r.projectCwd},a=this.name.replace(/[.[].*$/,""),n=this.name.replace(/^[^.[]*\.?/,"");if(typeof r.settings.get(a)>"u")throw new nt(`Couldn't find a configuration settings named "${a}"`);let f=this.home?h=>ze.updateHomeConfiguration(h):h=>ze.updateConfiguration(s(),h);return(await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout},async h=>{let E=!1;await f(C=>{if(!vB(C,this.name))return h.reportWarning(0,`Configuration doesn't contain setting ${this.name}; there is nothing to unset`),E=!0,C;let S=n?f0(C):{...C};return A0(S,this.name),S}),E||h.reportInfo(0,`Successfully unset ${this.name}`)})).exitCode()}};Ge();Dt();Yt();var tF=Ie("util"),fC=class extends ft{constructor(){super(...arguments);this.noDefaults=ge.Boolean("--no-defaults",!1,{description:"Omit the default values from the display"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.verbose=ge.Boolean("-v,--verbose",{hidden:!0});this.why=ge.Boolean("--why",{hidden:!0});this.names=ge.Rest()}static{this.paths=[["config"]]}static{this.usage=ot.Usage({description:"display the current configuration",details:` This command prints the current active configuration settings. `,examples:[["Print the active configuration settings","$0 config"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins,{strict:!1}),s=await SI({configuration:r,stdout:this.context.stdout,forceError:this.json},[{option:this.verbose,message:"The --verbose option is deprecated, the settings' descriptions are now always displayed"},{option:this.why,message:"The --why option is deprecated, the settings' sources are now always displayed"}]);if(s!==null)return s;let a=this.names.length>0?[...new Set(this.names)].sort():[...r.settings.keys()].sort(),n,c=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async f=>{if(r.invalid.size>0&&!this.json){for(let[p,h]of r.invalid)f.reportError(34,`Invalid configuration key "${p}" in ${h}`);f.reportSeparator()}if(this.json)for(let p of a){if(this.noDefaults&&!r.sources.has(p))continue;let h=r.settings.get(p);typeof h>"u"&&f.reportError(34,`No configuration key named "${p}"`);let E=r.getSpecial(p,{hideSecrets:!0,getNativePaths:!0}),C=r.sources.get(p)??"",S=C&&C[0]!=="<"?fe.fromPortablePath(C):C;f.reportJson({key:p,effective:E,source:S,...h})}else{let p={breakLength:1/0,colors:r.get("enableColors"),maxArrayLength:2},h={},E={children:h};for(let C of a){if(this.noDefaults&&!r.sources.has(C))continue;let S=r.settings.get(C),P=r.sources.get(C)??"",I=r.getSpecial(C,{hideSecrets:!0,getNativePaths:!0}),R={Description:{label:"Description",value:he.tuple(he.Type.MARKDOWN,{text:S.description,format:this.cli.format(),paragraphs:!1})},Source:{label:"Source",value:he.tuple(P[0]==="<"?he.Type.CODE:he.Type.PATH,P)}};h[C]={value:he.tuple(he.Type.CODE,C),children:R};let N=(U,W)=>{for(let[ee,ie]of W)if(ie instanceof Map){let ue={};U[ee]={children:ue},N(ue,ie)}else U[ee]={label:ee,value:he.tuple(he.Type.NO_HINT,(0,tF.inspect)(ie,p))}};I instanceof Map?N(R,I):R.Value={label:"Value",value:he.tuple(he.Type.NO_HINT,(0,tF.inspect)(I,p))}}a.length!==1&&(n=void 0),xs.emitTree(E,{configuration:r,json:this.json,stdout:this.context.stdout,separators:2})}});if(!this.json&&typeof n<"u"){let f=a[0],p=(0,tF.inspect)(r.getSpecial(f,{hideSecrets:!0,getNativePaths:!0}),{colors:r.get("enableColors")});this.context.stdout.write(` `),this.context.stdout.write(`${p} `)}return c.exitCode()}};Ge();Yt();Ul();var rF={};Vt(rF,{Strategy:()=>Xv,acceptedStrategies:()=>jlt,dedupe:()=>z5});Ge();Ge();var gye=ut(Go()),Xv=(e=>(e.HIGHEST="highest",e))(Xv||{}),jlt=new Set(Object.values(Xv)),Glt={highest:async(t,e,{resolver:r,fetcher:s,resolveOptions:a,fetchOptions:n})=>{let c=new Map;for(let[p,h]of t.storedResolutions){let E=t.storedDescriptors.get(p);if(typeof E>"u")throw new Error(`Assertion failed: The descriptor (${p}) should have been registered`);je.getSetWithDefault(c,E.identHash).add(h)}let f=new Map(je.mapAndFilter(t.storedDescriptors.values(),p=>G.isVirtualDescriptor(p)?je.mapAndFilter.skip:[p.descriptorHash,je.makeDeferred()]));for(let p of t.storedDescriptors.values()){let h=f.get(p.descriptorHash);if(typeof h>"u")throw new Error(`Assertion failed: The descriptor (${p.descriptorHash}) should have been registered`);let E=t.storedResolutions.get(p.descriptorHash);if(typeof E>"u")throw new Error(`Assertion failed: The resolution (${p.descriptorHash}) should have been registered`);let C=t.originalPackages.get(E);if(typeof C>"u")throw new Error(`Assertion failed: The package (${E}) should have been registered`);Promise.resolve().then(async()=>{let S=r.getResolutionDependencies(p,a),P=Object.fromEntries(await je.allSettledSafe(Object.entries(S).map(async([ee,ie])=>{let ue=f.get(ie.descriptorHash);if(typeof ue>"u")throw new Error(`Assertion failed: The descriptor (${ie.descriptorHash}) should have been registered`);let le=await ue.promise;if(!le)throw new Error("Assertion failed: Expected the dependency to have been through the dedupe process itself");return[ee,le.updatedPackage]})));if(e.length&&!gye.default.isMatch(G.stringifyIdent(p),e)||!r.shouldPersistResolution(C,a))return C;let I=c.get(p.identHash);if(typeof I>"u")throw new Error(`Assertion failed: The resolutions (${p.identHash}) should have been registered`);if(I.size===1)return C;let R=[...I].map(ee=>{let ie=t.originalPackages.get(ee);if(typeof ie>"u")throw new Error(`Assertion failed: The package (${ee}) should have been registered`);return ie}),N=await r.getSatisfying(p,P,R,a),U=N.locators?.[0];if(typeof U>"u"||!N.sorted)return C;let W=t.originalPackages.get(U.locatorHash);if(typeof W>"u")throw new Error(`Assertion failed: The package (${U.locatorHash}) should have been registered`);return W}).then(async S=>{let P=await t.preparePackage(S,{resolver:r,resolveOptions:a});h.resolve({descriptor:p,currentPackage:C,updatedPackage:S,resolvedPackage:P})}).catch(S=>{h.reject(S)})}return[...f.values()].map(p=>p.promise)}};async function z5(t,{strategy:e,patterns:r,cache:s,report:a}){let{configuration:n}=t,c=new ki,f=n.makeResolver(),p=n.makeFetcher(),h={cache:s,checksums:t.storedChecksums,fetcher:p,project:t,report:c,cacheOptions:{skipIntegrityCheck:!0}},E={project:t,resolver:f,report:c,fetchOptions:h};return await a.startTimerPromise("Deduplication step",async()=>{let C=Glt[e],S=await C(t,r,{resolver:f,resolveOptions:E,fetcher:p,fetchOptions:h}),P=Ao.progressViaCounter(S.length);await a.reportProgress(P);let I=0;await Promise.all(S.map(U=>U.then(W=>{if(W===null||W.currentPackage.locatorHash===W.updatedPackage.locatorHash)return;I++;let{descriptor:ee,currentPackage:ie,updatedPackage:ue}=W;a.reportInfo(0,`${G.prettyDescriptor(n,ee)} can be deduped from ${G.prettyLocator(n,ie)} to ${G.prettyLocator(n,ue)}`),a.reportJson({descriptor:G.stringifyDescriptor(ee),currentResolution:G.stringifyLocator(ie),updatedResolution:G.stringifyLocator(ue)}),t.storedResolutions.set(ee.descriptorHash,ue.locatorHash)}).finally(()=>P.tick())));let R;switch(I){case 0:R="No packages";break;case 1:R="One package";break;default:R=`${I} packages`}let N=he.pretty(n,e,he.Type.CODE);return a.reportInfo(0,`${R} can be deduped using the ${N} strategy`),I})}var AC=class extends ft{constructor(){super(...arguments);this.strategy=ge.String("-s,--strategy","highest",{description:"The strategy to use when deduping dependencies",validator:fo(Xv)});this.check=ge.Boolean("-c,--check",!1,{description:"Exit with exit code 1 when duplicates are found, without persisting the dependency tree"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:fo($l)});this.patterns=ge.Rest()}static{this.paths=[["dedupe"]]}static{this.usage=ot.Usage({description:"deduplicate dependencies with overlapping ranges",details:"\n Duplicates are defined as descriptors with overlapping ranges being resolved and locked to different locators. They are a natural consequence of Yarn's deterministic installs, but they can sometimes pile up and unnecessarily increase the size of your project.\n\n This command dedupes dependencies in the current project using different strategies (only one is implemented at the moment):\n\n - `highest`: Reuses (where possible) the locators with the highest versions. This means that dependencies can only be upgraded, never downgraded. It's also guaranteed that it never takes more than a single pass to dedupe the entire dependency tree.\n\n **Note:** Even though it never produces a wrong dependency tree, this command should be used with caution, as it modifies the dependency tree, which can sometimes cause problems when packages don't strictly follow semver recommendations. Because of this, it is recommended to also review the changes manually.\n\n If set, the `-c,--check` flag will only report the found duplicates, without persisting the modified dependency tree. If changes are found, the command will exit with a non-zero exit code, making it suitable for CI purposes.\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n\n This command accepts glob patterns as arguments (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n ### In-depth explanation:\n\n Yarn doesn't deduplicate dependencies by default, otherwise installs wouldn't be deterministic and the lockfile would be useless. What it actually does is that it tries to not duplicate dependencies in the first place.\n\n **Example:** If `foo@^2.3.4` (a dependency of a dependency) has already been resolved to `foo@2.3.4`, running `yarn add foo@*`will cause Yarn to reuse `foo@2.3.4`, even if the latest `foo` is actually `foo@2.10.14`, thus preventing unnecessary duplication.\n\n Duplication happens when Yarn can't unlock dependencies that have already been locked inside the lockfile.\n\n **Example:** If `foo@^2.3.4` (a dependency of a dependency) has already been resolved to `foo@2.3.4`, running `yarn add foo@2.10.14` will cause Yarn to install `foo@2.10.14` because the existing resolution doesn't satisfy the range `2.10.14`. This behavior can lead to (sometimes) unwanted duplication, since now the lockfile contains 2 separate resolutions for the 2 `foo` descriptors, even though they have overlapping ranges, which means that the lockfile can be simplified so that both descriptors resolve to `foo@2.10.14`.\n ",examples:[["Dedupe all packages","$0 dedupe"],["Dedupe all packages using a specific strategy","$0 dedupe --strategy highest"],["Dedupe a specific package","$0 dedupe lodash"],["Dedupe all packages with the `@babel/*` scope","$0 dedupe '@babel/*'"],["Check for duplicates (can be used as a CI step)","$0 dedupe --check"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=await Kr.find(r);await s.restoreInstallState({restoreResolutions:!1});let n=0,c=await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout,json:this.json},async f=>{n=await z5(s,{strategy:this.strategy,patterns:this.patterns,cache:a,report:f})});return c.hasErrors()?c.exitCode():this.check?n?1:0:await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:a,mode:this.mode})}};Ge();Yt();var pC=class extends ft{static{this.paths=[["--clipanion=definitions"]]}async execute(){let{plugins:e}=await ze.find(this.context.cwd,this.context.plugins),r=[];for(let c of e){let{commands:f}=c[1];if(f){let h=Ca.from(f).definitions();r.push([c[0],h])}}let s=this.cli.definitions(),a=(c,f)=>c.split(" ").slice(1).join()===f.split(" ").slice(1).join(),n=dye()["@yarnpkg/builder"].bundles.standard;for(let c of r){let f=c[1];for(let p of f)s.find(h=>a(h.path,p.path)).plugin={name:c[0],isDefault:n.includes(c[0])}}this.context.stdout.write(`${JSON.stringify(s,null,2)} `)}};var hC=class extends ft{static{this.paths=[["help"],["--help"],["-h"]]}async execute(){this.context.stdout.write(this.cli.usage(null))}};Ge();Dt();Yt();var gC=class extends ft{constructor(){super(...arguments);this.leadingArgument=ge.String();this.args=ge.Proxy()}async execute(){if(this.leadingArgument.match(/[\\/]/)&&!G.tryParseIdent(this.leadingArgument)){let r=J.resolve(this.context.cwd,fe.toPortablePath(this.leadingArgument));return await this.cli.run(this.args,{cwd:r})}else return await this.cli.run(["run",this.leadingArgument,...this.args])}};Ge();var dC=class extends ft{static{this.paths=[["-v"],["--version"]]}async execute(){this.context.stdout.write(`${fn||""} `)}};Ge();Ge();Yt();var mC=class extends ft{constructor(){super(...arguments);this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[["exec"]]}static{this.usage=ot.Usage({description:"execute a shell script",details:` This command simply executes a shell script within the context of the root directory of the active workspace using the portable shell. It also makes sure to call it in a way that's compatible with the current project (for example, on PnP projects the environment will be setup in such a way that PnP will be correctly injected into the environment). `,examples:[["Execute a single shell command","$0 exec echo Hello World"],["Execute a shell script",'$0 exec "tsc & babel src --out-dir lib"']]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,locator:a}=await Tt.find(r,this.context.cwd);return await s.restoreInstallState(),await In.executePackageShellcode(a,this.commandName,this.args,{cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,project:s})}};Ge();Yt();Ul();var yC=class extends ft{constructor(){super(...arguments);this.hash=ge.String({required:!1,validator:Nx(wE(),[Z2(/^p[0-9a-f]{6}$/)])})}static{this.paths=[["explain","peer-requirements"]]}static{this.usage=ot.Usage({description:"explain a set of peer requirements",details:` A peer requirement represents all peer requests that a subject must satisfy when providing a requested package to requesters. When the hash argument is specified, this command prints a detailed explanation of the peer requirement corresponding to the hash and whether it is satisfied or not. When used without arguments, this command lists all peer requirements and the corresponding hash that can be used to get detailed information about a given requirement. **Note:** A hash is a seven-letter code consisting of the letter 'p' followed by six characters that can be obtained from peer dependency warnings or from the list of all peer requirements(\`yarn explain peer-requirements\`). `,examples:[["Explain the corresponding peer requirement for a hash","$0 explain peer-requirements p1a4ed"],["List all peer requirements","$0 explain peer-requirements"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return await s.restoreInstallState({restoreResolutions:!1}),await s.applyLightResolution(),typeof this.hash<"u"?await Wlt(this.hash,s,{stdout:this.context.stdout}):await Ylt(s,{stdout:this.context.stdout})}};async function Wlt(t,e,r){let s=e.peerRequirementNodes.get(t);if(typeof s>"u")throw new Error(`No peerDependency requirements found for hash: "${t}"`);let a=new Set,n=p=>a.has(p.requester.locatorHash)?{value:he.tuple(he.Type.DEPENDENT,{locator:p.requester,descriptor:p.descriptor}),children:p.children.size>0?[{value:he.tuple(he.Type.NO_HINT,"...")}]:[]}:(a.add(p.requester.locatorHash),{value:he.tuple(he.Type.DEPENDENT,{locator:p.requester,descriptor:p.descriptor}),children:Object.fromEntries(Array.from(p.children.values(),h=>[G.stringifyLocator(h.requester),n(h)]))}),c=e.peerWarnings.find(p=>p.hash===t);return(await Ot.start({configuration:e.configuration,stdout:r.stdout,includeFooter:!1,includePrefix:!1},async p=>{let h=he.mark(e.configuration),E=c?h.Cross:h.Check;if(p.reportInfo(0,`Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} is requested to provide ${he.pretty(e.configuration,s.ident,he.Type.IDENT)} by its descendants`),p.reportSeparator(),p.reportInfo(0,he.pretty(e.configuration,s.subject,he.Type.LOCATOR)),xs.emitTree({children:Object.fromEntries(Array.from(s.requests.values(),C=>[G.stringifyLocator(C.requester),n(C)]))},{configuration:e.configuration,stdout:r.stdout,json:!1}),p.reportSeparator(),s.provided.range==="missing:"){let C=c?"":" , but all peer requests are optional";p.reportInfo(0,`${E} Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} does not provide ${he.pretty(e.configuration,s.ident,he.Type.IDENT)}${C}.`)}else{let C=e.storedResolutions.get(s.provided.descriptorHash);if(!C)throw new Error("Assertion failed: Expected the descriptor to be registered");let S=e.storedPackages.get(C);if(!S)throw new Error("Assertion failed: Expected the package to be registered");p.reportInfo(0,`${E} Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} provides ${he.pretty(e.configuration,s.ident,he.Type.IDENT)} with version ${G.prettyReference(e.configuration,S.version??"0.0.0")}, ${c?"which does not satisfy all requests.":"which satisfies all requests"}`),c?.type===3&&(c.range?p.reportInfo(0,` The combined requested range is ${he.pretty(e.configuration,c.range,he.Type.RANGE)}`):p.reportInfo(0," Unfortunately, the requested ranges have no overlap"))}})).exitCode()}async function Ylt(t,e){return(await Ot.start({configuration:t.configuration,stdout:e.stdout,includeFooter:!1,includePrefix:!1},async s=>{let a=he.mark(t.configuration),n=je.sortMap(t.peerRequirementNodes,[([,c])=>G.stringifyLocator(c.subject),([,c])=>G.stringifyIdent(c.ident)]);for(let[,c]of n.values()){if(!c.root)continue;let f=t.peerWarnings.find(E=>E.hash===c.hash),p=[...G.allPeerRequests(c)],h;if(p.length>2?h=` and ${p.length-1} other dependencies`:p.length===2?h=" and 1 other dependency":h="",c.provided.range!=="missing:"){let E=t.storedResolutions.get(c.provided.descriptorHash);if(!E)throw new Error("Assertion failed: Expected the resolution to have been registered");let C=t.storedPackages.get(E);if(!C)throw new Error("Assertion failed: Expected the provided package to have been registered");let S=`${he.pretty(t.configuration,c.hash,he.Type.CODE)} \u2192 ${f?a.Cross:a.Check} ${G.prettyLocator(t.configuration,c.subject)} provides ${G.prettyLocator(t.configuration,C)} to ${G.prettyLocator(t.configuration,p[0].requester)}${h}`;f?s.reportWarning(0,S):s.reportInfo(0,S)}else{let E=`${he.pretty(t.configuration,c.hash,he.Type.CODE)} \u2192 ${f?a.Cross:a.Check} ${G.prettyLocator(t.configuration,c.subject)} doesn't provide ${G.prettyIdent(t.configuration,c.ident)} to ${G.prettyLocator(t.configuration,p[0].requester)}${h}`;f?s.reportWarning(0,E):s.reportInfo(0,E)}}})).exitCode()}Ge();Yt();Ul();Ge();Ge();Dt();Yt();var mye=ut(Ai()),EC=class extends ft{constructor(){super(...arguments);this.useYarnPath=ge.Boolean("--yarn-path",{description:"Set the yarnPath setting even if the version can be accessed by Corepack"});this.onlyIfNeeded=ge.Boolean("--only-if-needed",!1,{description:"Only lock the Yarn version if it isn't already locked"});this.version=ge.String()}static{this.paths=[["set","version"]]}static{this.usage=ot.Usage({description:"lock the Yarn version used by the project",details:"\n This command will set a specific release of Yarn to be used by Corepack: https://nodejs.org/api/corepack.html.\n\n By default it only will set the `packageManager` field at the root of your project, but if the referenced release cannot be represented this way, if you already have `yarnPath` configured, or if you set the `--yarn-path` command line flag, then the release will also be downloaded from the Yarn GitHub repository, stored inside your project, and referenced via the `yarnPath` settings from your project `.yarnrc.yml` file.\n\n A very good use case for this command is to enforce the version of Yarn used by any single member of your team inside the same project - by doing this you ensure that you have control over Yarn upgrades and downgrades (including on your deployment servers), and get rid of most of the headaches related to someone using a slightly different version and getting different behavior.\n\n The version specifier can be:\n\n - a tag:\n - `latest` / `berry` / `stable` -> the most recent stable berry (`>=2.0.0`) release\n - `canary` -> the most recent canary (release candidate) berry (`>=2.0.0`) release\n - `classic` -> the most recent classic (`^0.x || ^1.x`) release\n\n - a semver range (e.g. `2.x`) -> the most recent version satisfying the range (limited to berry releases)\n\n - a semver version (e.g. `2.4.1`, `1.22.1`)\n\n - a local file referenced through either a relative or absolute path\n\n - `self` -> the version used to invoke the command\n ",examples:[["Download the latest release from the Yarn repository","$0 set version latest"],["Download the latest canary release from the Yarn repository","$0 set version canary"],["Download the latest classic release from the Yarn repository","$0 set version classic"],["Download the most recent Yarn 3 build","$0 set version 3.x"],["Download a specific Yarn 2 build","$0 set version 2.0.0-rc.30"],["Switch back to a specific Yarn 1 release","$0 set version 1.22.1"],["Use a release from the local filesystem","$0 set version ./yarn.cjs"],["Use a release from a URL","$0 set version https://repo.yarnpkg.com/3.1.0/packages/yarnpkg-cli/bin/yarn.js"],["Download the version used to invoke the command","$0 set version self"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(this.onlyIfNeeded&&r.get("yarnPath")){let f=r.sources.get("yarnPath");if(!f)throw new Error("Assertion failed: Expected 'yarnPath' to have a source");let p=r.projectCwd??r.startingCwd;if(J.contains(p,f))return 0}let s=()=>{if(typeof fn>"u")throw new nt("The --install flag can only be used without explicit version specifier from the Yarn CLI");return`file://${process.argv[1]}`},a,n=(f,p)=>({version:p,url:f.replace(/\{\}/g,p)});if(this.version==="self")a={url:s(),version:fn??"self"};else if(this.version==="latest"||this.version==="berry"||this.version==="stable")a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",await Zv(r,"stable"));else if(this.version==="canary")a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",await Zv(r,"canary"));else if(this.version==="classic")a={url:"https://classic.yarnpkg.com/latest.js",version:"classic"};else if(this.version.match(/^https?:/))a={url:this.version,version:"remote"};else if(this.version.match(/^\.{0,2}[\\/]/)||fe.isAbsolute(this.version))a={url:`file://${J.resolve(fe.toPortablePath(this.version))}`,version:"file"};else if(Fr.satisfiesWithPrereleases(this.version,">=2.0.0"))a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",this.version);else if(Fr.satisfiesWithPrereleases(this.version,"^0.x || ^1.x"))a=n("https://github.com/yarnpkg/yarn/releases/download/v{}/yarn-{}.js",this.version);else if(Fr.validRange(this.version))a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",await Vlt(r,this.version));else throw new nt(`Invalid version descriptor "${this.version}"`);return(await Ot.start({configuration:r,stdout:this.context.stdout,includeLogs:!this.context.quiet},async f=>{let p=async()=>{let h="file://";return a.url.startsWith(h)?(f.reportInfo(0,`Retrieving ${he.pretty(r,a.url,he.Type.PATH)}`),await ce.readFilePromise(a.url.slice(h.length))):(f.reportInfo(0,`Downloading ${he.pretty(r,a.url,he.Type.URL)}`),await nn.get(a.url,{configuration:r}))};await X5(r,a.version,p,{report:f,useYarnPath:this.useYarnPath})})).exitCode()}};async function Vlt(t,e){let s=(await nn.get("https://repo.yarnpkg.com/tags",{configuration:t,jsonResponse:!0})).tags.filter(a=>Fr.satisfiesWithPrereleases(a,e));if(s.length===0)throw new nt(`No matching release found for range ${he.pretty(t,e,he.Type.RANGE)}.`);return s[0]}async function Zv(t,e){let r=await nn.get("https://repo.yarnpkg.com/tags",{configuration:t,jsonResponse:!0});if(!r.latest[e])throw new nt(`Tag ${he.pretty(t,e,he.Type.RANGE)} not found`);return r.latest[e]}async function X5(t,e,r,{report:s,useYarnPath:a}){let n,c=async()=>(typeof n>"u"&&(n=await r()),n);if(e===null){let ee=await c();await ce.mktempPromise(async ie=>{let ue=J.join(ie,"yarn.cjs");await ce.writeFilePromise(ue,ee);let{stdout:le}=await qr.execvp(process.execPath,[fe.fromPortablePath(ue),"--version"],{cwd:ie,env:{...t.env,YARN_IGNORE_PATH:"1"}});if(e=le.trim(),!mye.default.valid(e))throw new Error(`Invalid semver version. ${he.pretty(t,"yarn --version",he.Type.CODE)} returned: ${e}`)})}let f=t.projectCwd??t.startingCwd,p=J.resolve(f,".yarn/releases"),h=J.resolve(p,`yarn-${e}.cjs`),E=J.relative(t.startingCwd,h),C=je.isTaggedYarnVersion(e),S=t.get("yarnPath"),P=!C,I=P||!!S||!!a;if(a===!1){if(P)throw new jt(0,"You explicitly opted out of yarnPath usage in your command line, but the version you specified cannot be represented by Corepack");I=!1}else!I&&!process.env.COREPACK_ROOT&&(s.reportWarning(0,`You don't seem to have ${he.applyHyperlink(t,"Corepack","https://nodejs.org/api/corepack.html")} enabled; we'll have to rely on ${he.applyHyperlink(t,"yarnPath","https://yarnpkg.com/configuration/yarnrc#yarnPath")} instead`),I=!0);if(I){let ee=await c();s.reportInfo(0,`Saving the new release in ${he.pretty(t,E,"magenta")}`),await ce.removePromise(J.dirname(h)),await ce.mkdirPromise(J.dirname(h),{recursive:!0}),await ce.writeFilePromise(h,ee,{mode:493}),await ze.updateConfiguration(f,{yarnPath:J.relative(f,h)})}else await ce.removePromise(J.dirname(h)),await ze.updateConfiguration(f,{yarnPath:ze.deleteProperty});let R=await Ut.tryFind(f)||new Ut;R.packageManager=`yarn@${C?e:await Zv(t,"stable")}`;let N={};R.exportTo(N);let U=J.join(f,Ut.fileName),W=`${JSON.stringify(N,null,R.indent)} `;return await ce.changeFilePromise(U,W,{automaticNewlines:!0}),{bundleVersion:e}}function yye(t){return Br[jx(t)]}var Jlt=/## (?YN[0-9]{4}) - `(?[A-Z_]+)`\n\n(?
    (?:.(?!##))+)/gs;async function Klt(t){let r=`https://repo.yarnpkg.com/${je.isTaggedYarnVersion(fn)?fn:await Zv(t,"canary")}/packages/docusaurus/docs/advanced/01-general-reference/error-codes.mdx`,s=await nn.get(r,{configuration:t});return new Map(Array.from(s.toString().matchAll(Jlt),({groups:a})=>{if(!a)throw new Error("Assertion failed: Expected the match to have been successful");let n=yye(a.code);if(a.name!==n)throw new Error(`Assertion failed: Invalid error code data: Expected "${a.name}" to be named "${n}"`);return[a.code,a.details]}))}var IC=class extends ft{constructor(){super(...arguments);this.code=ge.String({required:!1,validator:$2(wE(),[Z2(/^YN[0-9]{4}$/)])});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["explain"]]}static{this.usage=ot.Usage({description:"explain an error code",details:` When the code argument is specified, this command prints its name and its details. When used without arguments, this command lists all error codes and their names. `,examples:[["Explain an error code","$0 explain YN0006"],["List all error codes","$0 explain"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(typeof this.code<"u"){let s=yye(this.code),a=he.pretty(r,s,he.Type.CODE),n=this.cli.format().header(`${this.code} - ${a}`),f=(await Klt(r)).get(this.code),p=typeof f<"u"?he.jsonOrPretty(this.json,r,he.tuple(he.Type.MARKDOWN,{text:f,format:this.cli.format(),paragraphs:!0})):`This error code does not have a description. You can help us by editing this page on GitHub \u{1F642}: ${he.jsonOrPretty(this.json,r,he.tuple(he.Type.URL,"https://github.com/yarnpkg/berry/blob/master/packages/docusaurus/docs/advanced/01-general-reference/error-codes.mdx"))} `;this.json?this.context.stdout.write(`${JSON.stringify({code:this.code,name:s,details:p})} `):this.context.stdout.write(`${n} ${p} `)}else{let s={children:je.mapAndFilter(Object.entries(Br),([a,n])=>Number.isNaN(Number(a))?je.mapAndFilter.skip:{label:Yf(Number(a)),value:he.tuple(he.Type.CODE,n)})};xs.emitTree(s,{configuration:r,stdout:this.context.stdout,json:this.json})}}};Ge();Dt();Yt();var Eye=ut(Go()),CC=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Print versions of a package from the whole project"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Print information for all packages, including transitive dependencies"});this.extra=ge.Array("-X,--extra",[],{description:"An array of requests of extra data provided by plugins"});this.cache=ge.Boolean("--cache",!1,{description:"Print information about the cache entry of a package (path, size, checksum)"});this.dependents=ge.Boolean("--dependents",!1,{description:"Print all dependents for each matching package"});this.manifest=ge.Boolean("--manifest",!1,{description:"Print data obtained by looking at the package archive (license, homepage, ...)"});this.nameOnly=ge.Boolean("--name-only",!1,{description:"Only print the name for the matching packages"});this.virtuals=ge.Boolean("--virtuals",!1,{description:"Print each instance of the virtual packages"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.patterns=ge.Rest()}static{this.paths=[["info"]]}static{this.usage=ot.Usage({description:"see information related to packages",details:"\n This command prints various information related to the specified packages, accepting glob patterns.\n\n By default, if the locator reference is missing, Yarn will default to print the information about all the matching direct dependencies of the package for the active workspace. To instead print all versions of the package that are direct dependencies of any of your workspaces, use the `-A,--all` flag. Adding the `-R,--recursive` flag will also report transitive dependencies.\n\n Some fields will be hidden by default in order to keep the output readable, but can be selectively displayed by using additional options (`--dependents`, `--manifest`, `--virtuals`, ...) described in the option descriptions.\n\n Note that this command will only print the information directly related to the selected packages - if you wish to know why the package is there in the first place, use `yarn why` which will do just that (it also provides a `-R,--recursive` flag that may be of some help).\n ",examples:[["Show information about Lodash","$0 info lodash"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a&&!this.all)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let c=new Set(this.extra);this.cache&&c.add("cache"),this.dependents&&c.add("dependents"),this.manifest&&c.add("manifest");let f=(ie,{recursive:ue})=>{let le=ie.anchoredLocator.locatorHash,me=new Map,pe=[le];for(;pe.length>0;){let Be=pe.shift();if(me.has(Be))continue;let Ce=s.storedPackages.get(Be);if(typeof Ce>"u")throw new Error("Assertion failed: Expected the package to be registered");if(me.set(Be,Ce),G.isVirtualLocator(Ce)&&pe.push(G.devirtualizeLocator(Ce).locatorHash),!(!ue&&Be!==le))for(let g of Ce.dependencies.values()){let we=s.storedResolutions.get(g.descriptorHash);if(typeof we>"u")throw new Error("Assertion failed: Expected the resolution to be registered");pe.push(we)}}return me.values()},p=({recursive:ie})=>{let ue=new Map;for(let le of s.workspaces)for(let me of f(le,{recursive:ie}))ue.set(me.locatorHash,me);return ue.values()},h=({all:ie,recursive:ue})=>ie&&ue?s.storedPackages.values():ie?p({recursive:ue}):f(a,{recursive:ue}),E=({all:ie,recursive:ue})=>{let le=h({all:ie,recursive:ue}),me=this.patterns.map(Ce=>{let g=G.parseLocator(Ce),we=Eye.default.makeRe(G.stringifyIdent(g)),ye=G.isVirtualLocator(g),Ae=ye?G.devirtualizeLocator(g):g;return se=>{let Z=G.stringifyIdent(se);if(!we.test(Z))return!1;if(g.reference==="unknown")return!0;let De=G.isVirtualLocator(se),Re=De?G.devirtualizeLocator(se):se;return!(ye&&De&&g.reference!==se.reference||Ae.reference!==Re.reference)}}),pe=je.sortMap([...le],Ce=>G.stringifyLocator(Ce));return{selection:pe.filter(Ce=>me.length===0||me.some(g=>g(Ce))),sortedLookup:pe}},{selection:C,sortedLookup:S}=E({all:this.all,recursive:this.recursive});if(C.length===0)throw new nt("No package matched your request");let P=new Map;if(this.dependents)for(let ie of S)for(let ue of ie.dependencies.values()){let le=s.storedResolutions.get(ue.descriptorHash);if(typeof le>"u")throw new Error("Assertion failed: Expected the resolution to be registered");je.getArrayWithDefault(P,le).push(ie)}let I=new Map;for(let ie of S){if(!G.isVirtualLocator(ie))continue;let ue=G.devirtualizeLocator(ie);je.getArrayWithDefault(I,ue.locatorHash).push(ie)}let R={},N={children:R},U=r.makeFetcher(),W={project:s,fetcher:U,cache:n,checksums:s.storedChecksums,report:new ki,cacheOptions:{skipIntegrityCheck:!0}},ee=[async(ie,ue,le)=>{if(!ue.has("manifest"))return;let me=await U.fetch(ie,W),pe;try{pe=await Ut.find(me.prefixPath,{baseFs:me.packageFs})}finally{me.releaseFs?.()}le("Manifest",{License:he.tuple(he.Type.NO_HINT,pe.license),Homepage:he.tuple(he.Type.URL,pe.raw.homepage??null)})},async(ie,ue,le)=>{if(!ue.has("cache"))return;let me=s.storedChecksums.get(ie.locatorHash)??null,pe=n.getLocatorPath(ie,me),Be;if(pe!==null)try{Be=await ce.statPromise(pe)}catch{}let Ce=typeof Be<"u"?[Be.size,he.Type.SIZE]:void 0;le("Cache",{Checksum:he.tuple(he.Type.NO_HINT,me),Path:he.tuple(he.Type.PATH,pe),Size:Ce})}];for(let ie of C){let ue=G.isVirtualLocator(ie);if(!this.virtuals&&ue)continue;let le={},me={value:[ie,he.Type.LOCATOR],children:le};if(R[G.stringifyLocator(ie)]=me,this.nameOnly){delete me.children;continue}let pe=I.get(ie.locatorHash);typeof pe<"u"&&(le.Instances={label:"Instances",value:he.tuple(he.Type.NUMBER,pe.length)}),le.Version={label:"Version",value:he.tuple(he.Type.NO_HINT,ie.version)};let Be=(g,we)=>{let ye={};if(le[g]=ye,Array.isArray(we))ye.children=we.map(Ae=>({value:Ae}));else{let Ae={};ye.children=Ae;for(let[se,Z]of Object.entries(we))typeof Z>"u"||(Ae[se]={label:se,value:Z})}};if(!ue){for(let g of ee)await g(ie,c,Be);await r.triggerHook(g=>g.fetchPackageInfo,ie,c,Be)}ie.bin.size>0&&!ue&&Be("Exported Binaries",[...ie.bin.keys()].map(g=>he.tuple(he.Type.PATH,g)));let Ce=P.get(ie.locatorHash);typeof Ce<"u"&&Ce.length>0&&Be("Dependents",Ce.map(g=>he.tuple(he.Type.LOCATOR,g))),ie.dependencies.size>0&&!ue&&Be("Dependencies",[...ie.dependencies.values()].map(g=>{let we=s.storedResolutions.get(g.descriptorHash),ye=typeof we<"u"?s.storedPackages.get(we)??null:null;return he.tuple(he.Type.RESOLUTION,{descriptor:g,locator:ye})})),ie.peerDependencies.size>0&&ue&&Be("Peer dependencies",[...ie.peerDependencies.values()].map(g=>{let we=ie.dependencies.get(g.identHash),ye=typeof we<"u"?s.storedResolutions.get(we.descriptorHash)??null:null,Ae=ye!==null?s.storedPackages.get(ye)??null:null;return he.tuple(he.Type.RESOLUTION,{descriptor:g,locator:Ae})}))}xs.emitTree(N,{configuration:r,json:this.json,stdout:this.context.stdout,separators:this.nameOnly?0:2})}};Ge();Dt();wc();var nF=ut(Fd());Yt();var Z5=ut(Ai());Ul();var zlt=[{selector:t=>t===-1,name:"nodeLinker",value:"node-modules"},{selector:t=>t!==-1&&t<8,name:"enableGlobalCache",value:!1},{selector:t=>t!==-1&&t<8,name:"compressionLevel",value:"mixed"}],wC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.immutable=ge.Boolean("--immutable",{description:"Abort with an error exit code if the lockfile was to be modified"});this.immutableCache=ge.Boolean("--immutable-cache",{description:"Abort with an error exit code if the cache folder was to be modified"});this.refreshLockfile=ge.Boolean("--refresh-lockfile",{description:"Refresh the package metadata stored in the lockfile"});this.checkCache=ge.Boolean("--check-cache",{description:"Always refetch the packages and ensure that their checksums are consistent"});this.checkResolutions=ge.Boolean("--check-resolutions",{description:"Validates that the package resolutions are coherent"});this.inlineBuilds=ge.Boolean("--inline-builds",{description:"Verbosely print the output of the build steps of dependencies"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:fo($l)});this.cacheFolder=ge.String("--cache-folder",{hidden:!0});this.frozenLockfile=ge.Boolean("--frozen-lockfile",{hidden:!0});this.ignoreEngines=ge.Boolean("--ignore-engines",{hidden:!0});this.nonInteractive=ge.Boolean("--non-interactive",{hidden:!0});this.preferOffline=ge.Boolean("--prefer-offline",{hidden:!0});this.production=ge.Boolean("--production",{hidden:!0});this.registry=ge.String("--registry",{hidden:!0});this.silent=ge.Boolean("--silent",{hidden:!0});this.networkTimeout=ge.String("--network-timeout",{hidden:!0})}static{this.paths=[["install"],ot.Default]}static{this.usage=ot.Usage({description:"install the project dependencies",details:"\n This command sets up your project if needed. The installation is split into four different steps that each have their own characteristics:\n\n - **Resolution:** First the package manager will resolve your dependencies. The exact way a dependency version is privileged over another isn't standardized outside of the regular semver guarantees. If a package doesn't resolve to what you would expect, check that all dependencies are correctly declared (also check our website for more information: ).\n\n - **Fetch:** Then we download all the dependencies if needed, and make sure that they're all stored within our cache (check the value of `cacheFolder` in `yarn config` to see where the cache files are stored).\n\n - **Link:** Then we send the dependency tree information to internal plugins tasked with writing them on the disk in some form (for example by generating the `.pnp.cjs` file you might know).\n\n - **Build:** Once the dependency tree has been written on the disk, the package manager will now be free to run the build scripts for all packages that might need it, in a topological order compatible with the way they depend on one another. See https://yarnpkg.com/advanced/lifecycle-scripts for detail.\n\n Note that running this command is not part of the recommended workflow. Yarn supports zero-installs, which means that as long as you store your cache and your `.pnp.cjs` file inside your repository, everything will work without requiring any install right after cloning your repository or switching branches.\n\n If the `--immutable` option is set (defaults to true on CI), Yarn will abort with an error exit code if the lockfile was to be modified (other paths can be added using the `immutablePatterns` configuration setting). For backward compatibility we offer an alias under the name of `--frozen-lockfile`, but it will be removed in a later release.\n\n If the `--immutable-cache` option is set, Yarn will abort with an error exit code if the cache folder was to be modified (either because files would be added, or because they'd be removed).\n\n If the `--refresh-lockfile` option is set, Yarn will keep the same resolution for the packages currently in the lockfile but will refresh their metadata. If used together with `--immutable`, it can validate that the lockfile information are consistent. This flag is enabled by default when Yarn detects it runs within a pull request context.\n\n If the `--check-cache` option is set, Yarn will always refetch the packages and will ensure that their checksum matches what's 1/ described in the lockfile 2/ inside the existing cache files (if present). This is recommended as part of your CI workflow if you're both following the Zero-Installs model and accepting PRs from third-parties, as they'd otherwise have the ability to alter the checked-in packages before submitting them.\n\n If the `--inline-builds` option is set, Yarn will verbosely print the output of the build steps of your dependencies (instead of writing them into individual files). This is likely useful mostly for debug purposes only when using Docker-like environments.\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n ",examples:[["Install the project","$0 install"],["Validate a project when using Zero-Installs","$0 install --immutable --immutable-cache"],["Validate a project when using Zero-Installs (slightly safer if you accept external PRs)","$0 install --immutable --immutable-cache --check-cache"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);typeof this.inlineBuilds<"u"&&r.useWithSource("",{enableInlineBuilds:this.inlineBuilds},r.startingCwd,{overwrite:!0});let s=!!process.env.FUNCTION_TARGET||!!process.env.GOOGLE_RUNTIME,a=await SI({configuration:r,stdout:this.context.stdout},[{option:this.ignoreEngines,message:"The --ignore-engines option is deprecated; engine checking isn't a core feature anymore",error:!nF.default.VERCEL},{option:this.registry,message:"The --registry option is deprecated; prefer setting npmRegistryServer in your .yarnrc.yml file"},{option:this.preferOffline,message:"The --prefer-offline flag is deprecated; use the --cached flag with 'yarn add' instead",error:!nF.default.VERCEL},{option:this.production,message:"The --production option is deprecated on 'install'; use 'yarn workspaces focus' instead",error:!0},{option:this.nonInteractive,message:"The --non-interactive option is deprecated",error:!s},{option:this.frozenLockfile,message:"The --frozen-lockfile option is deprecated; use --immutable and/or --immutable-cache instead",callback:()=>this.immutable=this.frozenLockfile},{option:this.cacheFolder,message:"The cache-folder option has been deprecated; use rc settings instead",error:!nF.default.NETLIFY}]);if(a!==null)return a;let n=this.mode==="update-lockfile";if(n&&(this.immutable||this.immutableCache))throw new nt(`${he.pretty(r,"--immutable",he.Type.CODE)} and ${he.pretty(r,"--immutable-cache",he.Type.CODE)} cannot be used with ${he.pretty(r,"--mode=update-lockfile",he.Type.CODE)}`);let c=(this.immutable??r.get("enableImmutableInstalls"))&&!n,f=this.immutableCache&&!n;if(r.projectCwd!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{let U=!1;await $lt(r,c)&&(N.reportInfo(48,"Automatically removed core plugins that are now builtins \u{1F44D}"),U=!0),await Zlt(r,c)&&(N.reportInfo(48,"Automatically fixed merge conflicts \u{1F44D}"),U=!0),U&&N.reportSeparator()});if(R.hasErrors())return R.exitCode()}if(r.projectCwd!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{if(ze.telemetry?.isNew)ze.telemetry.commitTips(),N.reportInfo(65,"Yarn will periodically gather anonymous telemetry: https://yarnpkg.com/advanced/telemetry"),N.reportInfo(65,`Run ${he.pretty(r,"yarn config set --home enableTelemetry 0",he.Type.CODE)} to disable`),N.reportSeparator();else if(ze.telemetry?.shouldShowTips){let U=await nn.get("https://repo.yarnpkg.com/tags",{configuration:r,jsonResponse:!0}).catch(()=>null);if(U!==null){let W=null;if(fn!==null){let ie=Z5.default.prerelease(fn)?"canary":"stable",ue=U.latest[ie];Z5.default.gt(ue,fn)&&(W=[ie,ue])}if(W)ze.telemetry.commitTips(),N.reportInfo(88,`${he.applyStyle(r,`A new ${W[0]} version of Yarn is available:`,he.Style.BOLD)} ${G.prettyReference(r,W[1])}!`),N.reportInfo(88,`Upgrade now by running ${he.pretty(r,`yarn set version ${W[1]}`,he.Type.CODE)}`),N.reportSeparator();else{let ee=ze.telemetry.selectTip(U.tips);ee&&(N.reportInfo(89,he.pretty(r,ee.message,he.Type.MARKDOWN_INLINE)),ee.url&&N.reportInfo(89,`Learn more at ${ee.url}`),N.reportSeparator())}}}});if(R.hasErrors())return R.exitCode()}let{project:p,workspace:h}=await Tt.find(r,this.context.cwd),E=p.lockfileLastVersion;if(E!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{let U={};for(let W of zlt)W.selector(E)&&typeof r.sources.get(W.name)>"u"&&(r.use("",{[W.name]:W.value},p.cwd,{overwrite:!0}),U[W.name]=W.value);Object.keys(U).length>0&&(await ze.updateConfiguration(p.cwd,U),N.reportInfo(87,"Migrated your project to the latest Yarn version \u{1F680}"),N.reportSeparator())});if(R.hasErrors())return R.exitCode()}let C=await Kr.find(r,{immutable:f,check:this.checkCache});if(!h)throw new ar(p.cwd,this.context.cwd);await p.restoreInstallState({restoreResolutions:!1});let S=r.get("enableHardenedMode");S&&typeof r.sources.get("enableHardenedMode")>"u"&&await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async R=>{R.reportWarning(0,"Yarn detected that the current workflow is executed from a public pull request. For safety the hardened mode has been enabled."),R.reportWarning(0,`It will prevent malicious lockfile manipulations, in exchange for a slower install time. You can opt-out if necessary; check our ${he.applyHyperlink(r,"documentation","https://yarnpkg.com/features/security#hardened-mode")} for more details.`),R.reportSeparator()}),(this.refreshLockfile??S)&&(p.lockfileNeedsRefresh=!0);let P=this.checkResolutions??S;return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,forceSectionAlignment:!0,includeLogs:!0,includeVersion:!0},async R=>{await p.install({cache:C,report:R,immutable:c,checkResolutions:P,mode:this.mode})})).exitCode()}},Xlt="<<<<<<<";async function Zlt(t,e){if(!t.projectCwd)return!1;let r=J.join(t.projectCwd,Er.lockfile);if(!await ce.existsPromise(r)||!(await ce.readFilePromise(r,"utf8")).includes(Xlt))return!1;if(e)throw new jt(47,"Cannot autofix a lockfile when running an immutable install");let a=await qr.execvp("git",["rev-parse","MERGE_HEAD","HEAD"],{cwd:t.projectCwd});if(a.code!==0&&(a=await qr.execvp("git",["rev-parse","REBASE_HEAD","HEAD"],{cwd:t.projectCwd})),a.code!==0&&(a=await qr.execvp("git",["rev-parse","CHERRY_PICK_HEAD","HEAD"],{cwd:t.projectCwd})),a.code!==0)throw new jt(83,"Git returned an error when trying to find the commits pertaining to the conflict");let n=await Promise.all(a.stdout.trim().split(/\n/).map(async f=>{let p=await qr.execvp("git",["show",`${f}:./${Er.lockfile}`],{cwd:t.projectCwd});if(p.code!==0)throw new jt(83,`Git returned an error when trying to access the lockfile content in ${f}`);try{return ls(p.stdout)}catch{throw new jt(46,"A variant of the conflicting lockfile failed to parse")}}));n=n.filter(f=>!!f.__metadata);for(let f of n){if(f.__metadata.version<7)for(let p of Object.keys(f)){if(p==="__metadata")continue;let h=G.parseDescriptor(p,!0),E=t.normalizeDependency(h),C=G.stringifyDescriptor(E);C!==p&&(f[C]=f[p],delete f[p])}for(let p of Object.keys(f)){if(p==="__metadata")continue;let h=f[p].checksum;typeof h>"u"||h.includes("/")||(f[p].checksum=`${f.__metadata.cacheKey}/${h}`)}}let c=Object.assign({},...n);c.__metadata.version=`${Math.min(...n.map(f=>parseInt(f.__metadata.version??0)))}`,c.__metadata.cacheKey="merged";for(let[f,p]of Object.entries(c))typeof p=="string"&&delete c[f];return await ce.changeFilePromise(r,nl(c),{automaticNewlines:!0}),!0}async function $lt(t,e){if(!t.projectCwd)return!1;let r=[],s=J.join(t.projectCwd,".yarn/plugins/@yarnpkg");return await ze.updateConfiguration(t.projectCwd,{plugins:n=>{if(!Array.isArray(n))return n;let c=n.filter(f=>{if(!f.path)return!0;let p=J.resolve(t.projectCwd,f.path),h=ov.has(f.spec)&&J.contains(s,p);return h&&r.push(p),!h});return c.length===0?ze.deleteProperty:c.length===n.length?n:c}},{immutable:e})?(await Promise.all(r.map(async n=>{await ce.removePromise(n)})),!0):!1}Ge();Dt();Yt();var BC=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Link all workspaces belonging to the target projects to the current one"});this.private=ge.Boolean("-p,--private",!1,{description:"Also link private workspaces belonging to the target projects to the current one"});this.relative=ge.Boolean("-r,--relative",!1,{description:"Link workspaces using relative paths instead of absolute paths"});this.destinations=ge.Rest()}static{this.paths=[["link"]]}static{this.usage=ot.Usage({description:"connect the local project to another one",details:"\n This command will set a new `resolutions` field in the project-level manifest and point it to the workspace at the specified location (even if part of another project).\n ",examples:[["Register one or more remote workspaces for use in the current project","$0 link ~/ts-loader ~/jest"],["Register all workspaces from a remote project for use in the current project","$0 link ~/jest --all"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=s.topLevelWorkspace,f=[];for(let p of this.destinations){let h=J.resolve(this.context.cwd,fe.toPortablePath(p)),E=await ze.find(h,this.context.plugins,{useRc:!1,strict:!1}),{project:C,workspace:S}=await Tt.find(E,h);if(s.cwd===C.cwd)throw new nt(`Invalid destination '${p}'; Can't link the project to itself`);if(!S)throw new ar(C.cwd,h);if(this.all){let P=!1;for(let I of C.workspaces)I.manifest.name&&(!I.manifest.private||this.private)&&(f.push(I),P=!0);if(!P)throw new nt(`No workspace found to be linked in the target project: ${p}`)}else{if(!S.manifest.name)throw new nt(`The target workspace at '${p}' doesn't have a name and thus cannot be linked`);if(S.manifest.private&&!this.private)throw new nt(`The target workspace at '${p}' is marked private - use the --private flag to link it anyway`);f.push(S)}}for(let p of f){let h=G.stringifyIdent(p.anchoredLocator),E=this.relative?J.relative(s.cwd,p.cwd):p.cwd;c.manifest.resolutions.push({pattern:{descriptor:{fullName:h}},reference:`portal:${E}`})}return await s.installWithNewReport({stdout:this.context.stdout},{cache:n})}};Yt();var vC=class extends ft{constructor(){super(...arguments);this.args=ge.Proxy()}static{this.paths=[["node"]]}static{this.usage=ot.Usage({description:"run node with the hook already setup",details:` This command simply runs Node. It also makes sure to call it in a way that's compatible with the current project (for example, on PnP projects the environment will be setup in such a way that PnP will be correctly injected into the environment). The Node process will use the exact same version of Node as the one used to run Yarn itself, which might be a good way to ensure that your commands always use a consistent Node version. `,examples:[["Run a Node script","$0 node ./my-script.js"]]})}async execute(){return this.cli.run(["exec","node",...this.args])}};Ge();Yt();var SC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["plugin","check"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"find all third-party plugins that differ from their own spec",details:` Check only the plugins from https. If this command detects any plugin differences in the CI environment, it will throw an error. `,examples:[["find all third-party plugins that differ from their own spec","$0 plugin check"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=await ze.findRcFiles(this.context.cwd);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async n=>{for(let c of s)if(c.data?.plugins)for(let f of c.data.plugins){if(!f.checksum||!f.spec.match(/^https?:/))continue;let p=await nn.get(f.spec,{configuration:r}),h=Nn.makeHash(p);if(f.checksum===h)continue;let E=he.pretty(r,f.path,he.Type.PATH),C=he.pretty(r,f.spec,he.Type.URL),S=`${E} is different from the file provided by ${C}`;n.reportJson({...f,newChecksum:h}),n.reportError(0,S)}})).exitCode()}};Ge();Ge();Dt();Yt();var vye=Ie("os");Ge();Dt();Yt();var Iye=Ie("os");Ge();wc();Yt();var ect="https://raw.githubusercontent.com/yarnpkg/berry/master/plugins.yml";async function Sm(t,e){let r=await nn.get(ect,{configuration:t}),s=ls(r.toString());return Object.fromEntries(Object.entries(s).filter(([a,n])=>!e||Fr.satisfiesWithPrereleases(e,n.range??"<4.0.0-rc.1")))}var DC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["plugin","list"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"list the available official plugins",details:"\n This command prints the plugins available directly from the Yarn repository. Only those plugins can be referenced by name in `yarn plugin import`.\n ",examples:[["List the official plugins","$0 plugin list"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async a=>{let n=await Sm(r,fn);for(let[c,{experimental:f,...p}]of Object.entries(n)){let h=c;f&&(h+=" [experimental]"),a.reportJson({name:c,experimental:f,...p}),a.reportInfo(null,h)}})).exitCode()}};var tct=/^[0-9]+$/,rct=process.platform==="win32";function Cye(t){return tct.test(t)?`pull/${t}/head`:t}var nct=({repository:t,branch:e},r)=>[["git","init",fe.fromPortablePath(r)],["git","remote","add","origin",t],["git","fetch","origin","--depth=1",Cye(e)],["git","reset","--hard","FETCH_HEAD"]],ict=({branch:t})=>[["git","fetch","origin","--depth=1",Cye(t),"--force"],["git","reset","--hard","FETCH_HEAD"],["git","clean","-dfx","-e","packages/yarnpkg-cli/bundles"]],sct=({plugins:t,noMinify:e},r,s)=>[["yarn","build:cli",...new Array().concat(...t.map(a=>["--plugin",J.resolve(s,a)])),...e?["--no-minify"]:[],"|"],[rct?"move":"mv","packages/yarnpkg-cli/bundles/yarn.js",fe.fromPortablePath(r),"|"]],bC=class extends ft{constructor(){super(...arguments);this.installPath=ge.String("--path",{description:"The path where the repository should be cloned to"});this.repository=ge.String("--repository","https://github.com/yarnpkg/berry.git",{description:"The repository that should be cloned"});this.branch=ge.String("--branch","master",{description:"The branch of the repository that should be cloned"});this.plugins=ge.Array("--plugin",[],{description:"An array of additional plugins that should be included in the bundle"});this.dryRun=ge.Boolean("-n,--dry-run",!1,{description:"If set, the bundle will be built but not added to the project"});this.noMinify=ge.Boolean("--no-minify",!1,{description:"Build a bundle for development (debugging) - non-minified and non-mangled"});this.force=ge.Boolean("-f,--force",!1,{description:"Always clone the repository instead of trying to fetch the latest commits"});this.skipPlugins=ge.Boolean("--skip-plugins",!1,{description:"Skip updating the contrib plugins"})}static{this.paths=[["set","version","from","sources"]]}static{this.usage=ot.Usage({description:"build Yarn from master",details:` This command will clone the Yarn repository into a temporary folder, then build it. The resulting bundle will then be copied into the local project. By default, it also updates all contrib plugins to the same commit the bundle is built from. This behavior can be disabled by using the \`--skip-plugins\` flag. `,examples:[["Build Yarn from master","$0 set version from sources"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=typeof this.installPath<"u"?J.resolve(this.context.cwd,fe.toPortablePath(this.installPath)):J.resolve(fe.toPortablePath((0,Iye.tmpdir)()),"yarnpkg-sources",Nn.makeHash(this.repository).slice(0,6));return(await Ot.start({configuration:r,stdout:this.context.stdout},async c=>{await $5(this,{configuration:r,report:c,target:a}),c.reportSeparator(),c.reportInfo(0,"Building a fresh bundle"),c.reportSeparator();let f=await qr.execvp("git",["rev-parse","--short","HEAD"],{cwd:a,strict:!0}),p=J.join(a,`packages/yarnpkg-cli/bundles/yarn-${f.stdout.trim()}.js`);ce.existsSync(p)||(await $v(sct(this,p,a),{configuration:r,context:this.context,target:a}),c.reportSeparator());let h=await ce.readFilePromise(p);if(!this.dryRun){let{bundleVersion:E}=await X5(r,null,async()=>h,{report:c});this.skipPlugins||await oct(this,E,{project:s,report:c,target:a})}})).exitCode()}};async function $v(t,{configuration:e,context:r,target:s}){for(let[a,...n]of t){let c=n[n.length-1]==="|";if(c&&n.pop(),c)await qr.pipevp(a,n,{cwd:s,stdin:r.stdin,stdout:r.stdout,stderr:r.stderr,strict:!0});else{r.stdout.write(`${he.pretty(e,` $ ${[a,...n].join(" ")}`,"grey")} `);try{await qr.execvp(a,n,{cwd:s,strict:!0})}catch(f){throw r.stdout.write(f.stdout||f.stack),f}}}}async function $5(t,{configuration:e,report:r,target:s}){let a=!1;if(!t.force&&ce.existsSync(J.join(s,".git"))){r.reportInfo(0,"Fetching the latest commits"),r.reportSeparator();try{await $v(ict(t),{configuration:e,context:t.context,target:s}),a=!0}catch{r.reportSeparator(),r.reportWarning(0,"Repository update failed; we'll try to regenerate it")}}a||(r.reportInfo(0,"Cloning the remote repository"),r.reportSeparator(),await ce.removePromise(s),await ce.mkdirPromise(s,{recursive:!0}),await $v(nct(t,s),{configuration:e,context:t.context,target:s}))}async function oct(t,e,{project:r,report:s,target:a}){let n=await Sm(r.configuration,e),c=new Set(Object.keys(n));for(let f of r.configuration.plugins.keys())c.has(f)&&await eq(f,t,{project:r,report:s,target:a})}Ge();Ge();Dt();Yt();var wye=ut(Ai()),Bye=Ie("vm");var PC=class extends ft{constructor(){super(...arguments);this.name=ge.String();this.checksum=ge.Boolean("--checksum",!0,{description:"Whether to care if this plugin is modified"})}static{this.paths=[["plugin","import"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"download a plugin",details:` This command downloads the specified plugin from its remote location and updates the configuration to reference it in further CLI invocations. Three types of plugin references are accepted: - If the plugin is stored within the Yarn repository, it can be referenced by name. - Third-party plugins can be referenced directly through their public urls. - Local plugins can be referenced by their path on the disk. If the \`--no-checksum\` option is set, Yarn will no longer care if the plugin is modified. Plugins cannot be downloaded from the npm registry, and aren't allowed to have dependencies (they need to be bundled into a single file, possibly thanks to the \`@yarnpkg/builder\` package). `,examples:[['Download and activate the "@yarnpkg/plugin-exec" plugin',"$0 plugin import @yarnpkg/plugin-exec"],['Download and activate the "@yarnpkg/plugin-exec" plugin (shorthand)',"$0 plugin import exec"],["Download and activate a community plugin","$0 plugin import https://example.org/path/to/plugin.js"],["Activate a local plugin","$0 plugin import ./path/to/plugin.js"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,stdout:this.context.stdout},async a=>{let{project:n}=await Tt.find(r,this.context.cwd),c,f;if(this.name.match(/^\.{0,2}[\\/]/)||fe.isAbsolute(this.name)){let p=J.resolve(this.context.cwd,fe.toPortablePath(this.name));a.reportInfo(0,`Reading ${he.pretty(r,p,he.Type.PATH)}`),c=J.relative(n.cwd,p),f=await ce.readFilePromise(p)}else{let p;if(this.name.match(/^https?:/)){try{new URL(this.name)}catch{throw new jt(52,`Plugin specifier "${this.name}" is neither a plugin name nor a valid url`)}c=this.name,p=this.name}else{let h=G.parseLocator(this.name.replace(/^((@yarnpkg\/)?plugin-)?/,"@yarnpkg/plugin-"));if(h.reference!=="unknown"&&!wye.default.valid(h.reference))throw new jt(0,"Official plugins only accept strict version references. Use an explicit URL if you wish to download them from another location.");let E=G.stringifyIdent(h),C=await Sm(r,fn);if(!Object.hasOwn(C,E)){let S=`Couldn't find a plugin named ${G.prettyIdent(r,h)} on the remote registry. `;throw r.plugins.has(E)?S+=`A plugin named ${G.prettyIdent(r,h)} is already installed; possibly attempting to import a built-in plugin.`:S+=`Note that only the plugins referenced on our website (${he.pretty(r,"https://github.com/yarnpkg/berry/blob/master/plugins.yml",he.Type.URL)}) can be referenced by their name; any other plugin will have to be referenced through its public url (for example ${he.pretty(r,"https://github.com/yarnpkg/berry/raw/master/packages/plugin-typescript/bin/%40yarnpkg/plugin-typescript.js",he.Type.URL)}).`,new jt(51,S)}c=E,p=C[E].url,h.reference!=="unknown"?p=p.replace(/\/master\//,`/${E}/${h.reference}/`):fn!==null&&(p=p.replace(/\/master\//,`/@yarnpkg/cli/${fn}/`))}a.reportInfo(0,`Downloading ${he.pretty(r,p,"green")}`),f=await nn.get(p,{configuration:r})}await tq(c,f,{checksum:this.checksum,project:n,report:a})})).exitCode()}};async function tq(t,e,{checksum:r=!0,project:s,report:a}){let{configuration:n}=s,c={},f={exports:c};(0,Bye.runInNewContext)(e.toString(),{module:f,exports:c});let h=`.yarn/plugins/${f.exports.name}.cjs`,E=J.resolve(s.cwd,h);a.reportInfo(0,`Saving the new plugin in ${he.pretty(n,h,"magenta")}`),await ce.mkdirPromise(J.dirname(E),{recursive:!0}),await ce.writeFilePromise(E,e);let C={path:h,spec:t};r&&(C.checksum=Nn.makeHash(e)),await ze.addPlugin(s.cwd,[C])}var act=({pluginName:t,noMinify:e},r)=>[["yarn",`build:${t}`,...e?["--no-minify"]:[],"|"]],xC=class extends ft{constructor(){super(...arguments);this.installPath=ge.String("--path",{description:"The path where the repository should be cloned to"});this.repository=ge.String("--repository","https://github.com/yarnpkg/berry.git",{description:"The repository that should be cloned"});this.branch=ge.String("--branch","master",{description:"The branch of the repository that should be cloned"});this.noMinify=ge.Boolean("--no-minify",!1,{description:"Build a plugin for development (debugging) - non-minified and non-mangled"});this.force=ge.Boolean("-f,--force",!1,{description:"Always clone the repository instead of trying to fetch the latest commits"});this.name=ge.String()}static{this.paths=[["plugin","import","from","sources"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"build a plugin from sources",details:` This command clones the Yarn repository into a temporary folder, builds the specified contrib plugin and updates the configuration to reference it in further CLI invocations. The plugins can be referenced by their short name if sourced from the official Yarn repository. `,examples:[['Build and activate the "@yarnpkg/plugin-exec" plugin',"$0 plugin import from sources @yarnpkg/plugin-exec"],['Build and activate the "@yarnpkg/plugin-exec" plugin (shorthand)',"$0 plugin import from sources exec"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=typeof this.installPath<"u"?J.resolve(this.context.cwd,fe.toPortablePath(this.installPath)):J.resolve(fe.toPortablePath((0,vye.tmpdir)()),"yarnpkg-sources",Nn.makeHash(this.repository).slice(0,6));return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let{project:c}=await Tt.find(r,this.context.cwd),f=G.parseIdent(this.name.replace(/^((@yarnpkg\/)?plugin-)?/,"@yarnpkg/plugin-")),p=G.stringifyIdent(f),h=await Sm(r,fn);if(!Object.hasOwn(h,p))throw new jt(51,`Couldn't find a plugin named "${p}" on the remote registry. Note that only the plugins referenced on our website (https://github.com/yarnpkg/berry/blob/master/plugins.yml) can be built and imported from sources.`);let E=p;await $5(this,{configuration:r,report:n,target:s}),await eq(E,this,{project:c,report:n,target:s})})).exitCode()}};async function eq(t,{context:e,noMinify:r},{project:s,report:a,target:n}){let c=t.replace(/@yarnpkg\//,""),{configuration:f}=s;a.reportSeparator(),a.reportInfo(0,`Building a fresh ${c}`),a.reportSeparator(),await $v(act({pluginName:c,noMinify:r},n),{configuration:f,context:e,target:n}),a.reportSeparator();let p=J.resolve(n,`packages/${c}/bundles/${t}.js`),h=await ce.readFilePromise(p);await tq(t,h,{project:s,report:a})}Ge();Dt();Yt();var kC=class extends ft{constructor(){super(...arguments);this.name=ge.String()}static{this.paths=[["plugin","remove"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"remove a plugin",details:` This command deletes the specified plugin from the .yarn/plugins folder and removes it from the configuration. **Note:** The plugins have to be referenced by their name property, which can be obtained using the \`yarn plugin runtime\` command. Shorthands are not allowed. `,examples:[["Remove a plugin imported from the Yarn repository","$0 plugin remove @yarnpkg/plugin-typescript"],["Remove a plugin imported from a local file","$0 plugin remove my-local-plugin"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let c=this.name,f=G.parseIdent(c);if(!r.plugins.has(c))throw new nt(`${G.prettyIdent(r,f)} isn't referenced by the current configuration`);let p=`.yarn/plugins/${c}.cjs`,h=J.resolve(s.cwd,p);ce.existsSync(h)&&(n.reportInfo(0,`Removing ${he.pretty(r,p,he.Type.PATH)}...`),await ce.removePromise(h)),n.reportInfo(0,"Updating the configuration..."),await ze.updateConfiguration(s.cwd,{plugins:E=>{if(!Array.isArray(E))return E;let C=E.filter(S=>S.path!==p);return C.length===0?ze.deleteProperty:C.length===E.length?E:C}})})).exitCode()}};Ge();Yt();var QC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["plugin","runtime"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"list the active plugins",details:` This command prints the currently active plugins. Will be displayed both builtin plugins and external plugins. `,examples:[["List the currently active plugins","$0 plugin runtime"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async a=>{for(let n of r.plugins.keys()){let c=this.context.plugins.plugins.has(n),f=n;c&&(f+=" [builtin]"),a.reportJson({name:n,builtin:c}),a.reportInfo(null,`${f}`)}})).exitCode()}};Ge();Ge();Yt();var TC=class extends ft{constructor(){super(...arguments);this.idents=ge.Rest()}static{this.paths=[["rebuild"]]}static{this.usage=ot.Usage({description:"rebuild the project's native packages",details:` This command will automatically cause Yarn to forget about previous compilations of the given packages and to run them again. Note that while Yarn forgets the compilation, the previous artifacts aren't erased from the filesystem and may affect the next builds (in good or bad). To avoid this, you may remove the .yarn/unplugged folder, or any other relevant location where packages might have been stored (Yarn may offer a way to do that automatically in the future). By default all packages will be rebuilt, but you can filter the list by specifying the names of the packages you want to clear from memory. `,examples:[["Rebuild all packages","$0 rebuild"],["Rebuild fsevents only","$0 rebuild fsevents"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);let c=new Set;for(let f of this.idents)c.add(G.parseIdent(f).identHash);if(await s.restoreInstallState({restoreResolutions:!1}),await s.resolveEverything({cache:n,report:new ki}),c.size>0)for(let f of s.storedPackages.values())c.has(f.identHash)&&(s.storedBuildState.delete(f.locatorHash),s.skippedBuilds.delete(f.locatorHash));else s.storedBuildState.clear(),s.skippedBuilds.clear();return await s.installWithNewReport({stdout:this.context.stdout,quiet:this.context.quiet},{cache:n})}};Ge();Ge();Ge();Yt();var rq=ut(Go());Ul();var RC=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Apply the operation to all workspaces from the current project"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:fo($l)});this.patterns=ge.Rest()}static{this.paths=[["remove"]]}static{this.usage=ot.Usage({description:"remove dependencies from the project",details:` This command will remove the packages matching the specified patterns from the current workspace. If the \`--mode=\` option is set, Yarn will change which artifacts are generated. The modes currently supported are: - \`skip-build\` will not run the build scripts at all. Note that this is different from setting \`enableScripts\` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run. - \`update-lockfile\` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost. This command accepts glob patterns as arguments (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them. `,examples:[["Remove a dependency from the current project","$0 remove lodash"],["Remove a dependency from all workspaces at once","$0 remove lodash --all"],["Remove all dependencies starting with `eslint-`","$0 remove 'eslint-*'"],["Remove all dependencies with the `@babel` scope","$0 remove '@babel/*'"],["Remove all dependencies matching `react-dom` or `react-helmet`","$0 remove 'react-{dom,helmet}'"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.all?s.workspaces:[a],f=["dependencies","devDependencies","peerDependencies"],p=[],h=!1,E=[];for(let I of this.patterns){let R=!1,N=G.parseIdent(I);for(let U of c){let W=[...U.manifest.peerDependenciesMeta.keys()];for(let ee of(0,rq.default)(W,I))U.manifest.peerDependenciesMeta.delete(ee),h=!0,R=!0;for(let ee of f){let ie=U.manifest.getForScope(ee),ue=[...ie.values()].map(le=>G.stringifyIdent(le));for(let le of(0,rq.default)(ue,G.stringifyIdent(N))){let{identHash:me}=G.parseIdent(le),pe=ie.get(me);if(typeof pe>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");U.manifest[ee].delete(me),E.push([U,ee,pe]),h=!0,R=!0}}}R||p.push(I)}let C=p.length>1?"Patterns":"Pattern",S=p.length>1?"don't":"doesn't",P=this.all?"any":"this";if(p.length>0)throw new nt(`${C} ${he.prettyList(r,p,he.Type.CODE)} ${S} match any packages referenced by ${P} workspace`);return h?(await r.triggerMultipleHooks(I=>I.afterWorkspaceDependencyRemoval,E),await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})):0}};Ge();Ge();Yt();var Sye=Ie("util"),FC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["run"]]}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);return(await Ot.start({configuration:r,stdout:this.context.stdout,json:this.json},async c=>{let f=a.manifest.scripts,p=je.sortMap(f.keys(),C=>C),h={breakLength:1/0,colors:r.get("enableColors"),maxArrayLength:2},E=p.reduce((C,S)=>Math.max(C,S.length),0);for(let[C,S]of f.entries())c.reportInfo(null,`${C.padEnd(E," ")} ${(0,Sye.inspect)(S,h)}`),c.reportJson({name:C,script:S})})).exitCode()}};Ge();Ge();Yt();var NC=class extends ft{constructor(){super(...arguments);this.inspect=ge.String("--inspect",!1,{tolerateBoolean:!0,description:"Forwarded to the underlying Node process when executing a binary"});this.inspectBrk=ge.String("--inspect-brk",!1,{tolerateBoolean:!0,description:"Forwarded to the underlying Node process when executing a binary"});this.topLevel=ge.Boolean("-T,--top-level",!1,{description:"Check the root workspace for scripts and/or binaries instead of the current one"});this.binariesOnly=ge.Boolean("-B,--binaries-only",!1,{description:"Ignore any user defined scripts and only check for binaries"});this.require=ge.String("--require",{description:"Forwarded to the underlying Node process when executing a binary"});this.silent=ge.Boolean("--silent",{hidden:!0});this.scriptName=ge.String();this.args=ge.Proxy()}static{this.paths=[["run"]]}static{this.usage=ot.Usage({description:"run a script defined in the package.json",details:` This command will run a tool. The exact tool that will be executed will depend on the current state of your workspace: - If the \`scripts\` field from your local package.json contains a matching script name, its definition will get executed. - Otherwise, if one of the local workspace's dependencies exposes a binary with a matching name, this binary will get executed. - Otherwise, if the specified name contains a colon character and if one of the workspaces in the project contains exactly one script with a matching name, then this script will get executed. Whatever happens, the cwd of the spawned process will be the workspace that declares the script (which makes it possible to call commands cross-workspaces using the third syntax). `,examples:[["Run the tests from the local workspace","$0 run test"],['Same thing, but without the "run" keyword',"$0 test"],["Inspect Webpack while running","$0 run --inspect-brk webpack"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a,locator:n}=await Tt.find(r,this.context.cwd);await s.restoreInstallState();let c=this.topLevel?s.topLevelWorkspace.anchoredLocator:n;if(!this.binariesOnly&&await In.hasPackageScript(c,this.scriptName,{project:s}))return await In.executePackageScript(c,this.scriptName,this.args,{project:s,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});let f=await In.getPackageAccessibleBinaries(c,{project:s});if(f.get(this.scriptName)){let h=[];return this.inspect&&(typeof this.inspect=="string"?h.push(`--inspect=${this.inspect}`):h.push("--inspect")),this.inspectBrk&&(typeof this.inspectBrk=="string"?h.push(`--inspect-brk=${this.inspectBrk}`):h.push("--inspect-brk")),this.require&&h.push(`--require=${this.require}`),await In.executePackageAccessibleBinary(c,this.scriptName,this.args,{cwd:this.context.cwd,project:s,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,nodeArgs:h,packageAccessibleBinaries:f})}if(!this.topLevel&&!this.binariesOnly&&a&&this.scriptName.includes(":")){let E=(await Promise.all(s.workspaces.map(async C=>C.manifest.scripts.has(this.scriptName)?C:null))).filter(C=>C!==null);if(E.length===1)return await In.executeWorkspaceScript(E[0],this.scriptName,this.args,{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})}if(this.topLevel)throw this.scriptName==="node-gyp"?new nt(`Couldn't find a script name "${this.scriptName}" in the top-level (used by ${G.prettyLocator(r,n)}). This typically happens because some package depends on "node-gyp" to build itself, but didn't list it in their dependencies. To fix that, please run "yarn add node-gyp" into your top-level workspace. You also can open an issue on the repository of the specified package to suggest them to use an optional peer dependency.`):new nt(`Couldn't find a script name "${this.scriptName}" in the top-level (used by ${G.prettyLocator(r,n)}).`);{if(this.scriptName==="global")throw new nt("The 'yarn global' commands have been removed in 2.x - consider using 'yarn dlx' or a third-party plugin instead");let h=[this.scriptName].concat(this.args);for(let[E,C]of $I)for(let S of C)if(h.length>=S.length&&JSON.stringify(h.slice(0,S.length))===JSON.stringify(S))throw new nt(`Couldn't find a script named "${this.scriptName}", but a matching command can be found in the ${E} plugin. You can install it with "yarn plugin import ${E}".`);throw new nt(`Couldn't find a script named "${this.scriptName}".`)}}};Ge();Ge();Yt();var OC=class extends ft{constructor(){super(...arguments);this.descriptor=ge.String();this.resolution=ge.String()}static{this.paths=[["set","resolution"]]}static{this.usage=ot.Usage({description:"enforce a package resolution",details:'\n This command updates the resolution table so that `descriptor` is resolved by `resolution`.\n\n Note that by default this command only affect the current resolution table - meaning that this "manual override" will disappear if you remove the lockfile, or if the package disappear from the table. If you wish to make the enforced resolution persist whatever happens, edit the `resolutions` field in your top-level manifest.\n\n Note that no attempt is made at validating that `resolution` is a valid resolution entry for `descriptor`.\n ',examples:[["Force all instances of lodash@npm:^1.2.3 to resolve to 1.5.0","$0 set resolution lodash@npm:^1.2.3 npm:1.5.0"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(await s.restoreInstallState({restoreResolutions:!1}),!a)throw new ar(s.cwd,this.context.cwd);let c=G.parseDescriptor(this.descriptor,!0),f=G.makeDescriptor(c,this.resolution);return s.storedDescriptors.set(c.descriptorHash,c),s.storedDescriptors.set(f.descriptorHash,f),s.resolutionAliases.set(c.descriptorHash,f.descriptorHash),await s.installWithNewReport({stdout:this.context.stdout},{cache:n})}};Ge();Dt();Yt();var Dye=ut(Go()),LC=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Unlink all workspaces belonging to the target project from the current one"});this.leadingArguments=ge.Rest()}static{this.paths=[["unlink"]]}static{this.usage=ot.Usage({description:"disconnect the local project from another one",details:` This command will remove any resolutions in the project-level manifest that would have been added via a yarn link with similar arguments. `,examples:[["Unregister a remote workspace in the current project","$0 unlink ~/ts-loader"],["Unregister all workspaces from a remote project in the current project","$0 unlink ~/jest --all"],["Unregister all previously linked workspaces","$0 unlink --all"],["Unregister all workspaces matching a glob","$0 unlink '@babel/*' 'pkg-{a,b}'"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);let c=s.topLevelWorkspace,f=new Set;if(this.leadingArguments.length===0&&this.all)for(let{pattern:p,reference:h}of c.manifest.resolutions)h.startsWith("portal:")&&f.add(p.descriptor.fullName);if(this.leadingArguments.length>0)for(let p of this.leadingArguments){let h=J.resolve(this.context.cwd,fe.toPortablePath(p));if(je.isPathLike(p)){let E=await ze.find(h,this.context.plugins,{useRc:!1,strict:!1}),{project:C,workspace:S}=await Tt.find(E,h);if(!S)throw new ar(C.cwd,h);if(this.all){for(let P of C.workspaces)P.manifest.name&&f.add(G.stringifyIdent(P.anchoredLocator));if(f.size===0)throw new nt("No workspace found to be unlinked in the target project")}else{if(!S.manifest.name)throw new nt("The target workspace doesn't have a name and thus cannot be unlinked");f.add(G.stringifyIdent(S.anchoredLocator))}}else{let E=[...c.manifest.resolutions.map(({pattern:C})=>C.descriptor.fullName)];for(let C of(0,Dye.default)(E,p))f.add(C)}}return c.manifest.resolutions=c.manifest.resolutions.filter(({pattern:p})=>!f.has(p.descriptor.fullName)),await s.installWithNewReport({stdout:this.context.stdout,quiet:this.context.quiet},{cache:n})}};Ge();Ge();Ge();Yt();var bye=ut(Vv()),nq=ut(Go());Ul();var MC=class extends ft{constructor(){super(...arguments);this.interactive=ge.Boolean("-i,--interactive",{description:"Offer various choices, depending on the detected upgrade paths"});this.fixed=ge.Boolean("-F,--fixed",!1,{description:"Store dependency tags as-is instead of resolving them"});this.exact=ge.Boolean("-E,--exact",!1,{description:"Don't use any semver modifier on the resolved range"});this.tilde=ge.Boolean("-T,--tilde",!1,{description:"Use the `~` semver modifier on the resolved range"});this.caret=ge.Boolean("-C,--caret",!1,{description:"Use the `^` semver modifier on the resolved range"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Resolve again ALL resolutions for those packages"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:fo($l)});this.patterns=ge.Rest()}static{this.paths=[["up"]]}static{this.usage=ot.Usage({description:"upgrade dependencies across the project",details:"\n This command upgrades the packages matching the list of specified patterns to their latest available version across the whole project (regardless of whether they're part of `dependencies` or `devDependencies` - `peerDependencies` won't be affected). This is a project-wide command: all workspaces will be upgraded in the process.\n\n If `-R,--recursive` is set the command will change behavior and no other switch will be allowed. When operating under this mode `yarn up` will force all ranges matching the selected packages to be resolved again (often to the highest available versions) before being stored in the lockfile. It however won't touch your manifests anymore, so depending on your needs you might want to run both `yarn up` and `yarn up -R` to cover all bases.\n\n If `-i,--interactive` is set (or if the `preferInteractive` settings is toggled on) the command will offer various choices, depending on the detected upgrade paths. Some upgrades require this flag in order to resolve ambiguities.\n\n The, `-C,--caret`, `-E,--exact` and `-T,--tilde` options have the same meaning as in the `add` command (they change the modifier used when the range is missing or a tag, and are ignored when the range is explicitly set).\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n\n Generally you can see `yarn up` as a counterpart to what was `yarn upgrade --latest` in Yarn 1 (ie it ignores the ranges previously listed in your manifests), but unlike `yarn upgrade` which only upgraded dependencies in the current workspace, `yarn up` will upgrade all workspaces at the same time.\n\n This command accepts glob patterns as arguments (if valid Descriptors and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n **Note:** The ranges have to be static, only the package scopes and names can contain glob patterns.\n ",examples:[["Upgrade all instances of lodash to the latest release","$0 up lodash"],["Upgrade all instances of lodash to the latest release, but ask confirmation for each","$0 up lodash -i"],["Upgrade all instances of lodash to 1.2.3","$0 up lodash@1.2.3"],["Upgrade all instances of packages with the `@babel` scope to the latest release","$0 up '@babel/*'"],["Upgrade all instances of packages containing the word `jest` to the latest release","$0 up '*jest*'"],["Upgrade all instances of packages with the `@babel` scope to 7.0.0","$0 up '@babel/*@7.0.0'"]]})}static{this.schema=[tB("recursive",qf.Forbids,["interactive","exact","tilde","caret"],{ignore:[void 0,!1]})]}async execute(){return this.recursive?await this.executeUpRecursive():await this.executeUpClassic()}async executeUpRecursive(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=[...s.storedDescriptors.values()],f=c.map(E=>G.stringifyIdent(E)),p=new Set;for(let E of this.patterns){if(G.parseDescriptor(E).range!=="unknown")throw new nt("Ranges aren't allowed when using --recursive");for(let C of(0,nq.default)(f,E)){let S=G.parseIdent(C);p.add(S.identHash)}}let h=c.filter(E=>p.has(E.identHash));for(let E of h)s.storedDescriptors.delete(E.descriptorHash),s.storedResolutions.delete(E.descriptorHash);return await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})}async executeUpClassic(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.fixed,f=r.isInteractive({interactive:this.interactive,stdout:this.context.stdout}),p=Kv(this,s),h=f?["keep","reuse","project","latest"]:["project","latest"],E=[],C=[];for(let N of this.patterns){let U=!1,W=G.parseDescriptor(N),ee=G.stringifyIdent(W);for(let ie of s.workspaces)for(let ue of["dependencies","devDependencies"]){let me=[...ie.manifest.getForScope(ue).values()].map(Be=>G.stringifyIdent(Be)),pe=ee==="*"?me:(0,nq.default)(me,ee);for(let Be of pe){let Ce=G.parseIdent(Be),g=ie.manifest[ue].get(Ce.identHash);if(typeof g>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");let we=G.makeDescriptor(Ce,W.range);E.push(Promise.resolve().then(async()=>[ie,ue,g,await zv(we,{project:s,workspace:ie,cache:n,target:ue,fixed:c,modifier:p,strategies:h})])),U=!0}}U||C.push(N)}if(C.length>1)throw new nt(`Patterns ${he.prettyList(r,C,he.Type.CODE)} don't match any packages referenced by any workspace`);if(C.length>0)throw new nt(`Pattern ${he.prettyList(r,C,he.Type.CODE)} doesn't match any packages referenced by any workspace`);let S=await Promise.all(E),P=await lA.start({configuration:r,stdout:this.context.stdout,suggestInstall:!1},async N=>{for(let[,,U,{suggestions:W,rejections:ee}]of S){let ie=W.filter(ue=>ue.descriptor!==null);if(ie.length===0){let[ue]=ee;if(typeof ue>"u")throw new Error("Assertion failed: Expected an error to have been set");let le=this.cli.error(ue);s.configuration.get("enableNetwork")?N.reportError(27,`${G.prettyDescriptor(r,U)} can't be resolved to a satisfying range ${le}`):N.reportError(27,`${G.prettyDescriptor(r,U)} can't be resolved to a satisfying range (note: network resolution has been disabled) ${le}`)}else ie.length>1&&!f&&N.reportError(27,`${G.prettyDescriptor(r,U)} has multiple possible upgrade strategies; use -i to disambiguate manually`)}});if(P.hasErrors())return P.exitCode();let I=!1,R=[];for(let[N,U,,{suggestions:W}]of S){let ee,ie=W.filter(pe=>pe.descriptor!==null),ue=ie[0].descriptor,le=ie.every(pe=>G.areDescriptorsEqual(pe.descriptor,ue));ie.length===1||le?ee=ue:(I=!0,{answer:ee}=await(0,bye.prompt)({type:"select",name:"answer",message:`Which range do you want to use in ${G.prettyWorkspace(r,N)} \u276F ${U}?`,choices:W.map(({descriptor:pe,name:Be,reason:Ce})=>pe?{name:Be,hint:Ce,descriptor:pe}:{name:Be,hint:Ce,disabled:!0}),onCancel:()=>process.exit(130),result(pe){return this.find(pe,"descriptor")},stdin:this.context.stdin,stdout:this.context.stdout}));let me=N.manifest[U].get(ee.identHash);if(typeof me>"u")throw new Error("Assertion failed: This descriptor should have a matching entry");if(me.descriptorHash!==ee.descriptorHash)N.manifest[U].set(ee.identHash,ee),R.push([N,U,me,ee]);else{let pe=r.makeResolver(),Be={project:s,resolver:pe},Ce=r.normalizeDependency(me),g=pe.bindDescriptor(Ce,N.anchoredLocator,Be);s.forgetResolution(g)}}return await r.triggerMultipleHooks(N=>N.afterWorkspaceDependencyReplacement,R),I&&this.context.stdout.write(` `),await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})}};Ge();Ge();Ge();Yt();var UC=class extends ft{constructor(){super(...arguments);this.recursive=ge.Boolean("-R,--recursive",!1,{description:"List, for each workspace, what are all the paths that lead to the dependency"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.peers=ge.Boolean("--peers",!1,{description:"Also print the peer dependencies that match the specified name"});this.package=ge.String()}static{this.paths=[["why"]]}static{this.usage=ot.Usage({description:"display the reason why a package is needed",details:` This command prints the exact reasons why a package appears in the dependency tree. If \`-R,--recursive\` is set, the listing will go in depth and will list, for each workspaces, what are all the paths that lead to the dependency. Note that the display is somewhat optimized in that it will not print the package listing twice for a single package, so if you see a leaf named "Foo" when looking for "Bar", it means that "Foo" already got printed higher in the tree. `,examples:[["Explain why lodash is used in your project","$0 why lodash"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=G.parseIdent(this.package).identHash,c=this.recursive?cct(s,n,{configuration:r,peers:this.peers}):lct(s,n,{configuration:r,peers:this.peers});xs.emitTree(c,{configuration:r,stdout:this.context.stdout,json:this.json,separators:1})}};function lct(t,e,{configuration:r,peers:s}){let a=je.sortMap(t.storedPackages.values(),f=>G.stringifyLocator(f)),n={},c={children:n};for(let f of a){let p={};for(let E of f.dependencies.values()){if(!s&&f.peerDependencies.has(E.identHash))continue;let C=t.storedResolutions.get(E.descriptorHash);if(!C)throw new Error("Assertion failed: The resolution should have been registered");let S=t.storedPackages.get(C);if(!S)throw new Error("Assertion failed: The package should have been registered");if(S.identHash!==e)continue;{let I=G.stringifyLocator(f);n[I]={value:[f,he.Type.LOCATOR],children:p}}let P=G.stringifyLocator(S);p[P]={value:[{descriptor:E,locator:S},he.Type.DEPENDENT]}}}return c}function cct(t,e,{configuration:r,peers:s}){let a=je.sortMap(t.workspaces,S=>G.stringifyLocator(S.anchoredLocator)),n=new Set,c=new Set,f=S=>{if(n.has(S.locatorHash))return c.has(S.locatorHash);if(n.add(S.locatorHash),S.identHash===e)return c.add(S.locatorHash),!0;let P=!1;S.identHash===e&&(P=!0);for(let I of S.dependencies.values()){if(!s&&S.peerDependencies.has(I.identHash))continue;let R=t.storedResolutions.get(I.descriptorHash);if(!R)throw new Error("Assertion failed: The resolution should have been registered");let N=t.storedPackages.get(R);if(!N)throw new Error("Assertion failed: The package should have been registered");f(N)&&(P=!0)}return P&&c.add(S.locatorHash),P};for(let S of a)f(S.anchoredPackage);let p=new Set,h={},E={children:h},C=(S,P,I)=>{if(!c.has(S.locatorHash))return;let R=I!==null?he.tuple(he.Type.DEPENDENT,{locator:S,descriptor:I}):he.tuple(he.Type.LOCATOR,S),N={},U={value:R,children:N},W=G.stringifyLocator(S);if(P[W]=U,!(I!==null&&t.tryWorkspaceByLocator(S))&&!p.has(S.locatorHash)){p.add(S.locatorHash);for(let ee of S.dependencies.values()){if(!s&&S.peerDependencies.has(ee.identHash))continue;let ie=t.storedResolutions.get(ee.descriptorHash);if(!ie)throw new Error("Assertion failed: The resolution should have been registered");let ue=t.storedPackages.get(ie);if(!ue)throw new Error("Assertion failed: The package should have been registered");C(ue,N,ee)}}};for(let S of a)C(S.anchoredPackage,h,null);return E}Ge();var pq={};Vt(pq,{GitFetcher:()=>tS,GitResolver:()=>rS,default:()=>kct,gitUtils:()=>ka});Ge();Dt();var ka={};Vt(ka,{TreeishProtocols:()=>eS,clone:()=>Aq,fetchBase:()=>Jye,fetchChangedFiles:()=>Kye,fetchChangedWorkspaces:()=>Pct,fetchRoot:()=>Vye,isGitUrl:()=>jC,lsRemote:()=>Yye,normalizeLocator:()=>bct,normalizeRepoUrl:()=>_C,resolveUrl:()=>fq,splitRepoUrl:()=>W0,validateRepoUrl:()=>uq});Ge();Dt();Yt();ql();var qye=ut(Hye()),HC=ut(Ie("querystring")),lq=ut(Ai());function aq(t,e,r){let s=t.indexOf(r);return t.lastIndexOf(e,s>-1?s:1/0)}function jye(t){try{return new URL(t)}catch{return}}function Sct(t){let e=aq(t,"@","#"),r=aq(t,":","#");return r>e&&(t=`${t.slice(0,r)}/${t.slice(r+1)}`),aq(t,":","#")===-1&&t.indexOf("//")===-1&&(t=`ssh://${t}`),t}function Gye(t){return jye(t)||jye(Sct(t))}function _C(t,{git:e=!1}={}){if(t=t.replace(/^git\+https:/,"https:"),t=t.replace(/^(?:github:|https:\/\/github\.com\/|git:\/\/github\.com\/)?(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)(?:\.git)?(#.*)?$/,"https://github.com/$1/$2.git$3"),t=t.replace(/^https:\/\/github\.com\/(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)\/tarball\/(.+)?$/,"https://github.com/$1/$2.git#$3"),e){let r=Gye(t);r&&(t=r.href),t=t.replace(/^git\+([^:]+):/,"$1:")}return t}function Wye(){return{...process.env,GIT_SSH_COMMAND:process.env.GIT_SSH_COMMAND||`${process.env.GIT_SSH||"ssh"} -o BatchMode=yes`}}var Dct=[/^ssh:/,/^git(?:\+[^:]+)?:/,/^(?:git\+)?https?:[^#]+\/[^#]+(?:\.git)(?:#.*)?$/,/^git@[^#]+\/[^#]+\.git(?:#.*)?$/,/^(?:github:|https:\/\/github\.com\/)?(?!\.{1,2}\/)([a-zA-Z._0-9-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z._0-9-]+?)(?:\.git)?(?:#.*)?$/,/^https:\/\/github\.com\/(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)\/tarball\/(.+)?$/],eS=(a=>(a.Commit="commit",a.Head="head",a.Tag="tag",a.Semver="semver",a))(eS||{});function jC(t){return t?Dct.some(e=>!!t.match(e)):!1}function W0(t){t=_C(t);let e=t.indexOf("#");if(e===-1)return{repo:t,treeish:{protocol:"head",request:"HEAD"},extra:{}};let r=t.slice(0,e),s=t.slice(e+1);if(s.match(/^[a-z]+=/)){let a=HC.default.parse(s);for(let[p,h]of Object.entries(a))if(typeof h!="string")throw new Error(`Assertion failed: The ${p} parameter must be a literal string`);let n=Object.values(eS).find(p=>Object.hasOwn(a,p)),[c,f]=typeof n<"u"?[n,a[n]]:["head","HEAD"];for(let p of Object.values(eS))delete a[p];return{repo:r,treeish:{protocol:c,request:f},extra:a}}else{let a=s.indexOf(":"),[n,c]=a===-1?[null,s]:[s.slice(0,a),s.slice(a+1)];return{repo:r,treeish:{protocol:n,request:c},extra:{}}}}function bct(t){return G.makeLocator(t,_C(t.reference))}function uq(t,{configuration:e}){let r=_C(t,{git:!0});if(!nn.getNetworkSettings(`https://${(0,qye.default)(r).resource}`,{configuration:e}).enableNetwork)throw new jt(80,`Request to '${r}' has been blocked because of your configuration settings`);return r}async function Yye(t,e){let r=uq(t,{configuration:e}),s=await cq("listing refs",["ls-remote",r],{cwd:e.startingCwd,env:Wye()},{configuration:e,normalizedRepoUrl:r}),a=new Map,n=/^([a-f0-9]{40})\t([^\n]+)/gm,c;for(;(c=n.exec(s.stdout))!==null;)a.set(c[2],c[1]);return a}async function fq(t,e){let{repo:r,treeish:{protocol:s,request:a},extra:n}=W0(t),c=await Yye(r,e),f=(h,E)=>{switch(h){case"commit":{if(!E.match(/^[a-f0-9]{40}$/))throw new Error("Invalid commit hash");return HC.default.stringify({...n,commit:E})}case"head":{let C=c.get(E==="HEAD"?E:`refs/heads/${E}`);if(typeof C>"u")throw new Error(`Unknown head ("${E}")`);return HC.default.stringify({...n,commit:C})}case"tag":{let C=c.get(`refs/tags/${E}`);if(typeof C>"u")throw new Error(`Unknown tag ("${E}")`);return HC.default.stringify({...n,commit:C})}case"semver":{let C=Fr.validRange(E);if(!C)throw new Error(`Invalid range ("${E}")`);let S=new Map([...c.entries()].filter(([I])=>I.startsWith("refs/tags/")).map(([I,R])=>[lq.default.parse(I.slice(10)),R]).filter(I=>I[0]!==null)),P=lq.default.maxSatisfying([...S.keys()],C);if(P===null)throw new Error(`No matching range ("${E}")`);return HC.default.stringify({...n,commit:S.get(P)})}case null:{let C;if((C=p("commit",E))!==null||(C=p("tag",E))!==null||(C=p("head",E))!==null)return C;throw E.match(/^[a-f0-9]+$/)?new Error(`Couldn't resolve "${E}" as either a commit, a tag, or a head - if a commit, use the 40-characters commit hash`):new Error(`Couldn't resolve "${E}" as either a commit, a tag, or a head`)}default:throw new Error(`Invalid Git resolution protocol ("${h}")`)}},p=(h,E)=>{try{return f(h,E)}catch{return null}};return _C(`${r}#${f(s,a)}`)}async function Aq(t,e){return await e.getLimit("cloneConcurrency")(async()=>{let{repo:r,treeish:{protocol:s,request:a}}=W0(t);if(s!=="commit")throw new Error("Invalid treeish protocol when cloning");let n=uq(r,{configuration:e}),c=await ce.mktempPromise(),f={cwd:c,env:Wye()};return await cq("cloning the repository",["clone","-c","core.autocrlf=false",n,fe.fromPortablePath(c)],f,{configuration:e,normalizedRepoUrl:n}),await cq("switching branch",["checkout",`${a}`],f,{configuration:e,normalizedRepoUrl:n}),c})}async function Vye(t){let e,r=t;do{if(e=r,await ce.existsPromise(J.join(e,".git")))return e;r=J.dirname(e)}while(r!==e);return null}async function Jye(t,{baseRefs:e}){if(e.length===0)throw new nt("Can't run this command with zero base refs specified.");let r=[];for(let f of e){let{code:p}=await qr.execvp("git",["merge-base",f,"HEAD"],{cwd:t});p===0&&r.push(f)}if(r.length===0)throw new nt(`No ancestor could be found between any of HEAD and ${e.join(", ")}`);let{stdout:s}=await qr.execvp("git",["merge-base","HEAD",...r],{cwd:t,strict:!0}),a=s.trim(),{stdout:n}=await qr.execvp("git",["show","--quiet","--pretty=format:%s",a],{cwd:t,strict:!0}),c=n.trim();return{hash:a,title:c}}async function Kye(t,{base:e,project:r}){let s=je.buildIgnorePattern(r.configuration.get("changesetIgnorePatterns")),{stdout:a}=await qr.execvp("git",["diff","--name-only",`${e}`],{cwd:t,strict:!0}),n=a.split(/\r\n|\r|\n/).filter(h=>h.length>0).map(h=>J.resolve(t,fe.toPortablePath(h))),{stdout:c}=await qr.execvp("git",["ls-files","--others","--exclude-standard"],{cwd:t,strict:!0}),f=c.split(/\r\n|\r|\n/).filter(h=>h.length>0).map(h=>J.resolve(t,fe.toPortablePath(h))),p=[...new Set([...n,...f].sort())];return s?p.filter(h=>!J.relative(r.cwd,h).match(s)):p}async function Pct({ref:t,project:e}){if(e.configuration.projectCwd===null)throw new nt("This command can only be run from within a Yarn project");let r=[J.resolve(e.cwd,Er.lockfile),J.resolve(e.cwd,e.configuration.get("cacheFolder")),J.resolve(e.cwd,e.configuration.get("installStatePath")),J.resolve(e.cwd,e.configuration.get("virtualFolder"))];await e.configuration.triggerHook(c=>c.populateYarnPaths,e,c=>{c!=null&&r.push(c)});let s=await Vye(e.configuration.projectCwd);if(s==null)throw new nt("This command can only be run on Git repositories");let a=await Jye(s,{baseRefs:typeof t=="string"?[t]:e.configuration.get("changesetBaseRefs")}),n=await Kye(s,{base:a.hash,project:e});return new Set(je.mapAndFilter(n,c=>{let f=e.tryWorkspaceByFilePath(c);return f===null?je.mapAndFilter.skip:r.some(p=>c.startsWith(p))?je.mapAndFilter.skip:f}))}async function cq(t,e,r,{configuration:s,normalizedRepoUrl:a}){try{return await qr.execvp("git",e,{...r,strict:!0})}catch(n){if(!(n instanceof qr.ExecError))throw n;let c=n.reportExtra,f=n.stderr.toString();throw new jt(1,`Failed ${t}`,p=>{p.reportError(1,` ${he.prettyField(s,{label:"Repository URL",value:he.tuple(he.Type.URL,a)})}`);for(let h of f.matchAll(/^(.+?): (.*)$/gm)){let[,E,C]=h;E=E.toLowerCase();let S=E==="error"?"Error":`${bB(E)} Error`;p.reportError(1,` ${he.prettyField(s,{label:S,value:he.tuple(he.Type.NO_HINT,C)})}`)}c?.(p)})}}var tS=class{supports(e,r){return jC(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,a=new Map(r.checksums);a.set(e.locatorHash,s);let n={...r,checksums:a},c=await this.downloadHosted(e,n);if(c!==null)return c;let[f,p,h]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote repository`),loader:()=>this.cloneFromRemote(e,n),...r.cacheOptions});return{packageFs:f,releaseFs:p,prefixPath:G.getIdentVendorPath(e),checksum:h}}async downloadHosted(e,r){return r.project.configuration.reduceHook(s=>s.fetchHostedRepository,null,e,r)}async cloneFromRemote(e,r){let s=W0(e.reference),a=await Aq(e.reference,r.project.configuration),n=J.resolve(a,s.extra.cwd??vt.dot),c=J.join(n,"package.tgz");await In.prepareExternalProject(n,c,{configuration:r.project.configuration,report:r.report,workspace:s.extra.workspace,locator:e});let f=await ce.readFilePromise(c);return await je.releaseAfterUseAsync(async()=>await ps.convertToZip(f,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1}))}};Ge();Ge();var rS=class{supportsDescriptor(e,r){return jC(e.range)}supportsLocator(e,r){return jC(e.reference)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=await fq(e.range,s.project.configuration);return[G.makeLocator(e,a)]}async getSatisfying(e,r,s,a){let n=W0(e.range);return{locators:s.filter(f=>{if(f.identHash!==e.identHash)return!1;let p=W0(f.reference);return!(n.repo!==p.repo||n.treeish.protocol==="commit"&&n.treeish.request!==p.treeish.request)}),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var xct={configuration:{changesetBaseRefs:{description:"The base git refs that the current HEAD is compared against when detecting changes. Supports git branches, tags, and commits.",type:"STRING",isArray:!0,isNullable:!1,default:["master","origin/master","upstream/master","main","origin/main","upstream/main"]},changesetIgnorePatterns:{description:"Array of glob patterns; files matching them will be ignored when fetching the changed files",type:"STRING",default:[],isArray:!0},cloneConcurrency:{description:"Maximal number of concurrent clones",type:"NUMBER",default:2}},fetchers:[tS],resolvers:[rS]};var kct=xct;Yt();var GC=class extends ft{constructor(){super(...arguments);this.since=ge.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.noPrivate=ge.Boolean("--no-private",{description:"Exclude workspaces that have the private field set to true"});this.verbose=ge.Boolean("-v,--verbose",!1,{description:"Also return the cross-dependencies between workspaces"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["workspaces","list"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"list all available workspaces",details:"\n This command will print the list of all workspaces in the project.\n\n - If `--since` is set, Yarn will only list workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--no-private` is set, Yarn will not list any workspaces that have the `private` field set to `true`.\n\n - If both the `-v,--verbose` and `--json` options are set, Yarn will also return the cross-dependencies between each workspaces (useful when you wish to automatically generate Buck / Bazel rules).\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async n=>{let c=this.since?await ka.fetchChangedWorkspaces({ref:this.since,project:s}):s.workspaces,f=new Set(c);if(this.recursive)for(let p of[...c].map(h=>h.getRecursiveWorkspaceDependents()))for(let h of p)f.add(h);for(let p of f){let{manifest:h}=p;if(h.private&&this.noPrivate)continue;let E;if(this.verbose){let C=new Set,S=new Set;for(let P of Ut.hardDependencies)for(let[I,R]of h.getForScope(P)){let N=s.tryWorkspaceByDescriptor(R);N===null?s.workspacesByIdent.has(I)&&S.add(R):C.add(N)}E={workspaceDependencies:Array.from(C).map(P=>P.relativeCwd),mismatchedWorkspaceDependencies:Array.from(S).map(P=>G.stringifyDescriptor(P))}}n.reportInfo(null,`${p.relativeCwd}`),n.reportJson({location:p.relativeCwd,name:h.name?G.stringifyIdent(h.name):null,...E})}})).exitCode()}};Ge();Ge();Yt();var qC=class extends ft{constructor(){super(...arguments);this.workspaceName=ge.String();this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[["workspace"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"run a command within the specified workspace",details:` This command will run a given sub-command on a single workspace. `,examples:[["Add a package to a single workspace","yarn workspace components add -D react"],["Run build script on a single workspace","yarn workspace components run build"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=s.workspaces,c=new Map(n.map(p=>[G.stringifyIdent(p.anchoredLocator),p])),f=c.get(this.workspaceName);if(f===void 0){let p=Array.from(c.keys()).sort();throw new nt(`Workspace '${this.workspaceName}' not found. Did you mean any of the following: - ${p.join(` - `)}?`)}return this.cli.run([this.commandName,...this.args],{cwd:f.cwd})}};var Qct={configuration:{enableImmutableInstalls:{description:"If true (the default on CI), prevents the install command from modifying the lockfile",type:"BOOLEAN",default:zye.isCI},defaultSemverRangePrefix:{description:"The default save prefix: '^', '~' or ''",type:"STRING",values:["^","~",""],default:"^"},preferReuse:{description:"If true, `yarn add` will attempt to reuse the most common dependency range in other workspaces.",type:"BOOLEAN",default:!1}},commands:[aC,lC,cC,uC,OC,bC,EC,GC,pC,hC,gC,dC,sC,oC,fC,AC,mC,yC,IC,CC,wC,BC,LC,vC,SC,xC,PC,kC,DC,QC,TC,RC,FC,NC,MC,UC,qC]},Tct=Qct;var yq={};Vt(yq,{default:()=>Oct});Ge();Ge();var gq="catalog:";var dq=t=>t.startsWith(gq),Rct=t=>t.range.slice(gq.length)||null,Xye=t=>t===null?"default catalog":`catalog "${t}"`,Fct=t=>t.scope?`@${t.scope}/${t.name}`:t.name,mq=(t,e,r,s)=>{let a=Rct(e),n;if(a===null)n=t.configuration.get("catalog");else try{let E=t.configuration.get("catalogs");E&&(n=E.get(a))}catch{n=void 0}if(!n||n.size===0)throw new jt(82,`${G.prettyDescriptor(t.configuration,e)}: ${Xye(a)} not found or empty`);let c=Fct(e),f=n.get(c);if(!f)throw new jt(82,`${G.prettyDescriptor(t.configuration,e)}: entry not found in ${Xye(a)}`);let p=t.configuration.normalizeDependency(G.makeDescriptor(e,f));return r.supportsDescriptor(p,s)?r.bindDescriptor(p,t.topLevelWorkspace.anchoredLocator,s):p};var Nct={configuration:{catalog:{description:"The default catalog of packages",type:"MAP",valueDefinition:{description:"The catalog of packages",type:"STRING"}},catalogs:{description:"Named catalogs of packages",type:"MAP",valueDefinition:{description:"A named catalog",type:"MAP",valueDefinition:{description:"Package version in the catalog",type:"STRING"}}}},hooks:{beforeWorkspacePacking:(t,e)=>{let r=t.project,s=r.configuration.makeResolver(),a={project:r,resolver:s,report:new ki};for(let n of Ut.allDependencies){let c=e[n];if(c)for(let[f,p]of Object.entries(c)){if(typeof p!="string"||!dq(p))continue;let h=G.parseIdent(f),E=G.makeDescriptor(h,p),C=mq(r,E,s,a),{protocol:S,source:P,params:I,selector:R}=G.parseRange(G.convertToManifestRange(C.range));S===t.project.configuration.get("defaultProtocol")&&(S=null),c[f]=G.makeRange({protocol:S,source:P,params:I,selector:R})}}},reduceDependency:async(t,e,r,s,{resolver:a,resolveOptions:n})=>dq(t.range)?mq(e,t,a,n):t}},Oct=Nct;var Bq={};Vt(Bq,{default:()=>Mct});Ge();var Qt={optional:!0},Eq=[["@tailwindcss/aspect-ratio@<0.2.1",{peerDependencies:{tailwindcss:"^2.0.2"}}],["@tailwindcss/line-clamp@<0.2.1",{peerDependencies:{tailwindcss:"^2.0.2"}}],["@fullhuman/postcss-purgecss@3.1.3 || 3.1.3-alpha.0",{peerDependencies:{postcss:"^8.0.0"}}],["@samverschueren/stream-to-observable@<0.3.1",{peerDependenciesMeta:{rxjs:Qt,zenObservable:Qt}}],["any-observable@<0.5.1",{peerDependenciesMeta:{rxjs:Qt,zenObservable:Qt}}],["@pm2/agent@<1.0.4",{dependencies:{debug:"*"}}],["debug@<4.2.0",{peerDependenciesMeta:{"supports-color":Qt}}],["got@<11",{dependencies:{"@types/responselike":"^1.0.0","@types/keyv":"^3.1.1"}}],["cacheable-lookup@<4.1.2",{dependencies:{"@types/keyv":"^3.1.1"}}],["http-link-dataloader@*",{peerDependencies:{graphql:"^0.13.1 || ^14.0.0"}}],["typescript-language-server@*",{dependencies:{"vscode-jsonrpc":"^5.0.1","vscode-languageserver-protocol":"^3.15.0"}}],["postcss-syntax@*",{peerDependenciesMeta:{"postcss-html":Qt,"postcss-jsx":Qt,"postcss-less":Qt,"postcss-markdown":Qt,"postcss-scss":Qt}}],["jss-plugin-rule-value-function@<=10.1.1",{dependencies:{"tiny-warning":"^1.0.2"}}],["ink-select-input@<4.1.0",{peerDependencies:{react:"^16.8.2"}}],["license-webpack-plugin@<2.3.18",{peerDependenciesMeta:{webpack:Qt}}],["snowpack@>=3.3.0",{dependencies:{"node-gyp":"^7.1.0"}}],["promise-inflight@*",{peerDependenciesMeta:{bluebird:Qt}}],["reactcss@*",{peerDependencies:{react:"*"}}],["react-color@<=2.19.0",{peerDependencies:{react:"*"}}],["gatsby-plugin-i18n@*",{dependencies:{ramda:"^0.24.1"}}],["useragent@^2.0.0",{dependencies:{request:"^2.88.0",yamlparser:"0.0.x",semver:"5.5.x"}}],["@apollographql/apollo-tools@<=0.5.2",{peerDependencies:{graphql:"^14.2.1 || ^15.0.0"}}],["material-table@^2.0.0",{dependencies:{"@babel/runtime":"^7.11.2"}}],["@babel/parser@*",{dependencies:{"@babel/types":"^7.8.3"}}],["fork-ts-checker-webpack-plugin@<=6.3.4",{peerDependencies:{eslint:">= 6",typescript:">= 2.7",webpack:">= 4","vue-template-compiler":"*"},peerDependenciesMeta:{eslint:Qt,"vue-template-compiler":Qt}}],["rc-animate@<=3.1.1",{peerDependencies:{react:">=16.9.0","react-dom":">=16.9.0"}}],["react-bootstrap-table2-paginator@*",{dependencies:{classnames:"^2.2.6"}}],["react-draggable@<=4.4.3",{peerDependencies:{react:">= 16.3.0","react-dom":">= 16.3.0"}}],["apollo-upload-client@<14",{peerDependencies:{graphql:"14 - 15"}}],["react-instantsearch-core@<=6.7.0",{peerDependencies:{algoliasearch:">= 3.1 < 5"}}],["react-instantsearch-dom@<=6.7.0",{dependencies:{"react-fast-compare":"^3.0.0"}}],["ws@<7.2.1",{peerDependencies:{bufferutil:"^4.0.1","utf-8-validate":"^5.0.2"},peerDependenciesMeta:{bufferutil:Qt,"utf-8-validate":Qt}}],["react-portal@<4.2.2",{peerDependencies:{"react-dom":"^15.0.0-0 || ^16.0.0-0 || ^17.0.0-0"}}],["react-scripts@<=4.0.1",{peerDependencies:{react:"*"}}],["testcafe@<=1.10.1",{dependencies:{"@babel/plugin-transform-for-of":"^7.12.1","@babel/runtime":"^7.12.5"}}],["testcafe-legacy-api@<=4.2.0",{dependencies:{"testcafe-hammerhead":"^17.0.1","read-file-relative":"^1.2.0"}}],["@google-cloud/firestore@<=4.9.3",{dependencies:{protobufjs:"^6.8.6"}}],["gatsby-source-apiserver@*",{dependencies:{"babel-polyfill":"^6.26.0"}}],["@webpack-cli/package-utils@<=1.0.1-alpha.4",{dependencies:{"cross-spawn":"^7.0.3"}}],["gatsby-remark-prismjs@<3.3.28",{dependencies:{lodash:"^4"}}],["gatsby-plugin-favicon@*",{peerDependencies:{webpack:"*"}}],["gatsby-plugin-sharp@<=4.6.0-next.3",{dependencies:{debug:"^4.3.1"}}],["gatsby-react-router-scroll@<=5.6.0-next.0",{dependencies:{"prop-types":"^15.7.2"}}],["@rebass/forms@*",{dependencies:{"@styled-system/should-forward-prop":"^5.0.0"},peerDependencies:{react:"^16.8.6"}}],["rebass@*",{peerDependencies:{react:"^16.8.6"}}],["@ant-design/react-slick@<=0.28.3",{peerDependencies:{react:">=16.0.0"}}],["mqtt@<4.2.7",{dependencies:{duplexify:"^4.1.1"}}],["vue-cli-plugin-vuetify@<=2.0.3",{dependencies:{semver:"^6.3.0"},peerDependenciesMeta:{"sass-loader":Qt,"vuetify-loader":Qt}}],["vue-cli-plugin-vuetify@<=2.0.4",{dependencies:{"null-loader":"^3.0.0"}}],["vue-cli-plugin-vuetify@>=2.4.3",{peerDependencies:{vue:"*"}}],["@vuetify/cli-plugin-utils@<=0.0.4",{dependencies:{semver:"^6.3.0"},peerDependenciesMeta:{"sass-loader":Qt}}],["@vue/cli-plugin-typescript@<=5.0.0-alpha.0",{dependencies:{"babel-loader":"^8.1.0"}}],["@vue/cli-plugin-typescript@<=5.0.0-beta.0",{dependencies:{"@babel/core":"^7.12.16"},peerDependencies:{"vue-template-compiler":"^2.0.0"},peerDependenciesMeta:{"vue-template-compiler":Qt}}],["cordova-ios@<=6.3.0",{dependencies:{underscore:"^1.9.2"}}],["cordova-lib@<=10.0.1",{dependencies:{underscore:"^1.9.2"}}],["git-node-fs@*",{peerDependencies:{"js-git":"^0.7.8"},peerDependenciesMeta:{"js-git":Qt}}],["consolidate@<0.16.0",{peerDependencies:{mustache:"^3.0.0"},peerDependenciesMeta:{mustache:Qt}}],["consolidate@<=0.16.0",{peerDependencies:{velocityjs:"^2.0.1",tinyliquid:"^0.2.34","liquid-node":"^3.0.1",jade:"^1.11.0","then-jade":"*",dust:"^0.3.0","dustjs-helpers":"^1.7.4","dustjs-linkedin":"^2.7.5",swig:"^1.4.2","swig-templates":"^2.0.3","razor-tmpl":"^1.3.1",atpl:">=0.7.6",liquor:"^0.0.5",twig:"^1.15.2",ejs:"^3.1.5",eco:"^1.1.0-rc-3",jazz:"^0.0.18",jqtpl:"~1.1.0",hamljs:"^0.6.2",hamlet:"^0.3.3",whiskers:"^0.4.0","haml-coffee":"^1.14.1","hogan.js":"^3.0.2",templayed:">=0.2.3",handlebars:"^4.7.6",underscore:"^1.11.0",lodash:"^4.17.20",pug:"^3.0.0","then-pug":"*",qejs:"^3.0.5",walrus:"^0.10.1",mustache:"^4.0.1",just:"^0.1.8",ect:"^0.5.9",mote:"^0.2.0",toffee:"^0.3.6",dot:"^1.1.3","bracket-template":"^1.1.5",ractive:"^1.3.12",nunjucks:"^3.2.2",htmling:"^0.0.8","babel-core":"^6.26.3",plates:"~0.4.11","react-dom":"^16.13.1",react:"^16.13.1","arc-templates":"^0.5.3",vash:"^0.13.0",slm:"^2.0.0",marko:"^3.14.4",teacup:"^2.0.0","coffee-script":"^1.12.7",squirrelly:"^5.1.0",twing:"^5.0.2"},peerDependenciesMeta:{velocityjs:Qt,tinyliquid:Qt,"liquid-node":Qt,jade:Qt,"then-jade":Qt,dust:Qt,"dustjs-helpers":Qt,"dustjs-linkedin":Qt,swig:Qt,"swig-templates":Qt,"razor-tmpl":Qt,atpl:Qt,liquor:Qt,twig:Qt,ejs:Qt,eco:Qt,jazz:Qt,jqtpl:Qt,hamljs:Qt,hamlet:Qt,whiskers:Qt,"haml-coffee":Qt,"hogan.js":Qt,templayed:Qt,handlebars:Qt,underscore:Qt,lodash:Qt,pug:Qt,"then-pug":Qt,qejs:Qt,walrus:Qt,mustache:Qt,just:Qt,ect:Qt,mote:Qt,toffee:Qt,dot:Qt,"bracket-template":Qt,ractive:Qt,nunjucks:Qt,htmling:Qt,"babel-core":Qt,plates:Qt,"react-dom":Qt,react:Qt,"arc-templates":Qt,vash:Qt,slm:Qt,marko:Qt,teacup:Qt,"coffee-script":Qt,squirrelly:Qt,twing:Qt}}],["vue-loader@<=16.3.3",{peerDependencies:{"@vue/compiler-sfc":"^3.0.8",webpack:"^4.1.0 || ^5.0.0-0"},peerDependenciesMeta:{"@vue/compiler-sfc":Qt}}],["vue-loader@^16.7.0",{peerDependencies:{"@vue/compiler-sfc":"^3.0.8",vue:"^3.2.13"},peerDependenciesMeta:{"@vue/compiler-sfc":Qt,vue:Qt}}],["scss-parser@<=1.0.5",{dependencies:{lodash:"^4.17.21"}}],["query-ast@<1.0.5",{dependencies:{lodash:"^4.17.21"}}],["redux-thunk@<=2.3.0",{peerDependencies:{redux:"^4.0.0"}}],["skypack@<=0.3.2",{dependencies:{tar:"^6.1.0"}}],["@npmcli/metavuln-calculator@<2.0.0",{dependencies:{"json-parse-even-better-errors":"^2.3.1"}}],["bin-links@<2.3.0",{dependencies:{"mkdirp-infer-owner":"^1.0.2"}}],["rollup-plugin-polyfill-node@<=0.8.0",{peerDependencies:{rollup:"^1.20.0 || ^2.0.0"}}],["snowpack@<3.8.6",{dependencies:{"magic-string":"^0.25.7"}}],["elm-webpack-loader@*",{dependencies:{temp:"^0.9.4"}}],["winston-transport@<=4.4.0",{dependencies:{logform:"^2.2.0"}}],["jest-vue-preprocessor@*",{dependencies:{"@babel/core":"7.8.7","@babel/template":"7.8.6"},peerDependencies:{pug:"^2.0.4"},peerDependenciesMeta:{pug:Qt}}],["redux-persist@*",{peerDependencies:{react:">=16"},peerDependenciesMeta:{react:Qt}}],["sodium@>=3",{dependencies:{"node-gyp":"^3.8.0"}}],["babel-plugin-graphql-tag@<=3.1.0",{peerDependencies:{graphql:"^14.0.0 || ^15.0.0"}}],["@playwright/test@<=1.14.1",{dependencies:{"jest-matcher-utils":"^26.4.2"}}],...["babel-plugin-remove-graphql-queries@<3.14.0-next.1","babel-preset-gatsby-package@<1.14.0-next.1","create-gatsby@<1.14.0-next.1","gatsby-admin@<0.24.0-next.1","gatsby-cli@<3.14.0-next.1","gatsby-core-utils@<2.14.0-next.1","gatsby-design-tokens@<3.14.0-next.1","gatsby-legacy-polyfills@<1.14.0-next.1","gatsby-plugin-benchmark-reporting@<1.14.0-next.1","gatsby-plugin-graphql-config@<0.23.0-next.1","gatsby-plugin-image@<1.14.0-next.1","gatsby-plugin-mdx@<2.14.0-next.1","gatsby-plugin-netlify-cms@<5.14.0-next.1","gatsby-plugin-no-sourcemaps@<3.14.0-next.1","gatsby-plugin-page-creator@<3.14.0-next.1","gatsby-plugin-preact@<5.14.0-next.1","gatsby-plugin-preload-fonts@<2.14.0-next.1","gatsby-plugin-schema-snapshot@<2.14.0-next.1","gatsby-plugin-styletron@<6.14.0-next.1","gatsby-plugin-subfont@<3.14.0-next.1","gatsby-plugin-utils@<1.14.0-next.1","gatsby-recipes@<0.25.0-next.1","gatsby-source-shopify@<5.6.0-next.1","gatsby-source-wikipedia@<3.14.0-next.1","gatsby-transformer-screenshot@<3.14.0-next.1","gatsby-worker@<0.5.0-next.1"].map(t=>[t,{dependencies:{"@babel/runtime":"^7.14.8"}}]),["gatsby-core-utils@<2.14.0-next.1",{dependencies:{got:"8.3.2"}}],["gatsby-plugin-gatsby-cloud@<=3.1.0-next.0",{dependencies:{"gatsby-core-utils":"^2.13.0-next.0"}}],["gatsby-plugin-gatsby-cloud@<=3.2.0-next.1",{peerDependencies:{webpack:"*"}}],["babel-plugin-remove-graphql-queries@<=3.14.0-next.1",{dependencies:{"gatsby-core-utils":"^2.8.0-next.1"}}],["gatsby-plugin-netlify@3.13.0-next.1",{dependencies:{"gatsby-core-utils":"^2.13.0-next.0"}}],["clipanion-v3-codemod@<=0.2.0",{peerDependencies:{jscodeshift:"^0.11.0"}}],["react-live@*",{peerDependencies:{"react-dom":"*",react:"*"}}],["webpack@<4.44.1",{peerDependenciesMeta:{"webpack-cli":Qt,"webpack-command":Qt}}],["webpack@<5.0.0-beta.23",{peerDependenciesMeta:{"webpack-cli":Qt}}],["webpack-dev-server@<3.10.2",{peerDependenciesMeta:{"webpack-cli":Qt}}],["@docusaurus/responsive-loader@<1.5.0",{peerDependenciesMeta:{sharp:Qt,jimp:Qt}}],["eslint-module-utils@*",{peerDependenciesMeta:{"eslint-import-resolver-node":Qt,"eslint-import-resolver-typescript":Qt,"eslint-import-resolver-webpack":Qt,"@typescript-eslint/parser":Qt}}],["eslint-plugin-import@*",{peerDependenciesMeta:{"@typescript-eslint/parser":Qt}}],["critters-webpack-plugin@<3.0.2",{peerDependenciesMeta:{"html-webpack-plugin":Qt}}],["terser@<=5.10.0",{dependencies:{acorn:"^8.5.0"}}],["babel-preset-react-app@10.0.x <10.0.2",{dependencies:{"@babel/plugin-proposal-private-property-in-object":"^7.16.7"}}],["eslint-config-react-app@*",{peerDependenciesMeta:{typescript:Qt}}],["@vue/eslint-config-typescript@<11.0.0",{peerDependenciesMeta:{typescript:Qt}}],["unplugin-vue2-script-setup@<0.9.1",{peerDependencies:{"@vue/composition-api":"^1.4.3","@vue/runtime-dom":"^3.2.26"}}],["@cypress/snapshot@*",{dependencies:{debug:"^3.2.7"}}],["auto-relay@<=0.14.0",{peerDependencies:{"reflect-metadata":"^0.1.13"}}],["vue-template-babel-compiler@<1.2.0",{peerDependencies:{"vue-template-compiler":"^2.6.0"}}],["@parcel/transformer-image@<2.5.0",{peerDependencies:{"@parcel/core":"*"}}],["@parcel/transformer-js@<2.5.0",{peerDependencies:{"@parcel/core":"*"}}],["parcel@*",{peerDependenciesMeta:{"@parcel/core":Qt}}],["react-scripts@*",{peerDependencies:{eslint:"*"}}],["focus-trap-react@^8.0.0",{dependencies:{tabbable:"^5.3.2"}}],["react-rnd@<10.3.7",{peerDependencies:{react:">=16.3.0","react-dom":">=16.3.0"}}],["connect-mongo@<5.0.0",{peerDependencies:{"express-session":"^1.17.1"}}],["vue-i18n@<9",{peerDependencies:{vue:"^2"}}],["vue-router@<4",{peerDependencies:{vue:"^2"}}],["unified@<10",{dependencies:{"@types/unist":"^2.0.0"}}],["react-github-btn@<=1.3.0",{peerDependencies:{react:">=16.3.0"}}],["react-dev-utils@*",{peerDependencies:{typescript:">=2.7",webpack:">=4"},peerDependenciesMeta:{typescript:Qt}}],["@asyncapi/react-component@<=1.0.0-next.39",{peerDependencies:{react:">=16.8.0","react-dom":">=16.8.0"}}],["xo@*",{peerDependencies:{webpack:">=1.11.0"},peerDependenciesMeta:{webpack:Qt}}],["babel-plugin-remove-graphql-queries@<=4.20.0-next.0",{dependencies:{"@babel/types":"^7.15.4"}}],["gatsby-plugin-page-creator@<=4.20.0-next.1",{dependencies:{"fs-extra":"^10.1.0"}}],["gatsby-plugin-utils@<=3.14.0-next.1",{dependencies:{fastq:"^1.13.0"},peerDependencies:{graphql:"^15.0.0"}}],["gatsby-plugin-mdx@<3.1.0-next.1",{dependencies:{mkdirp:"^1.0.4"}}],["gatsby-plugin-mdx@^2",{peerDependencies:{gatsby:"^3.0.0-next"}}],["fdir@<=5.2.0",{peerDependencies:{picomatch:"2.x"},peerDependenciesMeta:{picomatch:Qt}}],["babel-plugin-transform-typescript-metadata@<=0.3.2",{peerDependencies:{"@babel/core":"^7","@babel/traverse":"^7"},peerDependenciesMeta:{"@babel/traverse":Qt}}],["graphql-compose@>=9.0.10",{peerDependencies:{graphql:"^14.2.0 || ^15.0.0 || ^16.0.0"}}],["vite-plugin-vuetify@<=1.0.2",{peerDependencies:{vue:"^3.0.0"}}],["webpack-plugin-vuetify@<=2.0.1",{peerDependencies:{vue:"^3.2.6"}}],["eslint-import-resolver-vite@<2.0.1",{dependencies:{debug:"^4.3.4",resolve:"^1.22.8"}}],["notistack@^3.0.0",{dependencies:{csstype:"^3.0.10"}}],["@fastify/type-provider-typebox@^5.0.0",{peerDependencies:{fastify:"^5.0.0"}}],["@fastify/type-provider-typebox@^4.0.0",{peerDependencies:{fastify:"^4.0.0"}}]];var Iq;function Zye(){return typeof Iq>"u"&&(Iq=Ie("zlib").brotliDecompressSync(Buffer.from("G7weAByFTVk3Vs7UfHhq4yykgEM7pbW7TI43SG2S5tvGrwHBAzdz+s/npQ6tgEvobvxisrPIadkXeUAJotBn5bDZ5kAhcRqsIHe3F75Walet5hNalwgFDtxb0BiDUjiUQkjG0yW2hto9HPgiCkm316d6bC0kST72YN7D7rfkhCE9x4J0XwB0yavalxpUu2t9xszHrmtwalOxT7VslsxWcB1qpqZwERUra4psWhTV8BgwWeizurec82Caf1ABL11YMfbf8FJ9JBceZOkgmvrQPbC9DUldX/yMbmX06UQluCEjSwUoyO+EZPIjofr+/oAZUck2enraRD+oWLlnlYnj8xB+gwSo9lmmks4fXv574qSqcWA6z21uYkzMu3EWj+K23RxeQlLqiE35/rC8GcS4CGkKHKKq+zAIQwD9iRDNfiAqueLLpicFFrNsAI4zeTD/eO9MHcnRa5m8UT+M2+V+AkFST4BlKneiAQRSdST8KEAIyFlULt6wa9EBd0Ds28VmpaxquJdVt+nwdEs5xUskI13OVtFyY0UrQIRAlCuvvWivvlSKQfTO+2Q8OyUR1W5RvetaPz4jD27hdtwHFFA1Ptx6Ee/t2cY2rg2G46M1pNDRf2pWhvpy8pqMnuI3++4OF3+7OFIWXGjh+o7Nr2jNvbiYcQdQS1h903/jVFgOpA0yJ78z+x759bFA0rq+6aY5qPB4FzS3oYoLupDUhD9nDz6F6H7hpnlMf18KNKDu4IKjTWwrAnY6MFQw1W6ymOALHlFyCZmQhldg1MQHaMVVQTVgDC60TfaBqG++Y8PEoFhN/PBTZT175KNP/BlHDYGOOBmnBdzqJKplZ/ljiVG0ZBzfqeBRrrUkn6rA54462SgiliKoYVnbeptMdXNfAuaupIEi0bApF10TlgHfmEJAPUVidRVFyDupSem5po5vErPqWKhKbUIp0LozpYsIKK57dM/HKr+nguF+7924IIWMICkQ8JUigs9D+W+c4LnNoRtPPKNRUiCYmP+Jfo2lfKCKw8qpraEeWU3uiNRO6zcyKQoXPR5htmzzLznke7b4YbXW3I1lIRzmgG02Udb58U+7TpwyN7XymCgH+wuPDthZVQvRZuEP+SnLtMicz9m5zASWOBiAcLmkuFlTKuHspSIhCBD0yUPKcxu81A+4YD78rA2vtwsUEday9WNyrShyrl60rWmA+SmbYZkQOwFJWArxRYYc5jGhA5ikxYw1rx3ei4NmeX/lKiwpZ9Ln1tV2Ae7sArvxuVLbJjqJRjW1vFXAyHpvLG+8MJ6T2Ubx5M2KDa2SN6vuIGxJ9WQM9Mk3Q7aCNiZONXllhqq24DmoLbQfW2rYWsOgHWjtOmIQMyMKdiHZDjoyIq5+U700nZ6odJAoYXPQBvFNiQ78d5jaXliBqLTJEqUCwi+LiH2mx92EmNKDsJL74Z613+3lf20pxkV1+erOrjj8pW00vsPaahKUM+05ssd5uwM7K482KWEf3TCwlg/o3e5ngto7qSMz7YteIgCsF1UOcsLk7F7MxWbvrPMY473ew0G+noVL8EPbkmEMftMSeL6HFub/zy+2JQ==","base64")).toString()),Iq}var Cq;function $ye(){return typeof Cq>"u"&&(Cq=Ie("zlib").brotliDecompressSync(Buffer.from("G8MSIIzURnVBnObTcvb3XE6v2S9Qgc2K801Oa5otNKEtK8BINZNcaQHy+9/vf/WXBimwutXC33P2DPc64pps5rz7NGGWaOKNSPL4Y2KRE8twut2lFOIN+OXPtRmPMRhMTILib2bEQx43az2I5d3YS8Roa5UZpF/ujHb3Djd3GDvYUfvFYSUQ39vb2cmifp/rgB4J/65JK3wRBTvMBoNBmn3mbXC63/gbBkW/2IRPri0O8bcsRBsmarF328pAln04nyJFkwUAvNu934supAqLtyerZZpJ8I8suJHhf/ocMV+scKwa8NOiDKIPXw6Ex/EEZD6TEGaW8N5zvNHYF10l6Lfooj7D5W2k3dgvQSbp2Wv8TGOayS978gxlOLVjTGXs66ozewbrjwElLtyrYNnWTfzzdEutgROUFPVMhnMoy8EjJLLlWwIEoySxliim9kYW30JUHiPVyjt0iAw/ZpPmCbUCltYPnq6ZNblIKhTNhqS/oqC9iya5sGKZTOVsTEg34n92uZTf2iPpcZih8rPW8CzA+adIGmyCPcKdLMsBLShd+zuEbTrqpwuh+DLmracZcjPC5Sdf5odDAhKpFuOsQS67RT+1VgWWygSv3YwxDnylc04/PYuaMeIzhBkLrvs7e/OUzRTF56MmfY6rI63QtEjEQzq637zQqJ39nNhu3NmoRRhW/086bHGBUtx0PE0j3aEGvkdh9WJC8y8j8mqqke9/dQ5la+Q3ba4RlhvTbnfQhPDDab3tUifkjKuOsp13mXEmO00Mu88F/M67R7LXfoFDFLNtgCSWjWX+3Jn1371pJTK9xPBiMJafvDjtFyAzu8rxeQ0TKMQXNPs5xxiBOd+BRJP8KP88XPtJIbZKh/cdW8KvBUkpqKpGoiIaA32c3/JnQr4efXt85mXvidOvn/eU3Pase1typLYBalJ14mCso9h79nuMOuCa/kZAOkJHmTjP5RM2WNoPasZUAnT1TAE/NH25hUxcQv6hQWR/m1PKk4ooXMcM4SR1iYU3fUohvqk4RY2hbmTVVIXv6TvqO+0doOjgeVFAcom+RlwJQmOVH7pr1Q9LoJT6n1DeQEB+NHygsATbIwTcOKZlJsY8G4+suX1uQLjUWwLjjs0mvSvZcLTpIGAekeR7GCgl8eo3ndAqEe2XCav4huliHjdbIPBsGJuPX7lrO9HX1UbXRH5opOe1x6JsOSgHZR+EaxuXVhpLLxm6jk1LJtZfHSc6BKPun3CpYYVMJGwEUyk8MTGG0XL5MfEwaXpnc9TKnBmlGn6nHiGREc3ysn47XIBDzA+YvFdjZzVIEDcKGpS6PbUJehFRjEne8D0lVU1XuRtlgszq6pTNlQ/3MzNOEgCWPyTct22V2mEi2krizn5VDo9B19/X2DB3hCGRMM7ONbtnAcIx/OWB1u5uPbW1gsH8irXxT/IzG0PoXWYjhbMsH3KTuoOl5o17PulcgvsfTSnKFM354GWI8luqZnrswWjiXy3G+Vbyo1KMopFmmvBwNELgaS8z8dNZchx/Cl/xjddxhMcyqtzFyONb2Zdu90NkI8pAeufe7YlXrp53v8Dj/l8vWeVspRKBGXScBBPI/HinSTGmLDOGGOCIyH0JFdOZx0gWsacNlQLJMIrBhqRxXxHF/5pseWwejlAAvZ3klZSDSYY8mkToaWejXhgNomeGtx1DTLEUFMRkgF5yFB22WYdJnaWN14r1YJj81hGi45+jrADS5nYRhCiSlCJJ1nL8pYX+HDSMhdTEWyRcgHVp/IsUIZYMfT+YYncUQPgcxNGCHfZ88vDdrcUuaGIl6zhAsiaq7R5dfqrqXH/JcBhfjT8D0azayIyEz75Nxp6YkcyDxlJq3EXnJUpqDohJJOysL1t1uNiHESlvsxPb5cpbW0+ICZqJmUZus1BMW0F5IVBODLIo2zHHjA0=","base64")).toString()),Cq}var wq;function eEe(){return typeof wq>"u"&&(wq=Ie("zlib").brotliDecompressSync(Buffer.from("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","base64")).toString()),wq}var tEe=new Map([[G.makeIdent(null,"fsevents").identHash,Zye],[G.makeIdent(null,"resolve").identHash,$ye],[G.makeIdent(null,"typescript").identHash,eEe]]),Lct={hooks:{registerPackageExtensions:async(t,e)=>{for(let[r,s]of Eq)e(G.parseDescriptor(r,!0),s)},getBuiltinPatch:async(t,e)=>{let r="compat/";if(!e.startsWith(r))return;let s=G.parseIdent(e.slice(r.length)),a=tEe.get(s.identHash)?.();return typeof a<"u"?a:null},reduceDependency:async(t,e,r,s)=>typeof tEe.get(t.identHash)>"u"?t:G.makeDescriptor(t,G.makeRange({protocol:"patch:",source:G.stringifyDescriptor(t),selector:`optional!builtin`,params:null}))}},Mct=Lct;var _q={};Vt(_q,{ConstraintsCheckCommand:()=>ZC,ConstraintsQueryCommand:()=>zC,ConstraintsSourceCommand:()=>XC,default:()=>nut});Ge();Ge();iS();var YC=class{constructor(e){this.project=e}createEnvironment(){let e=new WC(["cwd","ident"]),r=new WC(["workspace","type","ident"]),s=new WC(["ident"]),a={manifestUpdates:new Map,reportedErrors:new Map},n=new Map,c=new Map;for(let f of this.project.storedPackages.values()){let p=Array.from(f.peerDependencies.values(),h=>[G.stringifyIdent(h),h.range]);n.set(f.locatorHash,{workspace:null,ident:G.stringifyIdent(f),version:f.version,dependencies:new Map,peerDependencies:new Map(p.filter(([h])=>f.peerDependenciesMeta.get(h)?.optional!==!0)),optionalPeerDependencies:new Map(p.filter(([h])=>f.peerDependenciesMeta.get(h)?.optional===!0))})}for(let f of this.project.storedPackages.values()){let p=n.get(f.locatorHash);p.dependencies=new Map(Array.from(f.dependencies.values(),h=>{let E=this.project.storedResolutions.get(h.descriptorHash);if(typeof E>"u")throw new Error("Assertion failed: The resolution should have been registered");let C=n.get(E);if(typeof C>"u")throw new Error("Assertion failed: The package should have been registered");return[G.stringifyIdent(h),C]})),p.dependencies.delete(p.ident)}for(let f of this.project.workspaces){let p=G.stringifyIdent(f.anchoredLocator),h=f.manifest.exportTo({}),E=n.get(f.anchoredLocator.locatorHash);if(typeof E>"u")throw new Error("Assertion failed: The package should have been registered");let C=(R,N,{caller:U=Ui.getCaller()}={})=>{let W=nS(R),ee=je.getMapWithDefault(a.manifestUpdates,f.cwd),ie=je.getMapWithDefault(ee,W),ue=je.getSetWithDefault(ie,N);U!==null&&ue.add(U)},S=R=>C(R,void 0,{caller:Ui.getCaller()}),P=R=>{je.getArrayWithDefault(a.reportedErrors,f.cwd).push(R)},I=e.insert({cwd:f.relativeCwd,ident:p,manifest:h,pkg:E,set:C,unset:S,error:P});c.set(f,I);for(let R of Ut.allDependencies)for(let N of f.manifest[R].values()){let U=G.stringifyIdent(N),W=()=>{C([R,U],void 0,{caller:Ui.getCaller()})},ee=ue=>{C([R,U],ue,{caller:Ui.getCaller()})},ie=null;if(R!=="peerDependencies"&&(R!=="dependencies"||!f.manifest.devDependencies.has(N.identHash))){let ue=f.anchoredPackage.dependencies.get(N.identHash);if(ue){if(typeof ue>"u")throw new Error("Assertion failed: The dependency should have been registered");let le=this.project.storedResolutions.get(ue.descriptorHash);if(typeof le>"u")throw new Error("Assertion failed: The resolution should have been registered");let me=n.get(le);if(typeof me>"u")throw new Error("Assertion failed: The package should have been registered");ie=me}}r.insert({workspace:I,ident:U,range:N.range,type:R,resolution:ie,update:ee,delete:W,error:P})}}for(let f of this.project.storedPackages.values()){let p=this.project.tryWorkspaceByLocator(f);if(!p)continue;let h=c.get(p);if(typeof h>"u")throw new Error("Assertion failed: The workspace should have been registered");let E=n.get(f.locatorHash);if(typeof E>"u")throw new Error("Assertion failed: The package should have been registered");E.workspace=h}return{workspaces:e,dependencies:r,packages:s,result:a}}async process(){let e=this.createEnvironment(),r={Yarn:{workspace:a=>e.workspaces.find(a)[0]??null,workspaces:a=>e.workspaces.find(a),dependency:a=>e.dependencies.find(a)[0]??null,dependencies:a=>e.dependencies.find(a),package:a=>e.packages.find(a)[0]??null,packages:a=>e.packages.find(a)}},s=await this.project.loadUserConfig();return s?.constraints?(await s.constraints(r),e.result):null}};Ge();Ge();Yt();var zC=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.query=ge.String()}static{this.paths=[["constraints","query"]]}static{this.usage=ot.Usage({category:"Constraints-related commands",description:"query the constraints fact database",details:` This command will output all matches to the given prolog query. `,examples:[["List all dependencies throughout the workspace","yarn constraints query 'workspace_has_dependency(_, DependencyName, _, _).'"]]})}async execute(){let{Constraints:r}=await Promise.resolve().then(()=>(lS(),aS)),s=await ze.find(this.context.cwd,this.context.plugins),{project:a}=await Tt.find(s,this.context.cwd),n=await r.find(a),c=this.query;return c.endsWith(".")||(c=`${c}.`),(await Ot.start({configuration:s,json:this.json,stdout:this.context.stdout},async p=>{for await(let h of n.query(c)){let E=Array.from(Object.entries(h)),C=E.length,S=E.reduce((P,[I])=>Math.max(P,I.length),0);for(let P=0;P(lS(),aS)),s=await ze.find(this.context.cwd,this.context.plugins),{project:a}=await Tt.find(s,this.context.cwd),n=await r.find(a);this.context.stdout.write(this.verbose?n.fullSource:n.source)}};Ge();Ge();Yt();iS();var ZC=class extends ft{constructor(){super(...arguments);this.fix=ge.Boolean("--fix",!1,{description:"Attempt to automatically fix unambiguous issues, following a multi-pass process"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["constraints"]]}static{this.usage=ot.Usage({category:"Constraints-related commands",description:"check that the project constraints are met",details:` This command will run constraints on your project and emit errors for each one that is found but isn't met. If any error is emitted the process will exit with a non-zero exit code. If the \`--fix\` flag is used, Yarn will attempt to automatically fix the issues the best it can, following a multi-pass process (with a maximum of 10 iterations). Some ambiguous patterns cannot be autofixed, in which case you'll have to manually specify the right resolution. For more information as to how to write constraints, please consult our dedicated page on our website: https://yarnpkg.com/features/constraints. `,examples:[["Check that all constraints are satisfied","yarn constraints"],["Autofix all unmet constraints","yarn constraints --fix"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);await s.restoreInstallState();let a=await s.loadUserConfig(),n;if(a?.constraints)n=new YC(s);else{let{Constraints:h}=await Promise.resolve().then(()=>(lS(),aS));n=await h.find(s)}let c,f=!1,p=!1;for(let h=this.fix?10:1;h>0;--h){let E=await n.process();if(!E)break;let{changedWorkspaces:C,remainingErrors:S}=iF(s,E,{fix:this.fix}),P=[];for(let[I,R]of C){let N=I.manifest.indent;I.manifest=new Ut,I.manifest.indent=N,I.manifest.load(R),P.push(I.persistManifest())}if(await Promise.all(P),!(C.size>0&&h>1)){c=rEe(S,{configuration:r}),f=!1,p=!0;for(let[,I]of S)for(let R of I)R.fixable?f=!0:p=!1}}if(c.children.length===0)return 0;if(f){let h=p?`Those errors can all be fixed by running ${he.pretty(r,"yarn constraints --fix",he.Type.CODE)}`:`Errors prefixed by '\u2699' can be fixed by running ${he.pretty(r,"yarn constraints --fix",he.Type.CODE)}`;await Ot.start({configuration:r,stdout:this.context.stdout,includeNames:!1,includeFooter:!1},async E=>{E.reportInfo(0,h),E.reportSeparator()})}return c.children=je.sortMap(c.children,h=>h.value[1]),xs.emitTree(c,{configuration:r,stdout:this.context.stdout,json:this.json,separators:1}),1}};iS();var rut={configuration:{enableConstraintsChecks:{description:"If true, constraints will run during installs",type:"BOOLEAN",default:!1},constraintsPath:{description:"The path of the constraints file.",type:"ABSOLUTE_PATH",default:"./constraints.pro"}},commands:[zC,XC,ZC],hooks:{async validateProjectAfterInstall(t,{reportError:e}){if(!t.configuration.get("enableConstraintsChecks"))return;let r=await t.loadUserConfig(),s;if(r?.constraints)s=new YC(t);else{let{Constraints:c}=await Promise.resolve().then(()=>(lS(),aS));s=await c.find(t)}let a=await s.process();if(!a)return;let{remainingErrors:n}=iF(t,a);if(n.size!==0)if(t.configuration.isCI)for(let[c,f]of n)for(let p of f)e(84,`${he.pretty(t.configuration,c.anchoredLocator,he.Type.IDENT)}: ${p.text}`);else e(84,`Constraint check failed; run ${he.pretty(t.configuration,"yarn constraints",he.Type.CODE)} for more details`)}}},nut=rut;var Hq={};Vt(Hq,{CreateCommand:()=>$C,DlxCommand:()=>ew,default:()=>sut});Ge();Yt();var $C=class extends ft{constructor(){super(...arguments);this.pkg=ge.String("-p,--package",{description:"The package to run the provided command from"});this.quiet=ge.Boolean("-q,--quiet",!1,{description:"Only report critical errors instead of printing the full install logs"});this.command=ge.String();this.args=ge.Proxy()}static{this.paths=[["create"]]}async execute(){let r=[];this.pkg&&r.push("--package",this.pkg),this.quiet&&r.push("--quiet");let s=this.command.replace(/^(@[^@/]+)(@|$)/,"$1/create$2"),a=G.parseDescriptor(s),n=a.name.match(/^create(-|$)/)?a:a.scope?G.makeIdent(a.scope,`create-${a.name}`):G.makeIdent(null,`create-${a.name}`),c=G.stringifyIdent(n);return a.range!=="unknown"&&(c+=`@${a.range}`),this.cli.run(["dlx",...r,c,...this.args])}};Ge();Ge();Dt();Yt();var ew=class extends ft{constructor(){super(...arguments);this.packages=ge.Array("-p,--package",{description:"The package(s) to install before running the command"});this.quiet=ge.Boolean("-q,--quiet",!1,{description:"Only report critical errors instead of printing the full install logs"});this.command=ge.String();this.args=ge.Proxy()}static{this.paths=[["dlx"]]}static{this.usage=ot.Usage({description:"run a package in a temporary environment",details:"\n This command will install a package within a temporary environment, and run its binary script if it contains any. The binary will run within the current cwd.\n\n By default Yarn will download the package named `command`, but this can be changed through the use of the `-p,--package` flag which will instruct Yarn to still run the same command but from a different package.\n\n Using `yarn dlx` as a replacement of `yarn add` isn't recommended, as it makes your project non-deterministic (Yarn doesn't keep track of the packages installed through `dlx` - neither their name, nor their version).\n ",examples:[["Use create-vite to scaffold a new Vite project","yarn dlx create-vite"],["Install multiple packages for a single command",`yarn dlx -p typescript -p ts-node ts-node --transpile-only -e "console.log('hello!')"`]]})}async execute(){return ze.telemetry=null,await ce.mktempPromise(async r=>{let s=J.join(r,`dlx-${process.pid}`);await ce.mkdirPromise(s),await ce.writeFilePromise(J.join(s,"package.json"),`{} `),await ce.writeFilePromise(J.join(s,"yarn.lock"),"");let a=J.join(s,".yarnrc.yml"),n=await ze.findProjectCwd(this.context.cwd),f={enableGlobalCache:!(await ze.find(this.context.cwd,null,{strict:!1})).get("enableGlobalCache"),enableTelemetry:!1,logFilters:[{code:Yf(68),level:he.LogLevel.Discard}]},p=n!==null?J.join(n,".yarnrc.yml"):null;p!==null&&ce.existsSync(p)?(await ce.copyFilePromise(p,a),await ze.updateConfiguration(s,N=>{let U=je.toMerged(N,f);return Array.isArray(N.plugins)&&(U.plugins=N.plugins.map(W=>{let ee=typeof W=="string"?W:W.path,ie=fe.isAbsolute(ee)?ee:fe.resolve(fe.fromPortablePath(n),ee);return typeof W=="string"?ie:{path:ie,spec:W.spec}})),U})):await ce.writeJsonPromise(a,f);let h=this.packages??[this.command],E=G.parseDescriptor(this.command).name,C=await this.cli.run(["add","--fixed","--",...h],{cwd:s,quiet:this.quiet});if(C!==0)return C;this.quiet||this.context.stdout.write(` `);let S=await ze.find(s,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,s);if(I===null)throw new ar(P.cwd,s);await P.restoreInstallState();let R=await In.getWorkspaceAccessibleBinaries(I);return R.has(E)===!1&&R.size===1&&typeof this.packages>"u"&&(E=Array.from(R)[0][0]),await In.executeWorkspaceAccessibleBinary(I,E,this.args,{packageAccessibleBinaries:R,cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})})}};var iut={commands:[$C,ew]},sut=iut;var qq={};Vt(qq,{ExecFetcher:()=>uS,ExecResolver:()=>fS,default:()=>lut,execUtils:()=>lF});Ge();Ge();Dt();var cA="exec:";var lF={};Vt(lF,{loadGeneratorFile:()=>cS,makeLocator:()=>Gq,makeSpec:()=>PEe,parseSpec:()=>jq});Ge();Dt();function jq(t){let{params:e,selector:r}=G.parseRange(t),s=fe.toPortablePath(r);return{parentLocator:e&&typeof e.locator=="string"?G.parseLocator(e.locator):null,path:s}}function PEe({parentLocator:t,path:e,generatorHash:r,protocol:s}){let a=t!==null?{locator:G.stringifyLocator(t)}:{},n=typeof r<"u"?{hash:r}:{};return G.makeRange({protocol:s,source:e,selector:e,params:{...n,...a}})}function Gq(t,{parentLocator:e,path:r,generatorHash:s,protocol:a}){return G.makeLocator(t,PEe({parentLocator:e,path:r,generatorHash:s,protocol:a}))}async function cS(t,e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(t,{protocol:e}),n=J.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,n.localPath)}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=J.join(c.prefixPath,a);return await f.readFilePromise(p,"utf8")}var uS=class{supports(e,r){return!!e.reference.startsWith(cA)}getLocalPath(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:cA});if(J.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:J.resolve(n,a)}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async fetchFromDisk(e,r){let s=await cS(e.reference,cA,r);return ce.mktempPromise(async a=>{let n=J.join(a,"generator.js");return await ce.writeFilePromise(n,s),ce.mktempPromise(async c=>{if(await this.generatePackage(c,e,n,r),!ce.existsSync(J.join(c,"build")))throw new Error("The script should have generated a build directory");return await ps.makeArchiveFromDirectory(J.join(c,"build"),{prefixPath:G.getIdentVendorPath(e),compressionLevel:r.project.configuration.get("compressionLevel")})})})}async generatePackage(e,r,s,a){return await ce.mktempPromise(async n=>{let c=await In.makeScriptEnv({project:a.project,binFolder:n}),f=J.join(e,"runtime.js");return await ce.mktempPromise(async p=>{let h=J.join(p,"buildfile.log"),E=J.join(e,"generator"),C=J.join(e,"build");await ce.mkdirPromise(E),await ce.mkdirPromise(C);let S={tempDir:fe.fromPortablePath(E),buildDir:fe.fromPortablePath(C),locator:G.stringifyLocator(r)};await ce.writeFilePromise(f,` // Expose 'Module' as a global variable Object.defineProperty(global, 'Module', { get: () => require('module'), configurable: true, enumerable: false, }); // Expose non-hidden built-in modules as global variables for (const name of Module.builtinModules.filter((name) => name !== 'module' && !name.startsWith('_'))) { Object.defineProperty(global, name, { get: () => require(name), configurable: true, enumerable: false, }); } // Expose the 'execEnv' global variable Object.defineProperty(global, 'execEnv', { value: { ...${JSON.stringify(S)}, }, enumerable: true, }); `);let P=c.NODE_OPTIONS||"",I=/\s*--require\s+\S*\.pnp\.c?js\s*/g;P=P.replace(I," ").trim(),c.NODE_OPTIONS=P;let{stdout:R,stderr:N}=a.project.configuration.getSubprocessStreams(h,{header:`# This file contains the result of Yarn generating a package (${G.stringifyLocator(r)}) `,prefix:G.prettyLocator(a.project.configuration,r),report:a.report}),{code:U}=await qr.pipevp(process.execPath,["--require",fe.fromPortablePath(f),fe.fromPortablePath(s),G.stringifyIdent(r)],{cwd:e,env:c,stdin:null,stdout:R,stderr:N});if(U!==0)throw ce.detachTemp(p),new Error(`Package generation failed (exit code ${U}, logs can be found here: ${he.pretty(a.project.configuration,h,he.Type.PATH)})`)})})}};Ge();Ge();var out=2,fS=class{supportsDescriptor(e,r){return!!e.range.startsWith(cA)}supportsLocator(e,r){return!!e.reference.startsWith(cA)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{path:a,parentLocator:n}=jq(e.range);if(n===null)throw new Error("Assertion failed: The descriptor should have been bound");let c=await cS(G.makeRange({protocol:cA,source:a,selector:a,params:{locator:G.stringifyLocator(n)}}),cA,s.fetchOptions),f=Nn.makeHash(`${out}`,c).slice(0,6);return[Gq(e,{parentLocator:n,path:a,generatorHash:f,protocol:cA})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var aut={fetchers:[uS],resolvers:[fS]},lut=aut;var Yq={};Vt(Yq,{FileFetcher:()=>gS,FileResolver:()=>dS,TarballFileFetcher:()=>mS,TarballFileResolver:()=>yS,default:()=>fut,fileUtils:()=>xm});Ge();Dt();var tw=/^(?:[a-zA-Z]:[\\/]|\.{0,2}\/)/,AS=/^[^?]*\.(?:tar\.gz|tgz)(?:::.*)?$/,es="file:";var xm={};Vt(xm,{fetchArchiveFromLocator:()=>hS,makeArchiveFromLocator:()=>cF,makeBufferFromLocator:()=>Wq,makeLocator:()=>rw,makeSpec:()=>xEe,parseSpec:()=>pS});Ge();Dt();function pS(t){let{params:e,selector:r}=G.parseRange(t),s=fe.toPortablePath(r);return{parentLocator:e&&typeof e.locator=="string"?G.parseLocator(e.locator):null,path:s}}function xEe({parentLocator:t,path:e,hash:r,protocol:s}){let a=t!==null?{locator:G.stringifyLocator(t)}:{},n=typeof r<"u"?{hash:r}:{};return G.makeRange({protocol:s,source:e,selector:e,params:{...n,...a}})}function rw(t,{parentLocator:e,path:r,hash:s,protocol:a}){return G.makeLocator(t,xEe({parentLocator:e,path:r,hash:s,protocol:a}))}async function hS(t,e){let{parentLocator:r,path:s}=G.parseFileStyleRange(t.reference,{protocol:es}),a=J.isAbsolute(s)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await e.fetcher.fetch(r,e),n=a.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,a.localPath)}:a;a!==n&&a.releaseFs&&a.releaseFs();let c=n.packageFs,f=J.join(n.prefixPath,s);return await je.releaseAfterUseAsync(async()=>await c.readFilePromise(f),n.releaseFs)}async function cF(t,{protocol:e,fetchOptions:r,inMemory:s=!1}){let{parentLocator:a,path:n}=G.parseFileStyleRange(t.reference,{protocol:e}),c=J.isAbsolute(n)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(a,r),f=c.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,c.localPath)}:c;c!==f&&c.releaseFs&&c.releaseFs();let p=f.packageFs,h=J.join(f.prefixPath,n);return await je.releaseAfterUseAsync(async()=>await ps.makeArchiveFromDirectory(h,{baseFs:p,prefixPath:G.getIdentVendorPath(t),compressionLevel:r.project.configuration.get("compressionLevel"),inMemory:s}),f.releaseFs)}async function Wq(t,{protocol:e,fetchOptions:r}){return(await cF(t,{protocol:e,fetchOptions:r,inMemory:!0})).getBufferAndClose()}var gS=class{supports(e,r){return!!e.reference.startsWith(es)}getLocalPath(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:es});if(J.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:J.resolve(n,a)}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async fetchFromDisk(e,r){return cF(e,{protocol:es,fetchOptions:r})}};Ge();Ge();var cut=2,dS=class{supportsDescriptor(e,r){return e.range.match(tw)?!0:!!e.range.startsWith(es)}supportsLocator(e,r){return!!e.reference.startsWith(es)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return tw.test(e.range)&&(e=G.makeDescriptor(e,`${es}${e.range}`)),G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{path:a,parentLocator:n}=pS(e.range);if(n===null)throw new Error("Assertion failed: The descriptor should have been bound");let c=await Wq(G.makeLocator(e,G.makeRange({protocol:es,source:a,selector:a,params:{locator:G.stringifyLocator(n)}})),{protocol:es,fetchOptions:s.fetchOptions}),f=Nn.makeHash(`${cut}`,c).slice(0,6);return[rw(e,{parentLocator:n,path:a,hash:f,protocol:es})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};Ge();var mS=class{supports(e,r){return AS.test(e.reference)?!!e.reference.startsWith(es):!1}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),checksum:c}}async fetchFromDisk(e,r){let s=await hS(e,r);return await ps.convertToZip(s,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1})}};Ge();Ge();Ge();var yS=class{supportsDescriptor(e,r){return AS.test(e.range)?!!(e.range.startsWith(es)||tw.test(e.range)):!1}supportsLocator(e,r){return AS.test(e.reference)?!!e.reference.startsWith(es):!1}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return tw.test(e.range)&&(e=G.makeDescriptor(e,`${es}${e.range}`)),G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{path:a,parentLocator:n}=pS(e.range);if(n===null)throw new Error("Assertion failed: The descriptor should have been bound");let c=rw(e,{parentLocator:n,path:a,hash:"",protocol:es}),f=await hS(c,s.fetchOptions),p=Nn.makeHash(f).slice(0,6);return[rw(e,{parentLocator:n,path:a,hash:p,protocol:es})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var uut={fetchers:[mS,gS],resolvers:[yS,dS]},fut=uut;var Kq={};Vt(Kq,{GithubFetcher:()=>ES,default:()=>put,githubUtils:()=>uF});Ge();Dt();var uF={};Vt(uF,{invalidGithubUrlMessage:()=>TEe,isGithubUrl:()=>Vq,parseGithubUrl:()=>Jq});var kEe=ut(Ie("querystring")),QEe=[/^https?:\/\/(?:([^/]+?)@)?github.com\/([^/#]+)\/([^/#]+)\/tarball\/([^/#]+)(?:#(.*))?$/,/^https?:\/\/(?:([^/]+?)@)?github.com\/([^/#]+)\/([^/#]+?)(?:\.git)?(?:#(.*))?$/];function Vq(t){return t?QEe.some(e=>!!t.match(e)):!1}function Jq(t){let e;for(let f of QEe)if(e=t.match(f),e)break;if(!e)throw new Error(TEe(t));let[,r,s,a,n="master"]=e,{commit:c}=kEe.default.parse(n);return n=c||n.replace(/[^:]*:/,""),{auth:r,username:s,reponame:a,treeish:n}}function TEe(t){return`Input cannot be parsed as a valid GitHub URL ('${t}').`}var ES=class{supports(e,r){return!!Vq(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from GitHub`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s=await nn.get(this.getLocatorUrl(e,r),{configuration:r.project.configuration});return await ce.mktempPromise(async a=>{let n=new Sn(a);await ps.extractArchiveTo(s,n,{stripComponents:1});let c=ka.splitRepoUrl(e.reference),f=J.join(a,"package.tgz");await In.prepareExternalProject(a,f,{configuration:r.project.configuration,report:r.report,workspace:c.extra.workspace,locator:e});let p=await ce.readFilePromise(f);return await ps.convertToZip(p,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1})})}getLocatorUrl(e,r){let{auth:s,username:a,reponame:n,treeish:c}=Jq(e.reference);return`https://${s?`${s}@`:""}github.com/${a}/${n}/archive/${c}.tar.gz`}};var Aut={hooks:{async fetchHostedRepository(t,e,r){if(t!==null)return t;let s=new ES;if(!s.supports(e,r))return null;try{return await s.fetch(e,r)}catch{return null}}}},put=Aut;var zq={};Vt(zq,{TarballHttpFetcher:()=>CS,TarballHttpResolver:()=>wS,default:()=>gut});Ge();function IS(t){let e;try{e=new URL(t)}catch{return!1}return!(e.protocol!=="http:"&&e.protocol!=="https:"||!e.pathname.match(/(\.tar\.gz|\.tgz|\/[^.]+)$/))}var CS=class{supports(e,r){return IS(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote server`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s=await nn.get(e.reference,{configuration:r.project.configuration});return await ps.convertToZip(s,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1})}};Ge();Ge();var wS=class{supportsDescriptor(e,r){return IS(e.range)}supportsLocator(e,r){return IS(e.reference)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){return[G.convertDescriptorToLocator(e)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var hut={fetchers:[CS],resolvers:[wS]},gut=hut;var Xq={};Vt(Xq,{InitCommand:()=>z0,InitInitializerCommand:()=>nw,default:()=>mut});Yt();Ge();Ge();Dt();Yt();var z0=class extends ft{constructor(){super(...arguments);this.private=ge.Boolean("-p,--private",!1,{description:"Initialize a private package"});this.workspace=ge.Boolean("-w,--workspace",!1,{description:"Initialize a workspace root with a `packages/` directory"});this.install=ge.String("-i,--install",!1,{tolerateBoolean:!0,description:"Initialize a package with a specific bundle that will be locked in the project"});this.name=ge.String("-n,--name",{description:"Initialize a package with the given name"});this.usev2=ge.Boolean("-2",!1,{hidden:!0});this.yes=ge.Boolean("-y,--yes",{hidden:!0})}static{this.paths=[["init"]]}static{this.usage=ot.Usage({description:"create a new package",details:"\n This command will setup a new package in your local directory.\n\n If the `-p,--private` or `-w,--workspace` options are set, the package will be private by default.\n\n If the `-w,--workspace` option is set, the package will be configured to accept a set of workspaces in the `packages/` directory.\n\n If the `-i,--install` option is given a value, Yarn will first download it using `yarn set version` and only then forward the init call to the newly downloaded bundle. Without arguments, the downloaded bundle will be `latest`.\n\n The initial settings of the manifest can be changed by using the `initScope` and `initFields` configuration values. Additionally, Yarn will generate an EditorConfig file whose rules can be altered via `initEditorConfig`, and will initialize a Git repository in the current directory.\n ",examples:[["Create a new package in the local directory","yarn init"],["Create a new private package in the local directory","yarn init -p"],["Create a new package and store the Yarn release inside","yarn init -i=latest"],["Create a new private package and defines it as a workspace root","yarn init -w"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=typeof this.install=="string"?this.install:this.usev2||this.install===!0?"latest":null;return s!==null?await this.executeProxy(r,s):await this.executeRegular(r)}async executeProxy(r,s){if(r.projectCwd!==null&&r.projectCwd!==this.context.cwd)throw new nt("Cannot use the --install flag from within a project subdirectory");ce.existsSync(this.context.cwd)||await ce.mkdirPromise(this.context.cwd,{recursive:!0});let a=J.join(this.context.cwd,Er.lockfile);ce.existsSync(a)||await ce.writeFilePromise(a,"");let n=await this.cli.run(["set","version",s],{quiet:!0});if(n!==0)return n;let c=[];return this.private&&c.push("-p"),this.workspace&&c.push("-w"),this.name&&c.push(`-n=${this.name}`),this.yes&&c.push("-y"),await ce.mktempPromise(async f=>{let{code:p}=await qr.pipevp("yarn",["init",...c],{cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,env:await In.makeScriptEnv({binFolder:f})});return p})}async initialize(){}async executeRegular(r){let s=null;try{s=(await Tt.find(r,this.context.cwd)).project}catch{s=null}ce.existsSync(this.context.cwd)||await ce.mkdirPromise(this.context.cwd,{recursive:!0});let a=await Ut.tryFind(this.context.cwd),n=a??new Ut,c=Object.fromEntries(r.get("initFields").entries());n.load(c),n.name=n.name??G.makeIdent(r.get("initScope"),this.name??J.basename(this.context.cwd)),n.packageManager=fn&&je.isTaggedYarnVersion(fn)?`yarn@${fn}`:null,(!a&&this.workspace||this.private)&&(n.private=!0),this.workspace&&n.workspaceDefinitions.length===0&&(await ce.mkdirPromise(J.join(this.context.cwd,"packages"),{recursive:!0}),n.workspaceDefinitions=[{pattern:"packages/*"}]);let f={};n.exportTo(f);let p=J.join(this.context.cwd,Ut.fileName);await ce.changeFilePromise(p,`${JSON.stringify(f,null,2)} `,{automaticNewlines:!0});let h=[p],E=J.join(this.context.cwd,"README.md");if(ce.existsSync(E)||(await ce.writeFilePromise(E,`# ${G.stringifyIdent(n.name)} `),h.push(E)),!s||s.cwd===this.context.cwd){let C=J.join(this.context.cwd,Er.lockfile);ce.existsSync(C)||(await ce.writeFilePromise(C,""),h.push(C));let P=[".yarn/*","!.yarn/patches","!.yarn/plugins","!.yarn/releases","!.yarn/sdks","!.yarn/versions","","# Whether you use PnP or not, the node_modules folder is often used to store","# build artifacts that should be gitignored","node_modules","","# Swap the comments on the following lines if you wish to use zero-installs","# In that case, don't forget to run `yarn config set enableGlobalCache false`!","# Documentation here: https://yarnpkg.com/features/caching#zero-installs","","#!.yarn/cache",".pnp.*"].map(ue=>`${ue} `).join(""),I=J.join(this.context.cwd,".gitignore");ce.existsSync(I)||(await ce.writeFilePromise(I,P),h.push(I));let N=["/.yarn/** linguist-vendored","/.yarn/releases/* binary","/.yarn/plugins/**/* binary","/.pnp.* binary linguist-generated"].map(ue=>`${ue} `).join(""),U=J.join(this.context.cwd,".gitattributes");ce.existsSync(U)||(await ce.writeFilePromise(U,N),h.push(U));let W={"*":{charset:"utf-8",endOfLine:"lf",indentSize:2,indentStyle:"space",insertFinalNewline:!0}};je.mergeIntoTarget(W,r.get("initEditorConfig"));let ee=`root = true `;for(let[ue,le]of Object.entries(W)){ee+=` [${ue}] `;for(let[me,pe]of Object.entries(le)){let Be=me.replace(/[A-Z]/g,Ce=>`_${Ce.toLowerCase()}`);ee+=`${Be} = ${pe} `}}let ie=J.join(this.context.cwd,".editorconfig");ce.existsSync(ie)||(await ce.writeFilePromise(ie,ee),h.push(ie)),await this.cli.run(["install"],{quiet:!0}),await this.initialize(),ce.existsSync(J.join(this.context.cwd,".git"))||(await qr.execvp("git",["init"],{cwd:this.context.cwd}),await qr.execvp("git",["add","--",...h],{cwd:this.context.cwd}),await qr.execvp("git",["commit","--allow-empty","-m","First commit"],{cwd:this.context.cwd}))}}};var nw=class extends z0{constructor(){super(...arguments);this.initializer=ge.String();this.argv=ge.Proxy()}static{this.paths=[["init"]]}async initialize(){this.context.stdout.write(` `),await this.cli.run(["dlx",this.initializer,...this.argv],{quiet:!0})}};var dut={configuration:{initScope:{description:"Scope used when creating packages via the init command",type:"STRING",default:null},initFields:{description:"Additional fields to set when creating packages via the init command",type:"MAP",valueDefinition:{description:"",type:"ANY"}},initEditorConfig:{description:"Extra rules to define in the generator editorconfig",type:"MAP",valueDefinition:{description:"",type:"ANY"}}},commands:[z0,nw]},mut=dut;var JW={};Vt(JW,{SearchCommand:()=>Iw,UpgradeInteractiveCommand:()=>Cw,default:()=>Dgt});Ge();var FEe=ut(Ie("os"));function iw({stdout:t}){if(FEe.default.endianness()==="BE")throw new Error("Interactive commands cannot be used on big-endian systems because ink depends on yoga-layout-prebuilt which only supports little-endian architectures");if(!t.isTTY)throw new Error("Interactive commands can only be used inside a TTY environment")}Yt();var YIe=ut(g9()),d9={appId:"OFCNCOG2CU",apiKey:"6fe4476ee5a1832882e326b506d14126",indexName:"npm-search"},hAt=(0,YIe.default)(d9.appId,d9.apiKey).initIndex(d9.indexName),m9=async(t,e=0)=>await hAt.search(t,{analyticsTags:["yarn-plugin-interactive-tools"],attributesToRetrieve:["name","version","owner","repository","humanDownloadsLast30Days"],page:e,hitsPerPage:10});var CD=["regular","dev","peer"],Iw=class extends ft{static{this.paths=[["search"]]}static{this.usage=ot.Usage({category:"Interactive commands",description:"open the search interface",details:` This command opens a fullscreen terminal interface where you can search for and install packages from the npm registry. `,examples:[["Open the search window","yarn search"]]})}async execute(){iw(this.context);let{Gem:e}=await Promise.resolve().then(()=>(WF(),LW)),{ScrollableItems:r}=await Promise.resolve().then(()=>(KF(),JF)),{useKeypress:s}=await Promise.resolve().then(()=>(yD(),w2e)),{useMinistore:a}=await Promise.resolve().then(()=>(GW(),jW)),{renderForm:n}=await Promise.resolve().then(()=>($F(),ZF)),{default:c}=await Promise.resolve().then(()=>ut(T2e())),{Box:f,Text:p}=await Promise.resolve().then(()=>ut(Wc())),{default:h,useEffect:E,useState:C}=await Promise.resolve().then(()=>ut(hn())),S=await ze.find(this.context.cwd,this.context.plugins),P=()=>h.createElement(f,{flexDirection:"row"},h.createElement(f,{flexDirection:"column",width:48},h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},""),"/",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to move between packages.")),h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to select a package.")),h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," again to change the target."))),h.createElement(f,{flexDirection:"column"},h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to install the selected packages.")),h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to abort.")))),I=()=>h.createElement(h.Fragment,null,h.createElement(f,{width:15},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Owner")),h.createElement(f,{width:11},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Version")),h.createElement(f,{width:10},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Downloads"))),R=()=>h.createElement(f,{width:17},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Target")),N=({hit:pe,active:Be})=>{let[Ce,g]=a(pe.name,null);s({active:Be},(Ae,se)=>{if(se.name!=="space")return;if(!Ce){g(CD[0]);return}let Z=CD.indexOf(Ce)+1;Z===CD.length?g(null):g(CD[Z])},[Ce,g]);let we=G.parseIdent(pe.name),ye=G.prettyIdent(S,we);return h.createElement(f,null,h.createElement(f,{width:45},h.createElement(p,{bold:!0,wrap:"wrap"},ye)),h.createElement(f,{width:14,marginLeft:1},h.createElement(p,{bold:!0,wrap:"truncate"},pe.owner.name)),h.createElement(f,{width:10,marginLeft:1},h.createElement(p,{italic:!0,wrap:"truncate"},pe.version)),h.createElement(f,{width:16,marginLeft:1},h.createElement(p,null,pe.humanDownloadsLast30Days)))},U=({name:pe,active:Be})=>{let[Ce]=a(pe,null),g=G.parseIdent(pe);return h.createElement(f,null,h.createElement(f,{width:47},h.createElement(p,{bold:!0}," - ",G.prettyIdent(S,g))),CD.map(we=>h.createElement(f,{key:we,width:14,marginLeft:1},h.createElement(p,null," ",h.createElement(e,{active:Ce===we})," ",h.createElement(p,{bold:!0},we)))))},W=()=>h.createElement(f,{marginTop:1},h.createElement(p,null,"Powered by Algolia.")),ie=await n(({useSubmit:pe})=>{let Be=a();pe(Be);let Ce=Array.from(Be.keys()).filter(j=>Be.get(j)!==null),[g,we]=C(""),[ye,Ae]=C(0),[se,Z]=C([]),De=j=>{j.match(/\t| /)||we(j)},Re=async()=>{Ae(0);let j=await m9(g);j.query===g&&Z(j.hits)},mt=async()=>{let j=await m9(g,ye+1);j.query===g&&j.page-1===ye&&(Ae(j.page),Z([...se,...j.hits]))};return E(()=>{g?Re():Z([])},[g]),h.createElement(f,{flexDirection:"column"},h.createElement(P,null),h.createElement(f,{flexDirection:"row",marginTop:1},h.createElement(p,{bold:!0},"Search: "),h.createElement(f,{width:41},h.createElement(c,{value:g,onChange:De,placeholder:"i.e. babel, webpack, react...",showCursor:!1})),h.createElement(I,null)),se.length?h.createElement(r,{radius:2,loop:!1,children:se.map(j=>h.createElement(N,{key:j.name,hit:j,active:!1})),willReachEnd:mt}):h.createElement(p,{color:"gray"},"Start typing..."),h.createElement(f,{flexDirection:"row",marginTop:1},h.createElement(f,{width:49},h.createElement(p,{bold:!0},"Selected:")),h.createElement(R,null)),Ce.length?Ce.map(j=>h.createElement(U,{key:j,name:j,active:!1})):h.createElement(p,{color:"gray"},"No selected packages..."),h.createElement(W,null))},{},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof ie>"u")return 1;let ue=Array.from(ie.keys()).filter(pe=>ie.get(pe)==="regular"),le=Array.from(ie.keys()).filter(pe=>ie.get(pe)==="dev"),me=Array.from(ie.keys()).filter(pe=>ie.get(pe)==="peer");return ue.length&&await this.cli.run(["add",...ue]),le.length&&await this.cli.run(["add","--dev",...le]),me&&await this.cli.run(["add","--peer",...me]),0}};Ge();Yt();YG();var U2e=ut(Ai()),M2e=/^((?:[\^~]|>=?)?)([0-9]+)(\.[0-9]+)(\.[0-9]+)((?:-\S+)?)$/;function _2e(t,e){return t.length>0?[t.slice(0,e)].concat(_2e(t.slice(e),e)):[]}var Cw=class extends ft{static{this.paths=[["upgrade-interactive"]]}static{this.usage=ot.Usage({category:"Interactive commands",description:"open the upgrade interface",details:` This command opens a fullscreen terminal interface where you can see any out of date packages used by your application, their status compared to the latest versions available on the remote registry, and select packages to upgrade. `,examples:[["Open the upgrade window","yarn upgrade-interactive"]]})}async execute(){iw(this.context);let{ItemOptions:e}=await Promise.resolve().then(()=>(L2e(),O2e)),{Pad:r}=await Promise.resolve().then(()=>(VW(),N2e)),{ScrollableItems:s}=await Promise.resolve().then(()=>(KF(),JF)),{useMinistore:a}=await Promise.resolve().then(()=>(GW(),jW)),{renderForm:n}=await Promise.resolve().then(()=>($F(),ZF)),{Box:c,Text:f}=await Promise.resolve().then(()=>ut(Wc())),{default:p,useEffect:h,useRef:E,useState:C}=await Promise.resolve().then(()=>ut(hn())),S=await ze.find(this.context.cwd,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,this.context.cwd),R=await Kr.find(S);if(!I)throw new ar(P.cwd,this.context.cwd);await P.restoreInstallState({restoreResolutions:!1});let N=this.context.stdout.rows-7,U=(we,ye)=>{let Ae=mde(we,ye),se="";for(let Z of Ae)Z.added?se+=he.pretty(S,Z.value,"green"):Z.removed||(se+=Z.value);return se},W=(we,ye)=>{if(we===ye)return ye;let Ae=G.parseRange(we),se=G.parseRange(ye),Z=Ae.selector.match(M2e),De=se.selector.match(M2e);if(!Z||!De)return U(we,ye);let Re=["gray","red","yellow","green","magenta"],mt=null,j="";for(let rt=1;rt{let se=await Xu.fetchDescriptorFrom(we,Ae,{project:P,cache:R,preserveModifier:ye,workspace:I});return se!==null?se.range:we.range},ie=async we=>{let ye=U2e.default.valid(we.range)?`^${we.range}`:we.range,[Ae,se]=await Promise.all([ee(we,we.range,ye).catch(()=>null),ee(we,we.range,"latest").catch(()=>null)]),Z=[{value:null,label:we.range}];return Ae&&Ae!==we.range?Z.push({value:Ae,label:W(we.range,Ae)}):Z.push({value:null,label:""}),se&&se!==Ae&&se!==we.range?Z.push({value:se,label:W(we.range,se)}):Z.push({value:null,label:""}),Z},ue=()=>p.createElement(c,{flexDirection:"row"},p.createElement(c,{flexDirection:"column",width:49},p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},""),"/",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to select packages.")),p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},""),"/",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to select versions."))),p.createElement(c,{flexDirection:"column"},p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to install.")),p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to abort.")))),le=()=>p.createElement(c,{flexDirection:"row",paddingTop:1,paddingBottom:1},p.createElement(c,{width:50},p.createElement(f,{bold:!0},p.createElement(f,{color:"greenBright"},"?")," Pick the packages you want to upgrade.")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:"gray"},"Current")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:"gray"},"Range")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:"gray"},"Latest"))),me=({active:we,descriptor:ye,suggestions:Ae})=>{let[se,Z]=a(ye.descriptorHash,null),De=G.stringifyIdent(ye),Re=Math.max(0,45-De.length);return p.createElement(p.Fragment,null,p.createElement(c,null,p.createElement(c,{width:45},p.createElement(f,{bold:!0},G.prettyIdent(S,ye)),p.createElement(r,{active:we,length:Re})),p.createElement(e,{active:we,options:Ae,value:se,skewer:!0,onChange:Z,sizes:[17,17,17]})))},pe=({dependencies:we})=>{let[ye,Ae]=C(we.map(()=>null)),se=E(!0),Z=async De=>{let Re=await ie(De);return Re.filter(mt=>mt.label!=="").length<=1?null:{descriptor:De,suggestions:Re}};return h(()=>()=>{se.current=!1},[]),h(()=>{let De=Math.trunc(N*1.75),Re=we.slice(0,De),mt=we.slice(De),j=_2e(mt,N),rt=Re.map(Z).reduce(async(Fe,Ne)=>{await Fe;let Pe=await Ne;Pe!==null&&se.current&&Ae(Ve=>{let ke=Ve.findIndex(Ue=>Ue===null),it=[...Ve];return it[ke]=Pe,it})},Promise.resolve());j.reduce((Fe,Ne)=>Promise.all(Ne.map(Pe=>Promise.resolve().then(()=>Z(Pe)))).then(async Pe=>{Pe=Pe.filter(Ve=>Ve!==null),await Fe,se.current&&Ae(Ve=>{let ke=Ve.findIndex(it=>it===null);return Ve.slice(0,ke).concat(Pe).concat(Ve.slice(ke+Pe.length))})}),rt).then(()=>{se.current&&Ae(Fe=>Fe.filter(Ne=>Ne!==null))})},[]),ye.length?p.createElement(s,{radius:N>>1,children:ye.map((De,Re)=>De!==null?p.createElement(me,{key:Re,active:!1,descriptor:De.descriptor,suggestions:De.suggestions}):p.createElement(f,{key:Re},"Loading..."))}):p.createElement(f,null,"No upgrades found")},Ce=await n(({useSubmit:we})=>{we(a());let ye=new Map;for(let se of P.workspaces)for(let Z of["dependencies","devDependencies"])for(let De of se.manifest[Z].values())P.tryWorkspaceByDescriptor(De)===null&&(De.range.startsWith("link:")||ye.set(De.descriptorHash,De));let Ae=je.sortMap(ye.values(),se=>G.stringifyDescriptor(se));return p.createElement(c,{flexDirection:"column"},p.createElement(ue,null),p.createElement(le,null),p.createElement(pe,{dependencies:Ae}))},{},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof Ce>"u")return 1;let g=!1;for(let we of P.workspaces)for(let ye of["dependencies","devDependencies"]){let Ae=we.manifest[ye];for(let se of Ae.values()){let Z=Ce.get(se.descriptorHash);typeof Z<"u"&&Z!==null&&(Ae.set(se.identHash,G.makeDescriptor(se,Z)),g=!0)}}return g?await P.installWithNewReport({quiet:this.context.quiet,stdout:this.context.stdout},{cache:R}):0}};var Sgt={commands:[Iw,Cw]},Dgt=Sgt;var zW={};Vt(zW,{default:()=>kgt});Ge();var BD="jsr:";Ge();Ge();function ww(t){let e=t.range.slice(4);if(Fr.validRange(e))return G.makeDescriptor(t,`npm:${G.stringifyIdent(G.wrapIdentIntoScope(t,"jsr"))}@${e}`);let r=G.tryParseDescriptor(e,!0);if(r!==null)return G.makeDescriptor(t,`npm:${G.stringifyIdent(G.wrapIdentIntoScope(r,"jsr"))}@${r.range}`);throw new Error(`Invalid range: ${t.range}`)}function Bw(t){return G.makeLocator(G.wrapIdentIntoScope(t,"jsr"),`npm:${t.reference.slice(4)}`)}function KW(t){return G.makeLocator(G.unwrapIdentFromScope(t,"jsr"),`jsr:${t.reference.slice(4)}`)}var eN=class{supports(e,r){return e.reference.startsWith(BD)}getLocalPath(e,r){let s=Bw(e);return r.fetcher.getLocalPath(s,r)}fetch(e,r){let s=Bw(e);return r.fetcher.fetch(s,r)}};var tN=class{supportsDescriptor(e,r){return!!e.range.startsWith(BD)}supportsLocator(e,r){return!!e.reference.startsWith(BD)}shouldPersistResolution(e,r){let s=Bw(e);return r.resolver.shouldPersistResolution(s,r)}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{inner:ww(e)}}async getCandidates(e,r,s){let a=s.project.configuration.normalizeDependency(ww(e));return(await s.resolver.getCandidates(a,r,s)).map(c=>KW(c))}async getSatisfying(e,r,s,a){let n=a.project.configuration.normalizeDependency(ww(e));return a.resolver.getSatisfying(n,r,s,a)}async resolve(e,r){let s=Bw(e),a=await r.resolver.resolve(s,r);return{...a,...KW(a)}}};var bgt=["dependencies","devDependencies","peerDependencies"];function Pgt(t,e){for(let r of bgt)for(let s of t.manifest.getForScope(r).values()){if(!s.range.startsWith("jsr:"))continue;let a=ww(s),n=r==="dependencies"?G.makeDescriptor(s,"unknown"):null,c=n!==null&&t.manifest.ensureDependencyMeta(n).optional?"optionalDependencies":r;e[c][G.stringifyIdent(s)]=a.range}}var xgt={hooks:{beforeWorkspacePacking:Pgt},resolvers:[tN],fetchers:[eN]},kgt=xgt;var XW={};Vt(XW,{LinkFetcher:()=>vD,LinkResolver:()=>SD,PortalFetcher:()=>DD,PortalResolver:()=>bD,default:()=>Tgt});Ge();Dt();var rh="portal:",nh="link:";var vD=class{supports(e,r){return!!e.reference.startsWith(nh)}getLocalPath(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:nh});if(J.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:J.resolve(n,a)}async fetch(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:nh}),n=J.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,n.localPath),localPath:vt.root}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=J.resolve(c.localPath??c.packageFs.getRealPath(),c.prefixPath,a);return n.localPath?{packageFs:new Sn(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,discardFromLookup:!0,localPath:p}:{packageFs:new Hf(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,discardFromLookup:!0}}};Ge();Dt();var SD=class{supportsDescriptor(e,r){return!!e.range.startsWith(nh)}supportsLocator(e,r){return!!e.reference.startsWith(nh)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(nh.length);return[G.makeLocator(e,`${nh}${fe.toPortablePath(a)}`)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){return{...e,version:"0.0.0",languageName:r.project.configuration.get("defaultLanguageName"),linkType:"SOFT",conditions:null,dependencies:new Map,peerDependencies:new Map,dependenciesMeta:new Map,peerDependenciesMeta:new Map,bin:new Map}}};Ge();Dt();var DD=class{supports(e,r){return!!e.reference.startsWith(rh)}getLocalPath(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:rh});if(J.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:J.resolve(n,a)}async fetch(e,r){let{parentLocator:s,path:a}=G.parseFileStyleRange(e.reference,{protocol:rh}),n=J.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,n.localPath),localPath:vt.root}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=J.resolve(c.localPath??c.packageFs.getRealPath(),c.prefixPath,a);return n.localPath?{packageFs:new Sn(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,localPath:p}:{packageFs:new Hf(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot}}};Ge();Ge();Dt();var bD=class{supportsDescriptor(e,r){return!!e.range.startsWith(rh)}supportsLocator(e,r){return!!e.reference.startsWith(rh)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(rh.length);return[G.makeLocator(e,`${rh}${fe.toPortablePath(a)}`)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ut.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"SOFT",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var Qgt={fetchers:[vD,DD],resolvers:[SD,bD]},Tgt=Qgt;var FY={};Vt(FY,{NodeModulesLinker:()=>jD,NodeModulesMode:()=>kY,PnpLooseLinker:()=>GD,default:()=>Kdt});Dt();Ge();Dt();Dt();var $W=(t,e)=>`${t}@${e}`,H2e=(t,e)=>{let r=e.indexOf("#"),s=r>=0?e.substring(r+1):e;return $W(t,s)};var G2e=(t,e={})=>{let r=e.debugLevel||Number(process.env.NM_DEBUG_LEVEL||-1),s=e.check||r>=9,a=e.hoistingLimits||new Map,n={check:s,debugLevel:r,hoistingLimits:a,fastLookupPossible:!0},c;n.debugLevel>=0&&(c=Date.now());let f=Ugt(t,n),p=!1,h=0;do{let E=eY(f,[f],new Set([f.locator]),new Map,n);p=E.anotherRoundNeeded||E.isGraphChanged,n.fastLookupPossible=!1,h++}while(p);if(n.debugLevel>=0&&console.log(`hoist time: ${Date.now()-c}ms, rounds: ${h}`),n.debugLevel>=1){let E=PD(f);if(eY(f,[f],new Set([f.locator]),new Map,n).isGraphChanged)throw new Error(`The hoisting result is not terminal, prev tree: ${E}, next tree: ${PD(f)}`);let S=q2e(f);if(S)throw new Error(`${S}, after hoisting finished: ${PD(f)}`)}return n.debugLevel>=2&&console.log(PD(f)),_gt(f)},Rgt=t=>{let e=t[t.length-1],r=new Map,s=new Set,a=n=>{if(!s.has(n)){s.add(n);for(let c of n.hoistedDependencies.values())r.set(c.name,c);for(let c of n.dependencies.values())n.peerNames.has(c.name)||a(c)}};return a(e),r},Fgt=t=>{let e=t[t.length-1],r=new Map,s=new Set,a=new Set,n=(c,f)=>{if(s.has(c))return;s.add(c);for(let h of c.hoistedDependencies.values())if(!f.has(h.name)){let E;for(let C of t)E=C.dependencies.get(h.name),E&&r.set(E.name,E)}let p=new Set;for(let h of c.dependencies.values())p.add(h.name);for(let h of c.dependencies.values())c.peerNames.has(h.name)||n(h,p)};return n(e,a),r},j2e=(t,e)=>{if(e.decoupled)return e;let{name:r,references:s,ident:a,locator:n,dependencies:c,originalDependencies:f,hoistedDependencies:p,peerNames:h,reasons:E,isHoistBorder:C,hoistPriority:S,dependencyKind:P,hoistedFrom:I,hoistedTo:R}=e,N={name:r,references:new Set(s),ident:a,locator:n,dependencies:new Map(c),originalDependencies:new Map(f),hoistedDependencies:new Map(p),peerNames:new Set(h),reasons:new Map(E),decoupled:!0,isHoistBorder:C,hoistPriority:S,dependencyKind:P,hoistedFrom:new Map(I),hoistedTo:new Map(R)},U=N.dependencies.get(r);return U&&U.ident==N.ident&&N.dependencies.set(r,N),t.dependencies.set(N.name,N),N},Ngt=(t,e)=>{let r=new Map([[t.name,[t.ident]]]);for(let a of t.dependencies.values())t.peerNames.has(a.name)||r.set(a.name,[a.ident]);let s=Array.from(e.keys());s.sort((a,n)=>{let c=e.get(a),f=e.get(n);if(f.hoistPriority!==c.hoistPriority)return f.hoistPriority-c.hoistPriority;{let p=c.dependents.size+c.peerDependents.size;return f.dependents.size+f.peerDependents.size-p}});for(let a of s){let n=a.substring(0,a.indexOf("@",1)),c=a.substring(n.length+1);if(!t.peerNames.has(n)){let f=r.get(n);f||(f=[],r.set(n,f)),f.indexOf(c)<0&&f.push(c)}}return r},ZW=t=>{let e=new Set,r=(s,a=new Set)=>{if(!a.has(s)){a.add(s);for(let n of s.peerNames)if(!t.peerNames.has(n)){let c=t.dependencies.get(n);c&&!e.has(c)&&r(c,a)}e.add(s)}};for(let s of t.dependencies.values())t.peerNames.has(s.name)||r(s);return e},eY=(t,e,r,s,a,n=new Set)=>{let c=e[e.length-1];if(n.has(c))return{anotherRoundNeeded:!1,isGraphChanged:!1};n.add(c);let f=Hgt(c),p=Ngt(c,f),h=t==c?new Map:a.fastLookupPossible?Rgt(e):Fgt(e),E,C=!1,S=!1,P=new Map(Array.from(p.entries()).map(([R,N])=>[R,N[0]])),I=new Map;do{let R=Mgt(t,e,r,h,P,p,s,I,a);R.isGraphChanged&&(S=!0),R.anotherRoundNeeded&&(C=!0),E=!1;for(let[N,U]of p)U.length>1&&!c.dependencies.has(N)&&(P.delete(N),U.shift(),P.set(N,U[0]),E=!0)}while(E);for(let R of c.dependencies.values())if(!c.peerNames.has(R.name)&&!r.has(R.locator)){r.add(R.locator);let N=eY(t,[...e,R],r,I,a);N.isGraphChanged&&(S=!0),N.anotherRoundNeeded&&(C=!0),r.delete(R.locator)}return{anotherRoundNeeded:C,isGraphChanged:S}},Ogt=t=>{for(let[e,r]of t.dependencies)if(!t.peerNames.has(e)&&r.ident!==t.ident)return!0;return!1},Lgt=(t,e,r,s,a,n,c,f,{outputReason:p,fastLookupPossible:h})=>{let E,C=null,S=new Set;p&&(E=`${Array.from(e).map(N=>yo(N)).join("\u2192")}`);let P=r[r.length-1],R=!(s.ident===P.ident);if(p&&!R&&(C="- self-reference"),R&&(R=s.dependencyKind!==1,p&&!R&&(C="- workspace")),R&&s.dependencyKind===2&&(R=!Ogt(s),p&&!R&&(C="- external soft link with unhoisted dependencies")),R&&(R=!t.peerNames.has(s.name),p&&!R&&(C=`- cannot shadow peer: ${yo(t.originalDependencies.get(s.name).locator)} at ${E}`)),R){let N=!1,U=a.get(s.name);if(N=!U||U.ident===s.ident,p&&!N&&(C=`- filled by: ${yo(U.locator)} at ${E}`),N)for(let W=r.length-1;W>=1;W--){let ie=r[W].dependencies.get(s.name);if(ie&&ie.ident!==s.ident){N=!1;let ue=f.get(P);ue||(ue=new Set,f.set(P,ue)),ue.add(s.name),p&&(C=`- filled by ${yo(ie.locator)} at ${r.slice(0,W).map(le=>yo(le.locator)).join("\u2192")}`);break}}R=N}if(R&&(R=n.get(s.name)===s.ident,p&&!R&&(C=`- filled by: ${yo(c.get(s.name)[0])} at ${E}`)),R){let N=!0,U=new Set(s.peerNames);for(let W=r.length-1;W>=1;W--){let ee=r[W];for(let ie of U){if(ee.peerNames.has(ie)&&ee.originalDependencies.has(ie))continue;let ue=ee.dependencies.get(ie);ue&&t.dependencies.get(ie)!==ue&&(W===r.length-1?S.add(ue):(S=null,N=!1,p&&(C=`- peer dependency ${yo(ue.locator)} from parent ${yo(ee.locator)} was not hoisted to ${E}`))),U.delete(ie)}if(!N)break}R=N}if(R&&!h)for(let N of s.hoistedDependencies.values()){let U=a.get(N.name)||t.dependencies.get(N.name);if(!U||N.ident!==U.ident){R=!1,p&&(C=`- previously hoisted dependency mismatch, needed: ${yo(N.locator)}, available: ${yo(U?.locator)}`);break}}return S!==null&&S.size>0?{isHoistable:2,dependsOn:S,reason:C}:{isHoistable:R?0:1,reason:C}},rN=t=>`${t.name}@${t.locator}`,Mgt=(t,e,r,s,a,n,c,f,p)=>{let h=e[e.length-1],E=new Set,C=!1,S=!1,P=(U,W,ee,ie,ue)=>{if(E.has(ie))return;let le=[...W,rN(ie)],me=[...ee,rN(ie)],pe=new Map,Be=new Map;for(let Ae of ZW(ie)){let se=Lgt(h,r,[h,...U,ie],Ae,s,a,n,f,{outputReason:p.debugLevel>=2,fastLookupPossible:p.fastLookupPossible});if(Be.set(Ae,se),se.isHoistable===2)for(let Z of se.dependsOn){let De=pe.get(Z.name)||new Set;De.add(Ae.name),pe.set(Z.name,De)}}let Ce=new Set,g=(Ae,se,Z)=>{if(!Ce.has(Ae)){Ce.add(Ae),Be.set(Ae,{isHoistable:1,reason:Z});for(let De of pe.get(Ae.name)||[])g(ie.dependencies.get(De),se,p.debugLevel>=2?`- peer dependency ${yo(Ae.locator)} from parent ${yo(ie.locator)} was not hoisted`:"")}};for(let[Ae,se]of Be)se.isHoistable===1&&g(Ae,se,se.reason);let we=!1;for(let Ae of Be.keys())if(!Ce.has(Ae)){S=!0;let se=c.get(ie);se&&se.has(Ae.name)&&(C=!0),we=!0,ie.dependencies.delete(Ae.name),ie.hoistedDependencies.set(Ae.name,Ae),ie.reasons.delete(Ae.name);let Z=h.dependencies.get(Ae.name);if(p.debugLevel>=2){let De=Array.from(W).concat([ie.locator]).map(mt=>yo(mt)).join("\u2192"),Re=h.hoistedFrom.get(Ae.name);Re||(Re=[],h.hoistedFrom.set(Ae.name,Re)),Re.push(De),ie.hoistedTo.set(Ae.name,Array.from(e).map(mt=>yo(mt.locator)).join("\u2192"))}if(!Z)h.ident!==Ae.ident&&(h.dependencies.set(Ae.name,Ae),ue.add(Ae));else for(let De of Ae.references)Z.references.add(De)}if(ie.dependencyKind===2&&we&&(C=!0),p.check){let Ae=q2e(t);if(Ae)throw new Error(`${Ae}, after hoisting dependencies of ${[h,...U,ie].map(se=>yo(se.locator)).join("\u2192")}: ${PD(t)}`)}let ye=ZW(ie);for(let Ae of ye)if(Ce.has(Ae)){let se=Be.get(Ae);if((a.get(Ae.name)===Ae.ident||!ie.reasons.has(Ae.name))&&se.isHoistable!==0&&ie.reasons.set(Ae.name,se.reason),!Ae.isHoistBorder&&me.indexOf(rN(Ae))<0){E.add(ie);let De=j2e(ie,Ae);P([...U,ie],le,me,De,R),E.delete(ie)}}},I,R=new Set(ZW(h)),N=Array.from(e).map(U=>rN(U));do{I=R,R=new Set;for(let U of I){if(U.locator===h.locator||U.isHoistBorder)continue;let W=j2e(h,U);P([],Array.from(r),N,W,R)}}while(R.size>0);return{anotherRoundNeeded:C,isGraphChanged:S}},q2e=t=>{let e=[],r=new Set,s=new Set,a=(n,c,f)=>{if(r.has(n)||(r.add(n),s.has(n)))return;let p=new Map(c);for(let h of n.dependencies.values())n.peerNames.has(h.name)||p.set(h.name,h);for(let h of n.originalDependencies.values()){let E=p.get(h.name),C=()=>`${Array.from(s).concat([n]).map(S=>yo(S.locator)).join("\u2192")}`;if(n.peerNames.has(h.name)){let S=c.get(h.name);(S!==E||!S||S.ident!==h.ident)&&e.push(`${C()} - broken peer promise: expected ${h.ident} but found ${S&&S.ident}`)}else{let S=f.hoistedFrom.get(n.name),P=n.hoistedTo.get(h.name),I=`${S?` hoisted from ${S.join(", ")}`:""}`,R=`${P?` hoisted to ${P}`:""}`,N=`${C()}${I}`;E?E.ident!==h.ident&&e.push(`${N} - broken require promise for ${h.name}${R}: expected ${h.ident}, but found: ${E.ident}`):e.push(`${N} - broken require promise: no required dependency ${h.name}${R} found`)}}s.add(n);for(let h of n.dependencies.values())n.peerNames.has(h.name)||a(h,p,n);s.delete(n)};return a(t,t.dependencies,t),e.join(` `)},Ugt=(t,e)=>{let{identName:r,name:s,reference:a,peerNames:n}=t,c={name:s,references:new Set([a]),locator:$W(r,a),ident:H2e(r,a),dependencies:new Map,originalDependencies:new Map,hoistedDependencies:new Map,peerNames:new Set(n),reasons:new Map,decoupled:!0,isHoistBorder:!0,hoistPriority:0,dependencyKind:1,hoistedFrom:new Map,hoistedTo:new Map},f=new Map([[t,c]]),p=(h,E)=>{let C=f.get(h),S=!!C;if(!C){let{name:P,identName:I,reference:R,peerNames:N,hoistPriority:U,dependencyKind:W}=h,ee=e.hoistingLimits.get(E.locator);C={name:P,references:new Set([R]),locator:$W(I,R),ident:H2e(I,R),dependencies:new Map,originalDependencies:new Map,hoistedDependencies:new Map,peerNames:new Set(N),reasons:new Map,decoupled:!0,isHoistBorder:ee?ee.has(P):!1,hoistPriority:U||0,dependencyKind:W||0,hoistedFrom:new Map,hoistedTo:new Map},f.set(h,C)}if(E.dependencies.set(h.name,C),E.originalDependencies.set(h.name,C),S){let P=new Set,I=R=>{if(!P.has(R)){P.add(R),R.decoupled=!1;for(let N of R.dependencies.values())R.peerNames.has(N.name)||I(N)}};I(C)}else for(let P of h.dependencies)p(P,C)};for(let h of t.dependencies)p(h,c);return c},tY=t=>t.substring(0,t.indexOf("@",1)),_gt=t=>{let e={name:t.name,identName:tY(t.locator),references:new Set(t.references),dependencies:new Set},r=new Set([t]),s=(a,n,c)=>{let f=r.has(a),p;if(n===a)p=c;else{let{name:h,references:E,locator:C}=a;p={name:h,identName:tY(C),references:E,dependencies:new Set}}if(c.dependencies.add(p),!f){r.add(a);for(let h of a.dependencies.values())a.peerNames.has(h.name)||s(h,a,p);r.delete(a)}};for(let a of t.dependencies.values())s(a,t,e);return e},Hgt=t=>{let e=new Map,r=new Set([t]),s=c=>`${c.name}@${c.ident}`,a=c=>{let f=s(c),p=e.get(f);return p||(p={dependents:new Set,peerDependents:new Set,hoistPriority:0},e.set(f,p)),p},n=(c,f)=>{let p=!!r.has(f);if(a(f).dependents.add(c.ident),!p){r.add(f);for(let E of f.dependencies.values()){let C=a(E);C.hoistPriority=Math.max(C.hoistPriority,E.hoistPriority),f.peerNames.has(E.name)?C.peerDependents.add(f.ident):n(f,E)}}};for(let c of t.dependencies.values())t.peerNames.has(c.name)||n(t,c);return e},yo=t=>{if(!t)return"none";let e=t.indexOf("@",1),r=t.substring(0,e);r.endsWith("$wsroot$")&&(r=`wh:${r.replace("$wsroot$","")}`);let s=t.substring(e+1);if(s==="workspace:.")return".";if(s){let a=(s.indexOf("#")>0?s.split("#")[1]:s).replace("npm:","");return s.startsWith("virtual")&&(r=`v:${r}`),a.startsWith("workspace")&&(r=`w:${r}`,a=""),`${r}${a?`@${a}`:""}`}else return`${r}`};var PD=t=>{let e=0,r=(a,n,c="")=>{if(e>5e4||n.has(a))return"";e++;let f=Array.from(a.dependencies.values()).sort((h,E)=>h.name===E.name?0:h.name>E.name?1:-1),p="";n.add(a);for(let h=0;h":"")+(S!==E.name?`a:${E.name}:`:"")+yo(E.locator)+(C?` ${C}`:"")} `,p+=r(E,n,`${c}${h5e4?` Tree is too large, part of the tree has been dunped `:"")};var xD=(s=>(s.WORKSPACES="workspaces",s.DEPENDENCIES="dependencies",s.NONE="none",s))(xD||{}),W2e="node_modules",rg="$wsroot$";var kD=(t,e)=>{let{packageTree:r,hoistingLimits:s,errors:a,preserveSymlinksRequired:n}=Ggt(t,e),c=null;if(a.length===0){let f=G2e(r,{hoistingLimits:s});c=Wgt(t,f,e)}return{tree:c,errors:a,preserveSymlinksRequired:n}},pA=t=>`${t.name}@${t.reference}`,nY=t=>{let e=new Map;for(let[r,s]of t.entries())if(!s.dirList){let a=e.get(s.locator);a||(a={target:s.target,linkType:s.linkType,locations:[],aliases:s.aliases},e.set(s.locator,a)),a.locations.push(r)}for(let r of e.values())r.locations=r.locations.sort((s,a)=>{let n=s.split(J.delimiter).length,c=a.split(J.delimiter).length;return a===s?0:n!==c?c-n:a>s?1:-1});return e},Y2e=(t,e)=>{let r=G.isVirtualLocator(t)?G.devirtualizeLocator(t):t,s=G.isVirtualLocator(e)?G.devirtualizeLocator(e):e;return G.areLocatorsEqual(r,s)},rY=(t,e,r,s)=>{if(t.linkType!=="SOFT")return!1;let a=fe.toPortablePath(r.resolveVirtual&&e.reference&&e.reference.startsWith("virtual:")?r.resolveVirtual(t.packageLocation):t.packageLocation);return J.contains(s,a)===null},jgt=t=>{let e=t.getPackageInformation(t.topLevel);if(e===null)throw new Error("Assertion failed: Expected the top-level package to have been registered");if(t.findPackageLocator(e.packageLocation)===null)throw new Error("Assertion failed: Expected the top-level package to have a physical locator");let s=fe.toPortablePath(e.packageLocation.slice(0,-1)),a=new Map,n={children:new Map},c=t.getDependencyTreeRoots(),f=new Map,p=new Set,h=(S,P)=>{let I=pA(S);if(p.has(I))return;p.add(I);let R=t.getPackageInformation(S);if(R){let N=P?pA(P):"";if(pA(S)!==N&&R.linkType==="SOFT"&&!S.reference.startsWith("link:")&&!rY(R,S,t,s)){let U=V2e(R,S,t);(!f.get(U)||S.reference.startsWith("workspace:"))&&f.set(U,S)}for(let[U,W]of R.packageDependencies)W!==null&&(R.packagePeers.has(U)||h(t.getLocator(U,W),S))}};for(let S of c)h(S,null);let E=s.split(J.sep);for(let S of f.values()){let P=t.getPackageInformation(S),R=fe.toPortablePath(P.packageLocation.slice(0,-1)).split(J.sep).slice(E.length),N=n;for(let U of R){let W=N.children.get(U);W||(W={children:new Map},N.children.set(U,W)),N=W}N.workspaceLocator=S}let C=(S,P)=>{if(S.workspaceLocator){let I=pA(P),R=a.get(I);R||(R=new Set,a.set(I,R)),R.add(S.workspaceLocator)}for(let I of S.children.values())C(I,S.workspaceLocator||P)};for(let S of n.children.values())C(S,n.workspaceLocator);return a},Ggt=(t,e)=>{let r=[],s=!1,a=new Map,n=jgt(t),c=t.getPackageInformation(t.topLevel);if(c===null)throw new Error("Assertion failed: Expected the top-level package to have been registered");let f=t.findPackageLocator(c.packageLocation);if(f===null)throw new Error("Assertion failed: Expected the top-level package to have a physical locator");let p=fe.toPortablePath(c.packageLocation.slice(0,-1)),h={name:f.name,identName:f.name,reference:f.reference,peerNames:c.packagePeers,dependencies:new Set,dependencyKind:1},E=new Map,C=(P,I)=>`${pA(I)}:${P}`,S=(P,I,R,N,U,W,ee,ie)=>{let ue=C(P,R),le=E.get(ue),me=!!le;!me&&R.name===f.name&&R.reference===f.reference&&(le=h,E.set(ue,h));let pe=rY(I,R,t,p);if(!le){let Ae=0;pe?Ae=2:I.linkType==="SOFT"&&R.name.endsWith(rg)&&(Ae=1),le={name:P,identName:R.name,reference:R.reference,dependencies:new Set,peerNames:Ae===1?new Set:I.packagePeers,dependencyKind:Ae},E.set(ue,le)}let Be;if(pe?Be=2:U.linkType==="SOFT"?Be=1:Be=0,le.hoistPriority=Math.max(le.hoistPriority||0,Be),ie&&!pe){let Ae=pA({name:N.identName,reference:N.reference}),se=a.get(Ae)||new Set;a.set(Ae,se),se.add(le.name)}let Ce=new Map(I.packageDependencies);if(e.project){let Ae=e.project.workspacesByCwd.get(fe.toPortablePath(I.packageLocation.slice(0,-1)));if(Ae){let se=new Set([...Array.from(Ae.manifest.peerDependencies.values(),Z=>G.stringifyIdent(Z)),...Array.from(Ae.manifest.peerDependenciesMeta.keys())]);for(let Z of se)Ce.has(Z)||(Ce.set(Z,W.get(Z)||null),le.peerNames.add(Z))}}let g=pA({name:R.name.replace(rg,""),reference:R.reference}),we=n.get(g);if(we)for(let Ae of we)Ce.set(`${Ae.name}${rg}`,Ae.reference);(I!==U||I.linkType!=="SOFT"||!pe&&(!e.selfReferencesByCwd||e.selfReferencesByCwd.get(ee)))&&N.dependencies.add(le);let ye=R!==f&&I.linkType==="SOFT"&&!R.name.endsWith(rg)&&!pe;if(!me&&!ye){let Ae=new Map;for(let[se,Z]of Ce)if(Z!==null){let De=t.getLocator(se,Z),Re=t.getLocator(se.replace(rg,""),Z),mt=t.getPackageInformation(Re);if(mt===null)throw new Error("Assertion failed: Expected the package to have been registered");let j=rY(mt,De,t,p);if(e.validateExternalSoftLinks&&e.project&&j){mt.packageDependencies.size>0&&(s=!0);for(let[Ve,ke]of mt.packageDependencies)if(ke!==null){let it=G.parseLocator(Array.isArray(ke)?`${ke[0]}@${ke[1]}`:`${Ve}@${ke}`);if(pA(it)!==pA(De)){let Ue=Ce.get(Ve);if(Ue){let x=G.parseLocator(Array.isArray(Ue)?`${Ue[0]}@${Ue[1]}`:`${Ve}@${Ue}`);Y2e(x,it)||r.push({messageName:71,text:`Cannot link ${G.prettyIdent(e.project.configuration,G.parseIdent(De.name))} into ${G.prettyLocator(e.project.configuration,G.parseLocator(`${R.name}@${R.reference}`))} dependency ${G.prettyLocator(e.project.configuration,it)} conflicts with parent dependency ${G.prettyLocator(e.project.configuration,x)}`})}else{let x=Ae.get(Ve);if(x){let w=x.target,b=G.parseLocator(Array.isArray(w)?`${w[0]}@${w[1]}`:`${Ve}@${w}`);Y2e(b,it)||r.push({messageName:71,text:`Cannot link ${G.prettyIdent(e.project.configuration,G.parseIdent(De.name))} into ${G.prettyLocator(e.project.configuration,G.parseLocator(`${R.name}@${R.reference}`))} dependency ${G.prettyLocator(e.project.configuration,it)} conflicts with dependency ${G.prettyLocator(e.project.configuration,b)} from sibling portal ${G.prettyIdent(e.project.configuration,G.parseIdent(x.portal.name))}`})}else Ae.set(Ve,{target:it.reference,portal:De})}}}}let rt=e.hoistingLimitsByCwd?.get(ee),Fe=j?ee:J.relative(p,fe.toPortablePath(mt.packageLocation))||vt.dot,Ne=e.hoistingLimitsByCwd?.get(Fe);S(se,mt,De,le,I,Ce,Fe,rt==="dependencies"||Ne==="dependencies"||Ne==="workspaces")}}};return S(f.name,c,f,h,c,c.packageDependencies,vt.dot,!1),{packageTree:h,hoistingLimits:a,errors:r,preserveSymlinksRequired:s}};function V2e(t,e,r){let s=r.resolveVirtual&&e.reference&&e.reference.startsWith("virtual:")?r.resolveVirtual(t.packageLocation):t.packageLocation;return fe.toPortablePath(s||t.packageLocation)}function qgt(t,e,r){let s=e.getLocator(t.name.replace(rg,""),t.reference),a=e.getPackageInformation(s);if(a===null)throw new Error("Assertion failed: Expected the package to be registered");return r.pnpifyFs?{linkType:"SOFT",target:fe.toPortablePath(a.packageLocation)}:{linkType:a.linkType,target:V2e(a,t,e)}}var Wgt=(t,e,r)=>{let s=new Map,a=(E,C,S)=>{let{linkType:P,target:I}=qgt(E,t,r);return{locator:pA(E),nodePath:C,target:I,linkType:P,aliases:S}},n=E=>{let[C,S]=E.split("/");return S?{scope:C,name:S}:{scope:null,name:C}},c=new Set,f=(E,C,S)=>{if(c.has(E))return;c.add(E);let P=Array.from(E.references).sort().join("#");for(let I of E.dependencies){let R=Array.from(I.references).sort().join("#");if(I.identName===E.identName.replace(rg,"")&&R===P)continue;let N=Array.from(I.references).sort(),U={name:I.identName,reference:N[0]},{name:W,scope:ee}=n(I.name),ie=ee?[ee,W]:[W],ue=J.join(C,W2e),le=J.join(ue,...ie),me=`${S}/${U.name}`,pe=a(U,S,N.slice(1)),Be=!1;if(pe.linkType==="SOFT"&&r.project){let Ce=r.project.workspacesByCwd.get(pe.target.slice(0,-1));Be=!!(Ce&&!Ce.manifest.name)}if(!I.name.endsWith(rg)&&!Be){let Ce=s.get(le);if(Ce){if(Ce.dirList)throw new Error(`Assertion failed: ${le} cannot merge dir node with leaf node`);{let ye=G.parseLocator(Ce.locator),Ae=G.parseLocator(pe.locator);if(Ce.linkType!==pe.linkType)throw new Error(`Assertion failed: ${le} cannot merge nodes with different link types ${Ce.nodePath}/${G.stringifyLocator(ye)} and ${S}/${G.stringifyLocator(Ae)}`);if(ye.identHash!==Ae.identHash)throw new Error(`Assertion failed: ${le} cannot merge nodes with different idents ${Ce.nodePath}/${G.stringifyLocator(ye)} and ${S}/s${G.stringifyLocator(Ae)}`);pe.aliases=[...pe.aliases,...Ce.aliases,G.parseLocator(Ce.locator).reference]}}s.set(le,pe);let g=le.split("/"),we=g.indexOf(W2e);for(let ye=g.length-1;we>=0&&ye>we;ye--){let Ae=fe.toPortablePath(g.slice(0,ye).join(J.sep)),se=g[ye],Z=s.get(Ae);if(!Z)s.set(Ae,{dirList:new Set([se])});else if(Z.dirList){if(Z.dirList.has(se))break;Z.dirList.add(se)}}}f(I,pe.linkType==="SOFT"?pe.target:le,me)}},p=a({name:e.name,reference:Array.from(e.references)[0]},"",[]),h=p.target;return s.set(h,p),f(e,h,""),s};Ge();Ge();Dt();Dt();eA();wc();var wY={};Vt(wY,{PnpInstaller:()=>Gm,PnpLinker:()=>sg,UnplugCommand:()=>Sw,default:()=>Cdt,getPnpPath:()=>og,jsInstallUtils:()=>gA,pnpUtils:()=>HD,quotePathIfNeeded:()=>QBe});Dt();var kBe=Ie("url");Ge();Ge();Dt();Dt();var J2e={DEFAULT:{collapsed:!1,next:{"*":"DEFAULT"}},TOP_LEVEL:{collapsed:!1,next:{fallbackExclusionList:"FALLBACK_EXCLUSION_LIST",packageRegistryData:"PACKAGE_REGISTRY_DATA","*":"DEFAULT"}},FALLBACK_EXCLUSION_LIST:{collapsed:!1,next:{"*":"FALLBACK_EXCLUSION_ENTRIES"}},FALLBACK_EXCLUSION_ENTRIES:{collapsed:!0,next:{"*":"FALLBACK_EXCLUSION_DATA"}},FALLBACK_EXCLUSION_DATA:{collapsed:!0,next:{"*":"DEFAULT"}},PACKAGE_REGISTRY_DATA:{collapsed:!1,next:{"*":"PACKAGE_REGISTRY_ENTRIES"}},PACKAGE_REGISTRY_ENTRIES:{collapsed:!0,next:{"*":"PACKAGE_STORE_DATA"}},PACKAGE_STORE_DATA:{collapsed:!1,next:{"*":"PACKAGE_STORE_ENTRIES"}},PACKAGE_STORE_ENTRIES:{collapsed:!0,next:{"*":"PACKAGE_INFORMATION_DATA"}},PACKAGE_INFORMATION_DATA:{collapsed:!1,next:{packageDependencies:"PACKAGE_DEPENDENCIES","*":"DEFAULT"}},PACKAGE_DEPENDENCIES:{collapsed:!1,next:{"*":"PACKAGE_DEPENDENCY"}},PACKAGE_DEPENDENCY:{collapsed:!0,next:{"*":"DEFAULT"}}};function Ygt(t,e,r){let s="";s+="[";for(let a=0,n=t.length;a"u"||(f!==0&&(a+=", "),a+=JSON.stringify(p),a+=": ",a+=nN(p,h,e,r).replace(/^ +/g,""),f+=1)}return a+="}",a}function Kgt(t,e,r){let s=Object.keys(t),a=`${r} `,n="";n+=r,n+=`{ `;let c=0;for(let f=0,p=s.length;f"u"||(c!==0&&(n+=",",n+=` `),n+=a,n+=JSON.stringify(h),n+=": ",n+=nN(h,E,e,a).replace(/^ +/g,""),c+=1)}return c!==0&&(n+=` `),n+=r,n+="}",n}function nN(t,e,r,s){let{next:a}=J2e[r],n=a[t]||a["*"];return K2e(e,n,s)}function K2e(t,e,r){let{collapsed:s}=J2e[e];return Array.isArray(t)?s?Ygt(t,e,r):Vgt(t,e,r):typeof t=="object"&&t!==null?s?Jgt(t,e,r):Kgt(t,e,r):JSON.stringify(t)}function z2e(t){return K2e(t,"TOP_LEVEL","")}function QD(t,e){let r=Array.from(t);Array.isArray(e)||(e=[e]);let s=[];for(let n of e)s.push(r.map(c=>n(c)));let a=r.map((n,c)=>c);return a.sort((n,c)=>{for(let f of s){let p=f[n]f[c]?1:0;if(p!==0)return p}return 0}),a.map(n=>r[n])}function zgt(t){let e=new Map,r=QD(t.fallbackExclusionList||[],[({name:s,reference:a})=>s,({name:s,reference:a})=>a]);for(let{name:s,reference:a}of r){let n=e.get(s);typeof n>"u"&&e.set(s,n=new Set),n.add(a)}return Array.from(e).map(([s,a])=>[s,Array.from(a)])}function Xgt(t){return QD(t.fallbackPool||[],([e])=>e)}function Zgt(t){let e=[],r=t.dependencyTreeRoots.find(s=>t.packageRegistry.get(s.name)?.get(s.reference)?.packageLocation==="./");for(let[s,a]of QD(t.packageRegistry,([n])=>n===null?"0":`1${n}`)){if(s===null)continue;let n=[];e.push([s,n]);for(let[c,{packageLocation:f,packageDependencies:p,packagePeers:h,linkType:E,discardFromLookup:C}]of QD(a,([S])=>S===null?"0":`1${S}`)){if(c===null)continue;let S=[];s!==null&&c!==null&&!p.has(s)&&S.push([s,c]);for(let[U,W]of p)S.push([U,W]);let P=QD(S,([U])=>U),I=h&&h.size>0?Array.from(h):void 0,N={packageLocation:f,packageDependencies:P,packagePeers:I,linkType:E,discardFromLookup:C||void 0};n.push([c,N]),r&&s===r.name&&c===r.reference&&e.unshift([null,[[null,N]]])}}return e}function TD(t){return{__info:["This file is automatically generated. Do not touch it, or risk","your modifications being lost."],dependencyTreeRoots:t.dependencyTreeRoots,enableTopLevelFallback:t.enableTopLevelFallback||!1,ignorePatternData:t.ignorePattern||null,pnpZipBackend:t.pnpZipBackend,fallbackExclusionList:zgt(t),fallbackPool:Xgt(t),packageRegistryData:Zgt(t)}}var $2e=ut(Z2e());function eBe(t,e){return[t?`${t} `:"",`/* eslint-disable */ `,`// @ts-nocheck `,`"use strict"; `,` `,e,` `,(0,$2e.default)()].join("")}function $gt(t){return JSON.stringify(t,null,2)}function edt(t){return`'${t.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/\n/g,`\\ `)}'`}function tdt(t){return[`const RAW_RUNTIME_STATE = `,`${edt(z2e(t))}; `,`function $$SETUP_STATE(hydrateRuntimeState, basePath) { `,` return hydrateRuntimeState(JSON.parse(RAW_RUNTIME_STATE), {basePath: basePath || __dirname}); `,`} `].join("")}function rdt(){return[`function $$SETUP_STATE(hydrateRuntimeState, basePath) { `,` const fs = require('fs'); `,` const path = require('path'); `,` const pnpDataFilepath = path.resolve(__dirname, ${JSON.stringify(Er.pnpData)}); `,` return hydrateRuntimeState(JSON.parse(fs.readFileSync(pnpDataFilepath, 'utf8')), {basePath: basePath || __dirname}); `,`} `].join("")}function tBe(t){let e=TD(t),r=tdt(e);return eBe(t.shebang,r)}function rBe(t){let e=TD(t),r=rdt(),s=eBe(t.shebang,r);return{dataFile:$gt(e),loaderFile:s}}Dt();function sY(t,{basePath:e}){let r=fe.toPortablePath(e),s=J.resolve(r),a=t.ignorePatternData!==null?new RegExp(t.ignorePatternData):null,n=new Map,c=new Map(t.packageRegistryData.map(([C,S])=>[C,new Map(S.map(([P,I])=>{if(C===null!=(P===null))throw new Error("Assertion failed: The name and reference should be null, or neither should");let R=I.discardFromLookup??!1,N={name:C,reference:P},U=n.get(I.packageLocation);U?(U.discardFromLookup=U.discardFromLookup&&R,R||(U.locator=N)):n.set(I.packageLocation,{locator:N,discardFromLookup:R});let W=null;return[P,{packageDependencies:new Map(I.packageDependencies),packagePeers:new Set(I.packagePeers),linkType:I.linkType,discardFromLookup:R,get packageLocation(){return W||(W=J.join(s,I.packageLocation))}}]}))])),f=new Map(t.fallbackExclusionList.map(([C,S])=>[C,new Set(S)])),p=new Map(t.fallbackPool),h=t.dependencyTreeRoots,E=t.enableTopLevelFallback;return{basePath:r,dependencyTreeRoots:h,enableTopLevelFallback:E,fallbackExclusionList:f,pnpZipBackend:t.pnpZipBackend,fallbackPool:p,ignorePattern:a,packageLocatorsByLocations:n,packageRegistry:c}}Dt();Dt();var sh=Ie("module"),jm=Ie("url"),gY=Ie("util");var ta=Ie("url");var oBe=ut(Ie("assert"));var oY=Array.isArray,RD=JSON.stringify,FD=Object.getOwnPropertyNames,Hm=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),aY=(t,e)=>RegExp.prototype.exec.call(t,e),lY=(t,...e)=>RegExp.prototype[Symbol.replace].apply(t,e),ng=(t,...e)=>String.prototype.endsWith.apply(t,e),cY=(t,...e)=>String.prototype.includes.apply(t,e),uY=(t,...e)=>String.prototype.lastIndexOf.apply(t,e),ND=(t,...e)=>String.prototype.indexOf.apply(t,e),nBe=(t,...e)=>String.prototype.replace.apply(t,e),ig=(t,...e)=>String.prototype.slice.apply(t,e),hA=(t,...e)=>String.prototype.startsWith.apply(t,e),iBe=Map,sBe=JSON.parse;function OD(t,e,r){return class extends r{constructor(...s){super(e(...s)),this.code=t,this.name=`${r.name} [${t}]`}}}var aBe=OD("ERR_PACKAGE_IMPORT_NOT_DEFINED",(t,e,r)=>`Package import specifier "${t}" is not defined${e?` in package ${e}package.json`:""} imported from ${r}`,TypeError),fY=OD("ERR_INVALID_MODULE_SPECIFIER",(t,e,r=void 0)=>`Invalid module "${t}" ${e}${r?` imported from ${r}`:""}`,TypeError),lBe=OD("ERR_INVALID_PACKAGE_TARGET",(t,e,r,s=!1,a=void 0)=>{let n=typeof r=="string"&&!s&&r.length&&!hA(r,"./");return e==="."?((0,oBe.default)(s===!1),`Invalid "exports" main target ${RD(r)} defined in the package config ${t}package.json${a?` imported from ${a}`:""}${n?'; targets must start with "./"':""}`):`Invalid "${s?"imports":"exports"}" target ${RD(r)} defined for '${e}' in the package config ${t}package.json${a?` imported from ${a}`:""}${n?'; targets must start with "./"':""}`},Error),LD=OD("ERR_INVALID_PACKAGE_CONFIG",(t,e,r)=>`Invalid package config ${t}${e?` while importing ${e}`:""}${r?`. ${r}`:""}`,Error),cBe=OD("ERR_PACKAGE_PATH_NOT_EXPORTED",(t,e,r=void 0)=>e==="."?`No "exports" main defined in ${t}package.json${r?` imported from ${r}`:""}`:`Package subpath '${e}' is not defined by "exports" in ${t}package.json${r?` imported from ${r}`:""}`,Error);var sN=Ie("url");function uBe(t,e){let r=Object.create(null);for(let s=0;se):t+e}MD(r,t,s,c,a)}aY(ABe,ig(t,2))!==null&&MD(r,t,s,c,a);let p=new URL(t,s),h=p.pathname,E=new URL(".",s).pathname;if(hA(h,E)||MD(r,t,s,c,a),e==="")return p;if(aY(ABe,e)!==null){let C=n?nBe(r,"*",()=>e):r+e;sdt(C,s,c,a)}return n?new URL(lY(pBe,p.href,()=>e)):new URL(e,p)}function adt(t){let e=+t;return`${e}`!==t?!1:e>=0&&e<4294967295}function vw(t,e,r,s,a,n,c,f){if(typeof e=="string")return odt(e,r,s,t,a,n,c,f);if(oY(e)){if(e.length===0)return null;let p;for(let h=0;hn?-1:n>a||r===-1?1:s===-1||t.length>e.length?-1:e.length>t.length?1:0}function ldt(t,e,r){if(typeof t=="string"||oY(t))return!0;if(typeof t!="object"||t===null)return!1;let s=FD(t),a=!1,n=0;for(let c=0;c=h.length&&ng(e,C)&&gBe(n,h)===1&&uY(h,"*")===E&&(n=h,c=ig(e,E,e.length-C.length))}}if(n){let p=r[n],h=vw(t,p,c,n,s,!0,!1,a);return h==null&&AY(e,t,s),h}AY(e,t,s)}function mBe({name:t,base:e,conditions:r,readFileSyncFn:s}){if(t==="#"||hA(t,"#/")||ng(t,"/")){let c="is not a valid internal imports specifier name";throw new fY(t,c,(0,ta.fileURLToPath)(e))}let a,n=fBe(e,s);if(n.exists){a=(0,ta.pathToFileURL)(n.pjsonPath);let c=n.imports;if(c)if(Hm(c,t)&&!cY(t,"*")){let f=vw(a,c[t],"",t,e,!1,!0,r);if(f!=null)return f}else{let f="",p,h=FD(c);for(let E=0;E=C.length&&ng(t,P)&&gBe(f,C)===1&&uY(C,"*")===S&&(f=C,p=ig(t,S,t.length-P.length))}}if(f){let E=c[f],C=vw(a,E,p,f,e,!0,!0,r);if(C!=null)return C}}}idt(t,a,e)}Dt();var udt=new Set(["BUILTIN_NODE_RESOLUTION_FAILED","MISSING_DEPENDENCY","MISSING_PEER_DEPENDENCY","QUALIFIED_PATH_RESOLUTION_FAILED","UNDECLARED_DEPENDENCY"]);function gs(t,e,r={},s){s??=udt.has(t)?"MODULE_NOT_FOUND":t;let a={configurable:!0,writable:!0,enumerable:!1};return Object.defineProperties(new Error(e),{code:{...a,value:s},pnpCode:{...a,value:t},data:{...a,value:r}})}function lf(t){return fe.normalize(fe.fromPortablePath(t))}var CBe=ut(EBe());function wBe(t){return fdt(),hY[t]}var hY;function fdt(){hY||(hY={"--conditions":[],...IBe(Adt()),...IBe(process.execArgv)})}function IBe(t){return(0,CBe.default)({"--conditions":[String],"-C":"--conditions"},{argv:t,permissive:!0})}function Adt(){let t=[],e=pdt(process.env.NODE_OPTIONS||"",t);return t.length,e}function pdt(t,e){let r=[],s=!1,a=!0;for(let n=0;nparseInt(t,10)),BBe=ml>19||ml===19&&ih>=2||ml===18&&ih>=13,UZt=ml===20&&ih<6||ml===19&&ih>=3,_Zt=ml>19||ml===19&&ih>=6,HZt=ml>=21||ml===20&&ih>=10||ml===18&&ih>=19,jZt=ml>=21||ml===20&&ih>=10||ml===18&&ih>=20,GZt=ml>=22;function vBe(t){if(process.env.WATCH_REPORT_DEPENDENCIES&&process.send)if(t=t.map(e=>fe.fromPortablePath(uo.resolveVirtual(fe.toPortablePath(e)))),BBe)process.send({"watch:require":t});else for(let e of t)process.send({"watch:require":e})}function dY(t,e){let r=Number(process.env.PNP_ALWAYS_WARN_ON_FALLBACK)>0,s=Number(process.env.PNP_DEBUG_LEVEL),a=/^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:node:)?(?:@[^/]+\/)?[^/]+)\/*(.*|)$/,n=/^(\/|\.{1,2}(\/|$))/,c=/\/$/,f=/^\.{0,2}\//,p={name:null,reference:null},h=[],E=new Set;if(t.enableTopLevelFallback===!0&&h.push(p),e.compatibilityMode!==!1)for(let Fe of["react-scripts","gatsby"]){let Ne=t.packageRegistry.get(Fe);if(Ne)for(let Pe of Ne.keys()){if(Pe===null)throw new Error("Assertion failed: This reference shouldn't be null");h.push({name:Fe,reference:Pe})}}let{ignorePattern:C,packageRegistry:S,packageLocatorsByLocations:P}=t;function I(Fe,Ne){return{fn:Fe,args:Ne,error:null,result:null}}function R(Fe){let Ne=process.stderr?.hasColors?.()??process.stdout.isTTY,Pe=(it,Ue)=>`\x1B[${it}m${Ue}\x1B[0m`,Ve=Fe.error;console.error(Ve?Pe("31;1",`\u2716 ${Fe.error?.message.replace(/\n.*/s,"")}`):Pe("33;1","\u203C Resolution")),Fe.args.length>0&&console.error();for(let it of Fe.args)console.error(` ${Pe("37;1","In \u2190")} ${(0,gY.inspect)(it,{colors:Ne,compact:!0})}`);Fe.result&&(console.error(),console.error(` ${Pe("37;1","Out \u2192")} ${(0,gY.inspect)(Fe.result,{colors:Ne,compact:!0})}`));let ke=new Error().stack.match(/(?<=^ +)at.*/gm)?.slice(2)??[];if(ke.length>0){console.error();for(let it of ke)console.error(` ${Pe("38;5;244",it)}`)}console.error()}function N(Fe,Ne){if(e.allowDebug===!1)return Ne;if(Number.isFinite(s)){if(s>=2)return(...Pe)=>{let Ve=I(Fe,Pe);try{return Ve.result=Ne(...Pe)}catch(ke){throw Ve.error=ke}finally{R(Ve)}};if(s>=1)return(...Pe)=>{try{return Ne(...Pe)}catch(Ve){let ke=I(Fe,Pe);throw ke.error=Ve,R(ke),Ve}}}return Ne}function U(Fe){let Ne=g(Fe);if(!Ne)throw gs("INTERNAL","Couldn't find a matching entry in the dependency tree for the specified parent (this is probably an internal error)");return Ne}function W(Fe){if(Fe.name===null)return!0;for(let Ne of t.dependencyTreeRoots)if(Ne.name===Fe.name&&Ne.reference===Fe.reference)return!0;return!1}let ee=new Set(["node","require",...wBe("--conditions")]);function ie(Fe,Ne=ee,Pe){let Ve=Ae(J.join(Fe,"internal.js"),{resolveIgnored:!0,includeDiscardFromLookup:!0});if(Ve===null)throw gs("INTERNAL",`The locator that owns the "${Fe}" path can't be found inside the dependency tree (this is probably an internal error)`);let{packageLocation:ke}=U(Ve),it=J.join(ke,Er.manifest);if(!e.fakeFs.existsSync(it))return null;let Ue=JSON.parse(e.fakeFs.readFileSync(it,"utf8"));if(Ue.exports==null)return null;let x=J.contains(ke,Fe);if(x===null)throw gs("INTERNAL","unqualifiedPath doesn't contain the packageLocation (this is probably an internal error)");x!=="."&&!f.test(x)&&(x=`./${x}`);try{let w=dBe({packageJSONUrl:(0,jm.pathToFileURL)(fe.fromPortablePath(it)),packageSubpath:x,exports:Ue.exports,base:Pe?(0,jm.pathToFileURL)(fe.fromPortablePath(Pe)):null,conditions:Ne});return fe.toPortablePath((0,jm.fileURLToPath)(w))}catch(w){throw gs("EXPORTS_RESOLUTION_FAILED",w.message,{unqualifiedPath:lf(Fe),locator:Ve,pkgJson:Ue,subpath:lf(x),conditions:Ne},w.code)}}function ue(Fe,Ne,{extensions:Pe}){let Ve;try{Ne.push(Fe),Ve=e.fakeFs.statSync(Fe)}catch{}if(Ve&&!Ve.isDirectory())return e.fakeFs.realpathSync(Fe);if(Ve&&Ve.isDirectory()){let ke;try{ke=JSON.parse(e.fakeFs.readFileSync(J.join(Fe,Er.manifest),"utf8"))}catch{}let it;if(ke&&ke.main&&(it=J.resolve(Fe,ke.main)),it&&it!==Fe){let Ue=ue(it,Ne,{extensions:Pe});if(Ue!==null)return Ue}}for(let ke=0,it=Pe.length;ke{let x=JSON.stringify(Ue.name);if(Ve.has(x))return;Ve.add(x);let w=we(Ue);for(let b of w)if(U(b).packagePeers.has(Fe))ke(b);else{let F=Pe.get(b.name);typeof F>"u"&&Pe.set(b.name,F=new Set),F.add(b.reference)}};ke(Ne);let it=[];for(let Ue of[...Pe.keys()].sort())for(let x of[...Pe.get(Ue)].sort())it.push({name:Ue,reference:x});return it}function Ae(Fe,{resolveIgnored:Ne=!1,includeDiscardFromLookup:Pe=!1}={}){if(pe(Fe)&&!Ne)return null;let Ve=J.relative(t.basePath,Fe);Ve.match(n)||(Ve=`./${Ve}`),Ve.endsWith("/")||(Ve=`${Ve}/`);do{let ke=P.get(Ve);if(typeof ke>"u"||ke.discardFromLookup&&!Pe){Ve=Ve.substring(0,Ve.lastIndexOf("/",Ve.length-2)+1);continue}return ke.locator}while(Ve!=="");return null}function se(Fe){try{return e.fakeFs.readFileSync(fe.toPortablePath(Fe),"utf8")}catch(Ne){if(Ne.code==="ENOENT")return;throw Ne}}function Z(Fe,Ne,{considerBuiltins:Pe=!0}={}){if(Fe.startsWith("#"))throw new Error("resolveToUnqualified can not handle private import mappings");if(Fe==="pnpapi")return fe.toPortablePath(e.pnpapiResolution);if(Pe&&(0,sh.isBuiltin)(Fe))return null;let Ve=lf(Fe),ke=Ne&&lf(Ne);if(Ne&&pe(Ne)&&(!J.isAbsolute(Fe)||Ae(Fe)===null)){let x=me(Fe,Ne);if(x===!1)throw gs("BUILTIN_NODE_RESOLUTION_FAILED",`The builtin node resolution algorithm was unable to resolve the requested module (it didn't go through the pnp resolver because the issuer was explicitely ignored by the regexp) Require request: "${Ve}" Required by: ${ke} `,{request:Ve,issuer:ke});return fe.toPortablePath(x)}let it,Ue=Fe.match(a);if(Ue){if(!Ne)throw gs("API_ERROR","The resolveToUnqualified function must be called with a valid issuer when the path isn't a builtin nor absolute",{request:Ve,issuer:ke});let[,x,w]=Ue,b=Ae(Ne);if(!b){let Te=me(Fe,Ne);if(Te===!1)throw gs("BUILTIN_NODE_RESOLUTION_FAILED",`The builtin node resolution algorithm was unable to resolve the requested module (it didn't go through the pnp resolver because the issuer doesn't seem to be part of the Yarn-managed dependency tree). Require path: "${Ve}" Required by: ${ke} `,{request:Ve,issuer:ke});return fe.toPortablePath(Te)}let F=U(b).packageDependencies.get(x),z=null;if(F==null&&b.name!==null){let Te=t.fallbackExclusionList.get(b.name);if(!Te||!Te.has(b.reference)){for(let Ct=0,qt=h.length;CtW(lt))?X=gs("MISSING_PEER_DEPENDENCY",`${b.name} tried to access ${x} (a peer dependency) but it isn't provided by your application; this makes the require call ambiguous and unsound. Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} Required by: ${b.name}@${b.reference} (via ${ke}) ${Te.map(lt=>`Ancestor breaking the chain: ${lt.name}@${lt.reference} `).join("")} `,{request:Ve,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x,brokenAncestors:Te}):X=gs("MISSING_PEER_DEPENDENCY",`${b.name} tried to access ${x} (a peer dependency) but it isn't provided by its ancestors; this makes the require call ambiguous and unsound. Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} Required by: ${b.name}@${b.reference} (via ${ke}) ${Te.map(lt=>`Ancestor breaking the chain: ${lt.name}@${lt.reference} `).join("")} `,{request:Ve,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x,brokenAncestors:Te})}else F===void 0&&(!Pe&&(0,sh.isBuiltin)(Fe)?W(b)?X=gs("UNDECLARED_DEPENDENCY",`Your application tried to access ${x}. While this module is usually interpreted as a Node builtin, your resolver is running inside a non-Node resolution context where such builtins are ignored. Since ${x} isn't otherwise declared in your dependencies, this makes the require call ambiguous and unsound. Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} Required by: ${ke} `,{request:Ve,issuer:ke,dependencyName:x}):X=gs("UNDECLARED_DEPENDENCY",`${b.name} tried to access ${x}. While this module is usually interpreted as a Node builtin, your resolver is running inside a non-Node resolution context where such builtins are ignored. Since ${x} isn't otherwise declared in ${b.name}'s dependencies, this makes the require call ambiguous and unsound. Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} Required by: ${ke} `,{request:Ve,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x}):W(b)?X=gs("UNDECLARED_DEPENDENCY",`Your application tried to access ${x}, but it isn't declared in your dependencies; this makes the require call ambiguous and unsound. Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} Required by: ${ke} `,{request:Ve,issuer:ke,dependencyName:x}):X=gs("UNDECLARED_DEPENDENCY",`${b.name} tried to access ${x}, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound. Required package: ${x}${x!==Ve?` (via "${Ve}")`:""} Required by: ${b.name}@${b.reference} (via ${ke}) `,{request:Ve,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x}));if(F==null){if(z===null||X===null)throw X||new Error("Assertion failed: Expected an error to have been set");F=z;let Te=X.message.replace(/\n.*/g,"");X.message=Te,!E.has(Te)&&s!==0&&(E.add(Te),process.emitWarning(X))}let $=Array.isArray(F)?{name:F[0],reference:F[1]}:{name:x,reference:F},oe=U($);if(!oe.packageLocation)throw gs("MISSING_DEPENDENCY",`A dependency seems valid but didn't get installed for some reason. This might be caused by a partial install, such as dev vs prod. Required package: ${$.name}@${$.reference}${$.name!==Ve?` (via "${Ve}")`:""} Required by: ${b.name}@${b.reference} (via ${ke}) `,{request:Ve,issuer:ke,dependencyLocator:Object.assign({},$)});let xe=oe.packageLocation;w?it=J.join(xe,w):it=xe}else if(J.isAbsolute(Fe))it=J.normalize(Fe);else{if(!Ne)throw gs("API_ERROR","The resolveToUnqualified function must be called with a valid issuer when the path isn't a builtin nor absolute",{request:Ve,issuer:ke});let x=J.resolve(Ne);Ne.match(c)?it=J.normalize(J.join(x,Fe)):it=J.normalize(J.join(J.dirname(x),Fe))}return J.normalize(it)}function De(Fe,Ne,Pe=ee,Ve){if(n.test(Fe))return Ne;let ke=ie(Ne,Pe,Ve);return ke?J.normalize(ke):Ne}function Re(Fe,{extensions:Ne=Object.keys(sh.Module._extensions)}={}){let Pe=[],Ve=ue(Fe,Pe,{extensions:Ne});if(Ve)return J.normalize(Ve);{vBe(Pe.map(Ue=>fe.fromPortablePath(Ue)));let ke=lf(Fe),it=Ae(Fe);if(it){let{packageLocation:Ue}=U(it),x=!0;try{e.fakeFs.accessSync(Ue)}catch(w){if(w?.code==="ENOENT")x=!1;else{let b=(w?.message??w??"empty exception thrown").replace(/^[A-Z]/,y=>y.toLowerCase());throw gs("QUALIFIED_PATH_RESOLUTION_FAILED",`Required package exists but could not be accessed (${b}). Missing package: ${it.name}@${it.reference} Expected package location: ${lf(Ue)} `,{unqualifiedPath:ke,extensions:Ne})}}if(!x){let w=Ue.includes("/unplugged/")?"Required unplugged package missing from disk. This may happen when switching branches without running installs (unplugged packages must be fully materialized on disk to work).":"Required package missing from disk. If you keep your packages inside your repository then restarting the Node process may be enough. Otherwise, try to run an install first.";throw gs("QUALIFIED_PATH_RESOLUTION_FAILED",`${w} Missing package: ${it.name}@${it.reference} Expected package location: ${lf(Ue)} `,{unqualifiedPath:ke,extensions:Ne})}}throw gs("QUALIFIED_PATH_RESOLUTION_FAILED",`Qualified path resolution failed: we looked for the following paths, but none could be accessed. Source path: ${ke} ${Pe.map(Ue=>`Not found: ${lf(Ue)} `).join("")}`,{unqualifiedPath:ke,extensions:Ne})}}function mt(Fe,Ne,Pe){if(!Ne)throw new Error("Assertion failed: An issuer is required to resolve private import mappings");let Ve=mBe({name:Fe,base:(0,jm.pathToFileURL)(fe.fromPortablePath(Ne)),conditions:Pe.conditions??ee,readFileSyncFn:se});if(Ve instanceof URL)return Re(fe.toPortablePath((0,jm.fileURLToPath)(Ve)),{extensions:Pe.extensions});if(Ve.startsWith("#"))throw new Error("Mapping from one private import to another isn't allowed");return j(Ve,Ne,Pe)}function j(Fe,Ne,Pe={}){try{if(Fe.startsWith("#"))return mt(Fe,Ne,Pe);let{considerBuiltins:Ve,extensions:ke,conditions:it}=Pe,Ue=Z(Fe,Ne,{considerBuiltins:Ve});if(Fe==="pnpapi")return Ue;if(Ue===null)return null;let x=()=>Ne!==null?pe(Ne):!1,w=(!Ve||!(0,sh.isBuiltin)(Fe))&&!x()?De(Fe,Ue,it,Ne):Ue;return Re(w,{extensions:ke})}catch(Ve){throw Object.hasOwn(Ve,"pnpCode")&&Object.assign(Ve.data,{request:lf(Fe),issuer:Ne&&lf(Ne)}),Ve}}function rt(Fe){let Ne=J.normalize(Fe),Pe=uo.resolveVirtual(Ne);return Pe!==Ne?Pe:null}return{VERSIONS:Be,topLevel:Ce,getLocator:(Fe,Ne)=>Array.isArray(Ne)?{name:Ne[0],reference:Ne[1]}:{name:Fe,reference:Ne},getDependencyTreeRoots:()=>[...t.dependencyTreeRoots],getAllLocators(){let Fe=[];for(let[Ne,Pe]of S)for(let Ve of Pe.keys())Ne!==null&&Ve!==null&&Fe.push({name:Ne,reference:Ve});return Fe},getPackageInformation:Fe=>{let Ne=g(Fe);if(Ne===null)return null;let Pe=fe.fromPortablePath(Ne.packageLocation);return{...Ne,packageLocation:Pe}},findPackageLocator:Fe=>Ae(fe.toPortablePath(Fe)),resolveToUnqualified:N("resolveToUnqualified",(Fe,Ne,Pe)=>{let Ve=Ne!==null?fe.toPortablePath(Ne):null,ke=Z(fe.toPortablePath(Fe),Ve,Pe);return ke===null?null:fe.fromPortablePath(ke)}),resolveUnqualified:N("resolveUnqualified",(Fe,Ne)=>fe.fromPortablePath(Re(fe.toPortablePath(Fe),Ne))),resolveRequest:N("resolveRequest",(Fe,Ne,Pe)=>{let Ve=Ne!==null?fe.toPortablePath(Ne):null,ke=j(fe.toPortablePath(Fe),Ve,Pe);return ke===null?null:fe.fromPortablePath(ke)}),resolveVirtual:N("resolveVirtual",Fe=>{let Ne=rt(fe.toPortablePath(Fe));return Ne!==null?fe.fromPortablePath(Ne):null})}}Dt();var SBe=(t,e,r)=>{let s=TD(t),a=sY(s,{basePath:e}),n=fe.join(e,Er.pnpCjs);return dY(a,{fakeFs:r,pnpapiResolution:n})};var yY=ut(bBe());Yt();var gA={};Vt(gA,{checkManifestCompatibility:()=>PBe,extractBuildRequest:()=>oN,getExtractHint:()=>EY,hasBindingGyp:()=>IY});Ge();Dt();function PBe(t){return G.isPackageCompatible(t,Ui.getArchitectureSet())}function oN(t,e,r,{configuration:s}){let a=[];for(let n of["preinstall","install","postinstall"])e.manifest.scripts.has(n)&&a.push({type:0,script:n});return!e.manifest.scripts.has("install")&&e.misc.hasBindingGyp&&a.push({type:1,script:"node-gyp rebuild"}),a.length===0?null:t.linkType!=="HARD"?{skipped:!0,explain:n=>n.reportWarningOnce(6,`${G.prettyLocator(s,t)} lists build scripts, but is referenced through a soft link. Soft links don't support build scripts, so they'll be ignored.`)}:r&&r.built===!1?{skipped:!0,explain:n=>n.reportInfoOnce(5,`${G.prettyLocator(s,t)} lists build scripts, but its build has been explicitly disabled through configuration.`)}:!s.get("enableScripts")&&!r.built?{skipped:!0,explain:n=>n.reportWarningOnce(4,`${G.prettyLocator(s,t)} lists build scripts, but all build scripts have been disabled.`)}:PBe(t)?{skipped:!1,directives:a}:{skipped:!0,explain:n=>n.reportWarningOnce(76,`${G.prettyLocator(s,t)} The ${Ui.getArchitectureName()} architecture is incompatible with this package, build skipped.`)}}var gdt=new Set([".exe",".bin",".h",".hh",".hpp",".c",".cc",".cpp",".java",".jar",".node"]);function EY(t){return t.packageFs.getExtractHint({relevantExtensions:gdt})}function IY(t){let e=J.join(t.prefixPath,"binding.gyp");return t.packageFs.existsSync(e)}var HD={};Vt(HD,{getUnpluggedPath:()=>_D});Ge();Dt();function _D(t,{configuration:e}){return J.resolve(e.get("pnpUnpluggedFolder"),G.slugifyLocator(t))}var ddt=new Set([G.makeIdent(null,"open").identHash,G.makeIdent(null,"opn").identHash]),sg=class{constructor(){this.mode="strict";this.pnpCache=new Map}getCustomDataKey(){return JSON.stringify({name:"PnpLinker",version:2})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error("Assertion failed: Expected the PnP linker to be enabled");let s=og(r.project).cjs;if(!ce.existsSync(s))throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let a=je.getFactoryWithDefault(this.pnpCache,s,()=>je.dynamicRequire(s,{cachingStrategy:je.CachingStrategy.FsTime})),n={name:G.stringifyIdent(e),reference:e.reference},c=a.getPackageInformation(n);if(!c)throw new nt(`Couldn't find ${G.prettyLocator(r.project.configuration,e)} in the currently installed PnP map - running an install might help`);return fe.toPortablePath(c.packageLocation)}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=og(r.project).cjs;if(!ce.existsSync(s))return null;let n=je.getFactoryWithDefault(this.pnpCache,s,()=>je.dynamicRequire(s,{cachingStrategy:je.CachingStrategy.FsTime})).findPackageLocator(fe.fromPortablePath(e));return n?G.makeLocator(G.parseIdent(n.name),n.reference):null}makeInstaller(e){return new Gm(e)}isEnabled(e){return!(e.project.configuration.get("nodeLinker")!=="pnp"||e.project.configuration.get("pnpMode")!==this.mode)}},Gm=class{constructor(e){this.opts=e;this.mode="strict";this.asyncActions=new je.AsyncActions(10);this.packageRegistry=new Map;this.virtualTemplates=new Map;this.isESMLoaderRequired=!1;this.customData={store:new Map};this.unpluggedPaths=new Set;this.opts=e}attachCustomData(e){this.customData=e}async installPackage(e,r,s){let a=G.stringifyIdent(e),n=e.reference,c=!!this.opts.project.tryWorkspaceByLocator(e),f=G.isVirtualLocator(e),p=e.peerDependencies.size>0&&!f,h=!p&&!c,E=!p&&e.linkType!=="SOFT",C,S;if(h||E){let ee=f?G.devirtualizeLocator(e):e;C=this.customData.store.get(ee.locatorHash),typeof C>"u"&&(C=await mdt(r),e.linkType==="HARD"&&this.customData.store.set(ee.locatorHash,C)),C.manifest.type==="module"&&(this.isESMLoaderRequired=!0),S=this.opts.project.getDependencyMeta(ee,e.version)}let P=h?oN(e,C,S,{configuration:this.opts.project.configuration}):null,I=E?await this.unplugPackageIfNeeded(e,C,r,S,s):r.packageFs;if(J.isAbsolute(r.prefixPath))throw new Error(`Assertion failed: Expected the prefix path (${r.prefixPath}) to be relative to the parent`);let R=J.resolve(I.getRealPath(),r.prefixPath),N=CY(this.opts.project.cwd,R),U=new Map,W=new Set;if(f){for(let ee of e.peerDependencies.values())U.set(G.stringifyIdent(ee),null),W.add(G.stringifyIdent(ee));if(!c){let ee=G.devirtualizeLocator(e);this.virtualTemplates.set(ee.locatorHash,{location:CY(this.opts.project.cwd,uo.resolveVirtual(R)),locator:ee})}}return je.getMapWithDefault(this.packageRegistry,a).set(n,{packageLocation:N,packageDependencies:U,packagePeers:W,linkType:e.linkType,discardFromLookup:r.discardFromLookup||!1}),{packageLocation:R,buildRequest:P}}async attachInternalDependencies(e,r){let s=this.getPackageInformation(e);for(let[a,n]of r){let c=G.areIdentsEqual(a,n)?n.reference:[G.stringifyIdent(n),n.reference];s.packageDependencies.set(G.stringifyIdent(a),c)}}async attachExternalDependents(e,r){for(let s of r)this.getDiskInformation(s).packageDependencies.set(G.stringifyIdent(e),e.reference)}async finalizeInstall(){if(this.opts.project.configuration.get("pnpMode")!==this.mode)return;let e=og(this.opts.project);if(this.isEsmEnabled()||await ce.removePromise(e.esmLoader),this.opts.project.configuration.get("nodeLinker")!=="pnp"){await ce.removePromise(e.cjs),await ce.removePromise(e.data),await ce.removePromise(e.esmLoader),await ce.removePromise(this.opts.project.configuration.get("pnpUnpluggedFolder"));return}for(let{locator:C,location:S}of this.virtualTemplates.values())je.getMapWithDefault(this.packageRegistry,G.stringifyIdent(C)).set(C.reference,{packageLocation:S,packageDependencies:new Map,packagePeers:new Set,linkType:"SOFT",discardFromLookup:!1});let r=this.opts.project.configuration.get("pnpFallbackMode"),s=this.opts.project.workspaces.map(({anchoredLocator:C})=>({name:G.stringifyIdent(C),reference:C.reference})),a=r!=="none",n=[],c=new Map,f=je.buildIgnorePattern([".yarn/sdks/**",...this.opts.project.configuration.get("pnpIgnorePatterns")]),p=this.packageRegistry,h=this.opts.project.configuration.get("pnpShebang"),E=this.opts.project.configuration.get("pnpZipBackend");if(r==="dependencies-only")for(let C of this.opts.project.storedPackages.values())this.opts.project.tryWorkspaceByLocator(C)&&n.push({name:G.stringifyIdent(C),reference:C.reference});return await this.asyncActions.wait(),await this.finalizeInstallWithPnp({dependencyTreeRoots:s,enableTopLevelFallback:a,fallbackExclusionList:n,fallbackPool:c,ignorePattern:f,pnpZipBackend:E,packageRegistry:p,shebang:h}),{customData:this.customData}}async transformPnpSettings(e){}isEsmEnabled(){if(this.opts.project.configuration.sources.has("pnpEnableEsmLoader"))return this.opts.project.configuration.get("pnpEnableEsmLoader");if(this.isESMLoaderRequired)return!0;for(let e of this.opts.project.workspaces)if(e.manifest.type==="module")return!0;return!1}async finalizeInstallWithPnp(e){let r=og(this.opts.project),s=await this.locateNodeModules(e.ignorePattern);if(s.length>0){this.opts.report.reportWarning(31,"One or more node_modules have been detected and will be removed. This operation may take some time.");for(let n of s)await ce.removePromise(n)}if(await this.transformPnpSettings(e),this.opts.project.configuration.get("pnpEnableInlining")){let n=tBe(e);await ce.changeFilePromise(r.cjs,n,{automaticNewlines:!0,mode:493}),await ce.removePromise(r.data)}else{let{dataFile:n,loaderFile:c}=rBe(e);await ce.changeFilePromise(r.cjs,c,{automaticNewlines:!0,mode:493}),await ce.changeFilePromise(r.data,n,{automaticNewlines:!0,mode:420})}this.isEsmEnabled()&&(this.opts.report.reportWarning(0,"ESM support for PnP uses the experimental loader API and is therefore experimental"),await ce.changeFilePromise(r.esmLoader,(0,yY.default)(),{automaticNewlines:!0,mode:420}));let a=this.opts.project.configuration.get("pnpUnpluggedFolder");if(this.unpluggedPaths.size===0)await ce.removePromise(a);else for(let n of await ce.readdirPromise(a)){let c=J.resolve(a,n);this.unpluggedPaths.has(c)||await ce.removePromise(c)}}async locateNodeModules(e){let r=[],s=e?new RegExp(e):null;for(let a of this.opts.project.workspaces){let n=J.join(a.cwd,"node_modules");if(s&&s.test(J.relative(this.opts.project.cwd,a.cwd))||!ce.existsSync(n))continue;let c=await ce.readdirPromise(n,{withFileTypes:!0}),f=c.filter(p=>!p.isDirectory()||p.name===".bin"||!p.name.startsWith("."));if(f.length===c.length)r.push(n);else for(let p of f)r.push(J.join(n,p.name))}return r}async unplugPackageIfNeeded(e,r,s,a,n){return this.shouldBeUnplugged(e,r,a)?this.unplugPackage(e,s,n):s.packageFs}shouldBeUnplugged(e,r,s){return typeof s.unplugged<"u"?s.unplugged:ddt.has(e.identHash)||e.conditions!=null?!0:r.manifest.preferUnplugged!==null?r.manifest.preferUnplugged:!!(oN(e,r,s,{configuration:this.opts.project.configuration})?.skipped===!1||r.misc.extractHint)}async unplugPackage(e,r,s){let a=_D(e,{configuration:this.opts.project.configuration});return this.opts.project.disabledLocators.has(e.locatorHash)?new _f(a,{baseFs:r.packageFs,pathUtils:J}):(this.unpluggedPaths.add(a),s.holdFetchResult(this.asyncActions.set(e.locatorHash,async()=>{let n=J.join(a,r.prefixPath,".ready");await ce.existsPromise(n)||(this.opts.project.storedBuildState.delete(e.locatorHash),await ce.mkdirPromise(a,{recursive:!0}),await ce.copyPromise(a,vt.dot,{baseFs:r.packageFs,overwrite:!1}),await ce.writeFilePromise(n,""))})),new Sn(a))}getPackageInformation(e){let r=G.stringifyIdent(e),s=e.reference,a=this.packageRegistry.get(r);if(!a)throw new Error(`Assertion failed: The package information store should have been available (for ${G.prettyIdent(this.opts.project.configuration,e)})`);let n=a.get(s);if(!n)throw new Error(`Assertion failed: The package information should have been available (for ${G.prettyLocator(this.opts.project.configuration,e)})`);return n}getDiskInformation(e){let r=je.getMapWithDefault(this.packageRegistry,"@@disk"),s=CY(this.opts.project.cwd,e);return je.getFactoryWithDefault(r,s,()=>({packageLocation:s,packageDependencies:new Map,packagePeers:new Set,linkType:"SOFT",discardFromLookup:!1}))}};function CY(t,e){let r=J.relative(t,e);return r.match(/^\.{0,2}\//)||(r=`./${r}`),r.replace(/\/?$/,"/")}async function mdt(t){let e=await Ut.tryFind(t.prefixPath,{baseFs:t.packageFs})??new Ut,r=new Set(["preinstall","install","postinstall"]);for(let s of e.scripts.keys())r.has(s)||e.scripts.delete(s);return{manifest:{scripts:e.scripts,preferUnplugged:e.preferUnplugged,type:e.type},misc:{extractHint:EY(t),hasBindingGyp:IY(t)}}}Ge();Ge();Yt();var xBe=ut(Go());var Sw=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Unplug direct dependencies from the entire project"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Unplug both direct and transitive dependencies"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.patterns=ge.Rest()}static{this.paths=[["unplug"]]}static{this.usage=ot.Usage({description:"force the unpacking of a list of packages",details:"\n This command will add the selectors matching the specified patterns to the list of packages that must be unplugged when installed.\n\n A package being unplugged means that instead of being referenced directly through its archive, it will be unpacked at install time in the directory configured via `pnpUnpluggedFolder`. Note that unpacking packages this way is generally not recommended because it'll make it harder to store your packages within the repository. However, it's a good approach to quickly and safely debug some packages, and can even sometimes be required depending on the context (for example when the package contains shellscripts).\n\n Running the command will set a persistent flag inside your top-level `package.json`, in the `dependenciesMeta` field. As such, to undo its effects, you'll need to revert the changes made to the manifest and run `yarn install` to apply the modification.\n\n By default, only direct dependencies from the current workspace are affected. If `-A,--all` is set, direct dependencies from the entire project are affected. Using the `-R,--recursive` flag will affect transitive dependencies as well as direct ones.\n\n This command accepts glob patterns inside the scope and name components (not the range). Make sure to escape the patterns to prevent your own shell from trying to expand them.\n ",examples:[["Unplug the lodash dependency from the active workspace","yarn unplug lodash"],["Unplug all instances of lodash referenced by any workspace","yarn unplug lodash -A"],["Unplug all instances of lodash referenced by the active workspace and its dependencies","yarn unplug lodash -R"],["Unplug all instances of lodash, anywhere","yarn unplug lodash -AR"],["Unplug one specific version of lodash","yarn unplug lodash@1.2.3"],["Unplug all packages with the `@babel` scope","yarn unplug '@babel/*'"],["Unplug all packages (only for testing, not recommended)","yarn unplug -R '*'"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);if(r.get("nodeLinker")!=="pnp")throw new nt("This command can only be used if the `nodeLinker` option is set to `pnp`");await s.restoreInstallState();let c=new Set(this.patterns),f=this.patterns.map(P=>{let I=G.parseDescriptor(P),R=I.range!=="unknown"?I:G.makeDescriptor(I,"*");if(!Fr.validRange(R.range))throw new nt(`The range of the descriptor patterns must be a valid semver range (${G.prettyDescriptor(r,R)})`);return N=>{let U=G.stringifyIdent(N);return!xBe.default.isMatch(U,G.stringifyIdent(R))||N.version&&!Fr.satisfiesWithPrereleases(N.version,R.range)?!1:(c.delete(P),!0)}}),p=()=>{let P=[];for(let I of s.storedPackages.values())!s.tryWorkspaceByLocator(I)&&!G.isVirtualLocator(I)&&f.some(R=>R(I))&&P.push(I);return P},h=P=>{let I=new Set,R=[],N=(U,W)=>{if(I.has(U.locatorHash))return;let ee=!!s.tryWorkspaceByLocator(U);if(!(W>0&&!this.recursive&&ee)&&(I.add(U.locatorHash),!s.tryWorkspaceByLocator(U)&&f.some(ie=>ie(U))&&R.push(U),!(W>0&&!this.recursive)))for(let ie of U.dependencies.values()){let ue=s.storedResolutions.get(ie.descriptorHash);if(!ue)throw new Error("Assertion failed: The resolution should have been registered");let le=s.storedPackages.get(ue);if(!le)throw new Error("Assertion failed: The package should have been registered");N(le,W+1)}};for(let U of P)N(U.anchoredPackage,0);return R},E,C;if(this.all&&this.recursive?(E=p(),C="the project"):this.all?(E=h(s.workspaces),C="any workspace"):(E=h([a]),C="this workspace"),c.size>1)throw new nt(`Patterns ${he.prettyList(r,c,he.Type.CODE)} don't match any packages referenced by ${C}`);if(c.size>0)throw new nt(`Pattern ${he.prettyList(r,c,he.Type.CODE)} doesn't match any packages referenced by ${C}`);E=je.sortMap(E,P=>G.stringifyLocator(P));let S=await Ot.start({configuration:r,stdout:this.context.stdout,json:this.json},async P=>{for(let I of E){let R=I.version??"unknown",N=s.topLevelWorkspace.manifest.ensureDependencyMeta(G.makeDescriptor(I,R));N.unplugged=!0,P.reportInfo(0,`Will unpack ${G.prettyLocator(r,I)} to ${he.pretty(r,_D(I,{configuration:r}),he.Type.PATH)}`),P.reportJson({locator:G.stringifyLocator(I),version:R})}await s.topLevelWorkspace.persistManifest(),this.json||P.reportSeparator()});return S.hasErrors()?S.exitCode():await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n})}};var og=t=>({cjs:J.join(t.cwd,Er.pnpCjs),data:J.join(t.cwd,Er.pnpData),esmLoader:J.join(t.cwd,Er.pnpEsmLoader)}),QBe=t=>/\s/.test(t)?JSON.stringify(t):t;async function ydt(t,e,r){let s=/\s*--require\s+\S*\.pnp\.c?js\s*/g,a=/\s*--experimental-loader\s+\S*\.pnp\.loader\.mjs\s*/,n=(e.NODE_OPTIONS??"").replace(s," ").replace(a," ").trim();if(t.configuration.get("nodeLinker")!=="pnp"){e.NODE_OPTIONS=n||void 0;return}let c=og(t),f=`--require ${QBe(fe.fromPortablePath(c.cjs))}`;ce.existsSync(c.esmLoader)&&(f=`${f} --experimental-loader ${(0,kBe.pathToFileURL)(fe.fromPortablePath(c.esmLoader)).href}`),ce.existsSync(c.cjs)&&(e.NODE_OPTIONS=n?`${f} ${n}`:f)}async function Edt(t,e){let r=og(t);e(r.cjs),e(r.data),e(r.esmLoader),e(t.configuration.get("pnpUnpluggedFolder"))}var Idt={hooks:{populateYarnPaths:Edt,setupScriptEnvironment:ydt},configuration:{nodeLinker:{description:'The linker used for installing Node packages, one of: "pnp", "pnpm", or "node-modules"',type:"STRING",default:"pnp"},minizip:{description:"Whether Yarn should use minizip to extract archives",type:"BOOLEAN",default:!1},winLinkType:{description:"Whether Yarn should use Windows Junctions or symlinks when creating links on Windows.",type:"STRING",values:["junctions","symlinks"],default:"junctions"},pnpMode:{description:"If 'strict', generates standard PnP maps. If 'loose', merges them with the n_m resolution.",type:"STRING",default:"strict"},pnpShebang:{description:"String to prepend to the generated PnP script",type:"STRING",default:"#!/usr/bin/env node"},pnpIgnorePatterns:{description:"Array of glob patterns; files matching them will use the classic resolution",type:"STRING",default:[],isArray:!0},pnpZipBackend:{description:"Whether to use the experimental js implementation for the ZipFS",type:"STRING",values:["libzip","js"],default:"libzip"},pnpEnableEsmLoader:{description:"If true, Yarn will generate an ESM loader (`.pnp.loader.mjs`). If this is not explicitly set Yarn tries to automatically detect whether ESM support is required.",type:"BOOLEAN",default:!1},pnpEnableInlining:{description:"If true, the PnP data will be inlined along with the generated loader",type:"BOOLEAN",default:!0},pnpFallbackMode:{description:"If true, the generated PnP loader will follow the top-level fallback rule",type:"STRING",default:"dependencies-only"},pnpUnpluggedFolder:{description:"Folder where the unplugged packages must be stored",type:"ABSOLUTE_PATH",default:"./.yarn/unplugged"}},linkers:[sg],commands:[Sw]},Cdt=Idt;var UBe=ut(OBe());Yt();var xY=ut(Ie("crypto")),_Be=ut(Ie("fs")),HBe=1,Ti="node_modules",aN=".bin",jBe=".yarn-state.yml",Mdt=1e3,kY=(s=>(s.CLASSIC="classic",s.HARDLINKS_LOCAL="hardlinks-local",s.HARDLINKS_GLOBAL="hardlinks-global",s))(kY||{}),jD=class{constructor(){this.installStateCache=new Map}getCustomDataKey(){return JSON.stringify({name:"NodeModulesLinker",version:3})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error("Assertion failed: Expected the node-modules linker to be enabled");let s=r.project.tryWorkspaceByLocator(e);if(s)return s.cwd;let a=await je.getFactoryWithDefault(this.installStateCache,r.project.cwd,async()=>await PY(r.project,{unrollAliases:!0}));if(a===null)throw new nt("Couldn't find the node_modules state file - running an install might help (findPackageLocation)");let n=a.locatorMap.get(G.stringifyLocator(e));if(!n){let p=new nt(`Couldn't find ${G.prettyLocator(r.project.configuration,e)} in the currently installed node_modules map - running an install might help`);throw p.code="LOCATOR_NOT_INSTALLED",p}let c=n.locations.sort((p,h)=>p.split(J.sep).length-h.split(J.sep).length),f=J.join(r.project.configuration.startingCwd,Ti);return c.find(p=>J.contains(f,p))||n.locations[0]}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=await je.getFactoryWithDefault(this.installStateCache,r.project.cwd,async()=>await PY(r.project,{unrollAliases:!0}));if(s===null)return null;let{locationRoot:a,segments:n}=lN(J.resolve(e),{skipPrefix:r.project.cwd}),c=s.locationTree.get(a);if(!c)return null;let f=c.locator;for(let p of n){if(c=c.children.get(p),!c)break;f=c.locator||f}return G.parseLocator(f)}makeInstaller(e){return new bY(e)}isEnabled(e){return e.project.configuration.get("nodeLinker")==="node-modules"}},bY=class{constructor(e){this.opts=e;this.localStore=new Map;this.realLocatorChecksums=new Map;this.customData={store:new Map}}attachCustomData(e){this.customData=e}async installPackage(e,r){let s=J.resolve(r.packageFs.getRealPath(),r.prefixPath),a=this.customData.store.get(e.locatorHash);if(typeof a>"u"&&(a=await Udt(e,r),e.linkType==="HARD"&&this.customData.store.set(e.locatorHash,a)),!G.isPackageCompatible(e,this.opts.project.configuration.getSupportedArchitectures()))return{packageLocation:null,buildRequest:null};let n=new Map,c=new Set;n.has(G.stringifyIdent(e))||n.set(G.stringifyIdent(e),e.reference);let f=e;if(G.isVirtualLocator(e)){f=G.devirtualizeLocator(e);for(let E of e.peerDependencies.values())n.set(G.stringifyIdent(E),null),c.add(G.stringifyIdent(E))}let p={packageLocation:`${fe.fromPortablePath(s)}/`,packageDependencies:n,packagePeers:c,linkType:e.linkType,discardFromLookup:r.discardFromLookup??!1};this.localStore.set(e.locatorHash,{pkg:e,customPackageData:a,dependencyMeta:this.opts.project.getDependencyMeta(e,e.version),pnpNode:p});let h=r.checksum?r.checksum.substring(r.checksum.indexOf("/")+1):null;return this.realLocatorChecksums.set(f.locatorHash,h),{packageLocation:s,buildRequest:null}}async attachInternalDependencies(e,r){let s=this.localStore.get(e.locatorHash);if(typeof s>"u")throw new Error("Assertion failed: Expected information object to have been registered");for(let[a,n]of r){let c=G.areIdentsEqual(a,n)?n.reference:[G.stringifyIdent(n),n.reference];s.pnpNode.packageDependencies.set(G.stringifyIdent(a),c)}}async attachExternalDependents(e,r){throw new Error("External dependencies haven't been implemented for the node-modules linker")}async finalizeInstall(){if(this.opts.project.configuration.get("nodeLinker")!=="node-modules")return;let e=new uo({baseFs:new $f({maxOpenFiles:80,readOnlyArchives:!0})}),r=await PY(this.opts.project),s=this.opts.project.configuration.get("nmMode");(r===null||s!==r.nmMode)&&(this.opts.project.storedBuildState.clear(),r={locatorMap:new Map,binSymlinks:new Map,locationTree:new Map,nmMode:s,mtimeMs:0});let a=new Map(this.opts.project.workspaces.map(S=>{let P=this.opts.project.configuration.get("nmHoistingLimits");try{P=je.validateEnum(xD,S.manifest.installConfig?.hoistingLimits??P)}catch{let I=G.prettyWorkspace(this.opts.project.configuration,S);this.opts.report.reportWarning(57,`${I}: Invalid 'installConfig.hoistingLimits' value. Expected one of ${Object.values(xD).join(", ")}, using default: "${P}"`)}return[S.relativeCwd,P]})),n=new Map(this.opts.project.workspaces.map(S=>{let P=this.opts.project.configuration.get("nmSelfReferences");return P=S.manifest.installConfig?.selfReferences??P,[S.relativeCwd,P]})),c={VERSIONS:{std:1},topLevel:{name:null,reference:null},getLocator:(S,P)=>Array.isArray(P)?{name:P[0],reference:P[1]}:{name:S,reference:P},getDependencyTreeRoots:()=>this.opts.project.workspaces.map(S=>{let P=S.anchoredLocator;return{name:G.stringifyIdent(P),reference:P.reference}}),getPackageInformation:S=>{let P=S.reference===null?this.opts.project.topLevelWorkspace.anchoredLocator:G.makeLocator(G.parseIdent(S.name),S.reference),I=this.localStore.get(P.locatorHash);if(typeof I>"u")throw new Error("Assertion failed: Expected the package reference to have been registered");return I.pnpNode},findPackageLocator:S=>{let P=this.opts.project.tryWorkspaceByCwd(fe.toPortablePath(S));if(P!==null){let I=P.anchoredLocator;return{name:G.stringifyIdent(I),reference:I.reference}}throw new Error("Assertion failed: Unimplemented")},resolveToUnqualified:()=>{throw new Error("Assertion failed: Unimplemented")},resolveUnqualified:()=>{throw new Error("Assertion failed: Unimplemented")},resolveRequest:()=>{throw new Error("Assertion failed: Unimplemented")},resolveVirtual:S=>fe.fromPortablePath(uo.resolveVirtual(fe.toPortablePath(S)))},{tree:f,errors:p,preserveSymlinksRequired:h}=kD(c,{pnpifyFs:!1,validateExternalSoftLinks:!0,hoistingLimitsByCwd:a,project:this.opts.project,selfReferencesByCwd:n});if(!f){for(let{messageName:S,text:P}of p)this.opts.report.reportError(S,P);return}let E=nY(f);await Ydt(r,E,{baseFs:e,project:this.opts.project,report:this.opts.report,realLocatorChecksums:this.realLocatorChecksums,loadManifest:async S=>{let P=G.parseLocator(S),I=this.localStore.get(P.locatorHash);if(typeof I>"u")throw new Error("Assertion failed: Expected the slot to exist");return I.customPackageData.manifest}});let C=[];for(let[S,P]of E.entries()){if(WBe(S))continue;let I=G.parseLocator(S),R=this.localStore.get(I.locatorHash);if(typeof R>"u")throw new Error("Assertion failed: Expected the slot to exist");if(this.opts.project.tryWorkspaceByLocator(R.pkg))continue;let N=gA.extractBuildRequest(R.pkg,R.customPackageData,R.dependencyMeta,{configuration:this.opts.project.configuration});N&&C.push({buildLocations:P.locations,locator:I,buildRequest:N})}return h&&this.opts.report.reportWarning(72,`The application uses portals and that's why ${he.pretty(this.opts.project.configuration,"--preserve-symlinks",he.Type.CODE)} Node option is required for launching it`),{customData:this.customData,records:C}}};async function Udt(t,e){let r=await Ut.tryFind(e.prefixPath,{baseFs:e.packageFs})??new Ut,s=new Set(["preinstall","install","postinstall"]);for(let a of r.scripts.keys())s.has(a)||r.scripts.delete(a);return{manifest:{bin:r.bin,scripts:r.scripts},misc:{hasBindingGyp:gA.hasBindingGyp(e)}}}async function _dt(t,e,r,s,{installChangedByUser:a}){let n="";n+=`# Warning: This file is automatically generated. Removing it is fine, but will `,n+=`# cause your node_modules installation to become invalidated. `,n+=` `,n+=`__metadata: `,n+=` version: ${HBe} `,n+=` nmMode: ${s.value} `;let c=Array.from(e.keys()).sort(),f=G.stringifyLocator(t.topLevelWorkspace.anchoredLocator);for(let E of c){let C=e.get(E);n+=` `,n+=`${JSON.stringify(E)}: `,n+=` locations: `;for(let S of C.locations){let P=J.contains(t.cwd,S);if(P===null)throw new Error(`Assertion failed: Expected the path to be within the project (${S})`);n+=` - ${JSON.stringify(P)} `}if(C.aliases.length>0){n+=` aliases: `;for(let S of C.aliases)n+=` - ${JSON.stringify(S)} `}if(E===f&&r.size>0){n+=` bin: `;for(let[S,P]of r){let I=J.contains(t.cwd,S);if(I===null)throw new Error(`Assertion failed: Expected the path to be within the project (${S})`);n+=` ${JSON.stringify(I)}: `;for(let[R,N]of P){let U=J.relative(J.join(S,Ti),N);n+=` ${JSON.stringify(R)}: ${JSON.stringify(U)} `}}}}let p=t.cwd,h=J.join(p,Ti,jBe);a&&await ce.removePromise(h),await ce.changeFilePromise(h,n,{automaticNewlines:!0})}async function PY(t,{unrollAliases:e=!1}={}){let r=t.cwd,s=J.join(r,Ti,jBe),a;try{a=await ce.statPromise(s)}catch{}if(!a)return null;let n=ls(await ce.readFilePromise(s,"utf8"));if(n.__metadata.version>HBe)return null;let c=n.__metadata.nmMode||"classic",f=new Map,p=new Map;delete n.__metadata;for(let[h,E]of Object.entries(n)){let C=E.locations.map(P=>J.join(r,P)),S=E.bin;if(S)for(let[P,I]of Object.entries(S)){let R=J.join(r,fe.toPortablePath(P)),N=je.getMapWithDefault(p,R);for(let[U,W]of Object.entries(I))N.set(U,fe.toPortablePath([R,Ti,W].join(J.sep)))}if(f.set(h,{target:vt.dot,linkType:"HARD",locations:C,aliases:E.aliases||[]}),e&&E.aliases)for(let P of E.aliases){let{scope:I,name:R}=G.parseLocator(h),N=G.makeLocator(G.makeIdent(I,R),P),U=G.stringifyLocator(N);f.set(U,{target:vt.dot,linkType:"HARD",locations:C,aliases:[]})}}return{locatorMap:f,binSymlinks:p,locationTree:GBe(f,{skipPrefix:t.cwd}),nmMode:c,mtimeMs:a.mtimeMs}}var bw=async(t,e)=>{if(t.split(J.sep).indexOf(Ti)<0)throw new Error(`Assertion failed: trying to remove dir that doesn't contain node_modules: ${t}`);try{let r;if(!e.innerLoop&&(r=await ce.lstatPromise(t),!r.isDirectory()&&!r.isSymbolicLink()||r.isSymbolicLink()&&!e.isWorkspaceDir)){await ce.unlinkPromise(t);return}let s=await ce.readdirPromise(t,{withFileTypes:!0});for(let n of s){let c=J.join(t,n.name);n.isDirectory()?(n.name!==Ti||e&&e.innerLoop)&&await bw(c,{innerLoop:!0,contentsOnly:!1}):await ce.unlinkPromise(c)}let a=!e.innerLoop&&e.isWorkspaceDir&&r?.isSymbolicLink();!e.contentsOnly&&!a&&await ce.rmdirPromise(t)}catch(r){if(r.code!=="ENOENT"&&r.code!=="ENOTEMPTY")throw r}},LBe=4,lN=(t,{skipPrefix:e})=>{let r=J.contains(e,t);if(r===null)throw new Error(`Assertion failed: Writing attempt prevented to ${t} which is outside project root: ${e}`);let s=r.split(J.sep).filter(p=>p!==""),a=s.indexOf(Ti),n=s.slice(0,a).join(J.sep),c=J.join(e,n),f=s.slice(a);return{locationRoot:c,segments:f}},GBe=(t,{skipPrefix:e})=>{let r=new Map;if(t===null)return r;let s=()=>({children:new Map,linkType:"HARD"});for(let[a,n]of t.entries()){if(n.linkType==="SOFT"&&J.contains(e,n.target)!==null){let f=je.getFactoryWithDefault(r,n.target,s);f.locator=a,f.linkType=n.linkType}for(let c of n.locations){let{locationRoot:f,segments:p}=lN(c,{skipPrefix:e}),h=je.getFactoryWithDefault(r,f,s);for(let E=0;E{if(process.platform==="win32"&&r==="junctions"){let s;try{s=await ce.lstatPromise(t)}catch{}if(!s||s.isDirectory()){await ce.symlinkPromise(t,e,"junction");return}}await ce.symlinkPromise(J.relative(J.dirname(e),t),e)};async function qBe(t,e,r){let s=J.join(t,`${xY.default.randomBytes(16).toString("hex")}.tmp`);try{await ce.writeFilePromise(s,r);try{await ce.linkPromise(s,e)}catch{}}finally{await ce.unlinkPromise(s)}}async function Hdt({srcPath:t,dstPath:e,entry:r,globalHardlinksStore:s,baseFs:a,nmMode:n}){if(r.kind==="file"){if(n.value==="hardlinks-global"&&s&&r.digest){let f=J.join(s,r.digest.substring(0,2),`${r.digest.substring(2)}.dat`),p;try{let h=await ce.statPromise(f);if(h&&(!r.mtimeMs||h.mtimeMs>r.mtimeMs||h.mtimeMs{await ce.mkdirPromise(t,{recursive:!0});let f=async(E=vt.dot)=>{let C=J.join(e,E),S=await r.readdirPromise(C,{withFileTypes:!0}),P=new Map;for(let I of S){let R=J.join(E,I.name),N,U=J.join(C,I.name);if(I.isFile()){if(N={kind:"file",mode:(await r.lstatPromise(U)).mode},a.value==="hardlinks-global"){let W=await Nn.checksumFile(U,{baseFs:r,algorithm:"sha1"});N.digest=W}}else if(I.isDirectory())N={kind:"directory"};else if(I.isSymbolicLink())N={kind:"symlink",symlinkTo:await r.readlinkPromise(U)};else throw new Error(`Unsupported file type (file: ${U}, mode: 0o${await r.statSync(U).mode.toString(8).padStart(6,"0")})`);if(P.set(R,N),I.isDirectory()&&R!==Ti){let W=await f(R);for(let[ee,ie]of W)P.set(ee,ie)}}return P},p;if(a.value==="hardlinks-global"&&s&&c){let E=J.join(s,c.substring(0,2),`${c.substring(2)}.json`);try{p=new Map(Object.entries(JSON.parse(await ce.readFilePromise(E,"utf8"))))}catch{p=await f()}}else p=await f();let h=!1;for(let[E,C]of p){let S=J.join(e,E),P=J.join(t,E);if(C.kind==="directory")await ce.mkdirPromise(P,{recursive:!0});else if(C.kind==="file"){let I=C.mtimeMs;await Hdt({srcPath:S,dstPath:P,entry:C,nmMode:a,baseFs:r,globalHardlinksStore:s}),C.mtimeMs!==I&&(h=!0)}else C.kind==="symlink"&&await QY(J.resolve(J.dirname(P),C.symlinkTo),P,n)}if(a.value==="hardlinks-global"&&s&&h&&c){let E=J.join(s,c.substring(0,2),`${c.substring(2)}.json`);await ce.removePromise(E),await qBe(s,E,Buffer.from(JSON.stringify(Object.fromEntries(p))))}};function Gdt(t,e,r,s){let a=new Map,n=new Map,c=new Map,f=!1,p=(h,E,C,S,P)=>{let I=!0,R=J.join(h,E),N=new Set;if(E===Ti||E.startsWith("@")){let W;try{W=ce.statSync(R)}catch{}I=!!W,W?W.mtimeMs>r?(f=!0,N=new Set(ce.readdirSync(R))):N=new Set(C.children.get(E).children.keys()):f=!0;let ee=e.get(h);if(ee){let ie=J.join(h,Ti,aN),ue;try{ue=ce.statSync(ie)}catch{}if(!ue)f=!0;else if(ue.mtimeMs>r){f=!0;let le=new Set(ce.readdirSync(ie)),me=new Map;n.set(h,me);for(let[pe,Be]of ee)le.has(pe)&&me.set(pe,Be)}else n.set(h,ee)}}else I=P.has(E);let U=C.children.get(E);if(I){let{linkType:W,locator:ee}=U,ie={children:new Map,linkType:W,locator:ee};if(S.children.set(E,ie),ee){let ue=je.getSetWithDefault(c,ee);ue.add(R),c.set(ee,ue)}for(let ue of U.children.keys())p(R,ue,U,ie,N)}else U.locator&&s.storedBuildState.delete(G.parseLocator(U.locator).locatorHash)};for(let[h,E]of t){let{linkType:C,locator:S}=E,P={children:new Map,linkType:C,locator:S};if(a.set(h,P),S){let I=je.getSetWithDefault(c,E.locator);I.add(h),c.set(E.locator,I)}E.children.has(Ti)&&p(h,Ti,E,P,new Set)}return{locationTree:a,binSymlinks:n,locatorLocations:c,installChangedByUser:f}}function WBe(t){let e=G.parseDescriptor(t);return G.isVirtualDescriptor(e)&&(e=G.devirtualizeDescriptor(e)),e.range.startsWith("link:")}async function qdt(t,e,r,{loadManifest:s}){let a=new Map;for(let[f,{locations:p}]of t){let h=WBe(f)?null:await s(f,p[0]),E=new Map;if(h)for(let[C,S]of h.bin){let P=J.join(p[0],S);S!==""&&ce.existsSync(P)&&E.set(C,S)}a.set(f,E)}let n=new Map,c=(f,p,h)=>{let E=new Map,C=J.contains(r,f);if(h.locator&&C!==null){let S=a.get(h.locator);for(let[P,I]of S){let R=J.join(f,fe.toPortablePath(I));E.set(P,R)}for(let[P,I]of h.children){let R=J.join(f,P),N=c(R,R,I);N.size>0&&n.set(f,new Map([...n.get(f)||new Map,...N]))}}else for(let[S,P]of h.children){let I=c(J.join(f,S),p,P);for(let[R,N]of I)E.set(R,N)}return E};for(let[f,p]of e){let h=c(f,f,p);h.size>0&&n.set(f,new Map([...n.get(f)||new Map,...h]))}return n}var MBe=(t,e)=>{if(!t||!e)return t===e;let r=G.parseLocator(t);G.isVirtualLocator(r)&&(r=G.devirtualizeLocator(r));let s=G.parseLocator(e);return G.isVirtualLocator(s)&&(s=G.devirtualizeLocator(s)),G.areLocatorsEqual(r,s)};function TY(t){return J.join(t.get("globalFolder"),"store")}function Wdt(t,e){let r=s=>{let a=s.split(J.sep),n=a.lastIndexOf(Ti);if(n<0||n==a.length-1)throw new Error(`Assertion failed. Path is outside of any node_modules package ${s}`);return a.slice(0,n+(a[n+1].startsWith("@")?3:2)).join(J.sep)};for(let s of t.values())for(let[a,n]of s)e.has(r(n))&&s.delete(a)}async function Ydt(t,e,{baseFs:r,project:s,report:a,loadManifest:n,realLocatorChecksums:c}){let f=J.join(s.cwd,Ti),{locationTree:p,binSymlinks:h,locatorLocations:E,installChangedByUser:C}=Gdt(t.locationTree,t.binSymlinks,t.mtimeMs,s),S=GBe(e,{skipPrefix:s.cwd}),P=[],I=async({srcDir:Be,dstDir:Ce,linkType:g,globalHardlinksStore:we,nmMode:ye,windowsLinkType:Ae,packageChecksum:se})=>{let Z=(async()=>{try{g==="SOFT"?(await ce.mkdirPromise(J.dirname(Ce),{recursive:!0}),await QY(J.resolve(Be),Ce,Ae)):await jdt(Ce,Be,{baseFs:r,globalHardlinksStore:we,nmMode:ye,windowsLinkType:Ae,packageChecksum:se})}catch(De){throw De.message=`While persisting ${Be} -> ${Ce} ${De.message}`,De}finally{ie.tick()}})().then(()=>P.splice(P.indexOf(Z),1));P.push(Z),P.length>LBe&&await Promise.race(P)},R=async(Be,Ce,g)=>{let we=(async()=>{let ye=async(Ae,se,Z)=>{try{Z.innerLoop||await ce.mkdirPromise(se,{recursive:!0});let De=await ce.readdirPromise(Ae,{withFileTypes:!0});for(let Re of De){if(!Z.innerLoop&&Re.name===aN)continue;let mt=J.join(Ae,Re.name),j=J.join(se,Re.name);Re.isDirectory()?(Re.name!==Ti||Z&&Z.innerLoop)&&(await ce.mkdirPromise(j,{recursive:!0}),await ye(mt,j,{...Z,innerLoop:!0})):me.value==="hardlinks-local"||me.value==="hardlinks-global"?await ce.linkPromise(mt,j):await ce.copyFilePromise(mt,j,_Be.default.constants.COPYFILE_FICLONE)}}catch(De){throw Z.innerLoop||(De.message=`While cloning ${Ae} -> ${se} ${De.message}`),De}finally{Z.innerLoop||ie.tick()}};await ye(Be,Ce,g)})().then(()=>P.splice(P.indexOf(we),1));P.push(we),P.length>LBe&&await Promise.race(P)},N=async(Be,Ce,g)=>{if(g)for(let[we,ye]of Ce.children){let Ae=g.children.get(we);await N(J.join(Be,we),ye,Ae)}else{Ce.children.has(Ti)&&await bw(J.join(Be,Ti),{contentsOnly:!1});let we=J.basename(Be)===Ti&&p.has(J.join(J.dirname(Be)));await bw(Be,{contentsOnly:Be===f,isWorkspaceDir:we})}};for(let[Be,Ce]of p){let g=S.get(Be);for(let[we,ye]of Ce.children){if(we===".")continue;let Ae=g&&g.children.get(we),se=J.join(Be,we);await N(se,ye,Ae)}}let U=async(Be,Ce,g)=>{if(g){MBe(Ce.locator,g.locator)||await bw(Be,{contentsOnly:Ce.linkType==="HARD"});for(let[we,ye]of Ce.children){let Ae=g.children.get(we);await U(J.join(Be,we),ye,Ae)}}else{Ce.children.has(Ti)&&await bw(J.join(Be,Ti),{contentsOnly:!0});let we=J.basename(Be)===Ti&&S.has(J.join(J.dirname(Be)));await bw(Be,{contentsOnly:Ce.linkType==="HARD",isWorkspaceDir:we})}};for(let[Be,Ce]of S){let g=p.get(Be);for(let[we,ye]of Ce.children){if(we===".")continue;let Ae=g&&g.children.get(we);await U(J.join(Be,we),ye,Ae)}}let W=new Map,ee=[];for(let[Be,Ce]of E)for(let g of Ce){let{locationRoot:we,segments:ye}=lN(g,{skipPrefix:s.cwd}),Ae=S.get(we),se=we;if(Ae){for(let Z of ye)if(se=J.join(se,Z),Ae=Ae.children.get(Z),!Ae)break;if(Ae){let Z=MBe(Ae.locator,Be),De=e.get(Ae.locator),Re=De.target,mt=se,j=De.linkType;if(Z)W.has(Re)||W.set(Re,mt);else if(Re!==mt){let rt=G.parseLocator(Ae.locator);G.isVirtualLocator(rt)&&(rt=G.devirtualizeLocator(rt)),ee.push({srcDir:Re,dstDir:mt,linkType:j,realLocatorHash:rt.locatorHash})}}}}for(let[Be,{locations:Ce}]of e.entries())for(let g of Ce){let{locationRoot:we,segments:ye}=lN(g,{skipPrefix:s.cwd}),Ae=p.get(we),se=S.get(we),Z=we,De=e.get(Be),Re=G.parseLocator(Be);G.isVirtualLocator(Re)&&(Re=G.devirtualizeLocator(Re));let mt=Re.locatorHash,j=De.target,rt=g;if(j===rt)continue;let Fe=De.linkType;for(let Ne of ye)se=se.children.get(Ne);if(!Ae)ee.push({srcDir:j,dstDir:rt,linkType:Fe,realLocatorHash:mt});else for(let Ne of ye)if(Z=J.join(Z,Ne),Ae=Ae.children.get(Ne),!Ae){ee.push({srcDir:j,dstDir:rt,linkType:Fe,realLocatorHash:mt});break}}let ie=Ao.progressViaCounter(ee.length),ue=a.reportProgress(ie),le=s.configuration.get("nmMode"),me={value:le},pe=s.configuration.get("winLinkType");try{let Be=me.value==="hardlinks-global"?`${TY(s.configuration)}/v1`:null;if(Be&&!await ce.existsPromise(Be)){await ce.mkdirpPromise(Be);for(let g=0;g<256;g++)await ce.mkdirPromise(J.join(Be,g.toString(16).padStart(2,"0")))}for(let g of ee)(g.linkType==="SOFT"||!W.has(g.srcDir))&&(W.set(g.srcDir,g.dstDir),await I({...g,globalHardlinksStore:Be,nmMode:me,windowsLinkType:pe,packageChecksum:c.get(g.realLocatorHash)||null}));await Promise.all(P),P.length=0;for(let g of ee){let we=W.get(g.srcDir);g.linkType!=="SOFT"&&g.dstDir!==we&&await R(we,g.dstDir,{nmMode:me})}await Promise.all(P),await ce.mkdirPromise(f,{recursive:!0}),Wdt(h,new Set(ee.map(g=>g.dstDir)));let Ce=await qdt(e,S,s.cwd,{loadManifest:n});await Vdt(h,Ce,s.cwd,pe),await _dt(s,e,Ce,me,{installChangedByUser:C}),le=="hardlinks-global"&&me.value=="hardlinks-local"&&a.reportWarningOnce(74,"'nmMode' has been downgraded to 'hardlinks-local' due to global cache and install folder being on different devices")}finally{ue.stop()}}async function Vdt(t,e,r,s){for(let a of t.keys()){if(J.contains(r,a)===null)throw new Error(`Assertion failed. Excepted bin symlink location to be inside project dir, instead it was at ${a}`);if(!e.has(a)){let n=J.join(a,Ti,aN);await ce.removePromise(n)}}for(let[a,n]of e){if(J.contains(r,a)===null)throw new Error(`Assertion failed. Excepted bin symlink location to be inside project dir, instead it was at ${a}`);let c=J.join(a,Ti,aN),f=t.get(a)||new Map;await ce.mkdirPromise(c,{recursive:!0});for(let p of f.keys())n.has(p)||(await ce.removePromise(J.join(c,p)),process.platform==="win32"&&await ce.removePromise(J.join(c,`${p}.cmd`)));for(let[p,h]of n){let E=f.get(p),C=J.join(c,p);E!==h&&(process.platform==="win32"?await(0,UBe.default)(fe.fromPortablePath(h),fe.fromPortablePath(C),{createPwshFile:!1}):(await ce.removePromise(C),await QY(h,C,s),J.contains(r,await ce.realpathPromise(h))!==null&&await ce.chmodPromise(h,493)))}}}Ge();Dt();eA();var GD=class extends sg{constructor(){super(...arguments);this.mode="loose"}makeInstaller(r){return new RY(r)}},RY=class extends Gm{constructor(){super(...arguments);this.mode="loose"}async transformPnpSettings(r){let s=new uo({baseFs:new $f({maxOpenFiles:80,readOnlyArchives:!0})}),a=SBe(r,this.opts.project.cwd,s),{tree:n,errors:c}=kD(a,{pnpifyFs:!1,project:this.opts.project});if(!n){for(let{messageName:C,text:S}of c)this.opts.report.reportError(C,S);return}let f=new Map;r.fallbackPool=f;let p=(C,S)=>{let P=G.parseLocator(S.locator),I=G.stringifyIdent(P);I===C?f.set(C,P.reference):f.set(C,[I,P.reference])},h=J.join(this.opts.project.cwd,Er.nodeModules),E=n.get(h);if(!(typeof E>"u")){if("target"in E)throw new Error("Assertion failed: Expected the root junction point to be a directory");for(let C of E.dirList){let S=J.join(h,C),P=n.get(S);if(typeof P>"u")throw new Error("Assertion failed: Expected the child to have been registered");if("target"in P)p(C,P);else for(let I of P.dirList){let R=J.join(S,I),N=n.get(R);if(typeof N>"u")throw new Error("Assertion failed: Expected the subchild to have been registered");if("target"in N)p(`${C}/${I}`,N);else throw new Error("Assertion failed: Expected the leaf junction to be a package")}}}}};var Jdt={hooks:{cleanGlobalArtifacts:async t=>{let e=TY(t);await ce.removePromise(e)}},configuration:{nmHoistingLimits:{description:"Prevents packages to be hoisted past specific levels",type:"STRING",values:["workspaces","dependencies","none"],default:"none"},nmMode:{description:"Defines in which measure Yarn must use hardlinks and symlinks when generated `node_modules` directories.",type:"STRING",values:["classic","hardlinks-local","hardlinks-global"],default:"classic"},nmSelfReferences:{description:"Defines whether the linker should generate self-referencing symlinks for workspaces.",type:"BOOLEAN",default:!0}},linkers:[jD,GD]},Kdt=Jdt;var FK={};Vt(FK,{NpmHttpFetcher:()=>VD,NpmRemapResolver:()=>JD,NpmSemverFetcher:()=>oh,NpmSemverResolver:()=>KD,NpmTagResolver:()=>zD,default:()=>ubt,npmConfigUtils:()=>hi,npmHttpUtils:()=>en,npmPublishUtils:()=>v1});Ge();var $Be=ut(Ai());var oi="npm:";var en={};Vt(en,{AuthType:()=>zBe,customPackageError:()=>qm,del:()=>Amt,get:()=>Wm,getIdentUrl:()=>WD,getPackageMetadata:()=>Qw,handleInvalidAuthenticationError:()=>ag,post:()=>umt,put:()=>fmt});Ge();Ge();Dt();var LY=ut(Vv());ql();var KBe=ut(Ai());var hi={};Vt(hi,{RegistryType:()=>VBe,getAuditRegistry:()=>zdt,getAuthConfiguration:()=>OY,getDefaultRegistry:()=>qD,getPublishRegistry:()=>Xdt,getRegistryConfiguration:()=>JBe,getScopeConfiguration:()=>NY,getScopeRegistry:()=>Pw,isPackageApproved:()=>xw,normalizeRegistry:()=>Jc});Ge();var YBe=ut(Go()),VBe=(s=>(s.AUDIT_REGISTRY="npmAuditRegistry",s.FETCH_REGISTRY="npmRegistryServer",s.PUBLISH_REGISTRY="npmPublishRegistry",s))(VBe||{});function Jc(t){return t.replace(/\/$/,"")}function zdt({configuration:t}){return qD({configuration:t,type:"npmAuditRegistry"})}function Xdt(t,{configuration:e}){return t.publishConfig?.registry?Jc(t.publishConfig.registry):t.name?Pw(t.name.scope,{configuration:e,type:"npmPublishRegistry"}):qD({configuration:e,type:"npmPublishRegistry"})}function Pw(t,{configuration:e,type:r="npmRegistryServer"}){let s=NY(t,{configuration:e});if(s===null)return qD({configuration:e,type:r});let a=s.get(r);return a===null?qD({configuration:e,type:r}):Jc(a)}function qD({configuration:t,type:e="npmRegistryServer"}){let r=t.get(e);return Jc(r!==null?r:t.get("npmRegistryServer"))}function JBe(t,{configuration:e}){let r=e.get("npmRegistries"),s=Jc(t),a=r.get(s);if(typeof a<"u")return a;let n=r.get(s.replace(/^[a-z]+:/,""));return typeof n<"u"?n:null}var Zdt=new Map([["npmRegistryServer","https://npm.jsr.io/"]]);function NY(t,{configuration:e}){if(t===null)return null;let s=e.get("npmScopes").get(t);return s||(t==="jsr"?Zdt:null)}function OY(t,{configuration:e,ident:r}){let s=r&&NY(r.scope,{configuration:e});return s?.get("npmAuthIdent")||s?.get("npmAuthToken")?s:JBe(t,{configuration:e})||e}function $dt({configuration:t,version:e,publishTimes:r}){let s=t.get("npmMinimalAgeGate");if(s){let a=r?.[e];if(typeof a>"u"||(new Date().getTime()-new Date(a).getTime())/60/1e3emt(e,r,s))}function xw(t){return!$dt(t)||tmt(t)}var zBe=(a=>(a[a.NO_AUTH=0]="NO_AUTH",a[a.BEST_EFFORT=1]="BEST_EFFORT",a[a.CONFIGURATION=2]="CONFIGURATION",a[a.ALWAYS_AUTH=3]="ALWAYS_AUTH",a))(zBe||{});async function ag(t,{attemptedAs:e,registry:r,headers:s,configuration:a}){if(uN(t))throw new jt(41,"Invalid OTP token");if(t.originalError?.name==="HTTPError"&&t.originalError?.response.statusCode===401)throw new jt(41,`Invalid authentication (${typeof e!="string"?`as ${await hmt(r,s,{configuration:a})}`:`attempted as ${e}`})`)}function qm(t,e){let r=t.response?.statusCode;return r?r===404?"Package not found":r>=500&&r<600?`The registry appears to be down (using a ${he.applyHyperlink(e,"local cache","https://yarnpkg.com/advanced/lexicon#local-cache")} might have protected you against such outages)`:null:null}function WD(t){return t.scope?`/@${t.scope}%2f${t.name}`:`/${t.name}`}var XBe=new Map,rmt=new Map;async function nmt(t){return await je.getFactoryWithDefault(XBe,t,async()=>{let e=null;try{e=await ce.readJsonPromise(t)}catch{}return e})}async function imt(t,e,{configuration:r,cached:s,registry:a,headers:n,version:c,...f}){return await je.getFactoryWithDefault(rmt,t,async()=>await Wm(WD(e),{...f,customErrorMessage:qm,configuration:r,registry:a,ident:e,headers:{...n,"If-None-Match":s?.etag,"If-Modified-Since":s?.lastModified},wrapNetworkRequest:async p=>async()=>{let h=await p();if(h.statusCode===304){if(s===null)throw new Error("Assertion failed: cachedMetadata should not be null");return{...h,body:s.metadata}}let E=omt(JSON.parse(h.body.toString())),C={metadata:E,etag:h.headers.etag,lastModified:h.headers["last-modified"]};return XBe.set(t,Promise.resolve(C)),Promise.resolve().then(async()=>{let S=`${t}-${process.pid}.tmp`;await ce.mkdirPromise(J.dirname(S),{recursive:!0}),await ce.writeJsonPromise(S,C,{compact:!0}),await ce.renamePromise(S,t)}).catch(()=>{}),{...h,body:E}}}))}function smt(t){return t.scope!==null?`@${t.scope}-${t.name}-${t.scope.length}`:t.name}async function Qw(t,{cache:e,project:r,registry:s,headers:a,version:n,...c}){let{configuration:f}=r;s=YD(f,{ident:t,registry:s});let p=lmt(f,s),h=J.join(p,`${smt(t)}.json`),E=null;if(!r.lockfileNeedsRefresh&&(E=await nmt(h),E)){if(typeof n<"u"&&typeof E.metadata.versions[n]<"u")return E.metadata;if(f.get("enableOfflineMode")){let C=structuredClone(E.metadata),S=new Set;if(e){for(let I of Object.keys(C.versions)){let R=G.makeLocator(t,`npm:${I}`),N=e.getLocatorMirrorPath(R);(!N||!ce.existsSync(N))&&(delete C.versions[I],S.add(I))}let P=C["dist-tags"].latest;if(S.has(P)){let I=Object.keys(E.metadata.versions).sort(KBe.default.compare),R=I.indexOf(P);for(;S.has(I[R])&&R>=0;)R-=1;R>=0?C["dist-tags"].latest=I[R]:delete C["dist-tags"].latest}}return C}}return await imt(h,t,{...c,configuration:f,cached:E,registry:s,headers:a,version:n})}var ZBe=["name","dist.tarball","bin","scripts","os","cpu","libc","dependencies","dependenciesMeta","optionalDependencies","peerDependencies","peerDependenciesMeta","deprecated"];function omt(t){return{"dist-tags":t["dist-tags"],versions:Object.fromEntries(Object.entries(t.versions).map(([e,r])=>[e,Kd(r,ZBe)])),time:t.time}}var amt=Nn.makeHash("time",...ZBe).slice(0,6);function lmt(t,e){let r=cmt(t),s=new URL(e);return J.join(r,amt,s.hostname)}function cmt(t){return J.join(t.get("globalFolder"),"metadata/npm")}async function Wm(t,{configuration:e,headers:r,ident:s,authType:a,allowOidc:n,registry:c,...f}){c=YD(e,{ident:s,registry:c}),s&&s.scope&&typeof a>"u"&&(a=1);let p=await cN(c,{authType:a,allowOidc:n,configuration:e,ident:s});p&&(r={...r,authorization:p});try{return await nn.get(t.charAt(0)==="/"?`${c}${t}`:t,{configuration:e,headers:r,...f})}catch(h){throw await ag(h,{registry:c,configuration:e,headers:r}),h}}async function umt(t,e,{attemptedAs:r,configuration:s,headers:a,ident:n,authType:c=3,allowOidc:f,registry:p,otp:h,...E}){p=YD(s,{ident:n,registry:p});let C=await cN(p,{authType:c,allowOidc:f,configuration:s,ident:n});C&&(a={...a,authorization:C}),h&&(a={...a,...kw(h)});try{return await nn.post(p+t,e,{configuration:s,headers:a,...E})}catch(S){if(!uN(S)||h)throw await ag(S,{attemptedAs:r,registry:p,configuration:s,headers:a}),S;h=await MY(S,{configuration:s});let P={...a,...kw(h)};try{return await nn.post(`${p}${t}`,e,{configuration:s,headers:P,...E})}catch(I){throw await ag(I,{attemptedAs:r,registry:p,configuration:s,headers:a}),I}}}async function fmt(t,e,{attemptedAs:r,configuration:s,headers:a,ident:n,authType:c=3,allowOidc:f,registry:p,otp:h,...E}){p=YD(s,{ident:n,registry:p});let C=await cN(p,{authType:c,allowOidc:f,configuration:s,ident:n});C&&(a={...a,authorization:C}),h&&(a={...a,...kw(h)});try{return await nn.put(p+t,e,{configuration:s,headers:a,...E})}catch(S){if(!uN(S))throw await ag(S,{attemptedAs:r,registry:p,configuration:s,headers:a}),S;h=await MY(S,{configuration:s});let P={...a,...kw(h)};try{return await nn.put(`${p}${t}`,e,{configuration:s,headers:P,...E})}catch(I){throw await ag(I,{attemptedAs:r,registry:p,configuration:s,headers:a}),I}}}async function Amt(t,{attemptedAs:e,configuration:r,headers:s,ident:a,authType:n=3,allowOidc:c,registry:f,otp:p,...h}){f=YD(r,{ident:a,registry:f});let E=await cN(f,{authType:n,allowOidc:c,configuration:r,ident:a});E&&(s={...s,authorization:E}),p&&(s={...s,...kw(p)});try{return await nn.del(f+t,{configuration:r,headers:s,...h})}catch(C){if(!uN(C)||p)throw await ag(C,{attemptedAs:e,registry:f,configuration:r,headers:s}),C;p=await MY(C,{configuration:r});let S={...s,...kw(p)};try{return await nn.del(`${f}${t}`,{configuration:r,headers:S,...h})}catch(P){throw await ag(P,{attemptedAs:e,registry:f,configuration:r,headers:s}),P}}}function YD(t,{ident:e,registry:r}){if(typeof r>"u"&&e)return Pw(e.scope,{configuration:t});if(typeof r!="string")throw new Error("Assertion failed: The registry should be a string");return Jc(r)}async function cN(t,{authType:e=2,allowOidc:r=!1,configuration:s,ident:a}){let n=OY(t,{configuration:s,ident:a}),c=pmt(n,e);if(!c)return null;let f=await s.reduceHook(p=>p.getNpmAuthenticationHeader,void 0,t,{configuration:s,ident:a});if(f)return f;if(n.get("npmAuthToken"))return`Bearer ${n.get("npmAuthToken")}`;if(n.get("npmAuthIdent")){let p=n.get("npmAuthIdent");return p.includes(":")?`Basic ${Buffer.from(p).toString("base64")}`:`Basic ${p}`}if(r&&a){let p=await gmt(t,{configuration:s,ident:a});if(p)return`Bearer ${p}`}if(c&&e!==1)throw new jt(33,"No authentication configured for request");return null}function pmt(t,e){switch(e){case 2:return t.get("npmAlwaysAuth");case 1:case 3:return!0;case 0:return!1;default:throw new Error("Unreachable")}}async function hmt(t,e,{configuration:r}){if(typeof e>"u"||typeof e.authorization>"u")return"an anonymous user";try{return(await nn.get(new URL(`${t}/-/whoami`).href,{configuration:r,headers:e,jsonResponse:!0})).username??"an unknown user"}catch{return"an unknown user"}}async function MY(t,{configuration:e}){let r=t.originalError?.response.headers["npm-notice"];if(r&&(await Ot.start({configuration:e,stdout:process.stdout,includeFooter:!1},async a=>{if(a.reportInfo(0,r.replace(/(https?:\/\/\S+)/g,he.pretty(e,"$1",he.Type.URL))),!process.env.YARN_IS_TEST_ENV){let n=r.match(/open (https?:\/\/\S+)/i);if(n&&Ui.openUrl){let{openNow:c}=await(0,LY.prompt)({type:"confirm",name:"openNow",message:"Do you want to try to open this url now?",required:!0,initial:!0,onCancel:()=>process.exit(130)});c&&(await Ui.openUrl(n[1])||(a.reportSeparator(),a.reportWarning(0,"We failed to automatically open the url; you'll have to open it yourself in your browser of choice.")))}}}),process.stdout.write(` `)),process.env.YARN_IS_TEST_ENV)return process.env.YARN_INJECT_NPM_2FA_TOKEN||"";let{otp:s}=await(0,LY.prompt)({type:"password",name:"otp",message:"One-time password:",required:!0,onCancel:()=>process.exit(130)});return process.stdout.write(` `),s}function uN(t){if(t.originalError?.name!=="HTTPError")return!1;try{return(t.originalError?.response.headers["www-authenticate"].split(/,\s*/).map(r=>r.toLowerCase())).includes("otp")}catch{return!1}}function kw(t){return{"npm-otp":t}}async function gmt(t,{configuration:e,ident:r}){let s=null;if(process.env.GITLAB_CI)s=process.env.NPM_ID_TOKEN||null;else if(process.env.GITHUB_ACTIONS){if(!(process.env.ACTIONS_ID_TOKEN_REQUEST_URL&&process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN))return null;let a=`npm:${new URL(t).host.replace("registry.yarnpkg.com","registry.npmjs.org").replace("yarn.npmjs.org","registry.npmjs.org")}`,n=new URL(process.env.ACTIONS_ID_TOKEN_REQUEST_URL);n.searchParams.append("audience",a),s=(await nn.get(n.href,{configuration:e,jsonResponse:!0,headers:{Authorization:`Bearer ${process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN}`}})).value}if(!s)return null;try{return(await nn.post(`${t}/-/npm/v1/oidc/token/exchange/package${WD(r)}`,null,{configuration:e,jsonResponse:!0,headers:{Authorization:`Bearer ${s}`}})).token||null}catch{}return null}var VD=class{supports(e,r){if(!e.reference.startsWith(oi))return!1;let{selector:s,params:a}=G.parseRange(e.reference);return!(!$Be.default.valid(s)||a===null||typeof a.__archiveUrl!="string")}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote server`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let{params:s}=G.parseRange(e.reference);if(s===null||typeof s.__archiveUrl!="string")throw new Error("Assertion failed: The archiveUrl querystring parameter should have been available");let a=await Wm(s.__archiveUrl,{customErrorMessage:qm,configuration:r.project.configuration,ident:e});return await ps.convertToZip(a,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1})}};Ge();var JD=class{supportsDescriptor(e,r){return!(!e.range.startsWith(oi)||!G.tryParseDescriptor(e.range.slice(oi.length),!0))}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error("Unreachable")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){let s=r.project.configuration.normalizeDependency(G.parseDescriptor(e.range.slice(oi.length),!0));return r.resolver.getResolutionDependencies(s,r)}async getCandidates(e,r,s){let a=s.project.configuration.normalizeDependency(G.parseDescriptor(e.range.slice(oi.length),!0));return await s.resolver.getCandidates(a,r,s)}async getSatisfying(e,r,s,a){let n=a.project.configuration.normalizeDependency(G.parseDescriptor(e.range.slice(oi.length),!0));return a.resolver.getSatisfying(n,r,s,a)}resolve(e,r){throw new Error("Unreachable")}};Ge();Ge();var eve=ut(Ai());var oh=class t{supports(e,r){if(!e.reference.startsWith(oi))return!1;let s=new URL(e.reference);return!(!eve.default.valid(s.pathname)||s.searchParams.has("__archiveUrl"))}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote registry`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s;try{s=await Wm(t.getLocatorUrl(e),{customErrorMessage:qm,configuration:r.project.configuration,ident:e})}catch{s=await Wm(t.getLocatorUrl(e).replace(/%2f/g,"/"),{customErrorMessage:qm,configuration:r.project.configuration,ident:e})}return await ps.convertToZip(s,{configuration:r.project.configuration,prefixPath:G.getIdentVendorPath(e),stripComponents:1})}static isConventionalTarballUrl(e,r,{configuration:s}){let a=Pw(e.scope,{configuration:s}),n=t.getLocatorUrl(e);return r=r.replace(/^https?:(\/\/(?:[^/]+\.)?npmjs.org(?:$|\/))/,"https:$1"),a=a.replace(/^https:\/\/registry\.npmjs\.org($|\/)/,"https://registry.yarnpkg.com$1"),r=r.replace(/^https:\/\/registry\.npmjs\.org($|\/)/,"https://registry.yarnpkg.com$1"),r===a+n||r===a+n.replace(/%2f/g,"/")}static getLocatorUrl(e){let r=Fr.clean(e.reference.slice(oi.length));if(r===null)throw new jt(10,"The npm semver resolver got selected, but the version isn't semver");return`${WD(e)}/-/${e.name}-${r}.tgz`}};Ge();Ge();Ge();var UY=ut(Ai());var fN=G.makeIdent(null,"node-gyp"),dmt=/\b(node-gyp|prebuild-install)\b/,KD=class{supportsDescriptor(e,r){return e.range.startsWith(oi)?!!Fr.validRange(e.range.slice(oi.length)):!1}supportsLocator(e,r){if(!e.reference.startsWith(oi))return!1;let{selector:s}=G.parseRange(e.reference);return!!UY.default.valid(s)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=Fr.validRange(e.range.slice(oi.length));if(a===null)throw new Error(`Expected a valid range, got ${e.range.slice(oi.length)}`);let n=await Qw(e,{cache:s.fetchOptions?.cache,project:s.project,version:UY.default.valid(a.raw)?a.raw:void 0}),c=je.mapAndFilter(Object.keys(n.versions),h=>{try{let E=new Fr.SemVer(h);if(a.test(E))return xw({configuration:s.project.configuration,ident:e,version:h,publishTimes:n.time})?E:je.mapAndFilter.skip}catch{}return je.mapAndFilter.skip}),f=c.filter(h=>!n.versions[h.raw].deprecated),p=f.length>0?f:c;return p.sort((h,E)=>-h.compare(E)),p.map(h=>{let E=G.makeLocator(e,`${oi}${h.raw}`),C=n.versions[h.raw].dist.tarball;return oh.isConventionalTarballUrl(E,C,{configuration:s.project.configuration})?E:G.bindLocator(E,{__archiveUrl:C})})}async getSatisfying(e,r,s,a){let n=Fr.validRange(e.range.slice(oi.length));if(n===null)throw new Error(`Expected a valid range, got ${e.range.slice(oi.length)}`);return{locators:je.mapAndFilter(s,p=>{if(p.identHash!==e.identHash)return je.mapAndFilter.skip;let h=G.tryParseRange(p.reference,{requireProtocol:oi});if(!h)return je.mapAndFilter.skip;let E=new Fr.SemVer(h.selector);return n.test(E)?{locator:p,version:E}:je.mapAndFilter.skip}).sort((p,h)=>-p.version.compare(h.version)).map(({locator:p})=>p),sorted:!0}}async resolve(e,r){let{selector:s}=G.parseRange(e.reference),a=Fr.clean(s);if(a===null)throw new jt(10,"The npm semver resolver got selected, but the version isn't semver");let n=await Qw(e,{cache:r.fetchOptions?.cache,project:r.project,version:a});if(!Object.hasOwn(n,"versions"))throw new jt(15,'Registry returned invalid data for - missing "versions" field');if(!Object.hasOwn(n.versions,a))throw new jt(16,`Registry failed to return reference "${a}"`);let c=new Ut;if(c.load(n.versions[a]),!c.dependencies.has(fN.identHash)&&!c.peerDependencies.has(fN.identHash)){for(let f of c.scripts.values())if(f.match(dmt)){c.dependencies.set(fN.identHash,G.makeDescriptor(fN,"latest"));break}}return{...e,version:a,languageName:"node",linkType:"HARD",conditions:c.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(c.dependencies),peerDependencies:c.peerDependencies,dependenciesMeta:c.dependenciesMeta,peerDependenciesMeta:c.peerDependenciesMeta,bin:c.bin}}};Ge();Ge();var AN=ut(Ai());var zD=class{supportsDescriptor(e,r){return!(!e.range.startsWith(oi)||!Mp.test(e.range.slice(oi.length)))}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error("Unreachable")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(oi.length),n=await Qw(e,{cache:s.fetchOptions?.cache,project:s.project});if(!Object.hasOwn(n,"dist-tags"))throw new jt(15,'Registry returned invalid data - missing "dist-tags" field');let c=n["dist-tags"];if(!Object.hasOwn(c,a))throw new jt(16,`Registry failed to return tag "${a}"`);let f=Object.keys(n.versions),p=n.time,h=c[a];if(a==="latest"&&!xw({configuration:s.project.configuration,ident:e,version:h,publishTimes:p})){let S=h.includes("-"),P=AN.default.rsort(f).find(I=>AN.default.lt(I,h)&&(S||!I.includes("-"))&&xw({configuration:s.project.configuration,ident:e,version:I,publishTimes:p}));if(!P)throw new jt(16,`The version for tag "${a}" is quarantined, and no lower version is available`);h=P}let E=G.makeLocator(e,`${oi}${h}`),C=n.versions[h].dist.tarball;return oh.isConventionalTarballUrl(E,C,{configuration:s.project.configuration})?[E]:[G.bindLocator(E,{__archiveUrl:C})]}async getSatisfying(e,r,s,a){let n=[];for(let c of s){if(c.identHash!==e.identHash)continue;let f=G.tryParseRange(c.reference,{requireProtocol:oi});if(!(!f||!AN.default.valid(f.selector))){if(f.params?.__archiveUrl){let p=G.makeRange({protocol:oi,selector:f.selector,source:null,params:null}),[h]=await a.resolver.getCandidates(G.makeDescriptor(e,p),r,a);if(c.reference!==h.reference)continue}n.push(c)}}return{locators:n,sorted:!1}}async resolve(e,r){throw new Error("Unreachable")}};var v1={};Vt(v1,{getGitHead:()=>abt,getPublishAccess:()=>qxe,getReadmeContent:()=>Wxe,makePublishBody:()=>obt});Ge();Ge();Dt();var bV={};Vt(bV,{PackCommand:()=>jw,default:()=>KEt,packUtils:()=>yA});Ge();Ge();Ge();Dt();Yt();var yA={};Vt(yA,{genPackList:()=>NN,genPackStream:()=>DV,genPackageManifest:()=>QSe,hasPackScripts:()=>vV,prepareForPack:()=>SV});Ge();Dt();var BV=ut(Go()),xSe=ut(SSe()),kSe=Ie("zlib"),MEt=["/package.json","/readme","/readme.*","/license","/license.*","/licence","/licence.*","/changelog","/changelog.*"],UEt=["/package.tgz",".github",".git",".hg","node_modules",".npmignore",".gitignore",".#*",".DS_Store"];async function vV(t){return!!(In.hasWorkspaceScript(t,"prepack")||In.hasWorkspaceScript(t,"postpack"))}async function SV(t,{report:e},r){await In.maybeExecuteWorkspaceLifecycleScript(t,"prepack",{report:e});try{let s=J.join(t.cwd,Ut.fileName);await ce.existsPromise(s)&&await t.manifest.loadFile(s,{baseFs:ce}),await r()}finally{await In.maybeExecuteWorkspaceLifecycleScript(t,"postpack",{report:e})}}async function DV(t,e){typeof e>"u"&&(e=await NN(t));let r=new Set;for(let n of t.manifest.publishConfig?.executableFiles??new Set)r.add(J.normalize(n));for(let n of t.manifest.bin.values())r.add(J.normalize(n));let s=xSe.default.pack();process.nextTick(async()=>{for(let n of e){let c=J.normalize(n),f=J.resolve(t.cwd,c),p=J.join("package",c),h=await ce.lstatPromise(f),E={name:p,mtime:new Date(fi.SAFE_TIME*1e3)},C=r.has(c)?493:420,S,P,I=new Promise((N,U)=>{S=N,P=U}),R=N=>{N?P(N):S()};if(h.isFile()){let N;c==="package.json"?N=Buffer.from(JSON.stringify(await QSe(t),null,2)):N=await ce.readFilePromise(f),s.entry({...E,mode:C,type:"file"},N,R)}else h.isSymbolicLink()?s.entry({...E,mode:C,type:"symlink",linkname:await ce.readlinkPromise(f)},R):R(new Error(`Unsupported file type ${h.mode} for ${fe.fromPortablePath(c)}`));await I}s.finalize()});let a=(0,kSe.createGzip)();return s.pipe(a),a}async function QSe(t){let e=JSON.parse(JSON.stringify(t.manifest.raw));return await t.project.configuration.triggerHook(r=>r.beforeWorkspacePacking,t,e),e}async function NN(t){let e=t.project,r=e.configuration,s={accept:[],reject:[]};for(let C of UEt)s.reject.push(C);for(let C of MEt)s.accept.push(C);s.reject.push(r.get("rcFilename"));let a=C=>{if(C===null||!C.startsWith(`${t.cwd}/`))return;let S=J.relative(t.cwd,C),P=J.resolve(vt.root,S);s.reject.push(P)};a(J.resolve(e.cwd,Er.lockfile)),a(r.get("cacheFolder")),a(r.get("globalFolder")),a(r.get("installStatePath")),a(r.get("virtualFolder")),a(r.get("yarnPath")),await r.triggerHook(C=>C.populateYarnPaths,e,C=>{a(C)});for(let C of e.workspaces){let S=J.relative(t.cwd,C.cwd);S!==""&&!S.match(/^(\.\.)?\//)&&s.reject.push(`/${S}`)}let n={accept:[],reject:[]},c=t.manifest.publishConfig?.main??t.manifest.main,f=t.manifest.publishConfig?.module??t.manifest.module,p=t.manifest.publishConfig?.browser??t.manifest.browser,h=t.manifest.publishConfig?.bin??t.manifest.bin;c!=null&&n.accept.push(J.resolve(vt.root,c)),f!=null&&n.accept.push(J.resolve(vt.root,f)),typeof p=="string"&&n.accept.push(J.resolve(vt.root,p));for(let C of h.values())n.accept.push(J.resolve(vt.root,C));if(p instanceof Map)for(let[C,S]of p.entries())n.accept.push(J.resolve(vt.root,C)),typeof S=="string"&&n.accept.push(J.resolve(vt.root,S));let E=t.manifest.files!==null;if(E){n.reject.push("/*");for(let C of t.manifest.files)TSe(n.accept,C,{cwd:vt.root})}return await _Et(t.cwd,{hasExplicitFileList:E,globalList:s,ignoreList:n})}async function _Et(t,{hasExplicitFileList:e,globalList:r,ignoreList:s}){let a=[],n=new Hf(t),c=[[vt.root,[s]]];for(;c.length>0;){let[f,p]=c.pop(),h=await n.lstatPromise(f);if(!bSe(f,{globalList:r,ignoreLists:h.isDirectory()?null:p}))if(h.isDirectory()){let E=await n.readdirPromise(f),C=!1,S=!1;if(!e||f!==vt.root)for(let R of E)C=C||R===".gitignore",S=S||R===".npmignore";let P=S?await DSe(n,f,".npmignore"):C?await DSe(n,f,".gitignore"):null,I=P!==null?[P].concat(p):p;bSe(f,{globalList:r,ignoreLists:p})&&(I=[...p,{accept:[],reject:["**/*"]}]);for(let R of E)c.push([J.resolve(f,R),I])}else(h.isFile()||h.isSymbolicLink())&&a.push(J.relative(vt.root,f))}return a.sort()}async function DSe(t,e,r){let s={accept:[],reject:[]},a=await t.readFilePromise(J.join(e,r),"utf8");for(let n of a.split(/\n/g))TSe(s.reject,n,{cwd:e});return s}function HEt(t,{cwd:e}){let r=t[0]==="!";return r&&(t=t.slice(1)),t.match(/\.{0,1}\//)&&(t=J.resolve(e,t)),r&&(t=`!${t}`),t}function TSe(t,e,{cwd:r}){let s=e.trim();s===""||s[0]==="#"||t.push(HEt(s,{cwd:r}))}function bSe(t,{globalList:e,ignoreLists:r}){let s=FN(t,e.accept);if(s!==0)return s===2;let a=FN(t,e.reject);if(a!==0)return a===1;if(r!==null)for(let n of r){let c=FN(t,n.accept);if(c!==0)return c===2;let f=FN(t,n.reject);if(f!==0)return f===1}return!1}function FN(t,e){let r=e,s=[];for(let a=0;a{await SV(a,{report:p},async()=>{p.reportJson({base:fe.fromPortablePath(a.cwd)});let h=await NN(a);for(let E of h)p.reportInfo(null,fe.fromPortablePath(E)),p.reportJson({location:fe.fromPortablePath(E)});if(!this.dryRun){let E=await DV(a,h);await ce.mkdirPromise(J.dirname(c),{recursive:!0});let C=ce.createWriteStream(c);E.pipe(C),await new Promise(S=>{C.on("finish",S)})}}),this.dryRun||(p.reportInfo(0,`Package archive generated in ${he.pretty(r,c,he.Type.PATH)}`),p.reportJson({output:fe.fromPortablePath(c)}))})).exitCode()}};function jEt(t,{workspace:e}){let r=t.replace("%s",GEt(e)).replace("%v",qEt(e));return fe.toPortablePath(r)}function GEt(t){return t.manifest.name!==null?G.slugifyIdent(t.manifest.name):"package"}function qEt(t){return t.manifest.version!==null?t.manifest.version:"unknown"}var WEt=["dependencies","devDependencies","peerDependencies"],YEt="workspace:",VEt=(t,e)=>{e.publishConfig&&(e.publishConfig.type&&(e.type=e.publishConfig.type),e.publishConfig.main&&(e.main=e.publishConfig.main),e.publishConfig.browser&&(e.browser=e.publishConfig.browser),e.publishConfig.module&&(e.module=e.publishConfig.module),e.publishConfig.exports&&(e.exports=e.publishConfig.exports),e.publishConfig.imports&&(e.imports=e.publishConfig.imports),e.publishConfig.bin&&(e.bin=e.publishConfig.bin));let r=t.project;for(let s of WEt)for(let a of t.manifest.getForScope(s).values()){let n=r.tryWorkspaceByDescriptor(a),c=G.parseRange(a.range);if(c.protocol===YEt)if(n===null){if(r.tryWorkspaceByIdent(a)===null)throw new jt(21,`${G.prettyDescriptor(r.configuration,a)}: No local workspace found for this range`)}else{let f;G.areDescriptorsEqual(a,n.anchoredDescriptor)||c.selector==="*"?f=n.manifest.version??"0.0.0":c.selector==="~"||c.selector==="^"?f=`${c.selector}${n.manifest.version??"0.0.0"}`:f=c.selector;let p=s==="dependencies"?G.makeDescriptor(a,"unknown"):null,h=p!==null&&t.manifest.ensureDependencyMeta(p).optional?"optionalDependencies":s;e[h][G.stringifyIdent(a)]=f}}},JEt={hooks:{beforeWorkspacePacking:VEt},commands:[jw]},KEt=JEt;var Gxe=ut(HSe());Ge();var Hxe=ut(_xe()),{env:Bt}=process,XDt="application/vnd.in-toto+json",ZDt="https://in-toto.io/Statement/v0.1",$Dt="https://in-toto.io/Statement/v1",ebt="https://slsa.dev/provenance/v0.2",tbt="https://slsa.dev/provenance/v1",rbt="https://github.com/actions/runner",nbt="https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1",ibt="https://github.com/npm/cli/gitlab",sbt="v0alpha1",jxe=async(t,e)=>{let r;if(Bt.GITHUB_ACTIONS){if(!Bt.ACTIONS_ID_TOKEN_REQUEST_URL)throw new jt(91,'Provenance generation in GitHub Actions requires "write" access to the "id-token" permission');let s=(Bt.GITHUB_WORKFLOW_REF||"").replace(`${Bt.GITHUB_REPOSITORY}/`,""),a=s.indexOf("@"),n=s.slice(0,a),c=s.slice(a+1);r={_type:$Dt,subject:t,predicateType:tbt,predicate:{buildDefinition:{buildType:nbt,externalParameters:{workflow:{ref:c,repository:`${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}`,path:n}},internalParameters:{github:{event_name:Bt.GITHUB_EVENT_NAME,repository_id:Bt.GITHUB_REPOSITORY_ID,repository_owner_id:Bt.GITHUB_REPOSITORY_OWNER_ID}},resolvedDependencies:[{uri:`git+${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}@${Bt.GITHUB_REF}`,digest:{gitCommit:Bt.GITHUB_SHA}}]},runDetails:{builder:{id:`${rbt}/${Bt.RUNNER_ENVIRONMENT}`},metadata:{invocationId:`${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}/actions/runs/${Bt.GITHUB_RUN_ID}/attempts/${Bt.GITHUB_RUN_ATTEMPT}`}}}}}else if(Bt.GITLAB_CI){if(!Bt.SIGSTORE_ID_TOKEN)throw new jt(91,`Provenance generation in GitLab CI requires "SIGSTORE_ID_TOKEN" with "sigstore" audience to be present in "id_tokens". For more info see: https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html`);r={_type:ZDt,subject:t,predicateType:ebt,predicate:{buildType:`${ibt}/${sbt}`,builder:{id:`${Bt.CI_PROJECT_URL}/-/runners/${Bt.CI_RUNNER_ID}`},invocation:{configSource:{uri:`git+${Bt.CI_PROJECT_URL}`,digest:{sha1:Bt.CI_COMMIT_SHA},entryPoint:Bt.CI_JOB_NAME},parameters:{CI:Bt.CI,CI_API_GRAPHQL_URL:Bt.CI_API_GRAPHQL_URL,CI_API_V4_URL:Bt.CI_API_V4_URL,CI_BUILD_BEFORE_SHA:Bt.CI_BUILD_BEFORE_SHA,CI_BUILD_ID:Bt.CI_BUILD_ID,CI_BUILD_NAME:Bt.CI_BUILD_NAME,CI_BUILD_REF:Bt.CI_BUILD_REF,CI_BUILD_REF_NAME:Bt.CI_BUILD_REF_NAME,CI_BUILD_REF_SLUG:Bt.CI_BUILD_REF_SLUG,CI_BUILD_STAGE:Bt.CI_BUILD_STAGE,CI_COMMIT_BEFORE_SHA:Bt.CI_COMMIT_BEFORE_SHA,CI_COMMIT_BRANCH:Bt.CI_COMMIT_BRANCH,CI_COMMIT_REF_NAME:Bt.CI_COMMIT_REF_NAME,CI_COMMIT_REF_PROTECTED:Bt.CI_COMMIT_REF_PROTECTED,CI_COMMIT_REF_SLUG:Bt.CI_COMMIT_REF_SLUG,CI_COMMIT_SHA:Bt.CI_COMMIT_SHA,CI_COMMIT_SHORT_SHA:Bt.CI_COMMIT_SHORT_SHA,CI_COMMIT_TIMESTAMP:Bt.CI_COMMIT_TIMESTAMP,CI_COMMIT_TITLE:Bt.CI_COMMIT_TITLE,CI_CONFIG_PATH:Bt.CI_CONFIG_PATH,CI_DEFAULT_BRANCH:Bt.CI_DEFAULT_BRANCH,CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX:Bt.CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX,CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX:Bt.CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX,CI_DEPENDENCY_PROXY_SERVER:Bt.CI_DEPENDENCY_PROXY_SERVER,CI_DEPENDENCY_PROXY_USER:Bt.CI_DEPENDENCY_PROXY_USER,CI_JOB_ID:Bt.CI_JOB_ID,CI_JOB_NAME:Bt.CI_JOB_NAME,CI_JOB_NAME_SLUG:Bt.CI_JOB_NAME_SLUG,CI_JOB_STAGE:Bt.CI_JOB_STAGE,CI_JOB_STARTED_AT:Bt.CI_JOB_STARTED_AT,CI_JOB_URL:Bt.CI_JOB_URL,CI_NODE_TOTAL:Bt.CI_NODE_TOTAL,CI_PAGES_DOMAIN:Bt.CI_PAGES_DOMAIN,CI_PAGES_URL:Bt.CI_PAGES_URL,CI_PIPELINE_CREATED_AT:Bt.CI_PIPELINE_CREATED_AT,CI_PIPELINE_ID:Bt.CI_PIPELINE_ID,CI_PIPELINE_IID:Bt.CI_PIPELINE_IID,CI_PIPELINE_SOURCE:Bt.CI_PIPELINE_SOURCE,CI_PIPELINE_URL:Bt.CI_PIPELINE_URL,CI_PROJECT_CLASSIFICATION_LABEL:Bt.CI_PROJECT_CLASSIFICATION_LABEL,CI_PROJECT_DESCRIPTION:Bt.CI_PROJECT_DESCRIPTION,CI_PROJECT_ID:Bt.CI_PROJECT_ID,CI_PROJECT_NAME:Bt.CI_PROJECT_NAME,CI_PROJECT_NAMESPACE:Bt.CI_PROJECT_NAMESPACE,CI_PROJECT_NAMESPACE_ID:Bt.CI_PROJECT_NAMESPACE_ID,CI_PROJECT_PATH:Bt.CI_PROJECT_PATH,CI_PROJECT_PATH_SLUG:Bt.CI_PROJECT_PATH_SLUG,CI_PROJECT_REPOSITORY_LANGUAGES:Bt.CI_PROJECT_REPOSITORY_LANGUAGES,CI_PROJECT_ROOT_NAMESPACE:Bt.CI_PROJECT_ROOT_NAMESPACE,CI_PROJECT_TITLE:Bt.CI_PROJECT_TITLE,CI_PROJECT_URL:Bt.CI_PROJECT_URL,CI_PROJECT_VISIBILITY:Bt.CI_PROJECT_VISIBILITY,CI_REGISTRY:Bt.CI_REGISTRY,CI_REGISTRY_IMAGE:Bt.CI_REGISTRY_IMAGE,CI_REGISTRY_USER:Bt.CI_REGISTRY_USER,CI_RUNNER_DESCRIPTION:Bt.CI_RUNNER_DESCRIPTION,CI_RUNNER_ID:Bt.CI_RUNNER_ID,CI_RUNNER_TAGS:Bt.CI_RUNNER_TAGS,CI_SERVER_HOST:Bt.CI_SERVER_HOST,CI_SERVER_NAME:Bt.CI_SERVER_NAME,CI_SERVER_PORT:Bt.CI_SERVER_PORT,CI_SERVER_PROTOCOL:Bt.CI_SERVER_PROTOCOL,CI_SERVER_REVISION:Bt.CI_SERVER_REVISION,CI_SERVER_SHELL_SSH_HOST:Bt.CI_SERVER_SHELL_SSH_HOST,CI_SERVER_SHELL_SSH_PORT:Bt.CI_SERVER_SHELL_SSH_PORT,CI_SERVER_URL:Bt.CI_SERVER_URL,CI_SERVER_VERSION:Bt.CI_SERVER_VERSION,CI_SERVER_VERSION_MAJOR:Bt.CI_SERVER_VERSION_MAJOR,CI_SERVER_VERSION_MINOR:Bt.CI_SERVER_VERSION_MINOR,CI_SERVER_VERSION_PATCH:Bt.CI_SERVER_VERSION_PATCH,CI_TEMPLATE_REGISTRY_HOST:Bt.CI_TEMPLATE_REGISTRY_HOST,GITLAB_CI:Bt.GITLAB_CI,GITLAB_FEATURES:Bt.GITLAB_FEATURES,GITLAB_USER_ID:Bt.GITLAB_USER_ID,GITLAB_USER_LOGIN:Bt.GITLAB_USER_LOGIN,RUNNER_GENERATE_ARTIFACTS_METADATA:Bt.RUNNER_GENERATE_ARTIFACTS_METADATA},environment:{name:Bt.CI_RUNNER_DESCRIPTION,architecture:Bt.CI_RUNNER_EXECUTABLE_ARCH,server:Bt.CI_SERVER_URL,project:Bt.CI_PROJECT_PATH,job:{id:Bt.CI_JOB_ID},pipeline:{id:Bt.CI_PIPELINE_ID,ref:Bt.CI_CONFIG_PATH}}},metadata:{buildInvocationId:`${Bt.CI_JOB_URL}`,completeness:{parameters:!0,environment:!0,materials:!1},reproducible:!1},materials:[{uri:`git+${Bt.CI_PROJECT_URL}`,digest:{sha1:Bt.CI_COMMIT_SHA}}]}}}else throw new jt(91,"Provenance generation is only supported in GitHub Actions and GitLab CI");return Hxe.attest(Buffer.from(JSON.stringify(r)),XDt,e)};async function obt(t,e,{access:r,tag:s,registry:a,gitHead:n,provenance:c}){let f=t.manifest.name,p=t.manifest.version,h=G.stringifyIdent(f),E=Gxe.default.fromData(e,{algorithms:["sha1","sha512"]}),C=r??qxe(t,f),S=await Wxe(t),P=await yA.genPackageManifest(t),I=`${h}-${p}.tgz`,R=new URL(`${Jc(a)}/${h}/-/${I}`),N={[I]:{content_type:"application/octet-stream",data:e.toString("base64"),length:e.length}};if(c){let U={name:`pkg:npm/${h.replace(/^@/,"%40")}@${p}`,digest:{sha512:E.sha512[0].hexDigest()}},W=await jxe([U]),ee=JSON.stringify(W);N[`${h}-${p}.sigstore`]={content_type:W.mediaType,data:ee,length:ee.length}}return{_id:h,_attachments:N,name:h,access:C,"dist-tags":{[s]:p},versions:{[p]:{...P,_id:`${h}@${p}`,name:h,version:p,gitHead:n,dist:{shasum:E.sha1[0].hexDigest(),integrity:E.sha512[0].toString(),tarball:R.toString()}}},readme:S}}async function abt(t){try{let{stdout:e}=await qr.execvp("git",["rev-parse","--revs-only","HEAD"],{cwd:t});return e.trim()===""?void 0:e.trim()}catch{return}}function qxe(t,e){let r=t.project.configuration;return t.manifest.publishConfig&&typeof t.manifest.publishConfig.access=="string"?t.manifest.publishConfig.access:r.get("npmPublishAccess")!==null?r.get("npmPublishAccess"):e.scope?"restricted":"public"}async function Wxe(t){let e=fe.toPortablePath(`${t.cwd}/README.md`),r=t.manifest.name,a=`# ${G.stringifyIdent(r)} `;try{a=await ce.readFilePromise(e,"utf8")}catch(n){if(n.code==="ENOENT")return a;throw n}return a}var RK={npmAlwaysAuth:{description:"URL of the selected npm registry (note: npm enterprise isn't supported)",type:"BOOLEAN",default:!1},npmAuthIdent:{description:"Authentication identity for the npm registry (_auth in npm and yarn v1)",type:"SECRET",default:null},npmAuthToken:{description:"Authentication token for the npm registry (_authToken in npm and yarn v1)",type:"SECRET",default:null}},Yxe={npmAuditRegistry:{description:"Registry to query for audit reports",type:"STRING",default:null},npmPublishRegistry:{description:"Registry to push packages to",type:"STRING",default:null},npmRegistryServer:{description:"URL of the selected npm registry (note: npm enterprise isn't supported)",type:"STRING",default:"https://registry.yarnpkg.com"}},lbt={npmMinimalAgeGate:{description:"Minimum age of a package version according to the publish date on the npm registry to be considered for installation",type:"DURATION",unit:"m",default:"0m"},npmPreapprovedPackages:{description:"Array of package descriptors or package name glob patterns to exclude from the minimum release age check",type:"STRING",isArray:!0,default:[]}},cbt={configuration:{...RK,...Yxe,...lbt,npmScopes:{description:"Settings per package scope",type:"MAP",valueDefinition:{description:"",type:"SHAPE",properties:{...RK,...Yxe}}},npmRegistries:{description:"Settings per registry",type:"MAP",normalizeKeys:Jc,valueDefinition:{description:"",type:"SHAPE",properties:{...RK}}}},fetchers:[VD,oh],resolvers:[JD,KD,zD]},ubt=cbt;var qK={};Vt(qK,{NpmAuditCommand:()=>D1,NpmInfoCommand:()=>b1,NpmLoginCommand:()=>P1,NpmLogoutCommand:()=>k1,NpmPublishCommand:()=>Q1,NpmTagAddCommand:()=>R1,NpmTagListCommand:()=>T1,NpmTagRemoveCommand:()=>F1,NpmWhoamiCommand:()=>N1,default:()=>wbt,npmAuditTypes:()=>zb,npmAuditUtils:()=>kL});Ge();Ge();Yt();var UK=ut(Go());Ul();var zb={};Vt(zb,{Environment:()=>Jb,Severity:()=>Kb});var Jb=(s=>(s.All="all",s.Production="production",s.Development="development",s))(Jb||{}),Kb=(n=>(n.Info="info",n.Low="low",n.Moderate="moderate",n.High="high",n.Critical="critical",n))(Kb||{});var kL={};Vt(kL,{allSeverities:()=>S1,getPackages:()=>MK,getReportTree:()=>OK,getSeverityInclusions:()=>NK,getTopLevelDependencies:()=>LK});Ge();var Vxe=ut(Ai());var S1=["info","low","moderate","high","critical"];function NK(t){if(typeof t>"u")return new Set(S1);let e=S1.indexOf(t),r=S1.slice(e);return new Set(r)}function OK(t){let e={},r={children:e};for(let[s,a]of je.sortMap(Object.entries(t),n=>n[0]))for(let n of je.sortMap(a,c=>`${c.id}`))e[`${s}/${n.id}`]={value:he.tuple(he.Type.IDENT,G.parseIdent(s)),children:{ID:typeof n.id<"u"&&{label:"ID",value:he.tuple(he.Type.ID,n.id)},Issue:{label:"Issue",value:he.tuple(he.Type.NO_HINT,n.title)},URL:typeof n.url<"u"&&{label:"URL",value:he.tuple(he.Type.URL,n.url)},Severity:{label:"Severity",value:he.tuple(he.Type.NO_HINT,n.severity)},"Vulnerable Versions":{label:"Vulnerable Versions",value:he.tuple(he.Type.RANGE,n.vulnerable_versions)},"Tree Versions":{label:"Tree Versions",children:[...n.versions].sort(Vxe.default.compare).map(c=>({value:he.tuple(he.Type.REFERENCE,c)}))},Dependents:{label:"Dependents",children:je.sortMap(n.dependents,c=>G.stringifyLocator(c)).map(c=>({value:he.tuple(he.Type.LOCATOR,c)}))}}};return r}function LK(t,e,{all:r,environment:s}){let a=[],n=r?t.workspaces:[e],c=["all","production"].includes(s),f=["all","development"].includes(s);for(let p of n)for(let h of p.anchoredPackage.dependencies.values())(p.manifest.devDependencies.has(h.identHash)?!f:!c)||a.push({workspace:p,dependency:h});return a}function MK(t,e,{recursive:r}){let s=new Map,a=new Set,n=[],c=(f,p)=>{let h=t.storedResolutions.get(p.descriptorHash);if(typeof h>"u")throw new Error("Assertion failed: The resolution should have been registered");if(!a.has(h))a.add(h);else return;let E=t.storedPackages.get(h);if(typeof E>"u")throw new Error("Assertion failed: The package should have been registered");if(G.ensureDevirtualizedLocator(E).reference.startsWith("npm:")&&E.version!==null){let S=G.stringifyIdent(E),P=je.getMapWithDefault(s,S);je.getArrayWithDefault(P,E.version).push(f)}if(r)for(let S of E.dependencies.values())n.push([E,S])};for(let{workspace:f,dependency:p}of e)n.push([f.anchoredLocator,p]);for(;n.length>0;){let[f,p]=n.shift();c(f,p)}return s}var D1=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Audit dependencies from all workspaces"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Audit transitive dependencies as well"});this.environment=ge.String("--environment","all",{description:"Which environments to cover",validator:fo(Jb)});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.noDeprecations=ge.Boolean("--no-deprecations",!1,{description:"Don't warn about deprecated packages"});this.severity=ge.String("--severity","info",{description:"Minimal severity requested for packages to be displayed",validator:fo(Kb)});this.excludes=ge.Array("--exclude",[],{description:"Array of glob patterns of packages to exclude from audit"});this.ignores=ge.Array("--ignore",[],{description:"Array of glob patterns of advisory ID's to ignore in the audit report"})}static{this.paths=[["npm","audit"]]}static{this.usage=ot.Usage({description:"perform a vulnerability audit against the installed packages",details:` This command checks for known security reports on the packages you use. The reports are by default extracted from the npm registry, and may or may not be relevant to your actual program (not all vulnerabilities affect all code paths). For consistency with our other commands the default is to only check the direct dependencies for the active workspace. To extend this search to all workspaces, use \`-A,--all\`. To extend this search to both direct and transitive dependencies, use \`-R,--recursive\`. Applying the \`--severity\` flag will limit the audit table to vulnerabilities of the corresponding severity and above. Valid values are ${S1.map(r=>`\`${r}\``).join(", ")}. If the \`--json\` flag is set, Yarn will print the output exactly as received from the registry. Regardless of this flag, the process will exit with a non-zero exit code if a report is found for the selected packages. If certain packages produce false positives for a particular environment, the \`--exclude\` flag can be used to exclude any number of packages from the audit. This can also be set in the configuration file with the \`npmAuditExcludePackages\` option. If particular advisories are needed to be ignored, the \`--ignore\` flag can be used with Advisory ID's to ignore any number of advisories in the audit report. This can also be set in the configuration file with the \`npmAuditIgnoreAdvisories\` option. To understand the dependency tree requiring vulnerable packages, check the raw report with the \`--json\` flag or use \`yarn why package\` to get more information as to who depends on them. `,examples:[["Checks for known security issues with the installed packages. The output is a list of known issues.","yarn npm audit"],["Audit dependencies in all workspaces","yarn npm audit --all"],["Limit auditing to `dependencies` (excludes `devDependencies`)","yarn npm audit --environment production"],["Show audit report as valid JSON","yarn npm audit --json"],["Audit all direct and transitive dependencies","yarn npm audit --recursive"],["Output moderate (or more severe) vulnerabilities","yarn npm audit --severity moderate"],["Exclude certain packages","yarn npm audit --exclude package1 --exclude package2"],["Ignore specific advisories","yarn npm audit --ignore 1234567 --ignore 7654321"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=LK(s,a,{all:this.all,environment:this.environment}),c=MK(s,n,{recursive:this.recursive}),f=Array.from(new Set([...r.get("npmAuditExcludePackages"),...this.excludes])),p=Object.create(null);for(let[N,U]of c)f.some(W=>UK.default.isMatch(N,W))||(p[N]=[...U.keys()]);let h=hi.getAuditRegistry({configuration:r}),E,C=await lA.start({configuration:r,stdout:this.context.stdout},async()=>{let N=en.post("/-/npm/v1/security/advisories/bulk",p,{authType:en.AuthType.BEST_EFFORT,configuration:r,jsonResponse:!0,registry:h}),U=this.noDeprecations?[]:await Promise.all(Array.from(Object.entries(p),async([ee,ie])=>{let ue=await en.getPackageMetadata(G.parseIdent(ee),{project:s});return je.mapAndFilter(ie,le=>{let{deprecated:me}=ue.versions[le];return me?[ee,le,me]:je.mapAndFilter.skip})})),W=await N;for(let[ee,ie,ue]of U.flat(1))Object.hasOwn(W,ee)&&W[ee].some(le=>Fr.satisfiesWithPrereleases(ie,le.vulnerable_versions))||(W[ee]??=[],W[ee].push({id:`${ee} (deprecation)`,title:(typeof ue=="string"?ue:"").trim()||"This package has been deprecated.",severity:"moderate",vulnerable_versions:ie}));E=W});if(C.hasErrors())return C.exitCode();let S=NK(this.severity),P=Array.from(new Set([...r.get("npmAuditIgnoreAdvisories"),...this.ignores])),I=Object.create(null);for(let[N,U]of Object.entries(E)){let W=U.filter(ee=>!UK.default.isMatch(`${ee.id}`,P)&&S.has(ee.severity));W.length>0&&(I[N]=W.map(ee=>{let ie=c.get(N);if(typeof ie>"u")throw new Error("Assertion failed: Expected the registry to only return packages that were requested");let ue=[...ie.keys()].filter(me=>Fr.satisfiesWithPrereleases(me,ee.vulnerable_versions)),le=new Map;for(let me of ue)for(let pe of ie.get(me))le.set(pe.locatorHash,pe);return{...ee,versions:ue,dependents:[...le.values()]}}))}let R=Object.keys(I).length>0;return R?(xs.emitTree(OK(I),{configuration:r,json:this.json,stdout:this.context.stdout,separators:2}),1):(await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async N=>{N.reportInfo(1,"No audit suggestions")}),R?1:0)}};Ge();Ge();Dt();Yt();var _K=ut(Ai()),HK=Ie("util"),b1=class extends ft{constructor(){super(...arguments);this.fields=ge.String("-f,--fields",{description:"A comma-separated list of manifest fields that should be displayed"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.packages=ge.Rest()}static{this.paths=[["npm","info"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"show information about a package",details:"\n This command fetches information about a package from the npm registry and prints it in a tree format.\n\n The package does not have to be installed locally, but needs to have been published (in particular, local changes will be ignored even for workspaces).\n\n Append `@` to the package argument to provide information specific to the latest version that satisfies the range or to the corresponding tagged version. If the range is invalid or if there is no version satisfying the range, the command will print a warning and fall back to the latest version.\n\n If the `-f,--fields` option is set, it's a comma-separated list of fields which will be used to only display part of the package information.\n\n By default, this command won't return the `dist`, `readme`, and `users` fields, since they are often very long. To explicitly request those fields, explicitly list them with the `--fields` flag or request the output in JSON mode.\n ",examples:[["Show all available information about react (except the `dist`, `readme`, and `users` fields)","yarn npm info react"],["Show all available information about react as valid JSON (including the `dist`, `readme`, and `users` fields)","yarn npm info react --json"],["Show all available information about react@16.12.0","yarn npm info react@16.12.0"],["Show all available information about react@next","yarn npm info react@next"],["Show the description of react","yarn npm info react --fields description"],["Show all available versions of react","yarn npm info react --fields versions"],["Show the readme of react","yarn npm info react --fields readme"],["Show a few fields of react","yarn npm info react --fields homepage,repository"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=typeof this.fields<"u"?new Set(["name",...this.fields.split(/\s*,\s*/)]):null,n=[],c=!1,f=await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async p=>{for(let h of this.packages){let E;if(h==="."){let ie=s.topLevelWorkspace;if(!ie.manifest.name)throw new nt(`Missing ${he.pretty(r,"name",he.Type.CODE)} field in ${fe.fromPortablePath(J.join(ie.cwd,Er.manifest))}`);E=G.makeDescriptor(ie.manifest.name,"unknown")}else E=G.parseDescriptor(h);let C=en.getIdentUrl(E),S=jK(await en.get(C,{configuration:r,ident:E,jsonResponse:!0,customErrorMessage:en.customPackageError})),P=Object.keys(S.versions).sort(_K.default.compareLoose),R=S["dist-tags"].latest||P[P.length-1],N=Fr.validRange(E.range);if(N){let ie=_K.default.maxSatisfying(P,N);ie!==null?R=ie:(p.reportWarning(0,`Unmet range ${G.prettyRange(r,E.range)}; falling back to the latest version`),c=!0)}else Object.hasOwn(S["dist-tags"],E.range)?R=S["dist-tags"][E.range]:E.range!=="unknown"&&(p.reportWarning(0,`Unknown tag ${G.prettyRange(r,E.range)}; falling back to the latest version`),c=!0);let U=S.versions[R],W={...S,...U,version:R,versions:P},ee;if(a!==null){ee={};for(let ie of a){let ue=W[ie];if(typeof ue<"u")ee[ie]=ue;else{p.reportWarning(1,`The ${he.pretty(r,ie,he.Type.CODE)} field doesn't exist inside ${G.prettyIdent(r,E)}'s information`),c=!0;continue}}}else this.json||(delete W.dist,delete W.readme,delete W.users),ee=W;p.reportJson(ee),this.json||n.push(ee)}});HK.inspect.styles.name="cyan";for(let p of n)(p!==n[0]||c)&&this.context.stdout.write(` `),this.context.stdout.write(`${(0,HK.inspect)(p,{depth:1/0,colors:!0,compact:!1})} `);return f.exitCode()}};function jK(t){if(Array.isArray(t)){let e=[];for(let r of t)r=jK(r),r&&e.push(r);return e}else if(typeof t=="object"&&t!==null){let e={};for(let r of Object.keys(t)){if(r.startsWith("_"))continue;let s=jK(t[r]);s&&(e[r]=s)}return e}else return t||null}Ge();Ge();Yt();var GK=ut(Vv()),P1=class extends ft{constructor(){super(...arguments);this.scope=ge.String("-s,--scope",{description:"Login to the registry configured for a given scope"});this.publish=ge.Boolean("--publish",!1,{description:"Login to the publish registry"});this.alwaysAuth=ge.Boolean("--always-auth",{description:"Set the npmAlwaysAuth configuration"});this.webLogin=ge.Boolean("--web-login",{description:"Enable web login"})}static{this.paths=[["npm","login"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"store new login info to access the npm registry",details:"\n This command will ask you for your username, password, and 2FA One-Time-Password (when it applies). It will then modify your local configuration (in your home folder, never in the project itself) to reference the new tokens thus generated.\n\n Adding the `-s,--scope` flag will cause the authentication to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\n\n Adding the `--publish` flag will cause the authentication to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\n ",examples:[["Login to the default registry","yarn npm login"],["Login to the registry linked to the @my-scope registry","yarn npm login --scope my-scope"],["Login to the publish registry for the current package","yarn npm login --publish"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=await QL({configuration:r,cwd:this.context.cwd,publish:this.publish,scope:this.scope});return(await Ot.start({configuration:r,stdout:this.context.stdout,includeFooter:!1},async n=>{let c=await gbt({registry:s,configuration:r,report:n,webLogin:this.webLogin,stdin:this.context.stdin,stdout:this.context.stdout});return await mbt(s,c,{alwaysAuth:this.alwaysAuth,scope:this.scope}),n.reportInfo(0,"Successfully logged in")})).exitCode()}};async function QL({scope:t,publish:e,configuration:r,cwd:s}){return t&&e?hi.getScopeRegistry(t,{configuration:r,type:hi.RegistryType.PUBLISH_REGISTRY}):t?hi.getScopeRegistry(t,{configuration:r}):e?hi.getPublishRegistry((await eC(r,s)).manifest,{configuration:r}):hi.getDefaultRegistry({configuration:r})}async function fbt(t,e){let r;try{r=await en.post("/-/v1/login",null,{configuration:e,registry:t,authType:en.AuthType.NO_AUTH,jsonResponse:!0,headers:{"npm-auth-type":"web"}})}catch{return null}return r}async function Abt(t,e){let r=await nn.request(t,null,{configuration:e,jsonResponse:!0});if(r.statusCode===202){let s=r.headers["retry-after"]??"1";return{type:"waiting",sleep:parseInt(s,10)}}return r.statusCode===200?{type:"success",token:r.body.token}:null}async function pbt({registry:t,configuration:e,report:r}){let s=await fbt(t,e);if(!s)return null;if(Ui.openUrl){r.reportInfo(0,"Starting the web login process..."),r.reportSeparator();let{openNow:a}=await(0,GK.prompt)({type:"confirm",name:"openNow",message:"Do you want to try to open your browser now?",required:!0,initial:!0,onCancel:()=>process.exit(130)});r.reportSeparator(),(!a||!await Ui.openUrl(s.loginUrl))&&(r.reportWarning(0,"We failed to automatically open the url; you'll have to open it yourself in your browser of choice:"),r.reportWarning(0,he.pretty(e,s.loginUrl,he.Type.URL)),r.reportSeparator())}for(;;){let a=await Abt(s.doneUrl,e);if(a===null)return null;if(a.type==="waiting")await new Promise(n=>setTimeout(n,a.sleep*1e3));else return a.token}}var hbt=["https://registry.yarnpkg.com","https://registry.npmjs.org"];async function gbt(t){if(t.webLogin??hbt.includes(t.registry)){let e=await pbt(t);if(e!==null)return e}return await dbt(t)}async function dbt({registry:t,configuration:e,report:r,stdin:s,stdout:a}){let n=await ybt({configuration:e,registry:t,report:r,stdin:s,stdout:a}),c=`/-/user/org.couchdb.user:${encodeURIComponent(n.name)}`,f={_id:`org.couchdb.user:${n.name}`,name:n.name,password:n.password,type:"user",roles:[],date:new Date().toISOString()},p={attemptedAs:n.name,configuration:e,registry:t,jsonResponse:!0,authType:en.AuthType.NO_AUTH};try{return(await en.put(c,f,p)).token}catch(P){if(!(P.originalError?.name==="HTTPError"&&P.originalError?.response.statusCode===409))throw P}let h={...p,authType:en.AuthType.NO_AUTH,headers:{authorization:`Basic ${Buffer.from(`${n.name}:${n.password}`).toString("base64")}`}},E=await en.get(c,h);for(let[P,I]of Object.entries(E))(!f[P]||P==="roles")&&(f[P]=I);let C=`${c}/-rev/${f._rev}`;return(await en.put(C,f,h)).token}async function mbt(t,e,{alwaysAuth:r,scope:s}){let a=c=>f=>{let p=je.isIndexableObject(f)?f:{},h=p[c],E=je.isIndexableObject(h)?h:{};return{...p,[c]:{...E,...r!==void 0?{npmAlwaysAuth:r}:{},npmAuthToken:e}}},n=s?{npmScopes:a(s)}:{npmRegistries:a(t)};return await ze.updateHomeConfiguration(n)}async function ybt({configuration:t,registry:e,report:r,stdin:s,stdout:a}){r.reportInfo(0,`Logging in to ${he.pretty(t,e,he.Type.URL)}`);let n=!1;if(e.match(/^https:\/\/npm\.pkg\.github\.com(\/|$)/)&&(r.reportInfo(0,"You seem to be using the GitHub Package Registry. Tokens must be generated with the 'repo', 'write:packages', and 'read:packages' permissions."),n=!0),r.reportSeparator(),t.env.YARN_IS_TEST_ENV)return{name:t.env.YARN_INJECT_NPM_USER||"",password:t.env.YARN_INJECT_NPM_PASSWORD||""};let c=await(0,GK.prompt)([{type:"input",name:"name",message:"Username:",required:!0,onCancel:()=>process.exit(130),stdin:s,stdout:a},{type:"password",name:"password",message:n?"Token:":"Password:",required:!0,onCancel:()=>process.exit(130),stdin:s,stdout:a}]);return r.reportSeparator(),c}Ge();Ge();Yt();var x1=new Set(["npmAuthIdent","npmAuthToken"]),k1=class extends ft{constructor(){super(...arguments);this.scope=ge.String("-s,--scope",{description:"Logout of the registry configured for a given scope"});this.publish=ge.Boolean("--publish",!1,{description:"Logout of the publish registry"});this.all=ge.Boolean("-A,--all",!1,{description:"Logout of all registries"})}static{this.paths=[["npm","logout"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"logout of the npm registry",details:"\n This command will log you out by modifying your local configuration (in your home folder, never in the project itself) to delete all credentials linked to a registry.\n\n Adding the `-s,--scope` flag will cause the deletion to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\n\n Adding the `--publish` flag will cause the deletion to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\n\n Adding the `-A,--all` flag will cause the deletion to be done against all registries and scopes.\n ",examples:[["Logout of the default registry","yarn npm logout"],["Logout of the @my-scope scope","yarn npm logout --scope my-scope"],["Logout of the publish registry for the current package","yarn npm logout --publish"],["Logout of all registries","yarn npm logout --all"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=async()=>{let n=await QL({configuration:r,cwd:this.context.cwd,publish:this.publish,scope:this.scope}),c=await ze.find(this.context.cwd,this.context.plugins),f=G.makeIdent(this.scope??null,"pkg");return!hi.getAuthConfiguration(n,{configuration:c,ident:f}).get("npmAuthToken")};return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{if(this.all&&(await Ibt(),n.reportInfo(0,"Successfully logged out from everything")),this.scope){await Jxe("npmScopes",this.scope),await s()?n.reportInfo(0,`Successfully logged out from ${this.scope}`):n.reportWarning(0,"Scope authentication settings removed, but some other ones settings still apply to it");return}let c=await QL({configuration:r,cwd:this.context.cwd,publish:this.publish});await Jxe("npmRegistries",c),await s()?n.reportInfo(0,`Successfully logged out from ${c}`):n.reportWarning(0,"Registry authentication settings removed, but some other ones settings still apply to it")})).exitCode()}};function Ebt(t,e){let r=t[e];if(!je.isIndexableObject(r))return!1;let s=new Set(Object.keys(r));if([...x1].every(n=>!s.has(n)))return!1;for(let n of x1)s.delete(n);if(s.size===0)return t[e]=void 0,!0;let a={...r};for(let n of x1)delete a[n];return t[e]=a,!0}async function Ibt(){let t=e=>{let r=!1,s=je.isIndexableObject(e)?{...e}:{};s.npmAuthToken&&(delete s.npmAuthToken,r=!0);for(let a of Object.keys(s))Ebt(s,a)&&(r=!0);if(Object.keys(s).length!==0)return r?s:e};return await ze.updateHomeConfiguration({npmRegistries:t,npmScopes:t})}async function Jxe(t,e){return await ze.updateHomeConfiguration({[t]:r=>{let s=je.isIndexableObject(r)?r:{};if(!Object.hasOwn(s,e))return r;let a=s[e],n=je.isIndexableObject(a)?a:{},c=new Set(Object.keys(n));if([...x1].every(p=>!c.has(p)))return r;for(let p of x1)c.delete(p);if(c.size===0)return Object.keys(s).length===1?void 0:{...s,[e]:void 0};let f={};for(let p of x1)f[p]=void 0;return{...s,[e]:{...n,...f}}}})}Ge();Dt();Yt();var Q1=class extends ft{constructor(){super(...arguments);this.access=ge.String("--access",{description:"The access for the published package (public or restricted)"});this.tag=ge.String("--tag","latest",{description:"The tag on the registry that the package should be attached to"});this.tolerateRepublish=ge.Boolean("--tolerate-republish",!1,{description:"Warn and exit when republishing an already existing version of a package"});this.otp=ge.String("--otp",{description:"The OTP token to use with the command"});this.provenance=ge.Boolean("--provenance",!1,{description:"Generate provenance for the package. Only available in GitHub Actions and GitLab CI. Can be set globally through the `npmPublishProvenance` setting or the `YARN_NPM_CONFIG_PROVENANCE` environment variable, or per-package through the `publishConfig.provenance` field in package.json."});this.dryRun=ge.Boolean("-n,--dry-run",!1,{description:"Show what would be published without actually publishing"});this.json=ge.Boolean("--json",!1,{description:"Output the result in JSON format"})}static{this.paths=[["npm","publish"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"publish the active workspace to the npm registry",details:'\n This command will pack the active workspace into a fresh archive and upload it to the npm registry.\n\n The package will by default be attached to the `latest` tag on the registry, but this behavior can be overridden by using the `--tag` option.\n\n Note that for legacy reasons scoped packages are by default published with an access set to `restricted` (aka "private packages"). This requires you to register for a paid npm plan. In case you simply wish to publish a public scoped package to the registry (for free), just add the `--access public` flag. This behavior can be enabled by default through the `npmPublishAccess` settings.\n ',examples:[["Publish the active workspace","yarn npm publish"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);if(a.manifest.private)throw new nt("Private workspaces cannot be published");if(a.manifest.name===null||a.manifest.version===null)throw new nt("Workspaces must have valid names and versions to be published on an external registry");await s.restoreInstallState();let n=a.manifest.name,c=a.manifest.version,f=hi.getPublishRegistry(a.manifest,{configuration:r});return(await Ot.start({configuration:r,stdout:this.context.stdout,json:this.json},async h=>{if(this.tolerateRepublish)try{let E=await en.get(en.getIdentUrl(n),{configuration:r,registry:f,ident:n,jsonResponse:!0});if(!Object.hasOwn(E,"versions"))throw new jt(15,'Registry returned invalid data for - missing "versions" field');if(Object.hasOwn(E.versions,c)){let C=`Registry already knows about version ${c}; skipping.`;h.reportWarning(0,C),h.reportJson({name:G.stringifyIdent(n),version:c,registry:f,warning:C,skipped:!0});return}}catch(E){if(E.originalError?.response?.statusCode!==404)throw E}await In.maybeExecuteWorkspaceLifecycleScript(a,"prepublish",{report:h}),await yA.prepareForPack(a,{report:h},async()=>{let E=await yA.genPackList(a);for(let W of E)h.reportInfo(null,fe.fromPortablePath(W)),h.reportJson({file:fe.fromPortablePath(W)});let C=await yA.genPackStream(a,E),S=await je.bufferStream(C),P=await v1.getGitHead(a.cwd),I=!1,R="";a.manifest.publishConfig&&"provenance"in a.manifest.publishConfig?(I=!!a.manifest.publishConfig.provenance,R=I?"Generating provenance statement because `publishConfig.provenance` field is set.":"Skipping provenance statement because `publishConfig.provenance` field is set to false."):this.provenance?(I=!0,R="Generating provenance statement because `--provenance` flag is set."):r.get("npmPublishProvenance")&&(I=!0,R="Generating provenance statement because `npmPublishProvenance` setting is set."),R&&(h.reportInfo(null,R),h.reportJson({type:"provenance",enabled:I,provenanceMessage:R}));let N=await v1.makePublishBody(a,S,{access:this.access,tag:this.tag,registry:f,gitHead:P,provenance:I});this.dryRun||await en.put(en.getIdentUrl(n),N,{configuration:r,registry:f,ident:n,otp:this.otp,jsonResponse:!0,allowOidc:!!(process.env.CI&&(process.env.GITHUB_ACTIONS||process.env.GITLAB_CI))});let U=this.dryRun?`[DRY RUN] Package would be published to ${f} with tag ${this.tag}`:"Package archive published";h.reportInfo(0,U),h.reportJson({name:G.stringifyIdent(n),version:c,registry:f,tag:this.tag||"latest",files:E.map(W=>fe.fromPortablePath(W)),access:this.access||null,dryRun:this.dryRun,published:!this.dryRun,message:U,provenance:!!I})})})).exitCode()}};Ge();Yt();var Kxe=ut(Ai());Ge();Dt();Yt();var T1=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.package=ge.String({required:!1})}static{this.paths=[["npm","tag","list"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"list all dist-tags of a package",details:` This command will list all tags of a package from the npm registry. If the package is not specified, Yarn will default to the current workspace. `,examples:[["List all tags of package `my-pkg`","yarn npm tag list my-pkg"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n;if(typeof this.package<"u")n=G.parseIdent(this.package);else{if(!a)throw new ar(s.cwd,this.context.cwd);if(!a.manifest.name)throw new nt(`Missing 'name' field in ${fe.fromPortablePath(J.join(a.cwd,Er.manifest))}`);n=a.manifest.name}let c=await Xb(n,r),p={children:je.sortMap(Object.entries(c),([h])=>h).map(([h,E])=>({value:he.tuple(he.Type.RESOLUTION,{descriptor:G.makeDescriptor(n,h),locator:G.makeLocator(n,E)})}))};return xs.emitTree(p,{configuration:r,json:this.json,stdout:this.context.stdout})}};async function Xb(t,e){let r=`/-/package${en.getIdentUrl(t)}/dist-tags`;return en.get(r,{configuration:e,ident:t,jsonResponse:!0,customErrorMessage:en.customPackageError})}var R1=class extends ft{constructor(){super(...arguments);this.package=ge.String();this.tag=ge.String()}static{this.paths=[["npm","tag","add"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"add a tag for a specific version of a package",details:` This command will add a tag to the npm registry for a specific version of a package. If the tag already exists, it will be overwritten. `,examples:[["Add a `beta` tag for version `2.3.4-beta.4` of package `my-pkg`","yarn npm tag add my-pkg@2.3.4-beta.4 beta"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=G.parseDescriptor(this.package,!0),c=n.range;if(!Kxe.default.valid(c))throw new nt(`The range ${he.pretty(r,n.range,he.Type.RANGE)} must be a valid semver version`);let f=hi.getPublishRegistry(a.manifest,{configuration:r}),p=he.pretty(r,n,he.Type.IDENT),h=he.pretty(r,c,he.Type.RANGE),E=he.pretty(r,this.tag,he.Type.CODE);return(await Ot.start({configuration:r,stdout:this.context.stdout},async S=>{let P=await Xb(n,r);Object.hasOwn(P,this.tag)&&P[this.tag]===c&&S.reportWarning(0,`Tag ${E} is already set to version ${h}`);let I=`/-/package${en.getIdentUrl(n)}/dist-tags/${encodeURIComponent(this.tag)}`;await en.put(I,c,{configuration:r,registry:f,ident:n,jsonRequest:!0,jsonResponse:!0}),S.reportInfo(0,`Tag ${E} added to version ${h} of package ${p}`)})).exitCode()}};Ge();Yt();var F1=class extends ft{constructor(){super(...arguments);this.package=ge.String();this.tag=ge.String()}static{this.paths=[["npm","tag","remove"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"remove a tag from a package",details:` This command will remove a tag from a package from the npm registry. `,examples:[["Remove the `beta` tag from package `my-pkg`","yarn npm tag remove my-pkg beta"]]})}async execute(){if(this.tag==="latest")throw new nt("The 'latest' tag cannot be removed.");let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=G.parseIdent(this.package),c=hi.getPublishRegistry(a.manifest,{configuration:r}),f=he.pretty(r,this.tag,he.Type.CODE),p=he.pretty(r,n,he.Type.IDENT),h=await Xb(n,r);if(!Object.hasOwn(h,this.tag))throw new nt(`${f} is not a tag of package ${p}`);return(await Ot.start({configuration:r,stdout:this.context.stdout},async C=>{let S=`/-/package${en.getIdentUrl(n)}/dist-tags/${encodeURIComponent(this.tag)}`;await en.del(S,{configuration:r,registry:c,ident:n,jsonResponse:!0}),C.reportInfo(0,`Tag ${f} removed from package ${p}`)})).exitCode()}};Ge();Ge();Yt();var N1=class extends ft{constructor(){super(...arguments);this.scope=ge.String("-s,--scope",{description:"Print username for the registry configured for a given scope"});this.publish=ge.Boolean("--publish",!1,{description:"Print username for the publish registry"})}static{this.paths=[["npm","whoami"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"display the name of the authenticated user",details:"\n Print the username associated with the current authentication settings to the standard output.\n\n When using `-s,--scope`, the username printed will be the one that matches the authentication settings of the registry associated with the given scope (those settings can be overriden using the `npmRegistries` map, and the registry associated with the scope is configured via the `npmScopes` map).\n\n When using `--publish`, the registry we'll select will by default be the one used when publishing packages (`publishConfig.registry` or `npmPublishRegistry` if available, otherwise we'll fallback to the regular `npmRegistryServer`).\n ",examples:[["Print username for the default registry","yarn npm whoami"],["Print username for the registry on a given scope","yarn npm whoami --scope company"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s;return this.scope&&this.publish?s=hi.getScopeRegistry(this.scope,{configuration:r,type:hi.RegistryType.PUBLISH_REGISTRY}):this.scope?s=hi.getScopeRegistry(this.scope,{configuration:r}):this.publish?s=hi.getPublishRegistry((await eC(r,this.context.cwd)).manifest,{configuration:r}):s=hi.getDefaultRegistry({configuration:r}),(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let c;try{c=await en.get("/-/whoami",{configuration:r,registry:s,authType:en.AuthType.ALWAYS_AUTH,jsonResponse:!0,ident:this.scope?G.makeIdent(this.scope,""):void 0})}catch(f){if(f.response?.statusCode===401||f.response?.statusCode===403){n.reportError(41,"Authentication failed - your credentials may have expired");return}else throw f}n.reportInfo(0,c.username)})).exitCode()}};var Cbt={configuration:{npmPublishAccess:{description:"Default access of the published packages",type:"STRING",default:null},npmPublishProvenance:{description:"Whether to generate provenance for the published packages",type:"BOOLEAN",default:!1},npmAuditExcludePackages:{description:"Array of glob patterns of packages to exclude from npm audit",type:"STRING",default:[],isArray:!0},npmAuditIgnoreAdvisories:{description:"Array of glob patterns of advisory IDs to exclude from npm audit",type:"STRING",default:[],isArray:!0}},commands:[D1,b1,P1,k1,Q1,R1,T1,F1,N1]},wbt=Cbt;var XK={};Vt(XK,{PatchCommand:()=>H1,PatchCommitCommand:()=>_1,PatchFetcher:()=>rP,PatchResolver:()=>nP,default:()=>_bt,patchUtils:()=>gy});Ge();Ge();Dt();eA();var gy={};Vt(gy,{applyPatchFile:()=>RL,diffFolders:()=>KK,ensureUnpatchedDescriptor:()=>WK,ensureUnpatchedLocator:()=>NL,extractPackageToDisk:()=>JK,extractPatchFlags:()=>rke,isParentRequired:()=>VK,isPatchDescriptor:()=>FL,isPatchLocator:()=>Rg,loadPatchFiles:()=>tP,makeDescriptor:()=>OL,makeLocator:()=>YK,makePatchHash:()=>zK,parseDescriptor:()=>$b,parseLocator:()=>eP,parsePatchFile:()=>Zb,unpatchDescriptor:()=>Lbt,unpatchLocator:()=>Mbt});Ge();Dt();Ge();Dt();var Bbt=/^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@.*/;function O1(t){return J.relative(vt.root,J.resolve(vt.root,fe.toPortablePath(t)))}function vbt(t){let e=t.trim().match(Bbt);if(!e)throw new Error(`Bad header line: '${t}'`);return{original:{start:Math.max(Number(e[1]),1),length:Number(e[3]||1)},patched:{start:Math.max(Number(e[4]),1),length:Number(e[6]||1)}}}var Sbt=420,Dbt=493;var zxe=()=>({semverExclusivity:null,diffLineFromPath:null,diffLineToPath:null,oldMode:null,newMode:null,deletedFileMode:null,newFileMode:null,renameFrom:null,renameTo:null,beforeHash:null,afterHash:null,fromPath:null,toPath:null,hunks:null}),bbt=t=>({header:vbt(t),parts:[]}),Pbt={"@":"header","-":"deletion","+":"insertion"," ":"context","\\":"pragma",undefined:"context"};function xbt(t){let e=[],r=zxe(),s="parsing header",a=null,n=null;function c(){a&&(n&&(a.parts.push(n),n=null),r.hunks.push(a),a=null)}function f(){c(),e.push(r),r=zxe()}for(let p=0;p0?"patch":"mode change",W=null;switch(U){case"rename":{if(!E||!C)throw new Error("Bad parser state: rename from & to not given");e.push({type:"rename",semverExclusivity:s,fromPath:O1(E),toPath:O1(C)}),W=C}break;case"file deletion":{let ee=a||I;if(!ee)throw new Error("Bad parse state: no path given for file deletion");e.push({type:"file deletion",semverExclusivity:s,hunk:N&&N[0]||null,path:O1(ee),mode:TL(p),hash:S})}break;case"file creation":{let ee=n||R;if(!ee)throw new Error("Bad parse state: no path given for file creation");e.push({type:"file creation",semverExclusivity:s,hunk:N&&N[0]||null,path:O1(ee),mode:TL(h),hash:P})}break;case"patch":case"mode change":W=R||n;break;default:je.assertNever(U);break}W&&c&&f&&c!==f&&e.push({type:"mode change",semverExclusivity:s,path:O1(W),oldMode:TL(c),newMode:TL(f)}),W&&N&&N.length&&e.push({type:"patch",semverExclusivity:s,path:O1(W),hunks:N,beforeHash:S,afterHash:P})}if(e.length===0)throw new Error("Unable to parse patch file: No changes found. Make sure the patch is a valid UTF8 encoded string");return e}function TL(t){let e=parseInt(t,8)&511;if(e!==Sbt&&e!==Dbt)throw new Error(`Unexpected file mode string: ${t}`);return e}function Zb(t){let e=t.split(/\n/g);return e[e.length-1]===""&&e.pop(),kbt(xbt(e))}function Qbt(t){let e=0,r=0;for(let{type:s,lines:a}of t.parts)switch(s){case"context":r+=a.length,e+=a.length;break;case"deletion":e+=a.length;break;case"insertion":r+=a.length;break;default:je.assertNever(s);break}if(e!==t.header.original.length||r!==t.header.patched.length){let s=a=>a<0?a:`+${a}`;throw new Error(`hunk header integrity check failed (expected @@ ${s(t.header.original.length)} ${s(t.header.patched.length)} @@, got @@ ${s(e)} ${s(r)} @@)`)}}Ge();Dt();var L1=class extends Error{constructor(r,s){super(`Cannot apply hunk #${r+1}`);this.hunk=s}};async function M1(t,e,r){let s=await t.lstatPromise(e),a=await r();typeof a<"u"&&(e=a),await t.lutimesPromise(e,s.atime,s.mtime)}async function RL(t,{baseFs:e=new Yn,dryRun:r=!1,version:s=null}={}){for(let a of t)if(!(a.semverExclusivity!==null&&s!==null&&!Fr.satisfiesWithPrereleases(s,a.semverExclusivity)))switch(a.type){case"file deletion":if(r){if(!e.existsSync(a.path))throw new Error(`Trying to delete a file that doesn't exist: ${a.path}`)}else await M1(e,J.dirname(a.path),async()=>{await e.unlinkPromise(a.path)});break;case"rename":if(r){if(!e.existsSync(a.fromPath))throw new Error(`Trying to move a file that doesn't exist: ${a.fromPath}`)}else await M1(e,J.dirname(a.fromPath),async()=>{await M1(e,J.dirname(a.toPath),async()=>{await M1(e,a.fromPath,async()=>(await e.movePromise(a.fromPath,a.toPath),a.toPath))})});break;case"file creation":if(r){if(e.existsSync(a.path))throw new Error(`Trying to create a file that already exists: ${a.path}`)}else{let n=a.hunk?a.hunk.parts[0].lines.join(` `)+(a.hunk.parts[0].noNewlineAtEndOfFile?"":` `):"";await e.mkdirpPromise(J.dirname(a.path),{chmod:493,utimes:[fi.SAFE_TIME,fi.SAFE_TIME]}),await e.writeFilePromise(a.path,n,{mode:a.mode}),await e.utimesPromise(a.path,fi.SAFE_TIME,fi.SAFE_TIME)}break;case"patch":await M1(e,a.path,async()=>{await Fbt(a,{baseFs:e,dryRun:r})});break;case"mode change":{let c=(await e.statPromise(a.path)).mode;if(Xxe(a.newMode)!==Xxe(c))continue;await M1(e,a.path,async()=>{await e.chmodPromise(a.path,a.newMode)})}break;default:je.assertNever(a);break}}function Xxe(t){return(t&64)>0}function Zxe(t){return t.replace(/\s+$/,"")}function Rbt(t,e){return Zxe(t)===Zxe(e)}async function Fbt({hunks:t,path:e},{baseFs:r,dryRun:s=!1}){let a=await r.statSync(e).mode,c=(await r.readFileSync(e,"utf8")).split(/\n/),f=[],p=0,h=0;for(let C of t){let S=Math.max(h,C.header.patched.start+p),P=Math.max(0,S-h),I=Math.max(0,c.length-S-C.header.original.length),R=Math.max(P,I),N=0,U=0,W=null;for(;N<=R;){if(N<=P&&(U=S-N,W=$xe(C,c,U),W!==null)){N=-N;break}if(N<=I&&(U=S+N,W=$xe(C,c,U),W!==null))break;N+=1}if(W===null)throw new L1(t.indexOf(C),C);f.push(W),p+=N,h=U+C.header.original.length}if(s)return;let E=0;for(let C of f)for(let S of C)switch(S.type){case"splice":{let P=S.index+E;c.splice(P,S.numToDelete,...S.linesToInsert),E+=S.linesToInsert.length-S.numToDelete}break;case"pop":c.pop();break;case"push":c.push(S.line);break;default:je.assertNever(S);break}await r.writeFilePromise(e,c.join(` `),{mode:a})}function $xe(t,e,r){let s=[];for(let a of t.parts)switch(a.type){case"context":case"deletion":{for(let n of a.lines){let c=e[r];if(c==null||!Rbt(c,n))return null;r+=1}a.type==="deletion"&&(s.push({type:"splice",index:r-a.lines.length,numToDelete:a.lines.length,linesToInsert:[]}),a.noNewlineAtEndOfFile&&s.push({type:"push",line:""}))}break;case"insertion":s.push({type:"splice",index:r,numToDelete:0,linesToInsert:a.lines}),a.noNewlineAtEndOfFile&&s.push({type:"pop"});break;default:je.assertNever(a.type);break}return s}var Obt=/^builtin<([^>]+)>$/;function U1(t,e){let{protocol:r,source:s,selector:a,params:n}=G.parseRange(t);if(r!=="patch:")throw new Error("Invalid patch range");if(s===null)throw new Error("Patch locators must explicitly define their source");let c=a?a.split(/&/).map(E=>fe.toPortablePath(E)):[],f=n&&typeof n.locator=="string"?G.parseLocator(n.locator):null,p=n&&typeof n.version=="string"?n.version:null,h=e(s);return{parentLocator:f,sourceItem:h,patchPaths:c,sourceVersion:p}}function FL(t){return t.range.startsWith("patch:")}function Rg(t){return t.reference.startsWith("patch:")}function $b(t){let{sourceItem:e,...r}=U1(t.range,G.parseDescriptor);return{...r,sourceDescriptor:e}}function eP(t){let{sourceItem:e,...r}=U1(t.reference,G.parseLocator);return{...r,sourceLocator:e}}function Lbt(t){let{sourceItem:e}=U1(t.range,G.parseDescriptor);return e}function Mbt(t){let{sourceItem:e}=U1(t.reference,G.parseLocator);return e}function WK(t){if(!FL(t))return t;let{sourceItem:e}=U1(t.range,G.parseDescriptor);return e}function NL(t){if(!Rg(t))return t;let{sourceItem:e}=U1(t.reference,G.parseLocator);return e}function eke({parentLocator:t,sourceItem:e,patchPaths:r,sourceVersion:s,patchHash:a},n){let c=t!==null?{locator:G.stringifyLocator(t)}:{},f=typeof s<"u"?{version:s}:{},p=typeof a<"u"?{hash:a}:{};return G.makeRange({protocol:"patch:",source:n(e),selector:r.join("&"),params:{...f,...p,...c}})}function OL(t,{parentLocator:e,sourceDescriptor:r,patchPaths:s}){return G.makeDescriptor(t,eke({parentLocator:e,sourceItem:r,patchPaths:s},G.stringifyDescriptor))}function YK(t,{parentLocator:e,sourcePackage:r,patchPaths:s,patchHash:a}){return G.makeLocator(t,eke({parentLocator:e,sourceItem:r,sourceVersion:r.version,patchPaths:s,patchHash:a},G.stringifyLocator))}function tke({onAbsolute:t,onRelative:e,onProject:r,onBuiltin:s},a){let n=a.lastIndexOf("!");n!==-1&&(a=a.slice(n+1));let c=a.match(Obt);return c!==null?s(c[1]):a.startsWith("~/")?r(a.slice(2)):J.isAbsolute(a)?t(a):e(a)}function rke(t){let e=t.lastIndexOf("!");return{optional:(e!==-1?new Set(t.slice(0,e).split(/!/)):new Set).has("optional")}}function VK(t){return tke({onAbsolute:()=>!1,onRelative:()=>!0,onProject:()=>!1,onBuiltin:()=>!1},t)}async function tP(t,e,r){let s=t!==null?await r.fetcher.fetch(t,r):null,a=s&&s.localPath?{packageFs:new Sn(vt.root),prefixPath:J.relative(vt.root,s.localPath)}:s;s&&s!==a&&s.releaseFs&&s.releaseFs();let n=await je.releaseAfterUseAsync(async()=>await Promise.all(e.map(async c=>{let f=rke(c),p=await tke({onAbsolute:async h=>await ce.readFilePromise(h,"utf8"),onRelative:async h=>{if(a===null)throw new Error("Assertion failed: The parent locator should have been fetched");return await a.packageFs.readFilePromise(J.join(a.prefixPath,h),"utf8")},onProject:async h=>await ce.readFilePromise(J.join(r.project.cwd,h),"utf8"),onBuiltin:async h=>await r.project.configuration.firstHook(E=>E.getBuiltinPatch,r.project,h)},c);return{...f,source:p}})));for(let c of n)typeof c.source=="string"&&(c.source=c.source.replace(/\r\n?/g,` `));return n}async function JK(t,{cache:e,project:r}){let s=r.storedPackages.get(t.locatorHash);if(typeof s>"u")throw new Error("Assertion failed: Expected the package to be registered");let a=NL(t),n=r.storedChecksums,c=new ki,f=await ce.mktempPromise(),p=J.join(f,"source"),h=J.join(f,"user"),E=J.join(f,".yarn-patch.json"),C=r.configuration.makeFetcher(),S=[];try{let P,I;if(t.locatorHash===a.locatorHash){let R=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c});S.push(()=>R.releaseFs?.()),P=R,I=R}else P=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c}),S.push(()=>P.releaseFs?.()),I=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c}),S.push(()=>I.releaseFs?.());await Promise.all([ce.copyPromise(p,P.prefixPath,{baseFs:P.packageFs}),ce.copyPromise(h,I.prefixPath,{baseFs:I.packageFs}),ce.writeJsonPromise(E,{locator:G.stringifyLocator(t),version:s.version})])}finally{for(let P of S)P()}return ce.detachTemp(f),h}async function KK(t,e){let r=fe.fromPortablePath(t).replace(/\\/g,"/"),s=fe.fromPortablePath(e).replace(/\\/g,"/"),{stdout:a,stderr:n}=await qr.execvp("git",["-c","core.safecrlf=false","diff","--src-prefix=a/","--dst-prefix=b/","--ignore-cr-at-eol","--full-index","--no-index","--no-renames","--text",r,s],{cwd:fe.toPortablePath(process.cwd()),env:{...process.env,GIT_CONFIG_NOSYSTEM:"1",HOME:"",XDG_CONFIG_HOME:"",USERPROFILE:""}});if(n.length>0)throw new Error(`Unable to diff directories. Make sure you have a recent version of 'git' available in PATH. The following error was reported by 'git': ${n}`);let c=r.startsWith("/")?f=>f.slice(1):f=>f;return a.replace(new RegExp(`(a|b)(${je.escapeRegExp(`/${c(r)}/`)})`,"g"),"$1/").replace(new RegExp(`(a|b)${je.escapeRegExp(`/${c(s)}/`)}`,"g"),"$1/").replace(new RegExp(je.escapeRegExp(`${r}/`),"g"),"").replace(new RegExp(je.escapeRegExp(`${s}/`),"g"),"")}function zK(t,e){let r=[];for(let{source:s}of t){if(s===null)continue;let a=Zb(s);for(let n of a){let{semverExclusivity:c,...f}=n;c!==null&&e!==null&&!Fr.satisfiesWithPrereleases(e,c)||r.push(JSON.stringify(f))}}return Nn.makeHash(`${3}`,...r).slice(0,6)}Ge();function nke(t,{configuration:e,report:r}){for(let s of t.parts)for(let a of s.lines)switch(s.type){case"context":r.reportInfo(null,` ${he.pretty(e,a,"grey")}`);break;case"deletion":r.reportError(28,`- ${he.pretty(e,a,he.Type.REMOVED)}`);break;case"insertion":r.reportError(28,`+ ${he.pretty(e,a,he.Type.ADDED)}`);break;default:je.assertNever(s.type)}}var rP=class{supports(e,r){return!!Rg(e)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${G.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.patchPackage(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:G.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async patchPackage(e,r){let{parentLocator:s,sourceLocator:a,sourceVersion:n,patchPaths:c}=eP(e),f=await tP(s,c,r),p=await ce.mktempPromise(),h=J.join(p,"current.zip"),E=await r.fetcher.fetch(a,r),C=G.getIdentVendorPath(e),S=new As(h,{create:!0,level:r.project.configuration.get("compressionLevel")});await je.releaseAfterUseAsync(async()=>{await S.copyPromise(C,E.prefixPath,{baseFs:E.packageFs,stableSort:!0})},E.releaseFs),S.saveAndClose();for(let{source:P,optional:I}of f){if(P===null)continue;let R=new As(h,{level:r.project.configuration.get("compressionLevel")}),N=new Sn(J.resolve(vt.root,C),{baseFs:R});try{await RL(Zb(P),{baseFs:N,version:n})}catch(U){if(!(U instanceof L1))throw U;let W=r.project.configuration.get("enableInlineHunks"),ee=!W&&!I?" (set enableInlineHunks for details)":"",ie=`${G.prettyLocator(r.project.configuration,e)}: ${U.message}${ee}`,ue=le=>{W&&nke(U.hunk,{configuration:r.project.configuration,report:le})};if(R.discardAndClose(),I){r.report.reportWarningOnce(66,ie,{reportExtra:ue});continue}else throw new jt(66,ie,ue)}R.saveAndClose()}return new As(h,{level:r.project.configuration.get("compressionLevel")})}};Ge();var nP=class{supportsDescriptor(e,r){return!!FL(e)}supportsLocator(e,r){return!!Rg(e)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){let{patchPaths:a}=$b(e);return a.every(n=>!VK(n))?e:G.bindDescriptor(e,{locator:G.stringifyLocator(r)})}getResolutionDependencies(e,r){let{sourceDescriptor:s}=$b(e);return{sourceDescriptor:r.project.configuration.normalizeDependency(s)}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{parentLocator:a,patchPaths:n}=$b(e),c=await tP(a,n,s.fetchOptions),f=r.sourceDescriptor;if(typeof f>"u")throw new Error("Assertion failed: The dependency should have been resolved");let p=zK(c,f.version);return[YK(e,{parentLocator:a,sourcePackage:f,patchPaths:n,patchHash:p})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let{sourceLocator:s}=eP(e);return{...await r.resolver.resolve(s,r),...e}}};Ge();Dt();Yt();var _1=class extends ft{constructor(){super(...arguments);this.save=ge.Boolean("-s,--save",!1,{description:"Add the patch to your resolution entries"});this.patchFolder=ge.String()}static{this.paths=[["patch-commit"]]}static{this.usage=ot.Usage({description:"generate a patch out of a directory",details:"\n By default, this will print a patchfile on stdout based on the diff between the folder passed in and the original version of the package. Such file is suitable for consumption with the `patch:` protocol.\n\n With the `-s,--save` option set, the patchfile won't be printed on stdout anymore and will instead be stored within a local file (by default kept within `.yarn/patches`, but configurable via the `patchFolder` setting). A `resolutions` entry will also be added to your top-level manifest, referencing the patched package via the `patch:` protocol.\n\n Note that only folders generated by `yarn patch` are accepted as valid input for `yarn patch-commit`.\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=J.resolve(this.context.cwd,fe.toPortablePath(this.patchFolder)),c=J.join(n,"../source"),f=J.join(n,"../.yarn-patch.json");if(!ce.existsSync(c))throw new nt("The argument folder didn't get created by 'yarn patch'");let p=await KK(c,n),h=await ce.readJsonPromise(f),E=G.parseLocator(h.locator,!0);if(!s.storedPackages.has(E.locatorHash))throw new nt("No package found in the project for the given locator");if(!this.save){this.context.stdout.write(p);return}let C=r.get("patchFolder"),S=J.join(C,`${G.slugifyLocator(E)}.patch`);await ce.mkdirPromise(C,{recursive:!0}),await ce.writeFilePromise(S,p);let P=[],I=new Map;for(let R of s.storedPackages.values()){if(G.isVirtualLocator(R))continue;let N=R.dependencies.get(E.identHash);if(!N)continue;let U=G.ensureDevirtualizedDescriptor(N),W=WK(U),ee=s.storedResolutions.get(W.descriptorHash);if(!ee)throw new Error("Assertion failed: Expected the resolution to have been registered");if(!s.storedPackages.get(ee))throw new Error("Assertion failed: Expected the package to have been registered");let ue=s.tryWorkspaceByLocator(R);if(ue)P.push(ue);else{let le=s.originalPackages.get(R.locatorHash);if(!le)throw new Error("Assertion failed: Expected the original package to have been registered");let me=le.dependencies.get(N.identHash);if(!me)throw new Error("Assertion failed: Expected the original dependency to have been registered");I.set(me.descriptorHash,me)}}for(let R of P)for(let N of Ut.hardDependencies){let U=R.manifest[N].get(E.identHash);if(!U)continue;let W=OL(U,{parentLocator:null,sourceDescriptor:G.convertLocatorToDescriptor(E),patchPaths:[J.join(Er.home,J.relative(s.cwd,S))]});R.manifest[N].set(U.identHash,W)}for(let R of I.values()){let N=OL(R,{parentLocator:null,sourceDescriptor:G.convertLocatorToDescriptor(E),patchPaths:[J.join(Er.home,J.relative(s.cwd,S))]});s.topLevelWorkspace.manifest.resolutions.push({pattern:{descriptor:{fullName:G.stringifyIdent(N),description:R.range}},reference:N.range})}await s.persist()}};Ge();Dt();Yt();var H1=class extends ft{constructor(){super(...arguments);this.update=ge.Boolean("-u,--update",!1,{description:"Reapply local patches that already apply to this packages"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.package=ge.String()}static{this.paths=[["patch"]]}static{this.usage=ot.Usage({description:"prepare a package for patching",details:"\n This command will cause a package to be extracted in a temporary directory intended to be editable at will.\n\n Once you're done with your changes, run `yarn patch-commit -s path` (with `path` being the temporary directory you received) to generate a patchfile and register it into your top-level manifest via the `patch:` protocol. Run `yarn patch-commit -h` for more details.\n\n Calling the command when you already have a patch won't import it by default (in other words, the default behavior is to reset existing patches). However, adding the `-u,--update` flag will import any current patch.\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let c=G.parseLocator(this.package);if(c.reference==="unknown"){let f=je.mapAndFilter([...s.storedPackages.values()],p=>p.identHash!==c.identHash?je.mapAndFilter.skip:G.isVirtualLocator(p)?je.mapAndFilter.skip:Rg(p)!==this.update?je.mapAndFilter.skip:p);if(f.length===0)throw new nt("No package found in the project for the given locator");if(f.length>1)throw new nt(`Multiple candidate packages found; explicitly choose one of them (use \`yarn why \` to get more information as to who depends on them): ${f.map(p=>` - ${G.prettyLocator(r,p)}`).join("")}`);c=f[0]}if(!s.storedPackages.has(c.locatorHash))throw new nt("No package found in the project for the given locator");await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async f=>{let p=NL(c),h=await JK(c,{cache:n,project:s});f.reportJson({locator:G.stringifyLocator(p),path:fe.fromPortablePath(h)});let E=this.update?" along with its current modifications":"";f.reportInfo(0,`Package ${G.prettyLocator(r,p)} got extracted with success${E}!`),f.reportInfo(0,`You can now edit the following folder: ${he.pretty(r,fe.fromPortablePath(h),"magenta")}`),f.reportInfo(0,`Once you are done run ${he.pretty(r,`yarn patch-commit -s ${process.platform==="win32"?'"':""}${fe.fromPortablePath(h)}${process.platform==="win32"?'"':""}`,"cyan")} and Yarn will store a patchfile based on your changes.`)})}};var Ubt={configuration:{enableInlineHunks:{description:"If true, the installs will print unmatched patch hunks",type:"BOOLEAN",default:!1},patchFolder:{description:"Folder where the patch files must be written",type:"ABSOLUTE_PATH",default:"./.yarn/patches"}},commands:[_1,H1],fetchers:[rP],resolvers:[nP]},_bt=Ubt;var ez={};Vt(ez,{PnpmLinker:()=>iP,default:()=>Ybt});Ge();Dt();Yt();var iP=class{getCustomDataKey(){return JSON.stringify({name:"PnpmLinker",version:3})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error("Assertion failed: Expected the pnpm linker to be enabled");let s=this.getCustomDataKey(),a=r.project.linkersCustomData.get(s);if(!a)throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let n=a.pathsByLocator.get(e.locatorHash);if(typeof n>"u")throw new nt(`Couldn't find ${G.prettyLocator(r.project.configuration,e)} in the currently installed pnpm map - running an install might help`);return n.packageLocation}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=this.getCustomDataKey(),a=r.project.linkersCustomData.get(s);if(!a)throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let n=e.match(/(^.*\/node_modules\/(@[^/]*\/)?[^/]+)(\/.*$)/);if(n){let p=a.locatorByPath.get(n[1]);if(p)return p}let c=e,f=e;do{f=c,c=J.dirname(f);let p=a.locatorByPath.get(f);if(p)return p}while(c!==f);return null}makeInstaller(e){return new ZK(e)}isEnabled(e){return e.project.configuration.get("nodeLinker")==="pnpm"}},ZK=class{constructor(e){this.opts=e;this.asyncActions=new je.AsyncActions(10);this.customData={pathsByLocator:new Map,locatorByPath:new Map};this.indexFolderPromise=$P(ce,{indexPath:J.join(e.project.configuration.get("globalFolder"),"index")})}attachCustomData(e){}async installPackage(e,r,s){switch(e.linkType){case"SOFT":return this.installPackageSoft(e,r,s);case"HARD":return this.installPackageHard(e,r,s)}throw new Error("Assertion failed: Unsupported package link type")}async installPackageSoft(e,r,s){let a=J.resolve(r.packageFs.getRealPath(),r.prefixPath),n=this.opts.project.tryWorkspaceByLocator(e)?J.join(a,Er.nodeModules):null;return this.customData.pathsByLocator.set(e.locatorHash,{packageLocation:a,dependenciesLocation:n}),{packageLocation:a,buildRequest:null}}async installPackageHard(e,r,s){let a=jbt(e,{project:this.opts.project}),n=a.packageLocation;this.customData.locatorByPath.set(n,G.stringifyLocator(e)),this.customData.pathsByLocator.set(e.locatorHash,a),s.holdFetchResult(this.asyncActions.set(e.locatorHash,async()=>{await ce.mkdirPromise(n,{recursive:!0}),await ce.copyPromise(n,r.prefixPath,{baseFs:r.packageFs,overwrite:!1,linkStrategy:{type:"HardlinkFromIndex",indexPath:await this.indexFolderPromise,autoRepair:!0}})}));let f=G.isVirtualLocator(e)?G.devirtualizeLocator(e):e,p={manifest:await Ut.tryFind(r.prefixPath,{baseFs:r.packageFs})??new Ut,misc:{hasBindingGyp:gA.hasBindingGyp(r)}},h=this.opts.project.getDependencyMeta(f,e.version),E=gA.extractBuildRequest(e,p,h,{configuration:this.opts.project.configuration});return{packageLocation:n,buildRequest:E}}async attachInternalDependencies(e,r){if(this.opts.project.configuration.get("nodeLinker")!=="pnpm"||!ike(e,{project:this.opts.project}))return;let s=this.customData.pathsByLocator.get(e.locatorHash);if(typeof s>"u")throw new Error(`Assertion failed: Expected the package to have been registered (${G.stringifyLocator(e)})`);let{dependenciesLocation:a}=s;a&&this.asyncActions.reduce(e.locatorHash,async n=>{await ce.mkdirPromise(a,{recursive:!0});let c=await Gbt(a),f=new Map(c),p=[n],h=(C,S)=>{let P=S;ike(S,{project:this.opts.project})||(this.opts.report.reportWarningOnce(0,"The pnpm linker doesn't support providing different versions to workspaces' peer dependencies"),P=G.devirtualizeLocator(S));let I=this.customData.pathsByLocator.get(P.locatorHash);if(typeof I>"u")throw new Error(`Assertion failed: Expected the package to have been registered (${G.stringifyLocator(S)})`);let R=G.stringifyIdent(C),N=J.join(a,R),U=J.relative(J.dirname(N),I.packageLocation),W=f.get(R);f.delete(R),p.push(Promise.resolve().then(async()=>{if(W){if(W.isSymbolicLink()&&await ce.readlinkPromise(N)===U)return;await ce.removePromise(N)}await ce.mkdirpPromise(J.dirname(N)),process.platform=="win32"&&this.opts.project.configuration.get("winLinkType")==="junctions"?await ce.symlinkPromise(I.packageLocation,N,"junction"):await ce.symlinkPromise(U,N)}))},E=!1;for(let[C,S]of r)C.identHash===e.identHash&&(E=!0),h(C,S);!E&&!this.opts.project.tryWorkspaceByLocator(e)&&h(G.convertLocatorToDescriptor(e),e),p.push(qbt(a,f)),await Promise.all(p)})}async attachExternalDependents(e,r){throw new Error("External dependencies haven't been implemented for the pnpm linker")}async finalizeInstall(){let e=ske(this.opts.project);if(this.opts.project.configuration.get("nodeLinker")!=="pnpm")await ce.removePromise(e);else{let r;try{r=new Set(await ce.readdirPromise(e))}catch{r=new Set}for(let{dependenciesLocation:s}of this.customData.pathsByLocator.values()){if(!s)continue;let a=J.contains(e,s);if(a===null)continue;let[n]=a.split(J.sep);r.delete(n)}await Promise.all([...r].map(async s=>{await ce.removePromise(J.join(e,s))}))}return await this.asyncActions.wait(),await $K(e),this.opts.project.configuration.get("nodeLinker")!=="node-modules"&&await $K(Hbt(this.opts.project)),{customData:this.customData}}};function Hbt(t){return J.join(t.cwd,Er.nodeModules)}function ske(t){return t.configuration.get("pnpmStoreFolder")}function jbt(t,{project:e}){let r=G.slugifyLocator(t),s=ske(e),a=J.join(s,r,"package"),n=J.join(s,r,Er.nodeModules);return{packageLocation:a,dependenciesLocation:n}}function ike(t,{project:e}){return!G.isVirtualLocator(t)||!e.tryWorkspaceByLocator(t)}async function Gbt(t){let e=new Map,r=[];try{r=await ce.readdirPromise(t,{withFileTypes:!0})}catch(s){if(s.code!=="ENOENT")throw s}try{for(let s of r)if(!s.name.startsWith("."))if(s.name.startsWith("@")){let a=await ce.readdirPromise(J.join(t,s.name),{withFileTypes:!0});if(a.length===0)e.set(s.name,s);else for(let n of a)e.set(`${s.name}/${n.name}`,n)}else e.set(s.name,s)}catch(s){if(s.code!=="ENOENT")throw s}return e}async function qbt(t,e){let r=[],s=new Set;for(let a of e.keys()){r.push(ce.removePromise(J.join(t,a)));let n=G.tryParseIdent(a)?.scope;n&&s.add(`@${n}`)}return Promise.all(r).then(()=>Promise.all([...s].map(a=>$K(J.join(t,a)))))}async function $K(t){try{await ce.rmdirPromise(t)}catch(e){if(e.code!=="ENOENT"&&e.code!=="ENOTEMPTY"&&e.code!=="EBUSY")throw e}}var Wbt={configuration:{pnpmStoreFolder:{description:"By default, the store is stored in the 'node_modules/.store' of the project. Sometimes in CI scenario's it is convenient to store this in a different location so it can be cached and reused.",type:"ABSOLUTE_PATH",default:"./node_modules/.store"}},linkers:[iP]},Ybt=Wbt;var az={};Vt(az,{StageCommand:()=>j1,default:()=>nPt,stageUtils:()=>ML});Ge();Dt();Yt();Ge();Dt();var ML={};Vt(ML,{ActionType:()=>tz,checkConsensus:()=>LL,expandDirectory:()=>iz,findConsensus:()=>sz,findVcsRoot:()=>rz,genCommitMessage:()=>oz,getCommitPrefix:()=>oke,isYarnFile:()=>nz});Dt();var tz=(n=>(n[n.CREATE=0]="CREATE",n[n.DELETE=1]="DELETE",n[n.ADD=2]="ADD",n[n.REMOVE=3]="REMOVE",n[n.MODIFY=4]="MODIFY",n))(tz||{});async function rz(t,{marker:e}){do if(!ce.existsSync(J.join(t,e)))t=J.dirname(t);else return t;while(t!=="/");return null}function nz(t,{roots:e,names:r}){if(r.has(J.basename(t)))return!0;do if(!e.has(t))t=J.dirname(t);else return!0;while(t!=="/");return!1}function iz(t){let e=[],r=[t];for(;r.length>0;){let s=r.pop(),a=ce.readdirSync(s);for(let n of a){let c=J.resolve(s,n);ce.lstatSync(c).isDirectory()?r.push(c):e.push(c)}}return e}function LL(t,e){let r=0,s=0;for(let a of t)a!=="wip"&&(e.test(a)?r+=1:s+=1);return r>=s}function sz(t){let e=LL(t,/^(\w\(\w+\):\s*)?\w+s/),r=LL(t,/^(\w\(\w+\):\s*)?[A-Z]/),s=LL(t,/^\w\(\w+\):/);return{useThirdPerson:e,useUpperCase:r,useComponent:s}}function oke(t){return t.useComponent?"chore(yarn): ":""}var Vbt=new Map([[0,"create"],[1,"delete"],[2,"add"],[3,"remove"],[4,"update"]]);function oz(t,e){let r=oke(t),s=[],a=e.slice().sort((n,c)=>n[0]-c[0]);for(;a.length>0;){let[n,c]=a.shift(),f=Vbt.get(n);t.useUpperCase&&s.length===0&&(f=`${f[0].toUpperCase()}${f.slice(1)}`),t.useThirdPerson&&(f+="s");let p=[c];for(;a.length>0&&a[0][0]===n;){let[,E]=a.shift();p.push(E)}p.sort();let h=p.shift();p.length===1?h+=" (and one other)":p.length>1&&(h+=` (and ${p.length} others)`),s.push(`${f} ${h}`)}return`${r}${s.join(", ")}`}var Jbt="Commit generated via `yarn stage`",Kbt=11;async function ake(t){let{code:e,stdout:r}=await qr.execvp("git",["log","-1","--pretty=format:%H"],{cwd:t});return e===0?r.trim():null}async function zbt(t,e){let r=[],s=e.filter(h=>J.basename(h.path)==="package.json");for(let{action:h,path:E}of s){let C=J.relative(t,E);if(h===4){let S=await ake(t),{stdout:P}=await qr.execvp("git",["show",`${S}:${C}`],{cwd:t,strict:!0}),I=await Ut.fromText(P),R=await Ut.fromFile(E),N=new Map([...R.dependencies,...R.devDependencies]),U=new Map([...I.dependencies,...I.devDependencies]);for(let[W,ee]of U){let ie=G.stringifyIdent(ee),ue=N.get(W);ue?ue.range!==ee.range&&r.push([4,`${ie} to ${ue.range}`]):r.push([3,ie])}for(let[W,ee]of N)U.has(W)||r.push([2,G.stringifyIdent(ee)])}else if(h===0){let S=await Ut.fromFile(E);S.name?r.push([0,G.stringifyIdent(S.name)]):r.push([0,"a package"])}else if(h===1){let S=await ake(t),{stdout:P}=await qr.execvp("git",["show",`${S}:${C}`],{cwd:t,strict:!0}),I=await Ut.fromText(P);I.name?r.push([1,G.stringifyIdent(I.name)]):r.push([1,"a package"])}else throw new Error("Assertion failed: Unsupported action type")}let{code:a,stdout:n}=await qr.execvp("git",["log",`-${Kbt}`,"--pretty=format:%s"],{cwd:t}),c=a===0?n.split(/\n/g).filter(h=>h!==""):[],f=sz(c);return oz(f,r)}var Xbt={0:[" A ","?? "],4:[" M "],1:[" D "]},Zbt={0:["A "],4:["M "],1:["D "]},lke={async findRoot(t){return await rz(t,{marker:".git"})},async filterChanges(t,e,r,s){let{stdout:a}=await qr.execvp("git",["status","-s"],{cwd:t,strict:!0}),n=a.toString().split(/\n/g),c=s?.staged?Zbt:Xbt;return[].concat(...n.map(p=>{if(p==="")return[];let h=p.slice(0,3),E=J.resolve(t,p.slice(3));if(!s?.staged&&h==="?? "&&p.endsWith("/"))return iz(E).map(C=>({action:0,path:C}));{let S=[0,4,1].find(P=>c[P].includes(h));return S!==void 0?[{action:S,path:E}]:[]}})).filter(p=>nz(p.path,{roots:e,names:r}))},async genCommitMessage(t,e){return await zbt(t,e)},async makeStage(t,e){let r=e.map(s=>fe.fromPortablePath(s.path));await qr.execvp("git",["add","--",...r],{cwd:t,strict:!0})},async makeCommit(t,e,r){let s=e.map(a=>fe.fromPortablePath(a.path));await qr.execvp("git",["add","-N","--",...s],{cwd:t,strict:!0}),await qr.execvp("git",["commit","-m",`${r} ${Jbt} `,"--",...s],{cwd:t,strict:!0})},async makeReset(t,e){let r=e.map(s=>fe.fromPortablePath(s.path));await qr.execvp("git",["reset","HEAD","--",...r],{cwd:t,strict:!0})}};var $bt=[lke],j1=class extends ft{constructor(){super(...arguments);this.commit=ge.Boolean("-c,--commit",!1,{description:"Commit the staged files"});this.reset=ge.Boolean("-r,--reset",!1,{description:"Remove all files from the staging area"});this.dryRun=ge.Boolean("-n,--dry-run",!1,{description:"Print the commit message and the list of modified files without staging / committing"});this.update=ge.Boolean("-u,--update",!1,{hidden:!0})}static{this.paths=[["stage"]]}static{this.usage=ot.Usage({description:"add all yarn files to your vcs",details:"\n This command will add to your staging area the files belonging to Yarn (typically any modified `package.json` and `.yarnrc.yml` files, but also linker-generated files, cache data, etc). It will take your ignore list into account, so the cache files won't be added if the cache is ignored in a `.gitignore` file (assuming you use Git).\n\n Running `--reset` will instead remove them from the staging area (the changes will still be there, but won't be committed until you stage them back).\n\n Since the staging area is a non-existent concept in Mercurial, Yarn will always create a new commit when running this command on Mercurial repositories. You can get this behavior when using Git by using the `--commit` flag which will directly create a commit.\n ",examples:[["Adds all modified project files to the staging area","yarn stage"],["Creates a new commit containing all modified project files","yarn stage --commit"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),{driver:a,root:n}=await ePt(s.cwd),c=[r.get("cacheFolder"),r.get("globalFolder"),r.get("virtualFolder"),r.get("yarnPath")];await r.triggerHook(C=>C.populateYarnPaths,s,C=>{c.push(C)});let f=new Set;for(let C of c)for(let S of tPt(n,C))f.add(S);let p=new Set([r.get("rcFilename"),Er.lockfile,Er.manifest]),h=await a.filterChanges(n,f,p),E=await a.genCommitMessage(n,h);if(this.dryRun)if(this.commit)this.context.stdout.write(`${E} `);else for(let C of h)this.context.stdout.write(`${fe.fromPortablePath(C.path)} `);else if(this.reset){let C=await a.filterChanges(n,f,p,{staged:!0});C.length===0?this.context.stdout.write("No staged changes found!"):await a.makeReset(n,C)}else h.length===0?this.context.stdout.write("No changes found!"):this.commit?await a.makeCommit(n,h,E):(await a.makeStage(n,h),this.context.stdout.write(E))}};async function ePt(t){let e=null,r=null;for(let s of $bt)if((r=await s.findRoot(t))!==null){e=s;break}if(e===null||r===null)throw new nt("No stage driver has been found for your current project");return{driver:e,root:r}}function tPt(t,e){let r=[];if(e===null)return r;for(;;){(e===t||e.startsWith(`${t}/`))&&r.push(e);let s;try{s=ce.statSync(e)}catch{break}if(s.isSymbolicLink())e=J.resolve(J.dirname(e),ce.readlinkSync(e));else break}return r}var rPt={commands:[j1]},nPt=rPt;var lz={};Vt(lz,{default:()=>fPt});Ge();Ge();Dt();var fke=ut(Ai());Ge();var cke=ut(g9()),iPt="e8e1bd300d860104bb8c58453ffa1eb4",sPt="OFCNCOG2CU",uke=async(t,e)=>{let r=G.stringifyIdent(t),a=oPt(e).initIndex("npm-search");try{return(await a.getObject(r,{attributesToRetrieve:["types"]})).types?.ts==="definitely-typed"}catch{return!1}},oPt=t=>(0,cke.default)(sPt,iPt,{requester:{async send(r){try{let s=await nn.request(r.url,r.data||null,{configuration:t,headers:r.headers});return{content:s.body,isTimedOut:!1,status:s.statusCode}}catch(s){return{content:s.response.body,isTimedOut:!1,status:s.response.statusCode}}}}});var Ake=t=>t.scope?`${t.scope}__${t.name}`:`${t.name}`,aPt=async(t,e,r,s)=>{if(r.scope==="types")return;let{project:a}=t,{configuration:n}=a;if(!(n.get("tsEnableAutoTypes")??(ce.existsSync(J.join(t.cwd,"tsconfig.json"))||ce.existsSync(J.join(a.cwd,"tsconfig.json")))))return;let f=n.makeResolver(),p={project:a,resolver:f,report:new ki};if(!await uke(r,n))return;let E=Ake(r),C=G.parseRange(r.range).selector;if(!Fr.validRange(C)){let N=n.normalizeDependency(r),U=await f.getCandidates(N,{},p);C=G.parseRange(U[0].reference).selector}let S=fke.default.coerce(C);if(S===null)return;let P=`${Xu.Modifier.CARET}${S.major}`,I=G.makeDescriptor(G.makeIdent("types",E),P),R=je.mapAndFind(a.workspaces,N=>{let U=N.manifest.dependencies.get(r.identHash)?.descriptorHash,W=N.manifest.devDependencies.get(r.identHash)?.descriptorHash;if(U!==r.descriptorHash&&W!==r.descriptorHash)return je.mapAndFind.skip;let ee=[];for(let ie of Ut.allDependencies){let ue=N.manifest[ie].get(I.identHash);typeof ue>"u"||ee.push([ie,ue])}return ee.length===0?je.mapAndFind.skip:ee});if(typeof R<"u")for(let[N,U]of R)t.manifest[N].set(U.identHash,U);else{try{let N=n.normalizeDependency(I);if((await f.getCandidates(N,{},p)).length===0)return}catch{return}t.manifest[Xu.Target.DEVELOPMENT].set(I.identHash,I)}},lPt=async(t,e,r)=>{if(r.scope==="types")return;let{project:s}=t,{configuration:a}=s;if(!(a.get("tsEnableAutoTypes")??(ce.existsSync(J.join(t.cwd,"tsconfig.json"))||ce.existsSync(J.join(s.cwd,"tsconfig.json")))))return;let c=Ake(r),f=G.makeIdent("types",c);for(let p of Ut.allDependencies)typeof t.manifest[p].get(f.identHash)>"u"||t.manifest[p].delete(f.identHash)},cPt=(t,e)=>{e.publishConfig&&e.publishConfig.typings&&(e.typings=e.publishConfig.typings),e.publishConfig&&e.publishConfig.types&&(e.types=e.publishConfig.types)},uPt={configuration:{tsEnableAutoTypes:{description:"Whether Yarn should auto-install @types/ dependencies on 'yarn add'",type:"BOOLEAN",isNullable:!0,default:null}},hooks:{afterWorkspaceDependencyAddition:aPt,afterWorkspaceDependencyRemoval:lPt,beforeWorkspacePacking:cPt}},fPt=uPt;var pz={};Vt(pz,{VersionApplyCommand:()=>Y1,VersionCheckCommand:()=>V1,VersionCommand:()=>J1,default:()=>dPt,versionUtils:()=>W1});Ge();Ge();Yt();var W1={};Vt(W1,{Decision:()=>G1,applyPrerelease:()=>pke,applyReleases:()=>Az,applyStrategy:()=>sP,clearVersionFiles:()=>cz,getUndecidedDependentWorkspaces:()=>aP,getUndecidedWorkspaces:()=>UL,openVersionFile:()=>q1,requireMoreDecisions:()=>pPt,resolveVersionFiles:()=>oP,suggestStrategy:()=>fz,updateVersionFiles:()=>uz,validateReleaseDecision:()=>dy});Ge();Dt();wc();Yt();ql();var kA=ut(Ai()),APt=/^(>=|[~^]|)(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/,G1=(h=>(h.UNDECIDED="undecided",h.DECLINE="decline",h.MAJOR="major",h.MINOR="minor",h.PATCH="patch",h.PREMAJOR="premajor",h.PREMINOR="preminor",h.PREPATCH="prepatch",h.PRERELEASE="prerelease",h))(G1||{});function dy(t){let e=kA.default.valid(t);return e||je.validateEnum(O4(G1,"UNDECIDED"),t)}async function oP(t,{prerelease:e=null}={}){let r=new Map,s=t.configuration.get("deferredVersionFolder");if(!ce.existsSync(s))return r;let a=await ce.readdirPromise(s);for(let n of a){if(!n.endsWith(".yml"))continue;let c=J.join(s,n),f=await ce.readFilePromise(c,"utf8"),p=ls(f);for(let[h,E]of Object.entries(p.releases||{})){if(E==="decline")continue;let C=G.parseIdent(h),S=t.tryWorkspaceByIdent(C);if(S===null)throw new Error(`Assertion failed: Expected a release definition file to only reference existing workspaces (${J.basename(c)} references ${h})`);if(S.manifest.version===null)throw new Error(`Assertion failed: Expected the workspace to have a version (${G.prettyLocator(t.configuration,S.anchoredLocator)})`);let P=S.manifest.raw.stableVersion??S.manifest.version,I=r.get(S),R=sP(E==="prerelease"?S.manifest.version:P,dy(E));if(R===null)throw new Error(`Assertion failed: Expected ${P} to support being bumped via strategy ${E}`);let N=typeof I<"u"?kA.default.gt(R,I)?R:I:R;r.set(S,N)}}return e&&(r=new Map([...r].map(([n,c])=>[n,pke(c,{current:n.manifest.version,prerelease:e})]))),r}async function cz(t){let e=t.configuration.get("deferredVersionFolder");ce.existsSync(e)&&await ce.removePromise(e)}async function uz(t,e){let r=new Set(e),s=t.configuration.get("deferredVersionFolder");if(!ce.existsSync(s))return;let a=await ce.readdirPromise(s);for(let n of a){if(!n.endsWith(".yml"))continue;let c=J.join(s,n),f=await ce.readFilePromise(c,"utf8"),p=ls(f),h=p?.releases;if(h){for(let E of Object.keys(h)){let C=G.parseIdent(E),S=t.tryWorkspaceByIdent(C);(S===null||r.has(S))&&delete p.releases[E]}Object.keys(p.releases).length>0?await ce.changeFilePromise(c,nl(new nl.PreserveOrdering(p))):await ce.unlinkPromise(c)}}}async function q1(t,{allowEmpty:e=!1}={}){let r=t.configuration;if(r.projectCwd===null)throw new nt("This command can only be run from within a Yarn project");let s=await ka.fetchRoot(r.projectCwd),a=s!==null?await ka.fetchBase(s,{baseRefs:r.get("changesetBaseRefs")}):null,n=s!==null?await ka.fetchChangedFiles(s,{base:a.hash,project:t}):[],c=r.get("deferredVersionFolder"),f=n.filter(P=>J.contains(c,P)!==null);if(f.length>1)throw new nt(`Your current branch contains multiple versioning files; this isn't supported: - ${f.map(P=>fe.fromPortablePath(P)).join(` - `)}`);let p=new Set(je.mapAndFilter(n,P=>{let I=t.tryWorkspaceByFilePath(P);return I===null?je.mapAndFilter.skip:I}));if(f.length===0&&p.size===0&&!e)return null;let h=f.length===1?f[0]:J.join(c,`${Nn.makeHash(Math.random().toString()).slice(0,8)}.yml`),E=ce.existsSync(h)?await ce.readFilePromise(h,"utf8"):"{}",C=ls(E),S=new Map;for(let P of C.declined||[]){let I=G.parseIdent(P),R=t.getWorkspaceByIdent(I);S.set(R,"decline")}for(let[P,I]of Object.entries(C.releases||{})){let R=G.parseIdent(P),N=t.getWorkspaceByIdent(R);S.set(N,dy(I))}return{project:t,root:s,baseHash:a!==null?a.hash:null,baseTitle:a!==null?a.title:null,changedFiles:new Set(n),changedWorkspaces:p,releaseRoots:new Set([...p].filter(P=>P.manifest.version!==null)),releases:S,async saveAll(){let P={},I=[],R=[];for(let N of t.workspaces){if(N.manifest.version===null)continue;let U=G.stringifyIdent(N.anchoredLocator),W=S.get(N);W==="decline"?I.push(U):typeof W<"u"?P[U]=dy(W):p.has(N)&&R.push(U)}await ce.mkdirPromise(J.dirname(h),{recursive:!0}),await ce.changeFilePromise(h,nl(new nl.PreserveOrdering({releases:Object.keys(P).length>0?P:void 0,declined:I.length>0?I:void 0,undecided:R.length>0?R:void 0})))}}}function pPt(t){return UL(t).size>0||aP(t).length>0}function UL(t){let e=new Set;for(let r of t.changedWorkspaces)r.manifest.version!==null&&(t.releases.has(r)||e.add(r));return e}function aP(t,{include:e=new Set}={}){let r=[],s=new Map(je.mapAndFilter([...t.releases],([n,c])=>c==="decline"?je.mapAndFilter.skip:[n.anchoredLocator.locatorHash,n])),a=new Map(je.mapAndFilter([...t.releases],([n,c])=>c!=="decline"?je.mapAndFilter.skip:[n.anchoredLocator.locatorHash,n]));for(let n of t.project.workspaces)if(!(!e.has(n)&&(a.has(n.anchoredLocator.locatorHash)||s.has(n.anchoredLocator.locatorHash)))&&n.manifest.version!==null)for(let c of Ut.hardDependencies)for(let f of n.manifest.getForScope(c).values()){let p=t.project.tryWorkspaceByDescriptor(f);p!==null&&s.has(p.anchoredLocator.locatorHash)&&r.push([n,p])}return r}function fz(t,e){let r=kA.default.clean(e);for(let s of Object.values(G1))if(s!=="undecided"&&s!=="decline"&&kA.default.inc(t,s)===r)return s;return null}function sP(t,e){if(kA.default.valid(e))return e;if(t===null)throw new nt(`Cannot apply the release strategy "${e}" unless the workspace already has a valid version`);if(!kA.default.valid(t))throw new nt(`Cannot apply the release strategy "${e}" on a non-semver version (${t})`);let r=kA.default.inc(t,e);if(r===null)throw new nt(`Cannot apply the release strategy "${e}" on the specified version (${t})`);return r}function Az(t,e,{report:r,exact:s}){let a=new Map;for(let n of t.workspaces)for(let c of Ut.allDependencies)for(let f of n.manifest[c].values()){let p=t.tryWorkspaceByDescriptor(f);if(p===null||!e.has(p))continue;je.getArrayWithDefault(a,p).push([n,c,f.identHash])}for(let[n,c]of e){let f=n.manifest.version;n.manifest.version=c,kA.default.prerelease(c)===null?delete n.manifest.raw.stableVersion:n.manifest.raw.stableVersion||(n.manifest.raw.stableVersion=f);let p=n.manifest.name!==null?G.stringifyIdent(n.manifest.name):null;r.reportInfo(0,`${G.prettyLocator(t.configuration,n.anchoredLocator)}: Bumped to ${c}`),r.reportJson({cwd:fe.fromPortablePath(n.cwd),ident:p,oldVersion:f,newVersion:c});let h=a.get(n);if(!(typeof h>"u"))for(let[E,C,S]of h){let P=E.manifest[C].get(S);if(typeof P>"u")throw new Error("Assertion failed: The dependency should have existed");let I=P.range,R=!1;if(I.startsWith(Ei.protocol)&&(I=I.slice(Ei.protocol.length),R=!0,I===n.relativeCwd))continue;let N=I.match(APt);if(!N){r.reportWarning(0,`Couldn't auto-upgrade range ${I} (in ${G.prettyLocator(t.configuration,E.anchoredLocator)})`);continue}let U=s?`${c}`:`${N[1]}${c}`;R&&(U=`${Ei.protocol}${U}`);let W=G.makeDescriptor(P,U);E.manifest[C].set(S,W)}}}var hPt=new Map([["%n",{extract:t=>t.length>=1?[t[0],t.slice(1)]:null,generate:(t=0)=>`${t+1}`}]]);function pke(t,{current:e,prerelease:r}){let s=new kA.default.SemVer(e),a=s.prerelease.slice(),n=[];s.prerelease=[],s.format()!==t&&(a.length=0);let c=!0,f=r.split(/\./g);for(let p of f){let h=hPt.get(p);if(typeof h>"u")n.push(p),a[0]===p?a.shift():c=!1;else{let E=c?h.extract(a):null;E!==null&&typeof E[0]=="number"?(n.push(h.generate(E[0])),a=E[1]):(n.push(h.generate()),c=!1)}}return s.prerelease&&(s.prerelease=[]),`${t}-${n.join(".")}`}var Y1=class extends ft{constructor(){super(...arguments);this.all=ge.Boolean("--all",!1,{description:"Apply the deferred version changes on all workspaces"});this.dryRun=ge.Boolean("--dry-run",!1,{description:"Print the versions without actually generating the package archive"});this.prerelease=ge.String("--prerelease",{description:"Add a prerelease identifier to new versions",tolerateBoolean:!0});this.exact=ge.Boolean("--exact",!1,{description:"Use the exact version of each package, removes any range. Useful for nightly releases where the range might match another version."});this.recursive=ge.Boolean("-R,--recursive",{description:"Release the transitive workspaces as well"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["version","apply"]]}static{this.usage=ot.Usage({category:"Release-related commands",description:"apply all the deferred version bumps at once",details:` This command will apply the deferred version changes and remove their definitions from the repository. Note that if \`--prerelease\` is set, the given prerelease identifier (by default \`rc.%n\`) will be used on all new versions and the version definitions will be kept as-is. By default only the current workspace will be bumped, but you can configure this behavior by using one of: - \`--recursive\` to also apply the version bump on its dependencies - \`--all\` to apply the version bump on all packages in the repository Note that this command will also update the \`workspace:\` references across all your local workspaces, thus ensuring that they keep referring to the same workspaces even after the version bump. `,examples:[["Apply the version change to the local workspace","yarn version apply"],["Apply the version change to all the workspaces in the local workspace","yarn version apply --all"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async f=>{let p=this.prerelease?typeof this.prerelease!="boolean"?this.prerelease:"rc.%n":null,h=await oP(s,{prerelease:p}),E=new Map;if(this.all)E=h;else{let C=this.recursive?a.getRecursiveWorkspaceDependencies():[a];for(let S of C){let P=h.get(S);typeof P<"u"&&E.set(S,P)}}if(E.size===0){let C=h.size>0?" Did you want to add --all?":"";f.reportWarning(0,`The current workspace doesn't seem to require a version bump.${C}`);return}Az(s,E,{report:f,exact:this.exact}),this.dryRun||(p||(this.all?await cz(s):await uz(s,[...E.keys()])),f.reportSeparator())});return this.dryRun||c.hasErrors()?c.exitCode():await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n})}};Ge();Dt();Yt();var _L=ut(Ai());var V1=class extends ft{constructor(){super(...arguments);this.interactive=ge.Boolean("-i,--interactive",{description:"Open an interactive interface used to set version bumps"})}static{this.paths=[["version","check"]]}static{this.usage=ot.Usage({category:"Release-related commands",description:"check that all the relevant packages have been bumped",details:"\n **Warning:** This command currently requires Git.\n\n This command will check that all the packages covered by the files listed in argument have been properly bumped or declined to bump.\n\n In the case of a bump, the check will also cover transitive packages - meaning that should `Foo` be bumped, a package `Bar` depending on `Foo` will require a decision as to whether `Bar` will need to be bumped. This check doesn't cross packages that have declined to bump.\n\n In case no arguments are passed to the function, the list of modified files will be generated by comparing the HEAD against `master`.\n ",examples:[["Check whether the modified packages need a bump","yarn version check"]]})}async execute(){return this.interactive?await this.executeInteractive():await this.executeStandard()}async executeInteractive(){iw(this.context);let{Gem:r}=await Promise.resolve().then(()=>(WF(),LW)),{ScrollableItems:s}=await Promise.resolve().then(()=>(KF(),JF)),{FocusRequest:a}=await Promise.resolve().then(()=>(UW(),v2e)),{useListInput:n}=await Promise.resolve().then(()=>(VF(),S2e)),{renderForm:c}=await Promise.resolve().then(()=>($F(),ZF)),{Box:f,Text:p}=await Promise.resolve().then(()=>ut(Wc())),{default:h,useCallback:E,useState:C}=await Promise.resolve().then(()=>ut(hn())),S=await ze.find(this.context.cwd,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,this.context.cwd);if(!I)throw new ar(P.cwd,this.context.cwd);await P.restoreInstallState();let R=await q1(P);if(R===null||R.releaseRoots.size===0)return 0;if(R.root===null)throw new nt("This command can only be run on Git repositories");let N=()=>h.createElement(f,{flexDirection:"row",paddingBottom:1},h.createElement(f,{flexDirection:"column",width:60},h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},""),"/",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to select workspaces.")),h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},""),"/",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to select release strategies."))),h.createElement(f,{flexDirection:"column"},h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to save.")),h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to abort.")))),U=({workspace:me,active:pe,decision:Be,setDecision:Ce})=>{let g=me.manifest.raw.stableVersion??me.manifest.version;if(g===null)throw new Error(`Assertion failed: The version should have been set (${G.prettyLocator(S,me.anchoredLocator)})`);if(_L.default.prerelease(g)!==null)throw new Error(`Assertion failed: Prerelease identifiers shouldn't be found (${g})`);let we=["undecided","decline","patch","minor","major"];n(Be,we,{active:pe,minus:"left",plus:"right",set:Ce});let ye=Be==="undecided"?h.createElement(p,{color:"yellow"},g):Be==="decline"?h.createElement(p,{color:"green"},g):h.createElement(p,null,h.createElement(p,{color:"magenta"},g)," \u2192 ",h.createElement(p,{color:"green"},_L.default.valid(Be)?Be:_L.default.inc(g,Be)));return h.createElement(f,{flexDirection:"column"},h.createElement(f,null,h.createElement(p,null,G.prettyLocator(S,me.anchoredLocator)," - ",ye)),h.createElement(f,null,we.map(Ae=>h.createElement(f,{key:Ae,paddingLeft:2},h.createElement(p,null,h.createElement(r,{active:Ae===Be})," ",Ae)))))},W=me=>{let pe=new Set(R.releaseRoots),Be=new Map([...me].filter(([Ce])=>pe.has(Ce)));for(;;){let Ce=aP({project:R.project,releases:Be}),g=!1;if(Ce.length>0){for(let[we]of Ce)if(!pe.has(we)){pe.add(we),g=!0;let ye=me.get(we);typeof ye<"u"&&Be.set(we,ye)}}if(!g)break}return{relevantWorkspaces:pe,relevantReleases:Be}},ee=()=>{let[me,pe]=C(()=>new Map(R.releases)),Be=E((Ce,g)=>{let we=new Map(me);g!=="undecided"?we.set(Ce,g):we.delete(Ce);let{relevantReleases:ye}=W(we);pe(ye)},[me,pe]);return[me,Be]},ie=({workspaces:me,releases:pe})=>{let Be=[];Be.push(`${me.size} total`);let Ce=0,g=0;for(let we of me){let ye=pe.get(we);typeof ye>"u"?g+=1:ye!=="decline"&&(Ce+=1)}return Be.push(`${Ce} release${Ce===1?"":"s"}`),Be.push(`${g} remaining`),h.createElement(p,{color:"yellow"},Be.join(", "))},le=await c(({useSubmit:me})=>{let[pe,Be]=ee();me(pe);let{relevantWorkspaces:Ce}=W(pe),g=new Set([...Ce].filter(se=>!R.releaseRoots.has(se))),[we,ye]=C(0),Ae=E(se=>{switch(se){case a.BEFORE:ye(we-1);break;case a.AFTER:ye(we+1);break}},[we,ye]);return h.createElement(f,{flexDirection:"column"},h.createElement(N,null),h.createElement(f,null,h.createElement(p,{wrap:"wrap"},"The following files have been modified in your local checkout.")),h.createElement(f,{flexDirection:"column",marginTop:1,paddingLeft:2},[...R.changedFiles].map(se=>h.createElement(f,{key:se},h.createElement(p,null,h.createElement(p,{color:"grey"},fe.fromPortablePath(R.root)),fe.sep,fe.relative(fe.fromPortablePath(R.root),fe.fromPortablePath(se)))))),R.releaseRoots.size>0&&h.createElement(h.Fragment,null,h.createElement(f,{marginTop:1},h.createElement(p,{wrap:"wrap"},"Because of those files having been modified, the following workspaces may need to be released again (note that private workspaces are also shown here, because even though they won't be published, releasing them will allow us to flag their dependents for potential re-release):")),g.size>3?h.createElement(f,{marginTop:1},h.createElement(ie,{workspaces:R.releaseRoots,releases:pe})):null,h.createElement(f,{marginTop:1,flexDirection:"column"},h.createElement(s,{active:we%2===0,radius:1,size:2,onFocusRequest:Ae},[...R.releaseRoots].map(se=>h.createElement(U,{key:se.cwd,workspace:se,decision:pe.get(se)||"undecided",setDecision:Z=>Be(se,Z)}))))),g.size>0?h.createElement(h.Fragment,null,h.createElement(f,{marginTop:1},h.createElement(p,{wrap:"wrap"},"The following workspaces depend on other workspaces that have been marked for release, and thus may need to be released as well:")),h.createElement(f,null,h.createElement(p,null,"(Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to move the focus between the workspace groups.)")),g.size>5?h.createElement(f,{marginTop:1},h.createElement(ie,{workspaces:g,releases:pe})):null,h.createElement(f,{marginTop:1,flexDirection:"column"},h.createElement(s,{active:we%2===1,radius:2,size:2,onFocusRequest:Ae},[...g].map(se=>h.createElement(U,{key:se.cwd,workspace:se,decision:pe.get(se)||"undecided",setDecision:Z=>Be(se,Z)}))))):null)},{versionFile:R},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof le>"u")return 1;R.releases.clear();for(let[me,pe]of le)R.releases.set(me,pe);await R.saveAll()}async executeStandard(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);return await s.restoreInstallState(),(await Ot.start({configuration:r,stdout:this.context.stdout},async c=>{let f=await q1(s);if(f===null||f.releaseRoots.size===0)return;if(f.root===null)throw new nt("This command can only be run on Git repositories");if(c.reportInfo(0,`Your PR was started right after ${he.pretty(r,f.baseHash.slice(0,7),"yellow")} ${he.pretty(r,f.baseTitle,"magenta")}`),f.changedFiles.size>0){c.reportInfo(0,"You have changed the following files since then:"),c.reportSeparator();for(let S of f.changedFiles)c.reportInfo(null,`${he.pretty(r,fe.fromPortablePath(f.root),"gray")}${fe.sep}${fe.relative(fe.fromPortablePath(f.root),fe.fromPortablePath(S))}`)}let p=!1,h=!1,E=UL(f);if(E.size>0){p||c.reportSeparator();for(let S of E)c.reportError(0,`${G.prettyLocator(r,S.anchoredLocator)} has been modified but doesn't have a release strategy attached`);p=!0}let C=aP(f);for(let[S,P]of C)h||c.reportSeparator(),c.reportError(0,`${G.prettyLocator(r,S.anchoredLocator)} doesn't have a release strategy attached, but depends on ${G.prettyWorkspace(r,P)} which is planned for release.`),h=!0;(p||h)&&(c.reportSeparator(),c.reportInfo(0,"This command detected that at least some workspaces have received modifications without explicit instructions as to how they had to be released (if needed)."),c.reportInfo(0,"To correct these errors, run `yarn version check --interactive` then follow the instructions."))})).exitCode()}};Ge();Yt();var HL=ut(Ai());var J1=class extends ft{constructor(){super(...arguments);this.deferred=ge.Boolean("-d,--deferred",{description:"Prepare the version to be bumped during the next release cycle"});this.immediate=ge.Boolean("-i,--immediate",{description:"Bump the version immediately"});this.strategy=ge.String()}static{this.paths=[["version"]]}static{this.usage=ot.Usage({category:"Release-related commands",description:"apply a new version to the current package",details:"\n This command will bump the version number for the given package, following the specified strategy:\n\n - If `major`, the first number from the semver range will be increased (`X.0.0`).\n - If `minor`, the second number from the semver range will be increased (`0.X.0`).\n - If `patch`, the third number from the semver range will be increased (`0.0.X`).\n - If prefixed by `pre` (`premajor`, ...), a `-0` suffix will be set (`0.0.0-0`).\n - If `prerelease`, the suffix will be increased (`0.0.0-X`); the third number from the semver range will also be increased if there was no suffix in the previous version.\n - If `decline`, the nonce will be increased for `yarn version check` to pass without version bump.\n - If a valid semver range, it will be used as new version.\n - If unspecified, Yarn will ask you for guidance.\n\n For more information about the `--deferred` flag, consult our documentation (https://yarnpkg.com/features/release-workflow#deferred-versioning).\n ",examples:[["Immediately bump the version to the next major","yarn version major"],["Prepare the version to be bumped to the next major","yarn version major --deferred"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=r.get("preferDeferredVersions");this.deferred&&(n=!0),this.immediate&&(n=!1);let c=HL.default.valid(this.strategy),f=this.strategy==="decline",p;if(c)if(a.manifest.version!==null){let E=fz(a.manifest.version,this.strategy);E!==null?p=E:p=this.strategy}else p=this.strategy;else{let E=a.manifest.version;if(!f){if(E===null)throw new nt("Can't bump the version if there wasn't a version to begin with - use 0.0.0 as initial version then run the command again.");if(typeof E!="string"||!HL.default.valid(E))throw new nt(`Can't bump the version (${E}) if it's not valid semver`)}p=dy(this.strategy)}if(!n){let C=(await oP(s)).get(a);if(typeof C<"u"&&p!=="decline"){let S=sP(a.manifest.version,p);if(HL.default.lt(S,C))throw new nt(`Can't bump the version to one that would be lower than the current deferred one (${C})`)}}let h=await q1(s,{allowEmpty:!0});return h.releases.set(a,p),await h.saveAll(),n?0:await this.cli.run(["version","apply"])}};var gPt={configuration:{deferredVersionFolder:{description:"Folder where are stored the versioning files",type:"ABSOLUTE_PATH",default:"./.yarn/versions"},preferDeferredVersions:{description:"If true, running `yarn version` will assume the `--deferred` flag unless `--immediate` is set",type:"BOOLEAN",default:!1}},commands:[Y1,V1,J1]},dPt=gPt;var hz={};Vt(hz,{WorkspacesFocusCommand:()=>K1,WorkspacesForeachCommand:()=>X1,default:()=>EPt});Ge();Ge();Yt();var K1=class extends ft{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=ge.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=ge.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=ge.Rest()}static{this.paths=[["workspaces","focus"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Kr.find(r);await s.restoreInstallState({restoreResolutions:!1});let c;if(this.all)c=new Set(s.workspaces);else if(this.workspaces.length===0){if(!a)throw new ar(s.cwd,this.context.cwd);c=new Set([a])}else c=new Set(this.workspaces.map(f=>s.getWorkspaceByIdent(G.parseIdent(f))));for(let f of c)for(let p of this.production?["dependencies"]:Ut.hardDependencies)for(let h of f.manifest.getForScope(p).values()){let E=s.tryWorkspaceByDescriptor(h);E!==null&&c.add(E)}for(let f of s.workspaces)c.has(f)?this.production&&f.manifest.devDependencies.clear():(f.manifest.installConfig=f.manifest.installConfig||{},f.manifest.installConfig.selfReferences=!1,f.manifest.dependencies.clear(),f.manifest.devDependencies.clear(),f.manifest.peerDependencies.clear(),f.manifest.scripts.clear());return await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n,persistProject:!1})}};Ge();Ge();Ge();Yt();var z1=ut(Go()),gke=ut(Ld());Ul();var X1=class extends ft{constructor(){super(...arguments);this.from=ge.Array("--from",{description:"An array of glob pattern idents or paths from which to base any recursion"});this.all=ge.Boolean("-A,--all",{description:"Run the command on all workspaces of a project"});this.recursive=ge.Boolean("-R,--recursive",{description:"Run the command on the current workspace and all of its recursive dependencies"});this.worktree=ge.Boolean("-W,--worktree",{description:"Run the command on all workspaces of the current worktree"});this.verbose=ge.Counter("-v,--verbose",{description:"Increase level of logging verbosity up to 2 times"});this.parallel=ge.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=ge.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=ge.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:g_([fo(["unlimited"]),$2(h_(),[m_(),d_(1)])])});this.topological=ge.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=ge.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=ge.Array("--include",[],{description:"An array of glob pattern idents or paths; only matching workspaces will be traversed"});this.exclude=ge.Array("--exclude",[],{description:"An array of glob pattern idents or paths; matching workspaces won't be traversed"});this.publicOnly=ge.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=ge.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.dryRun=ge.Boolean("-n,--dry-run",{description:"Print the commands that would be run, without actually running them"});this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[["workspaces","foreach"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `-W,--worktree` is set, Yarn will find workspaces to run the command on by looking at the current worktree.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - If `--dry-run` is set, Yarn will explain what it would do without actually doing anything.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them. You can also use the `--no-private` flag to avoid running the command in private workspaces.\n\n The `-v,--verbose` flag can be passed up to twice: once to prefix output lines with the originating workspace's name, and again to include start/finish/timing log lines. Maximum verbosity is enabled by default in terminal environments.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish all packages","yarn workspaces foreach -A --no-private npm publish --tolerate-republish"],["Run the build script on all descendant packages","yarn workspaces foreach -A run build"],["Run the build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -Apt run build"],["Run the build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -Rpt --from '{workspace-a,workspace-b}' run build"]]})}static{this.schema=[tB("all",qf.Forbids,["from","recursive","since","worktree"],{missingIf:"undefined"}),y_(["all","recursive","since","worktree"],{missingIf:"undefined"})]}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!this.all&&!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=this.cli.process([this.commandName,...this.args]),c=n.path.length===1&&n.path[0]==="run"&&typeof n.scriptName<"u"?n.scriptName:null;if(n.path.length===0)throw new nt("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let f=Ce=>{this.dryRun&&this.context.stdout.write(`${Ce} `)},p=()=>{let Ce=this.from.map(g=>z1.default.matcher(g));return s.workspaces.filter(g=>{let we=G.stringifyIdent(g.anchoredLocator),ye=g.relativeCwd;return Ce.some(Ae=>Ae(we)||Ae(ye))})},h=[];if(this.since?(f("Option --since is set; selecting the changed workspaces as root for workspace selection"),h=Array.from(await ka.fetchChangedWorkspaces({ref:this.since,project:s}))):this.from?(f("Option --from is set; selecting the specified workspaces"),h=[...p()]):this.worktree?(f("Option --worktree is set; selecting the current workspace"),h=[a]):this.recursive?(f("Option --recursive is set; selecting the current workspace"),h=[a]):this.all&&(f("Option --all is set; selecting all workspaces"),h=[...s.workspaces]),this.dryRun&&!this.all){for(let Ce of h)f(` - ${Ce.relativeCwd} ${G.prettyLocator(r,Ce.anchoredLocator)}`);h.length>0&&f("")}let E;if(this.recursive?this.since?(f("Option --recursive --since is set; recursively selecting all dependent workspaces"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceDependents()]).flat())):(f("Option --recursive is set; recursively selecting all transitive dependencies"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceDependencies()]).flat())):this.worktree?(f("Option --worktree is set; recursively selecting all nested workspaces"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceChildren()]).flat())):E=null,E!==null&&(h=[...new Set([...h,...E])],this.dryRun))for(let Ce of E)f(` - ${Ce.relativeCwd} ${G.prettyLocator(r,Ce.anchoredLocator)}`);let C=[],S=!1;if(c?.includes(":")){for(let Ce of s.workspaces)if(Ce.manifest.scripts.has(c)&&(S=!S,S===!1))break}for(let Ce of h){if(c&&!Ce.manifest.scripts.has(c)&&!S&&!(await In.getWorkspaceAccessibleBinaries(Ce)).has(c)){f(`Excluding ${Ce.relativeCwd} because it doesn't have a "${c}" script`);continue}if(!(c===r.env.npm_lifecycle_event&&Ce.cwd===a.cwd)){if(this.include.length>0&&!z1.default.isMatch(G.stringifyIdent(Ce.anchoredLocator),this.include)&&!z1.default.isMatch(Ce.relativeCwd,this.include)){f(`Excluding ${Ce.relativeCwd} because it doesn't match the --include filter`);continue}if(this.exclude.length>0&&(z1.default.isMatch(G.stringifyIdent(Ce.anchoredLocator),this.exclude)||z1.default.isMatch(Ce.relativeCwd,this.exclude))){f(`Excluding ${Ce.relativeCwd} because it matches the --exclude filter`);continue}if(this.publicOnly&&Ce.manifest.private===!0){f(`Excluding ${Ce.relativeCwd} because it's a private workspace and --no-private was set`);continue}C.push(Ce)}}if(this.dryRun)return 0;let P=this.verbose??(this.context.stdout.isTTY?1/0:0),I=P>0,R=P>1,N=this.parallel?this.jobs==="unlimited"?1/0:Number(this.jobs)||Math.ceil(Ui.availableParallelism()/2):1,U=N===1?!1:this.parallel,W=U?this.interlaced:!0,ee=(0,gke.default)(N),ie=new Map,ue=new Set,le=0,me=null,pe=!1,Be=await Ot.start({configuration:r,stdout:this.context.stdout,includePrefix:!1},async Ce=>{let g=async(we,{commandIndex:ye})=>{if(pe)return-1;!U&&R&&ye>1&&Ce.reportSeparator();let Ae=mPt(we,{configuration:r,label:I,commandIndex:ye}),[se,Z]=hke(Ce,{prefix:Ae,interlaced:W}),[De,Re]=hke(Ce,{prefix:Ae,interlaced:W});try{R&&Ce.reportInfo(null,`${Ae?`${Ae} `:""}Process started`);let mt=Date.now(),j=await this.cli.run([this.commandName,...this.args],{cwd:we.cwd,stdout:se,stderr:De})||0;se.end(),De.end(),await Z,await Re;let rt=Date.now();if(R){let Fe=r.get("enableTimers")?`, completed in ${he.pretty(r,rt-mt,he.Type.DURATION)}`:"";Ce.reportInfo(null,`${Ae?`${Ae} `:""}Process exited (exit code ${j})${Fe}`)}return j===130&&(pe=!0,me=j),j}catch(mt){throw se.end(),De.end(),await Z,await Re,mt}};for(let we of C)ie.set(we.anchoredLocator.locatorHash,we);for(;ie.size>0&&!Ce.hasErrors();){let we=[];for(let[Z,De]of ie){if(ue.has(De.anchoredDescriptor.descriptorHash))continue;let Re=!0;if(this.topological||this.topologicalDev){let mt=this.topologicalDev?new Map([...De.manifest.dependencies,...De.manifest.devDependencies]):De.manifest.dependencies;for(let j of mt.values()){let rt=s.tryWorkspaceByDescriptor(j);if(Re=rt===null||!ie.has(rt.anchoredLocator.locatorHash),!Re)break}}if(Re&&(ue.add(De.anchoredDescriptor.descriptorHash),we.push(ee(async()=>{let mt=await g(De,{commandIndex:++le});return ie.delete(Z),ue.delete(De.anchoredDescriptor.descriptorHash),{workspace:De,exitCode:mt}})),!U))break}if(we.length===0){let Z=Array.from(ie.values()).map(De=>G.prettyLocator(r,De.anchoredLocator)).join(", ");Ce.reportError(3,`Dependency cycle detected (${Z})`);return}let ye=await Promise.all(we);ye.forEach(({workspace:Z,exitCode:De})=>{De!==0&&Ce.reportError(0,`The command failed in workspace ${G.prettyLocator(r,Z.anchoredLocator)} with exit code ${De}`)});let se=ye.map(Z=>Z.exitCode).find(Z=>Z!==0);(this.topological||this.topologicalDev)&&typeof se<"u"&&Ce.reportError(0,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return me!==null?me:Be.exitCode()}};function hke(t,{prefix:e,interlaced:r}){let s=t.createStreamReporter(e),a=new je.DefaultStream;a.pipe(s,{end:!1}),a.on("finish",()=>{s.end()});let n=new Promise(f=>{s.on("finish",()=>{f(a.active)})});if(r)return[a,n];let c=new je.BufferStream;return c.pipe(a,{end:!1}),c.on("finish",()=>{a.end()}),[c,n]}function mPt(t,{configuration:e,commandIndex:r,label:s}){if(!s)return null;let n=`[${G.stringifyIdent(t.anchoredLocator)}]:`,c=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],f=c[r%c.length];return he.pretty(e,n,f)}var yPt={commands:[K1,X1]},EPt=yPt;var tC=()=>({modules:new Map([["@yarnpkg/cli",Gv],["@yarnpkg/core",jv],["@yarnpkg/fslib",_2],["@yarnpkg/libzip",fv],["@yarnpkg/parsers",J2],["@yarnpkg/shell",mv],["clipanion",oB],["semver",IPt],["typanion",Ea],["@yarnpkg/plugin-essentials",hq],["@yarnpkg/plugin-catalog",yq],["@yarnpkg/plugin-compat",Bq],["@yarnpkg/plugin-constraints",_q],["@yarnpkg/plugin-dlx",Hq],["@yarnpkg/plugin-exec",qq],["@yarnpkg/plugin-file",Yq],["@yarnpkg/plugin-git",pq],["@yarnpkg/plugin-github",Kq],["@yarnpkg/plugin-http",zq],["@yarnpkg/plugin-init",Xq],["@yarnpkg/plugin-interactive-tools",JW],["@yarnpkg/plugin-jsr",zW],["@yarnpkg/plugin-link",XW],["@yarnpkg/plugin-nm",FY],["@yarnpkg/plugin-npm",FK],["@yarnpkg/plugin-npm-cli",qK],["@yarnpkg/plugin-pack",bV],["@yarnpkg/plugin-patch",XK],["@yarnpkg/plugin-pnp",wY],["@yarnpkg/plugin-pnpm",ez],["@yarnpkg/plugin-stage",az],["@yarnpkg/plugin-typescript",lz],["@yarnpkg/plugin-version",pz],["@yarnpkg/plugin-workspace-tools",hz]]),plugins:new Set(["@yarnpkg/plugin-essentials","@yarnpkg/plugin-catalog","@yarnpkg/plugin-compat","@yarnpkg/plugin-constraints","@yarnpkg/plugin-dlx","@yarnpkg/plugin-exec","@yarnpkg/plugin-file","@yarnpkg/plugin-git","@yarnpkg/plugin-github","@yarnpkg/plugin-http","@yarnpkg/plugin-init","@yarnpkg/plugin-interactive-tools","@yarnpkg/plugin-jsr","@yarnpkg/plugin-link","@yarnpkg/plugin-nm","@yarnpkg/plugin-npm","@yarnpkg/plugin-npm-cli","@yarnpkg/plugin-pack","@yarnpkg/plugin-patch","@yarnpkg/plugin-pnp","@yarnpkg/plugin-pnpm","@yarnpkg/plugin-stage","@yarnpkg/plugin-typescript","@yarnpkg/plugin-version","@yarnpkg/plugin-workspace-tools"])});function yke({cwd:t,pluginConfiguration:e}){let r=new Ca({binaryLabel:"Yarn Package Manager",binaryName:"yarn",binaryVersion:fn??""});return Object.assign(r,{defaultContext:{...Ca.defaultContext,cwd:t,plugins:e,quiet:!1,stdin:process.stdin,stdout:process.stdout,stderr:process.stderr}})}function CPt(t){if(je.parseOptionalBoolean(process.env.YARN_IGNORE_NODE))return!0;let r=process.versions.node,s=">=18.12.0";if(Fr.satisfiesWithPrereleases(r,s))return!0;let a=new nt(`This tool requires a Node version compatible with ${s} (got ${r}). Upgrade Node, or set \`YARN_IGNORE_NODE=1\` in your environment.`);return Ca.defaultContext.stdout.write(t.error(a)),!1}async function Eke({selfPath:t,pluginConfiguration:e}){return await ze.find(fe.toPortablePath(process.cwd()),e,{strict:!1,usePathCheck:t})}function wPt(t,e,{yarnPath:r}){if(!ce.existsSync(r))return t.error(new Error(`The "yarn-path" option has been set, but the specified location doesn't exist (${r}).`)),1;process.on("SIGINT",()=>{});let s={stdio:"inherit",env:{...process.env,YARN_IGNORE_PATH:"1"}};try{(0,dke.execFileSync)(process.execPath,[fe.fromPortablePath(r),...e],s)}catch(a){return a.status??1}return 0}function BPt(t,e){let r=null,s=e;return e.length>=2&&e[0]==="--cwd"?(r=fe.toPortablePath(e[1]),s=e.slice(2)):e.length>=1&&e[0].startsWith("--cwd=")?(r=fe.toPortablePath(e[0].slice(6)),s=e.slice(1)):e[0]==="add"&&e[e.length-2]==="--cwd"&&(r=fe.toPortablePath(e[e.length-1]),s=e.slice(0,e.length-2)),t.defaultContext.cwd=r!==null?J.resolve(r):J.cwd(),s}function vPt(t,{configuration:e}){if(!e.get("enableTelemetry")||mke.isCI||!process.stdout.isTTY)return;ze.telemetry=new ZI(e,"puba9cdc10ec5790a2cf4969dd413a47270");let s=/^@yarnpkg\/plugin-(.*)$/;for(let a of e.plugins.keys())$I.has(a.match(s)?.[1]??"")&&ze.telemetry?.reportPluginName(a);t.binaryVersion&&ze.telemetry.reportVersion(t.binaryVersion)}function Ike(t,{configuration:e}){for(let r of e.plugins.values())for(let s of r.commands||[])t.register(s)}async function SPt(t,e,{selfPath:r,pluginConfiguration:s}){if(!CPt(t))return 1;let a=await Eke({selfPath:r,pluginConfiguration:s}),n=a.get("yarnPath"),c=a.get("ignorePath");if(n&&!c)return wPt(t,e,{yarnPath:n});delete process.env.YARN_IGNORE_PATH;let f=BPt(t,e);vPt(t,{configuration:a}),Ike(t,{configuration:a});let p=t.process(f,t.defaultContext);return p.help||ze.telemetry?.reportCommandName(p.path.join(" ")),await t.run(p,t.defaultContext)}async function bde({cwd:t=J.cwd(),pluginConfiguration:e=tC()}={}){let r=yke({cwd:t,pluginConfiguration:e}),s=await Eke({pluginConfiguration:e,selfPath:null});return Ike(r,{configuration:s}),r}async function VR(t,{cwd:e=J.cwd(),selfPath:r,pluginConfiguration:s}){let a=yke({cwd:e,pluginConfiguration:s});function n(){Ca.defaultContext.stdout.write(`ERROR: Yarn is terminating due to an unexpected empty event loop. Please report this issue at https://github.com/yarnpkg/berry/issues.`)}process.once("beforeExit",n);try{process.exitCode=42,process.exitCode=await SPt(a,t,{selfPath:r,pluginConfiguration:s})}catch(c){Ca.defaultContext.stdout.write(a.error(c)),process.exitCode=1}finally{process.off("beforeExit",n),await ce.rmtempPromise()}}VR(process.argv.slice(2),{cwd:J.cwd(),selfPath:fe.toPortablePath(fe.resolve(process.argv[1])),pluginConfiguration:tC()});})(); /** @license Copyright (c) 2015, Rebecca Turner Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** @license Copyright Node.js contributors. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** @license The MIT License (MIT) Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** @license Copyright Joyent, Inc. and other Node contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*! Bundled license information: is-number/index.js: (*! * is-number * * Copyright (c) 2014-present, Jon Schlinkert. * Released under the MIT License. *) to-regex-range/index.js: (*! * to-regex-range * * Copyright (c) 2015-present, Jon Schlinkert. * Released under the MIT License. *) fill-range/index.js: (*! * fill-range * * Copyright (c) 2014-present, Jon Schlinkert. * Licensed under the MIT License. *) is-extglob/index.js: (*! * is-extglob * * Copyright (c) 2014-2016, Jon Schlinkert. * Licensed under the MIT License. *) is-glob/index.js: (*! * is-glob * * Copyright (c) 2014-2017, Jon Schlinkert. * Released under the MIT License. *) queue-microtask/index.js: (*! queue-microtask. MIT License. Feross Aboukhadijeh *) run-parallel/index.js: (*! run-parallel. MIT License. Feross Aboukhadijeh *) git-url-parse/lib/index.js: (*! * buildToken * Builds OAuth token prefix (helper function) * * @name buildToken * @function * @param {GitUrl} obj The parsed Git url object. * @return {String} token prefix *) object-assign/index.js: (* object-assign (c) Sindre Sorhus @license MIT *) react/cjs/react.production.min.js: (** @license React v17.0.2 * react.production.min.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. *) scheduler/cjs/scheduler.production.min.js: (** @license React v0.20.2 * scheduler.production.min.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. *) react-reconciler/cjs/react-reconciler.production.min.js: (** @license React v0.26.2 * react-reconciler.production.min.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. *) is-windows/index.js: (*! * is-windows * * Copyright © 2015-2018, Jon Schlinkert. * Released under the MIT License. *) */ ================================================ FILE: typescript-dto/.yarnrc.yml ================================================ yarnPath: .yarn/releases/yarn-4.12.0.cjs ================================================ FILE: typescript-dto/Dockerfile ================================================ # Copyright (c) 2018 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation ### # Builder Image # #FROM maven:3.3-jdk-8 as builder #ADD ./dto-pom.xml /generator/pom.xml #RUN cd /generator && mvn -U -DskipTests=true -Dfindbugs.skip=true -Dskip-validate-sources install ### # Publish image # FROM node:6.11.2 RUN npm i -g yarn@1.9.4 ADD package.json README.md /che/ COPY ./index.d.ts /che/index.d.ts ADD publish.sh publish.sh ARG NPM_AUTH_TOKEN ARG CHE_VERSION RUN cd /che && ../publish.sh ${CHE_VERSION} ================================================ FILE: typescript-dto/README.md ================================================ # This is Eclipse Che API generated from Java DTO interfaces ================================================ FILE: typescript-dto/build.sh ================================================ #!/usr/bin/env sh # # Copyright (c) 2019 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # See: https://sipb.mit.edu/doc/safe-shell/ set -e set -u rm -f ./index.d.ts set +e BUILDER=$(command -v podman) if [ ! -x "$BUILDER" ]; then BUILDER=$(command -v docker); fi $BUILDER run -i --rm -v "$HOME/.m2:/root/.m2" \ -v "$(pwd)/dto-pom.xml:/usr/src/mymaven/pom.xml" \ -w /usr/src/mymaven docker.io/maven:3.8-jdk-11 \ /bin/bash -c "mvn -q -U -DskipTests=true -Dfindbugs.skip=true -Dskip-validate-sources install \ && cat target/dts-dto-typescript.d.ts" >> index.d.ts set - # validate that index.d.ts has kind of valid output if ! grep "export namespace" index.d.ts; then echo "Invalid output generated for index.d.ts, printing its content:" cat index.d.ts exit 1 fi CHE_VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args="\${project.version}" --non-recursive exec:exec -f ../pom.xml) $BUILDER build -t eclipse-che-ts-api --build-arg CHE_VERSION="${CHE_VERSION}" --build-arg NPM_AUTH_TOKEN="${CHE_NPM_AUTH_TOKEN}" . ================================================ FILE: typescript-dto/dto-pom.xml ================================================ 4.0.0 maven-parent-pom org.eclipse.che.parent 7.15.0 dts-dto-typescript pom Che TypeScript DTO 7.118.0-SNAPSHOT true fail ossrh central public snapshots https://oss.sonatype.org/content/repositories/snapshots/ ossrh central public snapshots https://oss.sonatype.org/content/repositories/snapshots/ org.eclipse.che.core che-core-typescript-dto-maven-plugin ${che.version} build true org.eclipse.che.core che-core-api-factory-shared ${che.version} org.eclipse.che.core che-core-api-ssh-shared ${che.version} org.eclipse.che.core che-core-api-system-shared ${che.version} org.eclipse.che.core che-core-api-user-shared ${che.version} org.eclipse.che.core che-core-api-workspace-shared ${che.version} false ================================================ FILE: typescript-dto/package.json ================================================ { "name": "@eclipse-che/api", "version": "7.19.0-SNAPSHOT", "description": "Eclipse Che DTO API", "types": "index.d.ts", "license": "EPL-2.0", "files": [ "index.d.ts" ], "packageManager": "yarn@4.12.0" } ================================================ FILE: typescript-dto/publish.sh ================================================ #!/usr/bin/env sh # # Copyright (c) 2019 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # See: https://sipb.mit.edu/doc/safe-shell/ set -e set -u #get the latest version of @eclipse-che/api api_version=$(yarn -s info @eclipse-che/api version) #publish only if latest version doesn't match current version if [ "$api_version" != "$1" ]; then yarn publish --registry=https://registry.npmjs.org/ --no-git-tag-version --new-version "$1" fi ================================================ FILE: wsmaster/che-core-api-account/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-account Che Core :: API :: Account jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-commons-lang org.slf4j slf4j-api com.google.inject.extensions guice-persist provided org.eclipse.persistence jakarta.persistence provided ch.qos.logback logback-classic test com.google.code.gson gson test org.eclipse.che.core che-core-commons-json test org.eclipse.che.core che-core-commons-test test org.eclipse.che.core che-core-sql-schema test org.eclipse.persistence org.eclipse.persistence.jpa test org.flywaydb flyway-core test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test org.apache.maven.plugins maven-jar-plugin test-jar **/spi/tck/*.* ================================================ FILE: wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/api/AccountManager.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.account.api; import static java.util.Objects.requireNonNull; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.account.shared.model.Account; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.notification.EventService; /** * Facade for Account related operations. * * @author Sergii Leschenko */ @Deprecated @Singleton public class AccountManager { private final EventService eventService; @Inject public AccountManager(EventService eventService) { this.eventService = eventService; } /** * Account information is no longer persisted in the database * * @param account account to create * @throws NullPointerException when {@code account} is null * @throws ConflictException when account with such name or id already exists * @throws ServerException when any other error occurs during account creating */ public void create(Account account) throws ConflictException, ServerException { throw new ServerException("Account creation is obsolete and not expected to be invoked"); } /** * Updates account by replacing an existing account entity with a new one. * * @param account account to update * @throws NullPointerException when {@code account} is null * @throws NotFoundException when account with id {@code account.getId()} is not found * @throws ConflictException when account's new name is not unique * @throws ServerException when any other error occurs */ public void update(Account account) throws NotFoundException, ConflictException, ServerException { requireNonNull(account, "Required non-null account"); } /** * Gets account by identifier. * * @param id id of account to fetch * @return account instance with given id * @throws NullPointerException when {@code id} is null * @throws NotFoundException when account with given {@code id} was not found * @throws ServerException when any other error occurs during account fetching */ public Account getById(String id) throws NotFoundException, ServerException { requireNonNull(id, "Required non-null account id"); return null; } /** * Gets account by name. * * @param name name of account to fetch * @return account instance with given name * @throws NullPointerException when {@code name} is null * @throws NotFoundException when account with given {@code name} was not found * @throws ServerException when any other error occurs during account fetching */ public Account getByName(String name) throws NotFoundException, ServerException { requireNonNull(name, "Required non-null account name"); return null; } /** * Removes account by specified {@code id} * * @param id account identifier * @throws NullPointerException when {@code id} is null * @throws ServerException when any other error occurs */ public void remove(String id) throws ServerException { requireNonNull(id, "Required non-null account id"); } } ================================================ FILE: wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/shared/model/Account.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.account.shared.model; /** * @author Sergii Leschenko */ public interface Account { /** Returns account id */ String getId(); /** Returns name of account */ String getName(); /** Returns type of account */ String getType(); } ================================================ FILE: wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountDao.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.account.spi; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; /** * Defines data access object for {@link AccountImpl} * * @author Sergii Leschenko */ public interface AccountDao { /** * Creates account. * * @param account account to create * @throws NullPointerException when {@code account} is null * @throws ConflictException when account with such name or id already exists * @throws ServerException when any other error occurs during account creating */ void create(AccountImpl account) throws ConflictException, ServerException; /** * Updates account by replacing an existing entity with a new one. * * @param account account to update * @throws NullPointerException when {@code account} is null * @throws NotFoundException when account with id {@code account.getId()} doesn't exist * @throws ConflictException when name updated with a value which is not unique * @throws ServerException when any other error occurs */ void update(AccountImpl account) throws NotFoundException, ConflictException, ServerException; /** * Gets account by identifier. * * @param id account identifier * @return account instance with given id * @throws NullPointerException when {@code id} is null * @throws NotFoundException when account with given {@code id} was not found * @throws ServerException when any other error occurs during account fetching */ AccountImpl getById(String id) throws NotFoundException, ServerException; /** * Gets account by name. * * @param name account name * @return account instance with given name * @throws NullPointerException when {@code name} is null * @throws NotFoundException when account with given {@code name} was not found * @throws ServerException when any other error occurs during account fetching */ AccountImpl getByName(String name) throws ServerException, NotFoundException; /** * Removes account by specified {@code id} * * @param id account identifier * @throws NullPointerException when {@code id} is null * @throws ServerException when any other error occurs */ void remove(String id) throws ServerException; } ================================================ FILE: wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountImpl.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.account.spi; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; import org.eclipse.che.account.shared.model.Account; /** * Data object for {@link Account}. * * @author Sergii Leschenko * @author Yevhenii Voevodin */ @Entity(name = "Account") @NamedQueries({ @NamedQuery( name = "Account.getByName", query = "SELECT a " + "FROM Account a " + "WHERE a.name = :name") }) @Table(name = "account") public class AccountImpl implements Account { @Id @Column(name = "id") protected String id; @Column(nullable = false, name = "name") protected String name; @Column(name = "type") private String type; public AccountImpl() {} public AccountImpl(String id, String name, String type) { this.id = id; this.name = name; this.type = type; } public AccountImpl(Account account) { this(account.getId(), account.getName(), account.getType()); } @Override public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String getName() { return name; } @Override public String getType() { return type; } public void setType(String type) { this.type = type; } public void setName(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof AccountImpl)) return false; AccountImpl account = (AccountImpl) o; return Objects.equals(id, account.id) && Objects.equals(name, account.name); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hash(id); hash = 31 * hash + Objects.hash(name); return hash; } @Override public String toString() { return "AccountImpl{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountValidator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.account.spi; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.account.api.AccountManager; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.commons.lang.NameGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utils for account validation. * * @author Sergii Leschenko */ // TODO extract normalization code from the validator as it is not related to the validation at all @Singleton public class AccountValidator { private static final Logger LOG = LoggerFactory.getLogger(AccountValidator.class); private static final Pattern ILLEGAL_ACCOUNT_NAME_CHARACTERS = Pattern.compile( "[^a-zA-Z0-9-]" + // all character except allowed "|(?<=-)-+" + // non single hyphens "|^-+" + // hyphens at the beginning "|-+$"); // hyphens at the end private static final Pattern VALID_ACCOUNT_NAME = Pattern.compile("^(?:[a-zA-Z0-9]-?)*[a-zA-Z0-9]+"); private final AccountManager accountManager; @Inject public AccountValidator(AccountManager accountManager) { this.accountManager = accountManager; } /** * Validate name, if it doesn't contain illegal characters * * @param name account name * @return true if valid name, false otherwise */ public boolean isValidName(String name) { return name != null && VALID_ACCOUNT_NAME.matcher(name).matches(); } /** * Remove illegal characters from account name, to make it URL-friendly. If all characters are * illegal, return automatically generated account name with specified prefix. Also ensures * account name is unique, if not, adds digits to it's end. * * @param name account name * @param prefix prefix to add to generated name * @return account name without illegal characters */ public String normalizeAccountName(String name, String prefix) throws ServerException { String normalized = ILLEGAL_ACCOUNT_NAME_CHARACTERS.matcher(name).replaceAll(""); String candidate = normalized.isEmpty() ? NameGenerator.generate(prefix, 4) : normalized; int i = 1; try { while (accountExists(candidate)) { candidate = normalized.isEmpty() ? NameGenerator.generate(prefix, 4) : normalized + String.valueOf(i++); } } catch (ServerException e) { LOG.warn("Error occurred during account name normalization", e); throw e; } return candidate; } private boolean accountExists(String accountName) throws ServerException { try { accountManager.getByName(accountName); } catch (NotFoundException e) { return false; } return true; } } ================================================ FILE: wsmaster/che-core-api-account/src/test/java/org/eclipse/che/account/spi/AccountValidatorTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.account.spi; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; import org.eclipse.che.account.api.AccountManager; import org.eclipse.che.api.core.NotFoundException; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests of {@link AccountValidator}. * * @author Mihail Kuznyetsov * @author Yevhenii Voevodin * @author Sergii Leschenko */ @Listeners(MockitoTestNGListener.class) public class AccountValidatorTest { @Mock private AccountManager accountManager; @InjectMocks private AccountValidator accountValidator; @Test(dataProvider = "namesToNormalize") public void testNormalizeAccountName(String input, String expected) throws Exception { doThrow(NotFoundException.class).when(accountManager).getByName(anyString()); Assert.assertEquals(accountValidator.normalizeAccountName(input, "account"), expected); } @Test public void testNormalizeAccountNameWhenInputDoesNotContainAnyValidCharacter() throws Exception { doThrow(NotFoundException.class).when(accountManager).getByName(anyString()); Assert.assertTrue(accountValidator.normalizeAccountName("#", "name").startsWith("name")); } @Test(dataProvider = "namesToValidate") public void testValidUserName(String input, boolean expected) throws Exception { Assert.assertEquals(accountValidator.isValidName(input), expected); } @DataProvider public Object[][] namesToNormalize() { return new Object[][] { {"test", "test"}, {"test123", "test123"}, {"test 123", "test123"}, {"test@gmail.com", "testgmailcom"}, {"TEST", "TEST"}, {"test-", "test"}, {"-test", "test"}, {"te_st", "test"}, {"te#st", "test"}, {"-test", "test"}, {"test-", "test"}, {"--test--", "test"}, {"t-----e--s-t", "t-e-s-t"} }; } @DataProvider public Object[][] namesToValidate() { return new Object[][] { {"test", true}, {"t-e-s-t", true}, {"test123", true}, {"TEST", true}, {"te-st", true}, {"test 123", false}, {"test@gmail.com", false}, {"test-", false}, {"-test", false}, {"te_st", false}, {"te#st", false}, {"-test", false}, {"test-", false}, {"--test--", false}, {"te--st", false} }; } } ================================================ FILE: wsmaster/che-core-api-account/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule ================================================ org.eclipse.che.account.spi.tck.jpa.AccountJpaTckModule ================================================ FILE: wsmaster/che-core-api-account/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: wsmaster/che-core-api-auth/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-auth jar Che Core :: API :: Authentication ${project.build.directory}/generated-sources/dto/ false com.google.guava guava com.google.http-client google-http-client com.google.http-client google-http-client-jackson2 com.google.oauth-client google-oauth-client jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-auth-shared org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-factory org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-json org.eclipse.che.core che-core-commons-lang org.everrest everrest-core org.slf4j slf4j-api jakarta.servlet jakarta.servlet-api provided jakarta.ws.rs jakarta.ws.rs-api provided io.grpc grpc-api runtime error_prone_annotations com.google.errorprone jsr305 com.google.code.findbugs ch.qos.logback logback-classic test com.github.tomakehurst wiremock-jre8-standalone test commons-fileupload commons-fileupload test io.rest-assured rest-assured test org.eclipse.che.core che-core-commons-test test org.everrest everrest-assured test org.everrest everrest-test test org.hamcrest hamcrest test org.mockito mockito-core test org.mockito mockito-testng test org.slf4j jcl-over-slf4j test org.testng testng test org.wiremock wiremock-standalone test org.apache.maven.plugins maven-dependency-plugin analyze org.everrest:everrest-core ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPI.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static com.google.common.base.Strings.isNullOrEmpty; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.emptyList; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.commons.lang.UrlUtils.*; import static org.eclipse.che.commons.lang.UrlUtils.getParameter; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.eclipse.che.security.oauth.OAuthAuthenticator.SSL_ERROR_CODE; import static org.eclipse.che.security.oauth1.OAuthAuthenticationService.ERROR_QUERY_NAME; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriInfo; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLEncoder; import java.util.*; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.LinkParameter; import org.eclipse.che.api.core.util.LinksHelper; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of functional API component for {@link OAuthAuthenticationService}, that uses * {@link OAuthAuthenticator}. * * @author Mykhailo Kuznietsov */ @Singleton public class EmbeddedOAuthAPI implements OAuthAPI { private static final Logger LOG = LoggerFactory.getLogger(EmbeddedOAuthAPI.class); @Inject @Named("che.auth.access_denied_error_page") protected String errorPage; @Inject protected OAuthAuthenticatorProvider oauth2Providers; @Inject protected org.eclipse.che.security.oauth1.OAuthAuthenticatorProvider oauth1Providers; @Inject private PersonalAccessTokenManager personalAccessTokenManager; private String redirectAfterLogin; @Override public Response authenticate( UriInfo uriInfo, String oauthProvider, List scopes, String redirectAfterLogin, HttpServletRequest request) throws NotFoundException, OAuthAuthenticationException { this.redirectAfterLogin = redirectAfterLogin; OAuthAuthenticator oauth = getAuthenticator(oauthProvider); final String authUrl = oauth.getAuthenticateUrl(getRequestUrl(uriInfo), scopes == null ? emptyList() : scopes); return Response.temporaryRedirect(URI.create(authUrl)).build(); } @Override public Response callback(UriInfo uriInfo, @Nullable List errorValues) throws NotFoundException { URL requestUrl = getRequestUrl(uriInfo); Map> params = getQueryParametersFromState(getState(requestUrl)); errorValues = errorValues == null ? uriInfo.getQueryParameters().get("error") : errorValues; if (!isNullOrEmpty(redirectAfterLogin) && errorValues != null && errorValues.contains("access_denied")) { return Response.temporaryRedirect( URI.create(encodeRedirectUrl(redirectAfterLogin + "&error_code=access_denied"))) .build(); } final String providerName = getParameter(params, "oauth_provider"); OAuthAuthenticator oauth = getAuthenticator(providerName); final List scopes = params.get("scope"); try { String token = oauth.callback(requestUrl, scopes == null ? emptyList() : scopes); personalAccessTokenManager.store( new PersonalAccessToken( oauth.getEndpointUrl(), providerName, EnvironmentContext.getCurrent().getSubject().getUserId(), null, null, NameGenerator.generate(OAUTH_2_PREFIX, 5), NameGenerator.generate("id-", 5), token)); } catch (OAuthAuthenticationException e) { return Response.temporaryRedirect( URI.create( getParameter(params, "redirect_after_login") + String.format("&%s=access_denied", ERROR_QUERY_NAME))) .build(); } catch (UnsatisfiedScmPreconditionException | ScmConfigurationPersistenceException e) { // Skip exception, the token will be stored in the next request. LOG.error(e.getMessage(), e); } catch (ScmCommunicationException e) { if (e.getStatusCode() == SSL_ERROR_CODE) { return Response.temporaryRedirect( URI.create(getRedirectAfterLoginUrl(params, "ssl_exception"))) .build(); } else { LOG.error(e.getMessage(), e); } } return Response.temporaryRedirect(URI.create(getRedirectAfterLoginUrl(params, null))).build(); } /** * Returns the redirect after login URL from the query parameters. If the URL is encoded by the * CSM provider, it will be decoded, to avoid unsupported characters in the URL. * * @param parameters the query parameters * @param errorCode the error code or {@code null} * @return the redirect after login URL */ public static String getRedirectAfterLoginUrl( Map> parameters, @Nullable String errorCode) { String redirectAfterLogin = getParameter(parameters, "redirect_after_login"); try { URI.create(redirectAfterLogin); } catch (IllegalArgumentException e) { // the redirectUrl was decoded by the CSM provider, so we need to encode it back. redirectAfterLogin = encodeRedirectUrl(redirectAfterLogin); } if (errorCode != null) { redirectAfterLogin += String.format("&%s=%s", ERROR_QUERY_NAME, errorCode); } return redirectAfterLogin; } /** * Encode the redirect URL query parameters to avoid the error when the redirect URL contains * JSON, as a query parameter. This prevents passing unsupported characters, like '{' and '}' to * the {@link URI#create(String)} method. */ private static String encodeRedirectUrl(String url) { try { String query = new URL(url).getQuery(); return url.substring(0, url.indexOf(query)) + URLEncoder.encode(query, UTF_8); } catch (MalformedURLException e) { LOG.error(e.getMessage(), e); throw new RuntimeException(e); } } @Override public Set getRegisteredAuthenticators(UriInfo uriInfo) { Set result = new HashSet<>(); final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder().clone().path(OAuthAuthenticationService.class); Set registeredProviderNames = new HashSet<>(oauth2Providers.getRegisteredProviderNames()); registeredProviderNames.addAll(oauth1Providers.getRegisteredProviderNames()); for (String name : registeredProviderNames) { final List links = new LinkedList<>(); links.add( LinksHelper.createLink( HttpMethod.GET, uriBuilder .clone() .path(OAuthAuthenticationService.class, "authenticate") .build() .toString(), null, null, "Authenticate URL", newDto(LinkParameter.class) .withName("oauth_provider") .withRequired(true) .withDefaultValue(name), newDto(LinkParameter.class) .withName("mode") .withRequired(true) .withDefaultValue("federated_login"))); OAuthAuthenticator authenticator = oauth2Providers.getAuthenticator(name); result.add( newDto(OAuthAuthenticatorDescriptor.class) .withName(name) .withEndpointUrl( authenticator != null ? authenticator.getEndpointUrl() : oauth1Providers.getAuthenticator(name).getEndpointUrl()) .withLinks(links)); } return result; } @Override public OAuthToken getOrRefreshToken(String oauthProvider) throws NotFoundException, UnauthorizedException, ServerException { OAuthAuthenticator provider = getAuthenticator(oauthProvider); Subject subject = EnvironmentContext.getCurrent().getSubject(); try { OAuthToken token = provider.getOrRefreshToken(subject.getUserId()); if (token == null) { token = provider.getOrRefreshToken(subject.getUserName()); } if (token != null) { return token; } else { Optional tokenOptional; try { tokenOptional = personalAccessTokenManager.get(subject, oauthProvider, null, null); if (tokenOptional.isEmpty()) { tokenOptional = personalAccessTokenManager.get(subject, null, provider.getEndpointUrl(), null); } if (tokenOptional.isPresent()) { return newDto(OAuthToken.class).withToken(tokenOptional.get().getToken()); } } catch (ScmConfigurationPersistenceException | ScmCommunicationException e) { throw new RuntimeException(e); } } throw new UnauthorizedException( "OAuth token for user " + subject.getUserId() + " was not found"); } catch (IOException e) { throw new ServerException(e.getLocalizedMessage(), e); } } @Override public OAuthToken refreshToken(String oauthProvider) throws NotFoundException, UnauthorizedException, ServerException { OAuthAuthenticator provider = getAuthenticator(oauthProvider); Subject subject = EnvironmentContext.getCurrent().getSubject(); try { OAuthToken token = provider.refreshToken(subject.getUserId()); if (token == null) { token = provider.refreshToken(subject.getUserName()); } if (token != null) { return token; } else { throw new UnauthorizedException( "OAuth token for user " + subject.getUserId() + " was not found"); } } catch (IOException e) { throw new ServerException(e.getLocalizedMessage(), e); } } @Override public void invalidateToken(String oauthProvider) throws NotFoundException, UnauthorizedException, ServerException { OAuthAuthenticator oauth = getAuthenticator(oauthProvider); OAuthToken oauthToken = getOrRefreshToken(oauthProvider); try { if (!oauth.invalidateToken(oauthToken.getToken())) { throw new UnauthorizedException( "OAuth token for provider " + oauthProvider + " was not found"); } } catch (IOException e) { throw new ServerException(e.getMessage()); } } protected OAuthAuthenticator getAuthenticator(String oauthProviderName) throws NotFoundException { OAuthAuthenticator oauth = oauth2Providers.getAuthenticator(oauthProviderName); if (oauth == null) { LOG.warn("Unsupported OAuth provider {} ", oauthProviderName); throw new NotFoundException("Unsupported OAuth provider " + oauthProviderName); } return oauth; } } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuth2.gwt.xml ================================================ ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAPI.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; import java.util.List; import java.util.Set; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.*; import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor; /** * Interface of OAuth authentication service API component, that is used for. * * @author Mykhailo Kuznietsov */ public interface OAuthAPI { /** * Implementation of method {@link OAuthAuthenticationService#authenticate(String, String, List, * HttpServletRequest)} */ Response authenticate( UriInfo uriInfo, String oauthProvider, List scopes, String redirectAfterLogin, HttpServletRequest request) throws NotFoundException, OAuthAuthenticationException, ForbiddenException, BadRequestException; /** Implementation of method {@link OAuthAuthenticationService#callback(List)} */ Response callback(UriInfo uriInfo, List errorValues) throws NotFoundException, OAuthAuthenticationException, ForbiddenException; /** Implementation of method {@link OAuthAuthenticationService#getRegisteredAuthenticators()} */ Set getRegisteredAuthenticators(UriInfo uriInfo) throws ForbiddenException; /** Implementation of method {@link OAuthAuthenticationService#token(String)} */ OAuthToken getOrRefreshToken(String oauthProvider) throws NotFoundException, UnauthorizedException, ServerException, ForbiddenException, BadRequestException, ConflictException; /** * Refreshes the token for the given OAuth provider. * * @param oauthProvider - the OAuth provider name * @return the refreshed token */ OAuthToken refreshToken(String oauthProvider) throws NotFoundException, UnauthorizedException, ServerException, ForbiddenException; /** Implementation of method {@link OAuthAuthenticationService#invalidate(String)}} */ void invalidateToken(String oauthProvider) throws NotFoundException, UnauthorizedException, ServerException, ForbiddenException; } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticationException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; @SuppressWarnings("serial") public final class OAuthAuthenticationException extends Exception { public OAuthAuthenticationException(String message) { super(message); } public OAuthAuthenticationException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticationService.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.UriInfo; import java.util.List; import java.util.Set; import javax.inject.Inject; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.*; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.core.rest.annotations.Required; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor; /** RESTful wrapper for OAuthAuthenticator. */ @Path("oauth") public class OAuthAuthenticationService extends Service { @Context protected UriInfo uriInfo; @Context protected SecurityContext security; @Inject private OAuthAPI oAuthAPI; @Inject private AuthorisationRequestManager authorisationRequestManager; /** * Redirect request to OAuth provider site for authentication|authorization. Client must provide * query parameters, that may or may not be required, depending on the active implementation of * {@link OAuthAPI}. * * @param oauthProvider - * @param redirectAfterLogin * @param scopes - list * @return typically Response that redirect user for OAuth provider site */ @GET @Path("authenticate") public Response authenticate( @QueryParam("oauth_provider") String oauthProvider, @QueryParam("redirect_after_login") String redirectAfterLogin, @QueryParam("scope") List scopes, @Context HttpServletRequest request) throws NotFoundException, OAuthAuthenticationException, BadRequestException, ForbiddenException { return oAuthAPI.authenticate(uriInfo, oauthProvider, scopes, redirectAfterLogin, request); } @GET @Path("callback") /** Process OAuth callback */ public Response callback(@QueryParam("errorValues") List errorValues) throws OAuthAuthenticationException, NotFoundException, ForbiddenException { authorisationRequestManager.callback(uriInfo, errorValues); return oAuthAPI.callback(uriInfo, errorValues); } /** * Gets list of installed OAuth authenticators. * * @return list of installed OAuth authenticators */ @GET @Produces(MediaType.APPLICATION_JSON) public Set getRegisteredAuthenticators() throws ForbiddenException { return oAuthAPI.getRegisteredAuthenticators(uriInfo); } /** * Gets OAuth token for user. * * @param oauthProvider OAuth provider name * @return OAuthToken * @throws ServerException */ @GET @Path("token") @Produces(MediaType.APPLICATION_JSON) public OAuthToken token(@Required @QueryParam("oauth_provider") String oauthProvider) throws ServerException, UnauthorizedException, NotFoundException, ForbiddenException, BadRequestException, ConflictException { return oAuthAPI.getOrRefreshToken(oauthProvider); } /** * Invalidate OAuth token for user. * * @param oauthProvider OAuth provider name */ @DELETE @Path("token") public void invalidate(@Required @QueryParam("oauth_provider") String oauthProvider) throws UnauthorizedException, NotFoundException, ServerException, ForbiddenException { oAuthAPI.invalidateToken(oauthProvider); } } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static org.eclipse.che.dto.server.DtoFactory.newDto; import com.google.api.client.auth.oauth2.*; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.store.MemoryDataStoreFactory; import jakarta.ws.rs.core.MediaType; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import javax.net.ssl.SSLHandshakeException; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.json.JsonHelper; import org.eclipse.che.commons.json.JsonParseException; import org.eclipse.che.security.oauth.shared.OAuthTokenProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Authentication service which allow get access token from OAuth provider site. */ public abstract class OAuthAuthenticator { protected static final String AUTHENTICATOR_IS_NOT_CONFIGURED = "Authenticator is not configured"; protected static final int SSL_ERROR_CODE = 495; private static final Logger LOG = LoggerFactory.getLogger(OAuthAuthenticator.class); protected AuthorizationCodeFlow flow; protected Map redirectUrisMap; /** * @see {@link #configure(String, String, String[], String, String, MemoryDataStoreFactory, List)} */ protected void configure( String clientId, String clientSecret, String[] redirectUris, String authUri, String tokenUri, MemoryDataStoreFactory dataStoreFactory) throws IOException { configure( clientId, clientSecret, redirectUris, authUri, tokenUri, dataStoreFactory, Collections.emptyList()); } /** * This method should be invoked by child class for initialization default instance of {@link * AuthorizationCodeFlow} that will be used for authorization */ protected void configure( String clientId, String clientSecret, String[] redirectUris, String authUri, String tokenUri, MemoryDataStoreFactory dataStoreFactory, List scopes) throws IOException { final AuthorizationCodeFlow authorizationFlow = new AuthorizationCodeFlow.Builder( BearerToken.authorizationHeaderAccessMethod(), new NetHttpTransport(), new JacksonFactory(), new GenericUrl(tokenUri), new ClientParametersAuthentication(clientId, clientSecret), clientId, authUri) .setDataStoreFactory(dataStoreFactory) .setScopes(scopes) .build(); LOG.debug( "clientId={}, clientSecret={}, redirectUris={} , authUri={}, tokenUri={}, dataStoreFactory={}", clientId, clientSecret, redirectUris, authUri, tokenUri, dataStoreFactory); configure(authorizationFlow, Arrays.asList(redirectUris)); } /** * This method should be invoked by child class for setting instance of {@link * AuthorizationCodeFlow} that will be used for authorization */ protected void configure(AuthorizationCodeFlow flow, List redirectUris) { this.flow = flow; this.redirectUrisMap = new HashMap<>(redirectUris.size()); for (String uri : redirectUris) { // Redirect URI may be in form urn:ietf:wg:oauth:2.0:oob os use java.net.URI instead of // java.net.URL this.redirectUrisMap.put( Pattern.compile("([a-z0-9\\-]+\\.)?" + URI.create(uri).getHost()), uri); } } /** * Create authentication URL. * * @param requestUrl URL of current HTTP request. This parameter required to be able determine URL * for redirection after authentication. If URL contains query parameters they will be copy to * 'state' parameter and returned to callback method. * @param scopes specify exactly what type of access needed * @return URL for authentication */ public String getAuthenticateUrl(URL requestUrl, List scopes) throws OAuthAuthenticationException { if (!isConfigured()) { throw new OAuthAuthenticationException(AUTHENTICATOR_IS_NOT_CONFIGURED); } AuthorizationCodeRequestUrl url = flow.newAuthorizationUrl().setRedirectUri(findRedirectUrl(requestUrl)).setScopes(scopes); url.setState(prepareState(requestUrl)); return url.build(); } protected String prepareState(URL requestUrl) { StringBuilder state = new StringBuilder(); String query = requestUrl.getQuery(); if (query != null) { if (state.length() > 0) { state.append('&'); } state.append(query); } return URLEncoder.encode(state.toString(), StandardCharsets.UTF_8); } protected String findRedirectUrl(URL requestUrl) { final String requestHost = requestUrl.getHost(); for (Map.Entry e : redirectUrisMap.entrySet()) { if (e.getKey().matcher(requestHost).matches()) { return e.getValue(); } } return null; // TODO : throw exception instead of return null ??? } /** * Process callback request. * * @param requestUrl request URI. URI should contain authorization code generated by authorization * server * @param scopes specify exactly what type of access needed. This list must be exactly the same as * list passed to the method {@link #getAuthenticateUrl(URL, java.util.List)} * @return access token * @throws OAuthAuthenticationException if authentication failed or requestUrl does * not contain required parameters, e.g. 'code' * @throws ScmCommunicationException if communication with SCM failed */ public String callback(URL requestUrl, List scopes) throws OAuthAuthenticationException, ScmCommunicationException { if (!isConfigured()) { throw new OAuthAuthenticationException(AUTHENTICATOR_IS_NOT_CONFIGURED); } AuthorizationCodeResponseUrl authorizationCodeResponseUrl = new AuthorizationCodeResponseUrl(requestUrl.toString()); final String error = authorizationCodeResponseUrl.getError(); if (error != null) { throw new OAuthAuthenticationException("Authentication failed: " + error); } final String code = authorizationCodeResponseUrl.getCode(); if (code == null) { throw new OAuthAuthenticationException("Missing authorization code. "); } try { TokenResponse tokenResponse = getAuthorizationCodeTokenRequest(requestUrl, scopes, code).execute(); String userId = getUserFromUrl(authorizationCodeResponseUrl); if (userId == null) { userId = EnvironmentContext.getCurrent().getSubject().getUserId(); } flow.createAndStoreCredential(tokenResponse, userId); return tokenResponse.getAccessToken(); } catch (IOException ioe) { if (ioe instanceof SSLHandshakeException) { throw new ScmCommunicationException( "The required SSL certificate is missing or not trusted by the system. Please contact your administrator.", SSL_ERROR_CODE); } throw new OAuthAuthenticationException(ioe.getMessage()); } } /** * Creates a new {@link AuthorizationCodeTokenRequest} for the given authorization code. Intended * to be overridden by subclasses to customize the request. */ protected AuthorizationCodeTokenRequest getAuthorizationCodeTokenRequest( URL requestUrl, List scopes, String code) { return flow.newTokenRequest(code) .setRequestInitializer( request -> { if (request.getParser() == null) { request.setParser(flow.getJsonFactory().createJsonObjectParser()); } request.getHeaders().setAccept(MediaType.APPLICATION_JSON); }) .setRedirectUri(findRedirectUrl(requestUrl)) .setScopes(scopes); } /** * Get the name of OAuth provider supported by current implementation. * * @return oauth provider name */ public abstract String getOAuthProvider(); private String getUserFromUrl(AuthorizationCodeResponseUrl authorizationCodeResponseUrl) throws IOException { String state = authorizationCodeResponseUrl.getState(); if (!(state == null || state.isEmpty())) { String decoded = URLDecoder.decode(state, "UTF-8"); String[] items = decoded.split("&"); for (String str : items) { if (str.startsWith("userId=")) { return str.substring(7, str.length()); } } } return null; } protected O getJson(String getUserUrl, String accessToken, Class userClass) throws OAuthAuthenticationException { HttpURLConnection urlConnection = null; InputStream urlInputStream = null; try { urlConnection = (HttpURLConnection) new URL(getUserUrl).openConnection(); urlConnection.setRequestProperty("Authorization", "token " + accessToken); urlInputStream = urlConnection.getInputStream(); return JsonHelper.fromJson(urlInputStream, userClass, null); } catch (JsonParseException | IOException e) { throw new OAuthAuthenticationException(e.getMessage(), e); } finally { if (urlInputStream != null) { try { urlInputStream.close(); } catch (IOException ignored) { } } if (urlConnection != null) { urlConnection.disconnect(); } } } /** * Return authorization token by userId. * *

    WARN!!!. DO not use it directly. * * @param userId user identifier * @return token value or {@code null}. When user have valid token then it will be returned, when * user have expired token and it can be refreshed then refreshed value will be returned, when * none token found for user then {@code null} will be returned, when user have expired token * and it can't be refreshed then {@code null} will be returned * @throws IOException when error occurs during token loading * @see OAuthTokenProvider#getToken(String, String) TODO: return Optional to avoid * returning null. */ public OAuthToken getOrRefreshToken(String userId) throws IOException { if (!isConfigured()) { throw new IOException(AUTHENTICATOR_IS_NOT_CONFIGURED); } Credential credential = flow.loadCredential(userId); if (credential == null) { return null; } final Long expirationTime = credential.getExpiresInSeconds(); if (expirationTime != null && expirationTime < 0) { boolean tokenRefreshed; try { tokenRefreshed = credential.refreshToken(); } catch (IOException ioEx) { tokenRefreshed = false; } if (tokenRefreshed) { credential = flow.loadCredential(userId); } else { // if token is not refreshed then old value should be invalidated // and null result should be returned try { invalidateTokenByUser(userId); } catch (IOException ignored) { } return null; } } return newDto(OAuthToken.class).withToken(credential.getAccessToken()); } /** * Refresh personal access token. * * @param userId user identifier * @return a refreshed token value or {@code null} * @throws IOException when error occurs during token loading */ public OAuthToken refreshToken(String userId) throws IOException { if (!isConfigured()) { throw new IOException(AUTHENTICATOR_IS_NOT_CONFIGURED); } Credential credential = flow.loadCredential(userId); if (credential == null) { return null; } boolean tokenRefreshed; try { tokenRefreshed = credential.refreshToken(); } catch (IOException ioEx) { tokenRefreshed = false; } if (tokenRefreshed) { credential = flow.loadCredential(userId); } else { // if token is not refreshed then old value should be invalidated // and null result should be returned try { invalidateTokenByUser(userId); } catch (IOException ignored) { } return null; } return newDto(OAuthToken.class).withToken(credential.getAccessToken()); } /** * Invalidate OAuth token for specified user. * * @param userId user */ private void invalidateTokenByUser(String userId) throws IOException { Credential credential = flow.loadCredential(userId); if (credential != null) { flow.getCredentialDataStore().delete(userId); } } /** * Invalidate OAuth token. * * @param token oauth token * @return true if OAuth token invalidated and false otherwise, e.g. if * token was not found */ public boolean invalidateToken(String token) throws IOException { throw new UnsupportedOperationException("Should be implemented by specific provider"); } /** * Get endpoint URL. * * @return provider's endpoint URL */ public abstract String getEndpointUrl(); /** * Checks configuring of authenticator * * @return true only if authenticator have valid configuration data and it is able to authorize * otherwise returns false */ public boolean isConfigured() { return flow != null; } } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticatorProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import java.util.Set; /** Allow store and provide services which implementations of OAuthAuthenticator. */ public interface OAuthAuthenticatorProvider { /** * Get authentication service by name. * * @param oauthProviderName name of OAuth provider * @return OAuthAuthenticator instance or null if specified OAuth provider is not * supported */ OAuthAuthenticator getAuthenticator(String oauthProviderName); /** * Gets registered OAuth provider names * * @return set of registered OAuth provider names */ Set getRegisteredProviderNames(); } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticatorProviderImpl.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; @Singleton public class OAuthAuthenticatorProviderImpl implements OAuthAuthenticatorProvider { private final Map authenticatorMap = new HashMap<>(); @Inject public OAuthAuthenticatorProviderImpl(Set oAuthAuthenticators) { for (OAuthAuthenticator authenticator : oAuthAuthenticators) { if (authenticator.isConfigured()) { authenticatorMap.put(authenticator.getOAuthProvider(), authenticator); } } } @Override public OAuthAuthenticator getAuthenticator(String oauthProviderName) { return authenticatorMap.get(oauthProviderName); } @Override public Set getRegisteredProviderNames() { return Collections.unmodifiableSet(authenticatorMap.keySet()); } } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticatorTokenProvider.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import java.io.IOException; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.security.oauth.shared.OAuthTokenProvider; /** Retrieves OAuth token with help of OAuthAuthenticatorProvider. */ @Singleton public class OAuthAuthenticatorTokenProvider implements OAuthTokenProvider { private final OAuthAuthenticatorProvider oAuthAuthenticatorProvider; @Inject public OAuthAuthenticatorTokenProvider(OAuthAuthenticatorProvider oAuthAuthenticatorProvider) { this.oAuthAuthenticatorProvider = oAuthAuthenticatorProvider; } @Override public OAuthToken getToken(String oauthProviderName, String userId) throws IOException { OAuthAuthenticator oAuthAuthenticator = oAuthAuthenticatorProvider.getAuthenticator(oauthProviderName); OAuthToken token; if (oAuthAuthenticator != null && (token = oAuthAuthenticator.getOrRefreshToken(userId)) != null) { return token; } return null; } } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth1/OAuthAuthenticationException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth1; import org.eclipse.che.api.core.ServerException; /** * Exception raised when the OAuth authentication failed. * * @author Kevin Pollet * @author Igor Vinokur */ public class OAuthAuthenticationException extends ServerException { /** * Constructs an instance of {@link OAuthAuthenticationException}. * * @param message the exception message. */ public OAuthAuthenticationException(final String message) { super(message); } /** * Constructs an instance of {@link OAuthAuthenticationException}. * * @param cause the cause of the exception. */ public OAuthAuthenticationException(final Throwable cause) { super(cause); } } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth1/OAuthAuthenticationService.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth1; import static java.lang.String.format; import static org.eclipse.che.commons.lang.UrlUtils.getParameter; import static org.eclipse.che.commons.lang.UrlUtils.getQueryParametersFromState; import static org.eclipse.che.commons.lang.UrlUtils.getRequestUrl; import static org.eclipse.che.commons.lang.UrlUtils.getState; import static org.eclipse.che.security.oauth.EmbeddedOAuthAPI.getRedirectAfterLoginUrl; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import java.net.URI; import java.net.URL; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.commons.env.EnvironmentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * RESTful wrapper for OAuth 1.0. * * @author Kevin Pollet * @author Igor Vinokur */ @Path("oauth/1.0") public class OAuthAuthenticationService extends Service { private static final Logger LOG = LoggerFactory.getLogger(OAuthAuthenticationService.class); private static final String UNSUPPORTED_OAUTH_PROVIDER_ERROR = "Unsupported OAuth provider: %s"; public static final String ERROR_QUERY_NAME = "error_code"; @Inject protected OAuthAuthenticatorProvider providers; @GET @Path("authenticate") public Response authenticate( @QueryParam("oauth_provider") String providerName, @QueryParam("request_method") String requestMethod, @QueryParam("signature_method") String signatureMethod, @QueryParam("redirect_after_login") String redirectAfterLogin) throws OAuthAuthenticationException, BadRequestException { requiredNotNull(providerName, "Provider name"); requiredNotNull(redirectAfterLogin, "Redirect after login"); final OAuthAuthenticator oauth = getAuthenticator(providerName); final String authUrl = oauth.getAuthenticateUrl(getRequestUrl(uriInfo), requestMethod, signatureMethod); return Response.temporaryRedirect(URI.create(authUrl)).build(); } @GET @Path("callback") public Response callback() throws OAuthAuthenticationException, BadRequestException { final URL requestUrl = getRequestUrl(uriInfo); final Map> parameters = getQueryParametersFromState(getState(requestUrl)); final String providerName = getParameter(parameters, "oauth_provider"); final String redirectAfterLogin = getRedirectAfterLoginUrl(parameters, null); UriBuilder redirectUriBuilder = UriBuilder.fromUri(redirectAfterLogin); try { getAuthenticator(providerName).callback(requestUrl); } catch (UserDeniedOAuthAuthenticationException e) { redirectUriBuilder.queryParam(ERROR_QUERY_NAME, "access_denied"); } catch (OAuthAuthenticationException e) { redirectUriBuilder.queryParam(ERROR_QUERY_NAME, "invalid_request"); } return Response.temporaryRedirect(redirectUriBuilder.build()).build(); } @GET @Path("signature") public String signature( @QueryParam("oauth_provider") String providerName, @QueryParam("request_url") String requestUrl, @QueryParam("request_method") String requestMethod) throws OAuthAuthenticationException, BadRequestException { requiredNotNull(providerName, "Provider name"); requiredNotNull(requestUrl, "Request url"); requiredNotNull(requestMethod, "Request method"); return getAuthenticator(providerName) .computeAuthorizationHeader( EnvironmentContext.getCurrent().getSubject().getUserId(), requestMethod, requestUrl); } private OAuthAuthenticator getAuthenticator(String oauthProviderName) throws BadRequestException { OAuthAuthenticator oauth = providers.getAuthenticator(oauthProviderName); if (oauth == null) { LOG.warn(format(UNSUPPORTED_OAUTH_PROVIDER_ERROR, oauthProviderName)); throw new BadRequestException(format(UNSUPPORTED_OAUTH_PROVIDER_ERROR, oauthProviderName)); } return oauth; } /** * Checks object reference is not {@code null} * * @param object object reference to check * @param subject used as subject of exception message "{subject} required" * @throws BadRequestException when object reference is {@code null} */ private void requiredNotNull(Object object, String subject) throws BadRequestException { if (object == null) { throw new BadRequestException(subject + " required"); } } } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth1/OAuthAuthenticator.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth1; import static com.google.common.base.Strings.isNullOrEmpty; import static java.net.URLDecoder.decode; import static org.eclipse.che.dto.server.DtoFactory.newDto; import com.google.api.client.auth.oauth.OAuthAuthorizeTemporaryTokenUrl; import com.google.api.client.auth.oauth.OAuthCredentialsResponse; import com.google.api.client.auth.oauth.OAuthGetAccessToken; import com.google.api.client.auth.oauth.OAuthGetTemporaryToken; import com.google.api.client.auth.oauth.OAuthHmacSigner; import com.google.api.client.auth.oauth.OAuthParameters; import com.google.api.client.auth.oauth.OAuthRsaSigner; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.util.Base64; import java.io.UnsupportedEncodingException; import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; /** * Authentication service which allows get access token from OAuth provider site. * * @author Kevin Pollet * @author Igor Vinokur */ public abstract class OAuthAuthenticator { private static final String USER_ID_PARAM_KEY = "userId"; private static final String REQUEST_METHOD_PARAM_KEY = "request_method"; private static final String SIGNATURE_METHOD_PARAM_KEY = "signature_method"; private static final String STATE_PARAM_KEY = "state"; private static final String OAUTH_TOKEN_PARAM_KEY = "oauth_token"; private static final String OAUTH_VERIFIER_PARAM_KEY = "oauth_verifier"; private final String clientId; private final String clientSecret; private final String privateKey; private final String requestTokenUri; private final String accessTokenUri; private final String authorizeTokenUri; private final String redirectUri; private final HttpTransport httpTransport; private final Map credentialsStore; private final ReentrantLock credentialsStoreLock; private final Map sharedTokenSecrets; protected OAuthAuthenticator( String clientId, String requestTokenUri, String accessTokenUri, String authorizeTokenUri, String redirectUri, @Nullable String clientSecret, @Nullable String privateKey) { this.clientId = clientId; this.clientSecret = clientSecret; this.privateKey = privateKey; this.requestTokenUri = requestTokenUri; this.accessTokenUri = accessTokenUri; this.authorizeTokenUri = authorizeTokenUri; this.redirectUri = redirectUri; this.httpTransport = new NetHttpTransport(); this.credentialsStore = new HashMap<>(); this.credentialsStoreLock = new ReentrantLock(); this.sharedTokenSecrets = new HashMap<>(); } /** * Create authentication URL. * * @param requestUrl URL of current HTTP request. This parameter required to be able determine URL * for redirection after authentication. If URL contains query parameters they will be copied * to 'state' parameter and returned to callback method. * @param requestMethod HTTP request method that will be used to request temporary token * @param signatureMethod OAuth signature algorithm * @return URL for authentication. * @throws OAuthAuthenticationException if authentication failed. */ String getAuthenticateUrl( final URL requestUrl, @Nullable final String requestMethod, @Nullable final String signatureMethod) throws OAuthAuthenticationException { try { final GenericUrl callbackUrl = new GenericUrl(redirectUri); String userId = getParameterFromState(requestUrl.getQuery(), USER_ID_PARAM_KEY); String currentUserId = EnvironmentContext.getCurrent().getSubject().getUserId(); if (userId != null) { if (currentUserId.equals(userId)) { callbackUrl.put(STATE_PARAM_KEY, requestUrl.getQuery()); } else { throw new OAuthAuthenticationException( "Provided query parameter " + USER_ID_PARAM_KEY + "=" + userId + " does not match the current user id: " + currentUserId); } } else { callbackUrl.put( STATE_PARAM_KEY, requestUrl.getQuery() + "&" + USER_ID_PARAM_KEY + "=" + currentUserId); } OAuthGetTemporaryToken temporaryToken; if (requestMethod != null && "post".equalsIgnoreCase(requestMethod)) { temporaryToken = new OAuthPostTemporaryToken(requestTokenUri); } else { temporaryToken = new OAuthGetTemporaryToken(requestTokenUri); } if (signatureMethod != null && "rsa".equalsIgnoreCase(signatureMethod)) { temporaryToken.signer = getOAuthRsaSigner(); } else { temporaryToken.signer = getOAuthHmacSigner(null, null); } temporaryToken.consumerKey = clientId; temporaryToken.callback = callbackUrl.build(); temporaryToken.transport = httpTransport; final OAuthCredentialsResponse credentialsResponse = temporaryToken.execute(); final OAuthAuthorizeTemporaryTokenUrl authorizeTemporaryTokenUrl = new OAuthAuthorizeTemporaryTokenUrl(authorizeTokenUri); authorizeTemporaryTokenUrl.temporaryToken = credentialsResponse.token; sharedTokenSecrets.put(credentialsResponse.token, credentialsResponse.tokenSecret); return authorizeTemporaryTokenUrl.build(); } catch (Exception e) { throw new OAuthAuthenticationException(e.getMessage()); } } /** * Process callback request. * * @param requestUrl request URI. URI should contain OAuth token and OAuth verifier. * @return id of authenticated user * @throws OAuthAuthenticationException if authentication failed or {@code requestUrl} does not * contain required parameters. */ String callback(final URL requestUrl) throws OAuthAuthenticationException { try { final GenericUrl callbackUrl = new GenericUrl(requestUrl.toString()); if (callbackUrl.getFirst(OAUTH_TOKEN_PARAM_KEY) == null) { throw new OAuthAuthenticationException("Missing oauth_token parameter"); } if (callbackUrl.getFirst(OAUTH_VERIFIER_PARAM_KEY) == null) { throw new OAuthAuthenticationException("Missing oauth_verifier parameter"); } final String state = (String) callbackUrl.getFirst(STATE_PARAM_KEY); String requestMethod = getParameterFromState(state, REQUEST_METHOD_PARAM_KEY); String signatureMethod = getParameterFromState(state, SIGNATURE_METHOD_PARAM_KEY); final String oauthTemporaryToken = (String) callbackUrl.getFirst(OAUTH_TOKEN_PARAM_KEY); OAuthGetAccessToken getAccessToken; if (requestMethod != null && "post".equalsIgnoreCase(requestMethod)) { getAccessToken = new OAuthPostAccessToken(accessTokenUri); } else { getAccessToken = new OAuthGetAccessToken(accessTokenUri); } getAccessToken.consumerKey = clientId; getAccessToken.temporaryToken = oauthTemporaryToken; getAccessToken.verifier = (String) callbackUrl.getFirst(OAUTH_VERIFIER_PARAM_KEY); if ("denied".equals(getAccessToken.verifier)) { throw new UserDeniedOAuthAuthenticationException("Authorization denied"); } getAccessToken.transport = httpTransport; if (signatureMethod != null && "rsa".equalsIgnoreCase(signatureMethod)) { getAccessToken.signer = getOAuthRsaSigner(); } else { getAccessToken.signer = getOAuthHmacSigner(clientSecret, sharedTokenSecrets.remove(oauthTemporaryToken)); } final OAuthCredentialsResponse credentials = getAccessToken.execute(); String userId = getParameterFromState(state, USER_ID_PARAM_KEY); credentialsStoreLock.lock(); try { final OAuthCredentialsResponse userId2Credential = credentialsStore.get(userId); if (userId2Credential == null) { credentialsStore.put(userId, credentials); } else { userId2Credential.token = credentials.token; userId2Credential.tokenSecret = credentials.tokenSecret; } } finally { credentialsStoreLock.unlock(); } return userId; } catch (OAuthAuthenticationException e) { throw e; } catch (Exception e) { throw new OAuthAuthenticationException(e.getMessage()); } } /** * Get name of OAuth provider supported by current implementation. * * @return the oauth provider name. */ public abstract String getOAuthProvider(); /** * Returns URL to initiate authentication process using given authenticator. Typically points to * {@code /api/oauth/} or {@code /api/oauth/1.0} endpoint with necessary request params. * * @return URL to initiate authentication process */ public abstract String getLocalAuthenticateUrl(); /** * Get endpoint URL. * * @return provider's endpoint URL */ public abstract String getEndpointUrl(); /** * Compute the Authorization header to sign the OAuth 1 request. * * @param userId the user id. * @param requestMethod the HTTP request method. * @param requestUrl the HTTP request url with encoded query parameters. * @return the authorization header value, or {@code null} if token was not found for given user * id. * @throws OAuthAuthenticationException if authentication failed. */ public String computeAuthorizationHeader( final String userId, final String requestMethod, final String requestUrl) throws OAuthAuthenticationException { final OAuthCredentialsResponse credentials = new OAuthCredentialsResponse(); OAuthToken oauthToken = getToken(userId); credentials.token = oauthToken != null ? oauthToken.getToken() : null; if (credentials.token != null) { return computeAuthorizationHeader( requestMethod, requestUrl, credentials.token, credentials.tokenSecret); } return ""; } private OAuthToken getToken(final String userId) { OAuthCredentialsResponse credentials; credentialsStoreLock.lock(); try { credentials = credentialsStore.get(userId); } finally { credentialsStoreLock.unlock(); } if (credentials != null) { return newDto(OAuthToken.class) .withToken(credentials.token) .withScope(credentials.tokenSecret); } return null; } /** * Compute the Authorization header to sign the OAuth 1 request. * * @param requestMethod the HTTP request method. * @param requestUrl the HTTP request url with encoded query parameters. * @param token the token. * @param tokenSecret the secret token. * @return the authorization header value, or {@code null}. */ private String computeAuthorizationHeader( final String requestMethod, final String requestUrl, final String token, final String tokenSecret) throws OAuthAuthenticationException { OAuthParameters oauthParameters; try { oauthParameters = new OAuthParameters(); oauthParameters.consumerKey = clientId; oauthParameters.signer = clientSecret == null ? getOAuthRsaSigner() : getOAuthHmacSigner(clientSecret, tokenSecret); oauthParameters.token = token; oauthParameters.version = "1.0"; oauthParameters.computeNonce(); oauthParameters.computeTimestamp(); oauthParameters.computeSignature(requestMethod, new GenericUrl(requestUrl)); } catch (GeneralSecurityException e) { throw new OAuthAuthenticationException(e); } return oauthParameters.getAuthorizationHeader(); } private String getParameterFromState(String state, String parameterName) { if (isNullOrEmpty(state)) { return null; } for (final String param : extractStateParams(state)) { if (param.startsWith(parameterName + "=")) { return param.substring(parameterName.length() + 1); } } return null; } private String[] extractStateParams(String state) { try { String decodedState = decode(state, "UTF-8"); return decodedState.split("&"); } catch (UnsupportedEncodingException ignored) { // should never happen, UTF-8 supported. } return null; } private OAuthRsaSigner getOAuthRsaSigner() throws NoSuchAlgorithmException, InvalidKeySpecException { OAuthRsaSigner oAuthRsaSigner = new OAuthRsaSigner(); oAuthRsaSigner.privateKey = getPrivateKey(privateKey); return oAuthRsaSigner; } private OAuthHmacSigner getOAuthHmacSigner( @Nullable String clientSecret, @Nullable String oauthTemporaryToken) throws NoSuchAlgorithmException, InvalidKeySpecException { final OAuthHmacSigner signer = new OAuthHmacSigner(); signer.clientSharedSecret = clientSecret; signer.tokenSharedSecret = sharedTokenSecrets.remove(oauthTemporaryToken); return signer; } private PrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException { byte[] privateKeyBytes = Base64.decodeBase64(privateKey); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePrivate(keySpec); } private static class OAuthPostTemporaryToken extends OAuthGetTemporaryToken { OAuthPostTemporaryToken(String authorizationServerUrl) { super(authorizationServerUrl); super.usePost = true; } } private static class OAuthPostAccessToken extends OAuthGetAccessToken { OAuthPostAccessToken(String authorizationServerUrl) { super(authorizationServerUrl); super.usePost = true; } } } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth1/OAuthAuthenticatorProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth1; import static java.util.stream.Collectors.toUnmodifiableSet; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; /** * Allow store and provide services with implementations of {@link OAuthAuthenticator} for OAuth 1. * * @author Kevin Pollet * @author Igor Vinokur */ @Singleton public class OAuthAuthenticatorProvider { private final Map oAuthAuthenticators = new HashMap<>(); @Inject public OAuthAuthenticatorProvider(final Set oAuthAuthenticators) { oAuthAuthenticators.forEach( authenticator -> this.oAuthAuthenticators.put(authenticator.getOAuthProvider(), authenticator)); } /** * Get the OAuth authentication service by name. * * @param oauthProviderName name of the OAuth provider. * @return {@link OAuthAuthenticator} instance or {@code null} if specified OAuth provider is not * supported. */ public OAuthAuthenticator getAuthenticator(String oauthProviderName) { return oAuthAuthenticators.get(oauthProviderName); } /** * Gets registered OAuth1 provider names * * @return set of registered OAuth1 provider names */ public Set getRegisteredProviderNames() { return oAuthAuthenticators.keySet().stream() .filter(key -> !"Noop".equals(key)) .collect(toUnmodifiableSet()); } } ================================================ FILE: wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth1/UserDeniedOAuthAuthenticationException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth1; /** Exception used when a user denies access on the OAuth authorization page. */ public final class UserDeniedOAuthAuthenticationException extends OAuthAuthenticationException { public UserDeniedOAuthAuthenticationException(String message) { super(message); } public UserDeniedOAuthAuthenticationException(Throwable cause) { super(cause); } } ================================================ FILE: wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPITest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static java.net.URLEncoder.encode; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.eclipse.che.security.oauth.OAuthAuthenticator.SSL_ERROR_CODE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriInfo; import java.lang.reflect.Field; import java.net.URI; import java.net.URL; import java.util.Set; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Mykhailo Kuznietsov */ @Listeners(value = MockitoTestNGListener.class) public class EmbeddedOAuthAPITest { @Mock OAuthAuthenticatorProvider oauth2Providers; @Mock org.eclipse.che.security.oauth1.OAuthAuthenticatorProvider oauth1Providers; @Mock PersonalAccessTokenManager personalAccessTokenManager; @InjectMocks EmbeddedOAuthAPI embeddedOAuthAPI; @Test( expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Unsupported OAuth provider unknown") public void shouldThrowExceptionIfNoSuchProviderFound() throws Exception { embeddedOAuthAPI.getOrRefreshToken("unknown"); } @Test public void shouldBeAbleToGetUserToken() throws Exception { String provider = "myprovider"; String token = "token123"; OAuthAuthenticator authenticator = mock(OAuthAuthenticator.class); when(oauth2Providers.getAuthenticator(eq(provider))).thenReturn(authenticator); when(authenticator.getOrRefreshToken(anyString())) .thenReturn(newDto(OAuthToken.class).withToken(token)); OAuthToken result = embeddedOAuthAPI.getOrRefreshToken(provider); assertEquals(result.getToken(), token); } @Test public void shouldGetRegisteredAuthenticators() throws Exception { // given UriInfo uriInfo = mock(UriInfo.class); when(uriInfo.getBaseUriBuilder()).thenReturn(UriBuilder.fromUri("http://eclipse.che")); when(oauth2Providers.getRegisteredProviderNames()).thenReturn(Set.of("github")); when(oauth1Providers.getRegisteredProviderNames()).thenReturn(Set.of("bitbucket")); org.eclipse.che.security.oauth1.OAuthAuthenticator authenticator = mock(org.eclipse.che.security.oauth1.OAuthAuthenticator.class); when(oauth2Providers.getAuthenticator("github")).thenReturn(mock(OAuthAuthenticator.class)); when(oauth1Providers.getAuthenticator("bitbucket")).thenReturn(authenticator); // when Set registeredAuthenticators = embeddedOAuthAPI.getRegisteredAuthenticators(uriInfo); // then assertEquals(registeredAuthenticators.size(), 2); } @Test public void shouldEncodeRejectErrorForRedirectUrl() throws Exception { // given UriInfo uriInfo = mock(UriInfo.class); when(uriInfo.getRequestUri()).thenReturn(new URI("http://eclipse.che")); Field redirectAfterLogin = EmbeddedOAuthAPI.class.getDeclaredField("redirectAfterLogin"); redirectAfterLogin.setAccessible(true); redirectAfterLogin.set(embeddedOAuthAPI, "http://eclipse.che?quary=param"); // when Response callback = embeddedOAuthAPI.callback(uriInfo, singletonList("access_denied")); // then assertEquals( callback.getLocation().toString(), "http://eclipse.che?quary%3Dparam%26error_code%3Daccess_denied"); } @Test public void shouldAddSslErrorCode() throws Exception { // given UriInfo uriInfo = mock(UriInfo.class); OAuthAuthenticator authenticator = mock(OAuthAuthenticator.class); when(authenticator.callback(any(URL.class), anyList())) .thenThrow(new ScmCommunicationException("", SSL_ERROR_CODE)); when(uriInfo.getRequestUri()) .thenReturn( new URI( "http://eclipse.che?state=oauth_provider" + encode( "=github&redirect_after_login=https://redirecturl.com?params=", UTF_8))); when(oauth2Providers.getAuthenticator("github")).thenReturn(authenticator); // when Response callback = embeddedOAuthAPI.callback(uriInfo, singletonList("ssl_exception")); // then assertEquals( callback.getLocation().toString(), "https://redirecturl.com?params=&error_code=ssl_exception"); } @Test public void shouldStoreTokenOnCallback() throws Exception { // given UriInfo uriInfo = mock(UriInfo.class); OAuthAuthenticator authenticator = mock(OAuthAuthenticator.class); when(authenticator.getEndpointUrl()).thenReturn("http://eclipse.che"); when(authenticator.callback(any(URL.class), anyList())).thenReturn("token"); when(uriInfo.getRequestUri()) .thenReturn( new URI( "http://eclipse.che?state=oauth_provider%3Dgithub%26redirect_after_login%3DredirectUrl")); when(oauth2Providers.getAuthenticator("github")).thenReturn(authenticator); ArgumentCaptor tokenCapture = ArgumentCaptor.forClass(PersonalAccessToken.class); // when embeddedOAuthAPI.callback(uriInfo, emptyList()); // then verify(personalAccessTokenManager).store(tokenCapture.capture()); PersonalAccessToken token = tokenCapture.getValue(); assertEquals(token.getScmProviderUrl(), "http://eclipse.che"); assertEquals(token.getCheUserId(), "0000-00-0000"); assertTrue(token.getScmTokenId().startsWith("id-")); assertTrue(token.getScmTokenName().startsWith(OAUTH_2_PREFIX)); assertEquals(token.getToken(), "token"); } @Test public void shouldEncodeRedirectUrl() throws Exception { // given UriInfo uriInfo = mock(UriInfo.class); OAuthAuthenticator authenticator = mock(OAuthAuthenticator.class); when(uriInfo.getRequestUri()) .thenReturn( new URI( "http://eclipse.che?state=oauth_provider" + encode( "=github&redirect_after_login=https://redirecturl.com?params=" + encode("{}", UTF_8), UTF_8))); when(oauth2Providers.getAuthenticator("github")).thenReturn(authenticator); // when Response callback = embeddedOAuthAPI.callback(uriInfo, emptyList()); // then assertEquals(callback.getLocation().toString(), "https://redirecturl.com?params%3D%7B%7D"); } @Test public void shouldNotEncodeRedirectUrl() throws Exception { // given UriInfo uriInfo = mock(UriInfo.class); OAuthAuthenticator authenticator = mock(OAuthAuthenticator.class); when(uriInfo.getRequestUri()) .thenReturn( new URI( "http://eclipse.che?state=oauth_provider" + encode( "=github&redirect_after_login=https://redirecturl.com?params=" + encode(encode("{}", UTF_8), UTF_8), UTF_8))); when(oauth2Providers.getAuthenticator("github")).thenReturn(authenticator); // when Response callback = embeddedOAuthAPI.callback(uriInfo, emptyList()); // then assertEquals(callback.getLocation().toString(), "https://redirecturl.com?params=%7B%7D"); } } ================================================ FILE: wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth1/OAuthAuthenticationServiceTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth1; import static io.restassured.RestAssured.given; import static java.net.URLEncoder.encode; import static java.nio.charset.StandardCharsets.UTF_8; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; import static org.everrest.assured.JettyHttpServer.SECURE_PATH; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import io.restassured.response.Response; import jakarta.ws.rs.core.UriInfo; import java.lang.reflect.Field; import java.net.URI; import java.net.URL; import org.eclipse.che.api.core.rest.Service; import org.everrest.assured.EverrestJetty; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners({EverrestJetty.class, MockitoTestNGListener.class}) public class OAuthAuthenticationServiceTest { private final String REDIRECT_URI = "/dashboard"; private final String STATE = "oauth_provider=test-server&request_method=POST&signature_method=rsa&redirect_after_login=" + REDIRECT_URI; private final String OAUTH_TOKEN = "JeZlJxu8bd1ewAmCkG668PCLC5kJ9ne1"; private final String OAUTH_VERIFIER = "hfdp7dh39dks9884"; @Mock private OAuthAuthenticator oAuthAuthenticator; @Mock private UriInfo uriInfo; @Mock private OAuthAuthenticatorProvider oAuthProvider; @InjectMocks private OAuthAuthenticationService oAuthAuthenticationService; @Test public void shouldResolveCallbackWithoutError() throws OAuthAuthenticationException { when(oAuthProvider.getAuthenticator("test-server")).thenReturn(oAuthAuthenticator); when(oAuthAuthenticator.callback(any(URL.class))).thenReturn("user1"); final Response response = given() .redirects() .follow(false) .queryParam("state", STATE) .queryParam("oauth_token", OAUTH_TOKEN) .queryParam("oauth_verifier", OAUTH_VERIFIER) .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/oauth/1.0/callback"); assertEquals(response.statusCode(), 307); assertEquals(response.header("Location"), REDIRECT_URI); } @Test public void shouldResolveCallbackWithAccessDeniedError() throws OAuthAuthenticationException { when(oAuthProvider.getAuthenticator("test-server")).thenReturn(oAuthAuthenticator); when(oAuthAuthenticator.callback(any(URL.class))) .thenThrow(new UserDeniedOAuthAuthenticationException("Access denied")); final Response response = given() .redirects() .follow(false) .queryParam("state", STATE) .queryParam("oauth_token", OAUTH_TOKEN) .queryParam("oauth_verifier", "denied") .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/oauth/1.0/callback"); assertEquals(response.statusCode(), 307); assertEquals(response.header("Location"), REDIRECT_URI + "?error_code=access_denied"); } @Test public void shouldResolveCallbackWithInvalidRequestError() throws OAuthAuthenticationException { when(oAuthProvider.getAuthenticator("test-server")).thenReturn(oAuthAuthenticator); when(oAuthAuthenticator.callback(any(URL.class))) .thenThrow(new OAuthAuthenticationException("Invalid request")); final Response response = given() .redirects() .follow(false) .queryParam("state", STATE) .queryParam("oauth_token", OAUTH_TOKEN) .queryParam("oauth_verifier", OAUTH_VERIFIER) .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/oauth/1.0/callback"); assertEquals(response.statusCode(), 307); assertEquals(response.header("Location"), REDIRECT_URI + "?error_code=invalid_request"); } @Test public void shouldEncodeRedirectUrl() throws Exception { // given Field uriInfoField = Service.class.getDeclaredField("uriInfo"); uriInfoField.setAccessible(true); uriInfoField.set(oAuthAuthenticationService, uriInfo); when(uriInfo.getRequestUri()) .thenReturn( new URI( "http://eclipse.che?state=oauth_provider" + encode( "=bitbucket-server&redirect_after_login=https://redirecturl.com?params=" + encode("{}", UTF_8), UTF_8))); when(oAuthProvider.getAuthenticator("bitbucket-server")) .thenReturn(mock(OAuthAuthenticator.class)); // when jakarta.ws.rs.core.Response callback = oAuthAuthenticationService.callback(); // then assertEquals(callback.getLocation().toString(), "https://redirecturl.com?params%3D%7B%7D"); } @Test public void shouldNotEncodeRedirectUrl() throws Exception { // given Field uriInfoField = Service.class.getDeclaredField("uriInfo"); uriInfoField.setAccessible(true); uriInfoField.set(oAuthAuthenticationService, uriInfo); when(uriInfo.getRequestUri()) .thenReturn( new URI( "http://eclipse.che?state=oauth_provider" + encode( "=bitbucket-server&redirect_after_login=https://redirecturl.com?params=" + encode(encode("{}", UTF_8), UTF_8), UTF_8))); when(oAuthProvider.getAuthenticator("bitbucket-server")) .thenReturn(mock(OAuthAuthenticator.class)); // when jakarta.ws.rs.core.Response callback = oAuthAuthenticationService.callback(); // then assertEquals(callback.getLocation().toString(), "https://redirecturl.com?params=%7B%7D"); } } ================================================ FILE: wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth1/OAuthAuthenticatorTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth1; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertThrows; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import jakarta.ws.rs.core.UriBuilder; import java.net.MalformedURLException; import java.net.URL; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; public class OAuthAuthenticatorTest { private final String TEST_URI = "https://test-server"; private final String STATE = "oauth_provider=test-server&request_method=POST&signature_method=rsa&userId=user1"; private final String OAUTH_TOKEN = "JeZlJxu8bd1ewAmCkG668PCLC5kJ9ne1"; private OAuthAuthenticator oAuthAuthenticator; private WireMockServer wireMockServer; @BeforeClass void start() { wireMockServer = new WireMockServer(wireMockConfig().dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); oAuthAuthenticator = new OAuthAuthenticator( "test-server", null, wireMockServer.baseUrl() + "/access", null, null, null, "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDZvr2tssVQ46qE1UBK1DFRZrKuIBMCL5q+cltLAVJZ7dlsdv2Yr5mgt3il2BQa0CSmwxTsMdwYqRIDchroVREs5IAfcwOFAL6OnMos/8AEg8Gamnz/Fu1/968bmlV/abKfrdlfkUNuOtpzG5PCf8UAQEt0ajMtFWKFeXPl527BTwRqz1rRVU/wqDC3nS6PM335XCr6mjdBzFDMUC1M91l34M6wPJVPcDRi3cQDb1+YMrFmuzloEs6nlUtM9gH4t9SD9bmBOOj5M7lo3jMuNJ+tE/5M/wrjs1BYbFiUKL3n/BKdyPHpGDssFwo25UBvJWPZZ/YFu6HTD85uUfsbDuTlAgMBAAECggEAA0hY27GCQAHupCoC2h3w0GVX9EAPiUzmbFCVB8BxWWG4kWYJ1K9xBXc+nmFvjCfvJYRzYEwwIT8LQnoJ5c7Cf4bCV7cIKo0kUkoS0jLY1jiWRpploALceb1mKmhdOZqCUt3wFPy/o33HpUyZIamDcsmFWa/wLZHQ9moqUSD4Dnn3Wy6mLyipQDC8LIjPQceuU96VGbZa/XJR8sVMulpgUHvQRzr9PZ1tw4yAK+tcg3rfx4XT6qZS64o4mYrNGYO3QBH3AMUZl1BVG9Q2SlrUM+RGlS5c3DYsFCD40yDBCJIvW1Tfoc4nuDn71rgDEQUZzZlP9X6q1Eh8karaHCrEAQKBgQD16HUcfEd+asDa80BZm3U9Rp0lI1JIfrF/AWR6RR1rldnZOGnQD4untYSZF8vpGdSN5t26szYGJQz9SZl1dtz5sQsyXY9TrGnSf/byoy8yJ6FU3IRaIfoxiAV1fL/QxLsyDls4G5OF9kCu4u2IDKPgDQnb1x9Kq8dQHIyB44eWJQKBgQDirmZToQNZE2GKS+GMxFeeGBTLuV2ED3YuyhRAE3v1bvMRsxcNpMWeCxPMfu8Ctn51yxvTkhRYF3vUz7HjfHYfSMFPf+DwZjOCVWMTT6d8XOcjXIq+mY2Dyek7Kagbbx2oMyBC9HCe0/iK0nOhUNfYFi8PFyDrosvmcQO3qEX3wQKBgG/+5xeKIqWYySzvDKfC/apitr9rTtZlnUFSyQhG4hdVsFoWL1rrOZewPCvdgqkvcncOZn3ZkQlLZpcVJicxc4Lk90yA//4D0E5mqXnoiF43Xmrf5AeI4gIdCR9xKYtTjk5F65WqOY4RkXQVNkl4OEqapZrSZxYDFkuONRATKHVhAoGAQSku4wVa6AUpOc78RDHAmgKEH9fmKOsk5uhSD+VJ8dB18PWRP+vIntjCVTt7y0TYb1X2ZsgMLxJ5F0Co+yKw9ec9InQ5HgHS9rlC5K82DwrJqqGUhJuxUVv+PnKID3LOjKY9tOF9ajq2rHk4ofuSQFyIJIdagEHo6RI9plKp4kECgYAtVUUoXAn5EKLuNVPzlnH+E+iBco0WaQGtsWhIlu6RVhSwJNrldxMFIuWzG56RoFV5tu+KA05RZx82cbazcJJVfwn7S6rmHCdxri3bjpnwgNHmY9E9cxBsEBW2DIYTyI8tbEytbH8syYPGSxb5+VIZEuP+8qel12mVfcoNl/oCCw==") { @Override public String getOAuthProvider() { return null; } @Override public String getLocalAuthenticateUrl() { return null; } @Override public String getEndpointUrl() { return null; } }; } @AfterClass void stop() { if (wireMockServer != null) { wireMockServer.stop(); } } @Test public void shouldResolveCallbackWithUserId() throws MalformedURLException, OAuthAuthenticationException { URL requestURL = UriBuilder.fromUri(TEST_URI) .queryParam("state", STATE) .queryParam("oauth_token", OAUTH_TOKEN) .queryParam("oauth_verifier", "hfdp7dh39dks9884") .build() .toURL(); stubFor( post(urlPathEqualTo("/access")) .willReturn( aResponse() .withBody("oauth_token=ab3cd9j4ks73hf7g&oauth_token_secret=xyz4992k83j47x0b"))); String user = oAuthAuthenticator.callback(requestURL); verify(postRequestedFor(urlPathEqualTo("/access"))); assertEquals("user1", user); } @Test public void shouldThrowUserDeniedOAuthAuthenticationException() throws MalformedURLException { URL requestURL = UriBuilder.fromUri(TEST_URI) .queryParam("state", STATE) .queryParam("oauth_token", OAUTH_TOKEN) .queryParam("oauth_verifier", "denied") .build() .toURL(); assertThrows( UserDeniedOAuthAuthenticationException.class, () -> oAuthAuthenticator.callback(requestURL)); } @Test public void shouldThrowOAuthAuthenticationException() throws MalformedURLException { URL requestURL = UriBuilder.fromUri(TEST_URI) .queryParam("state", STATE) .queryParam("oauth_token", OAUTH_TOKEN) .build() .toURL(); assertThrows(OAuthAuthenticationException.class, () -> oAuthAuthenticator.callback(requestURL)); } } ================================================ FILE: wsmaster/che-core-api-auth/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n target/log/codenvy-factory-commons.log %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: wsmaster/che-core-api-auth-azure-devops/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-auth-azure-devops jar Che Core :: API :: Authentication Azure DevOps com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.core jackson-databind com.google.guava guava com.google.http-client google-http-client com.google.inject guice com.google.oauth-client google-oauth-client jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-shared org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-json org.eclipse.che.core che-core-commons-lang org.slf4j slf4j-api ================================================ FILE: wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsModule.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; /** * Setup AzureDevOpsOAuthAuthenticator in guice container. * * @author Anatolii Bazko */ public class AzureDevOpsModule extends AbstractModule { @Override protected void configure() { Multibinder oAuthAuthenticators = Multibinder.newSetBinder(binder(), OAuthAuthenticator.class); oAuthAuthenticators.addBinding().toProvider(AzureDevOpsOAuthAuthenticatorProvider.class); } } ================================================ FILE: wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsOAuthAuthenticator.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static java.lang.String.format; import static java.net.URLEncoder.encode; import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.che.commons.json.JsonHelper.fromJson; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import static org.eclipse.che.dto.server.DtoFactory.newDto; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl; import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest; import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.auth.oauth2.TokenResponse; import com.google.api.client.util.store.MemoryDataStoreFactory; import com.google.common.io.CharStreams; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; import java.net.URL; import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.Collections; import java.util.List; import javax.inject.Singleton; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.commons.json.JsonParseException; /** * OAuth2 authenticator for Azure DevOps Service. * * @author Anatolii Bazko */ @Singleton public class AzureDevOpsOAuthAuthenticator extends OAuthAuthenticator { private final String azureDevOpsScmApiEndpoint; private final String cheApiEndpoint; private final String azureDevOpsUserProfileDataApiUrl; private final String tokenUri; private final String[] redirectUris; private final String API_VERSION = "7.0"; private final String PROVIDER_NAME = "azure-devops"; private final String clientId; private final String clientSecret; private final boolean isDevOpsOauth; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public AzureDevOpsOAuthAuthenticator( String cheApiEndpoint, String clientId, String clientSecret, String azureDevOpsApiEndpoint, String azureDevOpsScmApiEndpoint, String authUri, String tokenUri, String[] redirectUris, boolean isDevOpsOauth) throws IOException { this.cheApiEndpoint = cheApiEndpoint; this.clientId = clientId; this.clientSecret = clientSecret; this.azureDevOpsScmApiEndpoint = trimEnd(azureDevOpsScmApiEndpoint, '/'); this.azureDevOpsUserProfileDataApiUrl = format( "%s/_apis/profile/profiles/me?api-version=%s", trimEnd(azureDevOpsApiEndpoint, '/'), API_VERSION); this.tokenUri = tokenUri; this.redirectUris = redirectUris; this.isDevOpsOauth = isDevOpsOauth; configure( clientId, clientSecret, redirectUris, authUri, tokenUri, new MemoryDataStoreFactory()); } /** * Returns the URL to redirect the user to in order to authenticate. Sets the {@code * response_type} to {@code redirect_uri} accordingly the Azure DevOps OAuth docs. See details at: * *

    https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?toc=%2Fazure%2Fdevops%2Fmarketplace-extensibility%2Ftoc.json&view=azure-devops#2-authorize-your-app. */ @Override public String getAuthenticateUrl(URL requestUrl, List scopes) { if (isDevOpsOauth) { scopes = Collections.singletonList("vso.code_write"); } AuthorizationCodeRequestUrl url = flow.newAuthorizationUrl().setScopes(scopes); if (isDevOpsOauth) { url.set("response_type", "Assertion"); } else { url.set("response_type", "code"); } url.set("redirect_uri", format("%s/oauth/callback", cheApiEndpoint)); url.setState(prepareState(requestUrl)); return url.build(); } @Override public final String getOAuthProvider() { return PROVIDER_NAME; } @Override public OAuthToken getOrRefreshToken(String userId) throws IOException { final OAuthToken token = super.getOrRefreshToken(userId); try { // check if user's token is valid by requesting user profile data if (token == null || token.getToken() == null || token.getToken().isEmpty() || getUserProfile(token.getToken()) == null) { return null; } } catch (OAuthAuthenticationException e) { return null; } return token; } public String getEndpointUrl() { return azureDevOpsScmApiEndpoint; } private AzureDevOpsUserProfile getUserProfile(String accessToken) throws OAuthAuthenticationException { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder(URI.create(azureDevOpsUserProfileDataApiUrl)) .header("Authorization", "Bearer " + accessToken) .build(); try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); return fromJson(response.body(), AzureDevOpsUserProfile.class, null); } catch (IOException | InterruptedException | JsonParseException e) { throw new OAuthAuthenticationException(e.getMessage(), e); } } private HttpRequest.BodyPublisher getParamsUrlEncoded(String refreshToken) { String urlEncoded = format( "client_assertion_type=%1s&" + "client_assertion=%2s&" + "grant_type=refresh_token&" + "assertion=%3s&" + "redirect_uri=%4s", encode("urn:ietf:params:oauth:client-assertion-type:jwt-bearer", UTF_8), encode(clientSecret, UTF_8), refreshToken, redirectUris[0]); return HttpRequest.BodyPublishers.ofString(urlEncoded); } /** * Refresh personal access token. * * @param userId user identifier * @return a refreshed token object or the previous token if the refresh failed * @throws IOException when error occurs during token loading */ public OAuthToken refreshToken(String userId) throws IOException { if (!isConfigured()) { throw new IOException(AUTHENTICATOR_IS_NOT_CONFIGURED); } Credential credential = flow.loadCredential(userId); if (credential == null) { return null; } HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder(URI.create(tokenUri)) .POST(getParamsUrlEncoded(credential.getRefreshToken())) .headers("Content-Type", "application/x-www-form-urlencoded") .build(); try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); AzureDevOpsRefreshToken token = OBJECT_MAPPER.readValue( CharStreams.toString(new InputStreamReader(response.body(), UTF_8)), AzureDevOpsRefreshToken.class); String accessToken = token.getAccessToken(); credential.setAccessToken(accessToken); return newDto(OAuthToken.class).withToken(accessToken); } catch (IOException | InterruptedException exception) { return newDto(OAuthToken.class).withToken(credential.getAccessToken()); } } /** * Returns the token request. Overrides the default implementation to set the {@code grant_type}, * {@code assertion}, {@code client_assertion} and {@code client_assertion_type} accordingly to * Azure DevOps OAuth docs. See details at: * *

    https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?toc=%2Fazure%2Fdevops%2Fmarketplace-extensibility%2Ftoc.json&view=azure-devops#3-get-an-access-and-refresh-token-for-the-user */ @Override protected AuthorizationCodeTokenRequest getAuthorizationCodeTokenRequest( URL requestUrl, List scopes, String code) { AuthorizationCodeTokenRequest request = super.getAuthorizationCodeTokenRequest(requestUrl, scopes, code); if (isDevOpsOauth) { request.set("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"); request.set("assertion", code); request.set("client_assertion", clientSecret); request.set( "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); request.setResponseClass(AzureDevOpsTokenResponse.class); } else { request.set("client_id", clientId); request.set("grant_type", "authorization_code"); request.set("client_secret", URLEncoder.encode(clientSecret)); request.setResponseClass(TokenResponse.class); } return request; } } ================================================ FILE: wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsOAuthAuthenticatorProvider.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static com.google.common.base.Strings.isNullOrEmpty; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Azure DevOps Service Authenticator provider. * * @author Anatolii Bazko */ @Singleton public class AzureDevOpsOAuthAuthenticatorProvider implements Provider { private static final Logger LOG = LoggerFactory.getLogger(AzureDevOpsOAuthAuthenticatorProvider.class); private final OAuthAuthenticator authenticator; @Inject public AzureDevOpsOAuthAuthenticatorProvider( @Named("che.api") String cheApiEndpoint, @Nullable @Named("che.oauth2.azure.devops.tenantid_filepath") String azureDevOpsTennantIdPath, @Nullable @Named("che.oauth2.azure.devops.clientid_filepath") String azureDevOpsClientIdPath, @Nullable @Named("che.oauth2.azure.devops.clientsecret_filepath") String azureDevOpsClientSecretPath, @Named("che.integration.azure.devops.api_endpoint") String azureDevOpsApiEndpoint, @Named("che.integration.azure.devops.scm.api_endpoint") String azureDevOpsScmApiEndpoint, @Named("che.oauth.azure.devops.authuri.template") String authUri, @Named("che.oauth.azure.devops.tokenuri.template") String tokenUri, @Named("che.oauth.azure.devops.redirecturis") String[] redirectUris) throws IOException { authenticator = getOAuthAuthenticator( cheApiEndpoint, azureDevOpsTennantIdPath, azureDevOpsClientIdPath, azureDevOpsClientSecretPath, azureDevOpsApiEndpoint, azureDevOpsScmApiEndpoint, authUri, tokenUri, redirectUris); LOG.debug("{} Azure DevOps OAuth Authenticator is used.", authenticator); } @Override public OAuthAuthenticator get() { return authenticator; } private OAuthAuthenticator getOAuthAuthenticator( String cheApiEndpoint, String tenantIdPath, String clientIdPath, String clientSecretPath, String azureDevOpsApiEndpoint, String azureDevOpsScmApiEndpoint, String authUriTemplate, String tokenUriTemplate, String[] redirectUris) throws IOException { if (!isNullOrEmpty(clientIdPath) && !isNullOrEmpty(clientSecretPath) && !isNullOrEmpty(tenantIdPath)) { // This flag is needed to support the deprecated Azure DevOps oauth apps. // TODO remove the related logic when the deprecated Azure DevOps oauth app is no longer // available, see // https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops#azure-devops-oauth-deprecated boolean isDevOpsOauth = false; String tenantId = null; try { tenantId = Files.readString(Path.of(tenantIdPath)).trim(); } catch (IOException e) { isDevOpsOauth = true; } final String clientId = Files.readString(Path.of(clientIdPath)).trim(); final String clientSecret = Files.readString(Path.of(clientSecretPath)).trim(); if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) { return new AzureDevOpsOAuthAuthenticator( cheApiEndpoint, clientId, clientSecret, azureDevOpsApiEndpoint, azureDevOpsScmApiEndpoint, isDevOpsOauth ? "https://app.vssps.visualstudio.com/oauth2/authorize" : String.format(authUriTemplate, tenantId), isDevOpsOauth ? "https://app.vssps.visualstudio.com/oauth2/authorize" : String.format(tokenUriTemplate, tenantId), redirectUris, isDevOpsOauth); } } return new NoopOAuthAuthenticator(); } static class NoopOAuthAuthenticator extends OAuthAuthenticator { @Override public String getOAuthProvider() { return "Noop"; } @Override public String getEndpointUrl() { return "Noop"; } } } ================================================ FILE: wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsRefreshToken.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; @JsonIgnoreProperties(ignoreUnknown = true) public class AzureDevOpsRefreshToken { /** Access token issued by the authorization server. */ private String accessToken; /** Token type. */ private String tokenType; /** Refresh token which can be used to obtain new access tokens. */ private String refreshToken; /** * Lifetime in seconds of the access token (for example 3600 for an hour) or {@code null} for * none. */ private String expiresInSeconds; /** Scope of the access token. */ private String scope; public String getAccessToken() { return accessToken; } public String getTokenType() { return tokenType; } public String getRefreshToken() { return refreshToken; } public String getScope() { return scope; } public String getExpiresInSeconds() { return expiresInSeconds; } @JsonProperty("access_token") public void setAccessToken(String accessToken) { this.accessToken = accessToken; } @JsonProperty("token_type") public void setTokenType(String tokenType) { this.tokenType = tokenType; } @JsonProperty("refresh_token") public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } @JsonProperty("expires_in") public void setExpiresInSeconds(String expiresInSeconds) { this.expiresInSeconds = expiresInSeconds; } public void setScope(String scope) { this.scope = scope; } @Override public String toString() { return "AzureDevOpsRefreshToken{" + "accessToken='" + accessToken + '\'' + ", tokenType='" + tokenType + '\'' + ", refreshToken='" + refreshToken + '\'' + ", expiresInSeconds='" + expiresInSeconds + '\'' + ", scope='" + scope + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AzureDevOpsRefreshToken that = (AzureDevOpsRefreshToken) o; return Objects.equals(accessToken, that.accessToken) && Objects.equals(tokenType, that.tokenType) && Objects.equals(refreshToken, that.refreshToken) && Objects.equals(expiresInSeconds, that.expiresInSeconds) && Objects.equals(scope, that.scope); } @Override public int hashCode() { return Objects.hash(accessToken, tokenType, refreshToken, expiresInSeconds, scope); } } ================================================ FILE: wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsTokenResponse.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import com.google.api.client.auth.oauth2.TokenResponse; import com.google.api.client.json.JsonString; import com.google.api.client.util.Key; /** * The only difference between from {@link TokenResponse} is that {@link #expiresInSeconds} field is * represented in a {@link String} format. * *

    https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops#response---authorize-app * * @author Anatolii Bazko */ public class AzureDevOpsTokenResponse extends TokenResponse { @JsonString @Key("expires_in") private Long expiresInSeconds; public Long getExpiresInSeconds() { return expiresInSeconds; } public AzureDevOpsTokenResponse setExpiresInSeconds(Long expiresInSeconds) { this.expiresInSeconds = expiresInSeconds; return this; } } ================================================ FILE: wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsUserProfile.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** * Represents Azure DevOps user profile data. See details at: * https://learn.microsoft.com/en-us/rest/api/azure/devops/profile/profiles/get?view=azure-devops-rest-7.0&tabs=HTTP#profile * * @author Anatolii Bazko */ @JsonIgnoreProperties(ignoreUnknown = true) public class AzureDevOpsUserProfile { private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String toString() { return "AzureDevOpsUserProfile{" + "id='" + id + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-auth-bitbucket/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-auth-bitbucket jar Che Core :: API :: Authentication Bitbucket com.google.guava guava com.google.http-client google-http-client com.google.inject guice com.google.oauth-client google-oauth-client jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-shared org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang org.slf4j slf4j-api ch.qos.logback logback-classic test org.testng testng test org.wiremock wiremock-standalone test ================================================ FILE: wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/BitbucketModule.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import org.eclipse.che.security.oauth.BitbucketOAuthAuthenticatorProvider; import org.eclipse.che.security.oauth1.BitbucketServerOAuthAuthenticatorProvider; import org.eclipse.che.security.oauth1.OAuthAuthenticator; /** * Setup BitbucketServerOAuthAuthenticator in guice container. * * @author Sergii Kabashniuk */ public class BitbucketModule extends AbstractModule { @Override protected void configure() { Multibinder oAuth1Authenticators = Multibinder.newSetBinder(binder(), OAuthAuthenticator.class); oAuth1Authenticators.addBinding().toProvider(BitbucketServerOAuthAuthenticatorProvider.class); Multibinder oAuth2Authenticators = Multibinder.newSetBinder(binder(), org.eclipse.che.security.oauth.OAuthAuthenticator.class); oAuth2Authenticators.addBinding().toProvider(BitbucketOAuthAuthenticatorProvider.class); } } ================================================ FILE: wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticator.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static com.google.common.base.Strings.isNullOrEmpty; import static java.net.URLEncoder.encode; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl; import com.google.api.client.util.store.MemoryDataStoreFactory; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.List; import javax.inject.Singleton; import org.eclipse.che.api.auth.shared.dto.OAuthToken; /** OAuth authentication for BitBucket SAAS account. */ @Singleton public class BitbucketOAuthAuthenticator extends OAuthAuthenticator { private final String bitbucketEndpoint; private static final String BITBUCKET_CLOUD_ENDPOINT = "https://bitbucket.org"; private static final String BITBUCKET_NAME = "bitbucket"; private static final String BITBUCKET_SERVER_NAME = "bitbucket-server"; public BitbucketOAuthAuthenticator( String bitbucketEndpoint, String clientId, String clientSecret, String[] redirectUris, String authUri, String tokenUri) throws IOException { this.bitbucketEndpoint = bitbucketEndpoint; configure( clientId, clientSecret, redirectUris, authUri, tokenUri, new MemoryDataStoreFactory()); } @Override public String getAuthenticateUrl(URL requestUrl, List scopes) { AuthorizationCodeRequestUrl url = flow.newAuthorizationUrl().setScopes(scopes); String state = prepareState(requestUrl); // Although the state is encoded in the OAuthAuthenticator class, we need to additionally encode // it because Bitbucket Server decodes it on callback request. url.setState(BITBUCKET_CLOUD_ENDPOINT.equals(bitbucketEndpoint) ? state : encode(state, UTF_8)); url.set("redirect_uri", findRedirectUrl(requestUrl)); return url.build(); } @Override public final String getOAuthProvider() { // Bitbucket Cloud and Bitbucket Server have different provider names. return BITBUCKET_CLOUD_ENDPOINT.equals(bitbucketEndpoint) ? BITBUCKET_NAME : BITBUCKET_SERVER_NAME; } @Override public OAuthToken getOrRefreshToken(String userId) throws IOException { final OAuthToken token = super.getOrRefreshToken(userId); // Need to check if token is valid for requests, if valid - return it to caller. try { if (token == null || isNullOrEmpty(token.getToken())) { return null; } testRequest(getTestRequestUrl(), token.getToken()); } catch (OAuthAuthenticationException e) { return null; } return token; } /** * Generate an API request URL, to use for a token validation. * * @return Bitbucket Cloud or Server API request URL */ private String getTestRequestUrl() { return BITBUCKET_CLOUD_ENDPOINT.equals(bitbucketEndpoint) ? "https://api.bitbucket.org/2.0/user" : bitbucketEndpoint + "/plugins/servlet/applinks/whoami"; } @Override public String getEndpointUrl() { return bitbucketEndpoint; } private void testRequest(String requestUrl, String accessToken) throws OAuthAuthenticationException { HttpURLConnection urlConnection = null; InputStream urlInputStream = null; String result; try { urlConnection = (HttpURLConnection) new URL(requestUrl).openConnection(); urlConnection.setRequestProperty("Authorization", "Bearer " + accessToken); urlInputStream = urlConnection.getInputStream(); result = new String(urlInputStream.readAllBytes(), UTF_8); } catch (IOException e) { throw new OAuthAuthenticationException(e.getMessage(), e); } finally { if (urlInputStream != null) { try { urlInputStream.close(); } catch (IOException ignored) { } } if (urlConnection != null) { urlConnection.disconnect(); } } if (isNullOrEmpty(result)) { throw new OAuthAuthenticationException("Empty response from Bitbucket Server API"); } } } ================================================ FILE: wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticatorProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides implementation of BitBucket {@link OAuthAuthenticator} based on available configuration. */ @Singleton public class BitbucketOAuthAuthenticatorProvider implements Provider { private static final Logger LOG = LoggerFactory.getLogger(BitbucketOAuthAuthenticatorProvider.class); private final OAuthAuthenticator authenticator; @Inject public BitbucketOAuthAuthenticatorProvider( @Named("che.oauth.bitbucket.endpoint") String oauthEndpoint, @Nullable @Named("che.oauth2.bitbucket.clientid_filepath") String bitbucketClientIdPath, @Nullable @Named("che.oauth2.bitbucket.clientsecret_filepath") String bitbucketClientSecretPath, @Nullable @Named("che.oauth.bitbucket.redirecturis") String[] redirectUris, @Nullable @Named("che.oauth.bitbucket.authuri") String authUri, @Nullable @Named("che.oauth.bitbucket.tokenuri") String tokenUri) throws IOException { authenticator = getOAuthAuthenticator( oauthEndpoint, bitbucketClientIdPath, bitbucketClientSecretPath, redirectUris, authUri, tokenUri); LOG.debug("{} Bitbucket OAuth Authenticator is used.", authenticator); } @Override public OAuthAuthenticator get() { return authenticator; } private OAuthAuthenticator getOAuthAuthenticator( String oauthEndpoint, @Nullable String clientIdPath, @Nullable String clientSecretPath, @Nullable String[] redirectUris, @Nullable String authUri, @Nullable String tokenUri) throws IOException { if (!isNullOrEmpty(clientIdPath) && !isNullOrEmpty(clientSecretPath) && !isNullOrEmpty(authUri) && !isNullOrEmpty(tokenUri) && Objects.nonNull(redirectUris) && redirectUris.length != 0) { String trimmedOauthEndpoint = trimEnd(oauthEndpoint, '/'); boolean isBitbucketCloud = trimmedOauthEndpoint.equals("https://bitbucket.org"); authUri = isBitbucketCloud ? authUri : trimmedOauthEndpoint + "/rest/oauth2/1.0/authorize"; tokenUri = isBitbucketCloud ? tokenUri : trimmedOauthEndpoint + "/rest/oauth2/1.0/token"; final String clientId = Files.readString(Path.of(clientIdPath)).trim(); final String clientSecret = Files.readString(Path.of(clientSecretPath)).trim(); if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) { return new BitbucketOAuthAuthenticator( trimmedOauthEndpoint, clientId, clientSecret, redirectUris, authUri, tokenUri); } } return new NoopOAuthAuthenticator(); } static class NoopOAuthAuthenticator extends OAuthAuthenticator { @Override public String getOAuthProvider() { return "Noop"; } @Override public String getEndpointUrl() { return "Noop"; } } } ================================================ FILE: wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketUser.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import org.eclipse.che.security.oauth.shared.User; /** Represents Bitbucket user. */ public class BitbucketUser implements User { private String name; private String id; @Override public String getId() { return id; } @Override public void setId(String id) { this.id = id; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } @Override public String getEmail() { return "email"; } @Override public void setEmail(String email) {} public void setUsername(String name) { this.name = name; } public void setAccount_id(String id) { this.id = id; } } ================================================ FILE: wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuthAuthenticator.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth1; import com.google.inject.Singleton; /** * OAuth1 authentication for Bitbucket Server account. * * @author Igor Vinokur */ @Singleton public class BitbucketServerOAuthAuthenticator extends OAuthAuthenticator { public static final String AUTHENTICATOR_NAME = "bitbucket-server"; private final String bitbucketEndpoint; private final String apiEndpoint; public BitbucketServerOAuthAuthenticator( String consumerKey, String privateKey, String bitbucketEndpoint, String apiEndpoint) { super( consumerKey, bitbucketEndpoint + "/plugins/servlet/oauth/request-token", bitbucketEndpoint + "/plugins/servlet/oauth/access-token", bitbucketEndpoint + "/plugins/servlet/oauth/authorize", apiEndpoint + "/oauth/1.0/callback", null, privateKey); this.bitbucketEndpoint = bitbucketEndpoint; this.apiEndpoint = apiEndpoint; } @Override public final String getOAuthProvider() { return AUTHENTICATOR_NAME; } @Override public String getLocalAuthenticateUrl() { return apiEndpoint + "/oauth/1.0/authenticate?oauth_provider=" + AUTHENTICATOR_NAME + "&request_method=POST&signature_method=rsa"; } @Override public String getEndpointUrl() { return bitbucketEndpoint; } } ================================================ FILE: wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuthAuthenticatorProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth1; import static com.google.common.base.Strings.isNullOrEmpty; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Singleton public class BitbucketServerOAuthAuthenticatorProvider implements Provider { private static final Logger LOG = LoggerFactory.getLogger(BitbucketServerOAuthAuthenticatorProvider.class); private final OAuthAuthenticator authenticator; @Inject public BitbucketServerOAuthAuthenticatorProvider( @Nullable @Named("che.oauth1.bitbucket.consumerkeypath") String consumerKeyPath, @Nullable @Named("che.oauth1.bitbucket.privatekeypath") String privateKeyPath, @Nullable @Named("che.oauth.bitbucket.endpoint") String bitbucketEndpoint, @Named("che.api") String apiEndpoint) throws IOException { authenticator = getOAuthAuthenticator(consumerKeyPath, privateKeyPath, bitbucketEndpoint, apiEndpoint); LOG.debug("{} Bitbucket OAuthAuthenticator is used.", authenticator); } @Override public OAuthAuthenticator get() { return authenticator; } private static OAuthAuthenticator getOAuthAuthenticator( String consumerKeyPath, String privateKeyPath, String bitbucketEndpoint, String apiEndpoint) throws IOException { if (!isNullOrEmpty(bitbucketEndpoint) && !isNullOrEmpty(consumerKeyPath) && !isNullOrEmpty(privateKeyPath)) { String consumerKey = Files.readString(Path.of(consumerKeyPath)); String privateKey = Files.readString(Path.of(privateKeyPath)); if (!isNullOrEmpty(consumerKey) && !isNullOrEmpty(privateKey)) { return new BitbucketServerOAuthAuthenticator( consumerKey, privateKey, bitbucketEndpoint, apiEndpoint); } } return new NoopOAuthAuthenticator(); } } ================================================ FILE: wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/NoopOAuthAuthenticator.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth1; import java.net.URL; /** * Dummy implementation of @{@link OAuthAuthenticator} used in the case if no Bitbucket Server * integration is configured to register an empty @{@link BitbucketServerApiClient}. */ public class NoopOAuthAuthenticator extends OAuthAuthenticator { public NoopOAuthAuthenticator() { super(null, null, null, null, null, null, null); } @Override public String getOAuthProvider() { return "Noop"; } @Override String getAuthenticateUrl(URL requestUrl, String requestMethod, String signatureMethod) throws OAuthAuthenticationException { throw new OAuthAuthenticationException( "The fallback noop authenticator cannot be used for authentication. Make sure OAuth is properly configured."); } @Override String callback(URL requestUrl) throws OAuthAuthenticationException { throw new OAuthAuthenticationException( "The fallback noop authenticator cannot be used for authentication. Make sure OAuth is properly configured."); } @Override public String computeAuthorizationHeader(String userId, String requestMethod, String requestUrl) throws OAuthAuthenticationException { throw new OAuthAuthenticationException( "The fallback noop authenticator cannot be used for authentication. Make sure OAuth is properly configured."); } @Override public String getLocalAuthenticateUrl() { return "Noop URL"; } @Override public String getEndpointUrl() { return "Noop URL"; } } ================================================ FILE: wsmaster/che-core-api-auth-bitbucket/src/test/java/org/eclipse/che/security/BitbucketOAuthAuthenticatorProviderTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security; import static java.util.Collections.emptyList; import static org.testng.Assert.assertTrue; import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.charset.Charset; import org.eclipse.che.security.oauth.BitbucketOAuthAuthenticatorProvider; import org.eclipse.che.security.oauth.OAuthAuthenticator; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; public class BitbucketOAuthAuthenticatorProviderTest { private BitbucketOAuthAuthenticatorProvider provider; private File cfgFile; @BeforeClass public void setup() throws IOException { cfgFile = File.createTempFile("BitbucketOAuthAuthenticatorProviderTest-", "-cfg"); Files.asCharSink(cfgFile, Charset.defaultCharset()).write("tmp-data"); cfgFile.deleteOnExit(); provider = new BitbucketOAuthAuthenticatorProvider( "http://bitubucket-server.com", cfgFile.getPath(), cfgFile.getPath(), new String[] {"http://che.server.com"}, "http://auth.uri", "http://token.uri"); } @Test public void shouldReturnAuthenticationUrlEncodedOnce() throws Exception { // given BitbucketOAuthAuthenticatorProvider provider = new BitbucketOAuthAuthenticatorProvider( "https://bitbucket.org", cfgFile.getPath(), cfgFile.getPath(), new String[] {"http://che.server.com"}, "http://auth.uri", "http://token.uri"); OAuthAuthenticator authenticator = provider.get(); URL url = new URL("http://che.server.com?query=param"); // when String authenticateUrl = authenticator.getAuthenticateUrl(url, emptyList()); // then assertTrue(authenticateUrl.endsWith("&state=query%253Dparam")); } @Test public void shouldReturnAuthenticationUrlEncodedTwice() throws Exception { // given OAuthAuthenticator authenticator = provider.get(); URL url = new URL("http://che.server.com?query=param"); // when String authenticateUrl = authenticator.getAuthenticateUrl(url, emptyList()); // then assertTrue(authenticateUrl.endsWith("&state=query%25253Dparam")); } } ================================================ FILE: wsmaster/che-core-api-auth-bitbucket/src/test/java/org/eclipse/che/security/oauth1/BitbucketServerOAuthAuthenticatorProviderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth1; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class BitbucketServerOAuthAuthenticatorProviderTest { private File cfgFile; private File emptyFile; @BeforeClass public void setup() throws IOException { cfgFile = File.createTempFile("BitbucketServerOAuthAuthenticatorProviderTest-", "-cfg"); Files.asCharSink(cfgFile, Charset.defaultCharset()).write("tmp-data"); cfgFile.deleteOnExit(); emptyFile = File.createTempFile("BitbucketServerOAuthAuthenticatorProviderTest-", "-empty"); emptyFile.deleteOnExit(); } @Test(dataProvider = "noopConfig") public void shouldProvideNoopOAuthAuthenticatorIfSomeConfigurationIsNotSet( String consumerKeyPath, String privateKeyPath, String bitbucketEndpoint) throws IOException { // given BitbucketServerOAuthAuthenticatorProvider provider = new BitbucketServerOAuthAuthenticatorProvider( consumerKeyPath, privateKeyPath, bitbucketEndpoint, "http://che.server.com"); // when OAuthAuthenticator actual = provider.get(); // then assertNotNull(actual); assertTrue(NoopOAuthAuthenticator.class.isAssignableFrom(actual.getClass())); } @Test public void shouldBeAbleToConfigureValidBitbucketServerOAuthAuthenticator() throws IOException { // given BitbucketServerOAuthAuthenticatorProvider provider = new BitbucketServerOAuthAuthenticatorProvider( cfgFile.getPath(), cfgFile.getPath(), "http://bitubucket.com", "http://che.server.com"); // when OAuthAuthenticator actual = provider.get(); // then assertNotNull(actual); assertTrue(BitbucketServerOAuthAuthenticator.class.isAssignableFrom(actual.getClass())); } @DataProvider(name = "noopConfig") public Object[][] noopConfig() { return new Object[][] { {null, null, null}, {cfgFile.getPath(), null, null}, {null, cfgFile.getPath(), null}, {cfgFile.getPath(), cfgFile.getPath(), null}, {emptyFile.getPath(), null, null}, {null, emptyFile.getPath(), null}, {emptyFile.getPath(), emptyFile.getPath(), null}, {cfgFile.getPath(), emptyFile.getPath(), null}, {emptyFile.getPath(), cfgFile.getPath(), null}, {emptyFile.getPath(), emptyFile.getPath(), "http://bitubucket.com"}, {cfgFile.getPath(), emptyFile.getPath(), "http://bitubucket.com"}, {emptyFile.getPath(), cfgFile.getPath(), "http://bitubucket.com"}, {null, null, "http://bitubucket.com"} }; } } ================================================ FILE: wsmaster/che-core-api-auth-bitbucket/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: wsmaster/che-core-api-auth-github/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-auth-github jar Che Core :: API :: Authentication Github com.google.inject guice jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-github-common org.eclipse.che.core che-core-commons-annotations com.github.tomakehurst wiremock-jre8-standalone test com.google.guava guava test org.testng testng test org.wiremock wiremock-standalone test ================================================ FILE: wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import java.io.IOException; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; /** * Provides implementation of GitHub {@link OAuthAuthenticator} based on available configuration. * * @author Pavol Baran */ @Singleton public class GitHubOAuthAuthenticatorProvider extends AbstractGitHubOAuthAuthenticatorProvider { private static final String PROVIDER_NAME = "github"; @Inject public GitHubOAuthAuthenticatorProvider( @Nullable @Named("che.oauth2.github.clientid_filepath") String gitHubClientIdPath, @Nullable @Named("che.oauth2.github.clientsecret_filepath") String gitHubClientSecretPath, @Nullable @Named("che.oauth.github.redirecturis") String[] redirectUris, @Nullable @Named("che.integration.github.oauth_endpoint") String oauthEndpoint, @Nullable @Named("che.oauth.github.authuri") String authUri, @Nullable @Named("che.oauth.github.tokenuri") String tokenUri) throws IOException { super( gitHubClientIdPath, gitHubClientSecretPath, redirectUris, oauthEndpoint, authUri, tokenUri, PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProviderSecond.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import java.io.IOException; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; /** * Provides implementation of GitHub {@link OAuthAuthenticator} based on available configuration. * * @author Anatolii Bazko */ @Singleton public class GitHubOAuthAuthenticatorProviderSecond extends AbstractGitHubOAuthAuthenticatorProvider { private static final String PROVIDER_NAME = "github_2"; @Inject public GitHubOAuthAuthenticatorProviderSecond( @Nullable @Named("che.oauth2.github.clientid_filepath_2") String gitHubClientIdPath, @Nullable @Named("che.oauth2.github.clientsecret_filepath_2") String gitHubClientSecretPath, @Nullable @Named("che.oauth.github.redirecturis") String[] redirectUris, @Nullable @Named("che.integration.github.oauth_endpoint_2") String oauthEndpoint, @Nullable @Named("che.oauth.github.authuri_2") String authUri, @Nullable @Named("che.oauth.github.tokenuri_2") String tokenUri) throws IOException { super( gitHubClientIdPath, gitHubClientSecretPath, redirectUris, oauthEndpoint, authUri, tokenUri, PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GithubModule.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; /** * Setup GitHubOAuthAuthenticator in guice container. * * @author Sergii Kabashniuk */ public class GithubModule extends AbstractModule { @Override protected void configure() { Multibinder oAuthAuthenticators = Multibinder.newSetBinder(binder(), OAuthAuthenticator.class); oAuthAuthenticators.addBinding().toProvider(GitHubOAuthAuthenticatorProvider.class); oAuthAuthenticators.addBinding().toProvider(GitHubOAuthAuthenticatorProviderSecond.class); } } ================================================ FILE: wsmaster/che-core-api-auth-github/src/test/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProviderTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.delete; import static com.github.tomakehurst.wiremock.client.WireMock.matching; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class GitHubOAuthAuthenticatorProviderTest { private static final String TEST_URI = "https://api.github.com"; private File credentialFile; private File emptyFile; WireMockServer wireMockServer; WireMock wireMock; @BeforeClass public void setup() throws IOException { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); credentialFile = File.createTempFile("GitHubOAuthAuthenticatorProviderTest-", "-credentials"); Files.asCharSink(credentialFile, Charset.defaultCharset()).write("id/secret"); credentialFile.deleteOnExit(); emptyFile = File.createTempFile("GitHubOAuthAuthenticatorProviderTest-", "-empty"); emptyFile.deleteOnExit(); } @Test(dataProvider = "noopConfig") public void shouldProvideNoopAuthenticatorWhenInvalidConfigurationSet( String gitHubClientIdPath, String gitHubClientSecretPath, String[] redirectUris, String oauthEndpoint, String authUri, String tokenUri) throws IOException { // given GitHubOAuthAuthenticatorProvider provider = new GitHubOAuthAuthenticatorProvider( gitHubClientIdPath, gitHubClientSecretPath, redirectUris, oauthEndpoint, authUri, tokenUri); // when OAuthAuthenticator authenticator = provider.get(); // then assertNotNull(authenticator); assertTrue( GitHubOAuthAuthenticatorProvider.NoopOAuthAuthenticator.class.isAssignableFrom( authenticator.getClass())); } @Test public void shouldProvideNoopAuthenticatorWhenConfigFilesAreEmpty() throws IOException { // given GitHubOAuthAuthenticatorProvider provider = new GitHubOAuthAuthenticatorProvider( emptyFile.getPath(), emptyFile.getPath(), new String[] {TEST_URI}, null, TEST_URI, TEST_URI); // when OAuthAuthenticator authenticator = provider.get(); // then assertNotNull(authenticator); assertTrue( GitHubOAuthAuthenticatorProvider.NoopOAuthAuthenticator.class.isAssignableFrom( authenticator.getClass())); } @Test public void shouldProvideValidGitHubOAuthAuthenticator() throws IOException { // given GitHubOAuthAuthenticatorProvider provider = new GitHubOAuthAuthenticatorProvider( credentialFile.getPath(), credentialFile.getPath(), new String[] {TEST_URI}, null, TEST_URI, TEST_URI); // when OAuthAuthenticator authenticator = provider.get(); // then assertNotNull(authenticator); assertTrue(GitHubOAuthAuthenticator.class.isAssignableFrom(authenticator.getClass())); } @Test public void shouldProvideValidGitHubOAuthAuthenticatorWithConfiguredOAuthEndpoint() throws IOException { // given GitHubOAuthAuthenticatorProvider provider = new GitHubOAuthAuthenticatorProvider( credentialFile.getPath(), credentialFile.getPath(), new String[] {TEST_URI}, "https://custom.github.com/", TEST_URI, TEST_URI); // when OAuthAuthenticator authenticator = provider.get(); // then assertNotNull(authenticator); assertTrue(GitHubOAuthAuthenticator.class.isAssignableFrom(authenticator.getClass())); } @Test public void shouldReturnEndpointUrl() throws IOException { // given GitHubOAuthAuthenticatorProvider provider = new GitHubOAuthAuthenticatorProvider( credentialFile.getPath(), credentialFile.getPath(), new String[] {TEST_URI}, null, TEST_URI, TEST_URI); OAuthAuthenticator authenticator = provider.get(); // when String endpointUrl = authenticator.getEndpointUrl(); // then assertEquals(endpointUrl, "https://github.com"); } @Test public void shouldInvalidateToken() throws IOException { // given stubFor( delete(urlEqualTo("/api/v3/applications/id/secret/grant")) .withBasicAuth("id/secret", "id/secret") .withRequestBody(matching("\\{\"access_token\"\\:\"token\"\\}")) .willReturn(aResponse().withStatus(204))); GitHubOAuthAuthenticatorProvider provider = new GitHubOAuthAuthenticatorProvider( credentialFile.getPath(), credentialFile.getPath(), new String[] {TEST_URI}, wireMockServer.url("/"), TEST_URI, TEST_URI); OAuthAuthenticator authenticator = provider.get(); // when boolean result = authenticator.invalidateToken("token"); // then assertTrue(result); } @DataProvider(name = "noopConfig") public Object[][] noopConfig() { return new Object[][] { {null, null, null, null, null, null}, {null, null, null, "", null, null}, {null, null, null, TEST_URI, null, null}, {credentialFile.getPath(), emptyFile.getPath(), null, null, TEST_URI, null}, {emptyFile.getPath(), emptyFile.getPath(), null, null, null, TEST_URI}, {null, emptyFile.getPath(), null, null, TEST_URI, TEST_URI}, {null, credentialFile.getPath(), new String[] {}, null, null, null}, {emptyFile.getPath(), null, new String[] {}, null, "", ""}, {credentialFile.getPath(), null, new String[] {}, null, "", null}, {null, emptyFile.getPath(), new String[] {}, null, null, ""}, {credentialFile.getPath(), null, new String[] {}, null, TEST_URI, null}, {null, emptyFile.getPath(), new String[] {}, null, TEST_URI, TEST_URI}, {emptyFile.getPath(), null, new String[] {TEST_URI}, null, null, null}, {credentialFile.getPath(), null, new String[] {TEST_URI}, null, "", ""}, { credentialFile.getPath(), credentialFile.getPath(), new String[] {TEST_URI}, null, null, TEST_URI }, { credentialFile.getPath(), emptyFile.getPath(), new String[] {TEST_URI}, null, TEST_URI, null }, { credentialFile.getPath(), credentialFile.getPath(), new String[] {TEST_URI}, null, TEST_URI, "" }, {emptyFile.getPath(), emptyFile.getPath(), new String[] {TEST_URI}, null, "", TEST_URI} }; } } ================================================ FILE: wsmaster/che-core-api-auth-github-common/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-auth-github-common jar Che Core :: API :: Authentication Github Common com.google.guava guava com.google.http-client google-http-client jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-shared org.eclipse.che.core che-core-commons-lang org.slf4j slf4j-api com.github.tomakehurst wiremock-jre8-standalone test org.testng testng test org.wiremock wiremock-standalone test ================================================ FILE: wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitHubOAuthAuthenticatorProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; import javax.inject.Provider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides implementation of GitHub {@link OAuthAuthenticator} based on available configuration. * * @author Pavol Baran */ public abstract class AbstractGitHubOAuthAuthenticatorProvider implements Provider { private static final Logger LOG = LoggerFactory.getLogger(AbstractGitHubOAuthAuthenticatorProvider.class); private final String providerName; private final OAuthAuthenticator authenticator; public AbstractGitHubOAuthAuthenticatorProvider( String gitHubClientIdPath, String gitHubClientSecretPath, String[] redirectUris, String oauthEndpoint, String authUri, String tokenUri, String providerName) throws IOException { this.providerName = providerName; authenticator = getOAuthAuthenticator( gitHubClientIdPath, gitHubClientSecretPath, redirectUris, oauthEndpoint, authUri, tokenUri); LOG.debug("{} GitHub OAuth Authenticator is used.", authenticator); } @Override public OAuthAuthenticator get() { return authenticator; } private OAuthAuthenticator getOAuthAuthenticator( String clientIdPath, String clientSecretPath, String[] redirectUris, String oauthEndpoint, String authUri, String tokenUri) throws IOException { String trimmedOauthEndpoint = isNullOrEmpty(oauthEndpoint) ? null : trimEnd(oauthEndpoint, '/'); authUri = isNullOrEmpty(trimmedOauthEndpoint) ? authUri : trimmedOauthEndpoint + "/login/oauth/authorize"; tokenUri = isNullOrEmpty(trimmedOauthEndpoint) ? tokenUri : trimmedOauthEndpoint + "/login/oauth/access_token"; if (!isNullOrEmpty(clientIdPath) && !isNullOrEmpty(clientSecretPath) && !isNullOrEmpty(authUri) && !isNullOrEmpty(tokenUri) && Objects.nonNull(redirectUris) && redirectUris.length != 0) { final String clientId = Files.readString(Path.of(clientIdPath)).trim(); final String clientSecret = Files.readString(Path.of(clientSecretPath)).trim(); if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) { return new GitHubOAuthAuthenticator( clientId, clientSecret, redirectUris, trimmedOauthEndpoint, authUri, tokenUri, providerName); } } return new NoopOAuthAuthenticator(); } static class NoopOAuthAuthenticator extends OAuthAuthenticator { @Override public String getOAuthProvider() { return "Noop"; } @Override public String getEndpointUrl() { return "Noop"; } } } ================================================ FILE: wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.google.api.client.util.store.MemoryDataStoreFactory; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Base64; import org.eclipse.che.api.auth.shared.dto.OAuthToken; /** OAuth authentication for github account. */ public class GitHubOAuthAuthenticator extends OAuthAuthenticator { private final String clientId; private final String clientSecret; private final String githubApiUrl; private final String providerUrl; private final String providerName; public GitHubOAuthAuthenticator( String clientId, String clientSecret, String[] redirectUris, String authEndpoint, String authUri, String tokenUri, String providerName) throws IOException { this.clientId = clientId; this.clientSecret = clientSecret; this.providerName = providerName; providerUrl = isNullOrEmpty(authEndpoint) ? "https://github.com" : trimEnd(authEndpoint, '/'); githubApiUrl = providerUrl.equals("https://github.com") ? "https://api.github.com" : providerUrl + "/api/v3"; configure( clientId, clientSecret, redirectUris, authUri, tokenUri, new MemoryDataStoreFactory()); } @Override public final String getOAuthProvider() { return providerName; } @Override public boolean invalidateToken(String token) throws IOException { HttpURLConnection urlConnection = null; try { String creds = clientId + ":" + clientSecret; String basicAuth = "Basic " + new String(Base64.getEncoder().encode(creds.getBytes())); String jsonInputString = String.format("{\"access_token\":\"%s\"}", token); urlConnection = (HttpURLConnection) new URL(String.format("%s/applications/%s/grant", githubApiUrl, clientId)) .openConnection(); urlConnection.setRequestMethod("DELETE"); urlConnection.setRequestProperty("Authorization", basicAuth); urlConnection.setDoOutput(true); try (OutputStream os = urlConnection.getOutputStream()) { os.write(jsonInputString.getBytes()); } if (urlConnection.getResponseCode() != 204) { return false; } } finally { if (urlConnection != null) { urlConnection.disconnect(); } } return true; } @Override public OAuthToken getOrRefreshToken(String oauthProvider) throws IOException { final OAuthToken token = super.getOrRefreshToken(oauthProvider); // Need to check if token which is stored is valid for requests, then if valid - we returns it // to // caller try { if (token == null || token.getToken() == null || token.getToken().isEmpty() || getJson(githubApiUrl + "/user", token.getToken(), GitHubUser.class) == null) { return null; } } catch (OAuthAuthenticationException e) { return null; } return token; } public String getEndpointUrl() { return providerUrl; } } ================================================ FILE: wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubUser.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import org.eclipse.che.security.oauth.shared.User; /** Represents GitHub user. */ public class GitHubUser implements User { private String name; private String company; private String email; @Override public final String getId() { return email; } @Override public final void setId(String id) { // JSON response from Github API contains key 'id' but it has different purpose. // Ignore calls of this method. Email address is used as user identifier. } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } public String getCompany() { return company; } public void setCompany(String company) { this.company = company; } @Override public String getEmail() { return email; } @Override public void setEmail(String email) { this.email = email; } @Override public String toString() { return "GitHubUser{" + "id='" + getId() + '\'' + ", name='" + name + '\'' + ", company='" + company + '\'' + ", email='" + email + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-auth-gitlab/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-auth-gitlab jar Che Core :: API :: Authentication GitLab com.google.inject guice jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-gitlab-common org.eclipse.che.core che-core-commons-annotations com.google.guava guava test com.google.http-client google-http-client test com.google.oauth-client google-oauth-client test org.eclipse.che.core che-core-api-auth-shared test org.testng testng test org.wiremock wiremock-standalone test ================================================ FILE: wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabModule.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; /** * Setup GitlabOAuthAuthenticator in guice container. * * @author Pavol Baran */ public class GitLabModule extends AbstractModule { @Override protected void configure() { Multibinder oAuthAuthenticators = Multibinder.newSetBinder(binder(), OAuthAuthenticator.class); oAuthAuthenticators.addBinding().toProvider(GitLabOAuthAuthenticatorProvider.class); oAuthAuthenticators.addBinding().toProvider(GitLabOAuthAuthenticatorProviderSecond.class); } } ================================================ FILE: wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import java.io.IOException; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; /** * Provides implementation of GitLab {@link OAuthAuthenticator} based on available configuration. * * @author Pavol Baran */ @Singleton public class GitLabOAuthAuthenticatorProvider extends AbstractGitLabOAuthAuthenticatorProvider { private static final String PROVIDER_NAME = "gitlab"; @Inject public GitLabOAuthAuthenticatorProvider( @Nullable @Named("che.oauth2.gitlab.clientid_filepath") String clientIdPath, @Nullable @Named("che.oauth2.gitlab.clientsecret_filepath") String clientSecretPath, @Nullable @Named("che.integration.gitlab.oauth_endpoint") String gitlabEndpoint, @Named("che.api") String cheApiEndpoint) throws IOException { super(clientIdPath, clientSecretPath, gitlabEndpoint, cheApiEndpoint, PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProviderSecond.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import java.io.IOException; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; /** * Provides implementation of GitLab {@link OAuthAuthenticator} based on available configuration. * * @author Pavol Baran */ @Singleton public class GitLabOAuthAuthenticatorProviderSecond extends AbstractGitLabOAuthAuthenticatorProvider { private static final String PROVIDER_NAME = "gitlab_2"; @Inject public GitLabOAuthAuthenticatorProviderSecond( @Nullable @Named("che.oauth2.gitlab.clientid_filepath_2") String clientIdPath, @Nullable @Named("che.oauth2.gitlab.clientsecret_filepath_2") String clientSecretPath, @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String gitlabEndpoint, @Named("che.api") String cheApiEndpoint) throws IOException { super(clientIdPath, clientSecretPath, gitlabEndpoint, cheApiEndpoint, PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-auth-gitlab/src/test/java/org/eclipse/che/security/oauth/GitLabAuthenticatorTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.api.client.auth.oauth2.StoredCredential; import com.google.api.client.util.store.MemoryDataStoreFactory; import com.google.common.net.HttpHeaders; import java.lang.reflect.Field; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; public class GitLabAuthenticatorTest { WireMockServer wireMockServer; WireMock wireMock; @BeforeClass public void setup() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); } @Test public void shouldGetToken() throws Exception { // given GitLabOAuthAuthenticator gitLabOAuthAuthenticator = new GitLabOAuthAuthenticator( "id", "secret", wireMockServer.url("/"), "https://che.api.com", "gitlab"); Field flowField = OAuthAuthenticator.class.getDeclaredField("flow"); Field credentialDataStoreField = ((Class) flowField.getGenericType()).getDeclaredField("credentialDataStore"); credentialDataStoreField.setAccessible(true); credentialDataStoreField.set( flowField.get(gitLabOAuthAuthenticator), new MemoryDataStoreFactory() .getDataStore("test") .set("userId", new StoredCredential().setAccessToken("token"))); stubFor( get(urlEqualTo("/api/v4/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .willReturn(aResponse().withBody("{\"id\": \"testId\"}"))); // when OAuthToken token = gitLabOAuthAuthenticator.getOrRefreshToken("userId"); // then assertEquals(token.getToken(), "token"); } @Test public void shouldGetEmptyToken() throws Exception { // given GitLabOAuthAuthenticator gitLabOAuthAuthenticator = new GitLabOAuthAuthenticator( "id", "secret", wireMockServer.url("/"), "https://che.api.com", "gitlab"); Field flowField = OAuthAuthenticator.class.getDeclaredField("flow"); Field credentialDataStoreField = ((Class) flowField.getGenericType()).getDeclaredField("credentialDataStore"); credentialDataStoreField.setAccessible(true); credentialDataStoreField.set( flowField.get(gitLabOAuthAuthenticator), new MemoryDataStoreFactory() .getDataStore("test") .set("userId", new StoredCredential().setAccessToken("token"))); stubFor( get(urlEqualTo("/api/v4/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .willReturn(aResponse().withBody("{}"))); // when OAuthToken token = gitLabOAuthAuthenticator.getOrRefreshToken("userId"); // then assertNull(token); } } ================================================ FILE: wsmaster/che-core-api-auth-gitlab/src/test/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProviderTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class GitLabOAuthAuthenticatorProviderTest { private static final String TEST_URI = "https://gitlab.com"; private File credentialFile; private File emptyFile; @BeforeClass public void setup() throws IOException { credentialFile = File.createTempFile("GitLabOAuthAuthenticatorProviderTest-", "-credentials"); Files.asCharSink(credentialFile, Charset.defaultCharset()).write("id/secret"); credentialFile.deleteOnExit(); emptyFile = File.createTempFile("GitLabOAuthAuthenticatorProviderTest-", "-empty"); emptyFile.deleteOnExit(); } @Test(dataProvider = "noopConfig") public void shouldProvideNoopAuthenticatorWhenInvalidConfigurationSet( String gitHubClientIdPath, String gitHubClientSecretPath, String gitlabEndpoint) throws IOException { // given GitLabOAuthAuthenticatorProvider provider = new GitLabOAuthAuthenticatorProvider( gitHubClientIdPath, gitHubClientSecretPath, gitlabEndpoint, "che.api"); // when OAuthAuthenticator authenticator = provider.get(); // then assertNotNull(authenticator); assertTrue( GitLabOAuthAuthenticatorProvider.NoopOAuthAuthenticator.class.isAssignableFrom( authenticator.getClass())); } @Test public void shouldProvideNoopAuthenticatorWhenConfigFilesAreEmpty() throws IOException { // given GitLabOAuthAuthenticatorProvider provider = new GitLabOAuthAuthenticatorProvider( emptyFile.getPath(), emptyFile.getPath(), TEST_URI, "che.api"); // when OAuthAuthenticator authenticator = provider.get(); // then assertNotNull(authenticator); assertTrue( GitLabOAuthAuthenticatorProvider.NoopOAuthAuthenticator.class.isAssignableFrom( authenticator.getClass())); } @Test public void shouldProvideValidGitLabOAuthAuthenticator() throws IOException { // given GitLabOAuthAuthenticatorProvider provider = new GitLabOAuthAuthenticatorProvider( credentialFile.getPath(), credentialFile.getPath(), TEST_URI, "che.api"); // when OAuthAuthenticator authenticator = provider.get(); // then assertNotNull(authenticator); assertTrue(GitLabOAuthAuthenticator.class.isAssignableFrom(authenticator.getClass())); } @DataProvider(name = "noopConfig") public Object[][] noopConfig() { return new Object[][] { {null, null, null}, {null, null, TEST_URI}, {"", "", TEST_URI}, {"", emptyFile.getPath(), TEST_URI}, {emptyFile.getPath(), "", TEST_URI}, {emptyFile.getPath(), emptyFile.getPath(), null}, {credentialFile.getPath(), credentialFile.getPath(), null}, {null, emptyFile.getPath(), TEST_URI}, {emptyFile.getPath(), null, TEST_URI}, {credentialFile.getPath(), null, TEST_URI}, {null, credentialFile.getPath(), TEST_URI}, {credentialFile.getPath(), "", TEST_URI}, {"", credentialFile.getPath(), TEST_URI}, {credentialFile.getPath(), null, null}, {credentialFile.getPath(), credentialFile.getPath(), ""}, {credentialFile.getPath(), credentialFile.getPath(), null}, {credentialFile.getPath(), emptyFile.getPath(), TEST_URI}, {emptyFile.getPath(), credentialFile.getPath(), TEST_URI}, }; } } ================================================ FILE: wsmaster/che-core-api-auth-gitlab-common/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-auth-gitlab-common jar Che Core :: API :: Authentication GitLab Common com.google.guava guava com.google.http-client google-http-client jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-shared org.eclipse.che.core che-core-commons-json org.eclipse.che.core che-core-commons-lang org.slf4j slf4j-api org.testng testng test org.wiremock wiremock-standalone test ================================================ FILE: wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitLabOAuthAuthenticatorProvider.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static com.google.common.base.Strings.isNullOrEmpty; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import javax.inject.Provider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides implementation of GitLab {@link OAuthAuthenticator} based on available configuration. * * @author Pavol Baran */ public class AbstractGitLabOAuthAuthenticatorProvider implements Provider { private static final Logger LOG = LoggerFactory.getLogger(AbstractGitLabOAuthAuthenticatorProvider.class); private final OAuthAuthenticator authenticator; private final String providerName; public AbstractGitLabOAuthAuthenticatorProvider( String clientIdPath, String clientSecretPath, String gitlabEndpoint, String cheApiEndpoint, String providerName) throws IOException { this.providerName = providerName; authenticator = getOAuthAuthenticator(clientIdPath, clientSecretPath, gitlabEndpoint, cheApiEndpoint); LOG.debug("{} GitLab OAuth Authenticator is used.", authenticator); } @Override public OAuthAuthenticator get() { return authenticator; } private OAuthAuthenticator getOAuthAuthenticator( String clientIdPath, String clientSecretPath, String gitlabEndpoint, String cheApiEndpoint) throws IOException { if (!isNullOrEmpty(clientIdPath) && !isNullOrEmpty(clientSecretPath) && !isNullOrEmpty(gitlabEndpoint)) { String clientId = Files.readString(Path.of(clientIdPath)); String clientSecret = Files.readString(Path.of(clientSecretPath)); if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) { return new GitLabOAuthAuthenticator( clientId, clientSecret, gitlabEndpoint, cheApiEndpoint, providerName); } } return new NoopOAuthAuthenticator(); } static class NoopOAuthAuthenticator extends OAuthAuthenticator { @Override public String getOAuthProvider() { return "Noop"; } @Override public String getEndpointUrl() { return "Noop"; } } } ================================================ FILE: wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.google.api.client.util.store.MemoryDataStoreFactory; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import javax.inject.Singleton; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.commons.json.JsonHelper; import org.eclipse.che.commons.json.JsonParseException; /** * OAuth2 authenticator for GitLab account. * * @author Pavol Baran */ @Singleton public class GitLabOAuthAuthenticator extends OAuthAuthenticator { private final String gitlabUserEndpoint; private final String cheApiEndpoint; private final String clientId; private final String clientSecret; private final String gitlabEndpoint; private final String providerName; public GitLabOAuthAuthenticator( String clientId, String clientSecret, String gitlabEndpoint, String cheApiEndpoint, String providerName) throws IOException { this.clientId = clientId; this.clientSecret = clientSecret; this.gitlabEndpoint = trimEnd(gitlabEndpoint, '/'); this.providerName = providerName; String trimmedGitlabEndpoint = trimEnd(gitlabEndpoint, '/'); this.gitlabUserEndpoint = trimmedGitlabEndpoint + "/api/v4/user"; this.cheApiEndpoint = cheApiEndpoint; configure( clientId, clientSecret, new String[] {}, trimmedGitlabEndpoint + "/oauth/authorize", trimmedGitlabEndpoint + "/oauth/token", new MemoryDataStoreFactory()); } @Override public String getOAuthProvider() { return providerName; } @Override protected String findRedirectUrl(URL requestUrl) { return cheApiEndpoint + "/oauth/callback"; } @Override protected O getJson(String getUserUrl, String accessToken, Class userClass) throws OAuthAuthenticationException { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder(URI.create(getUserUrl)) .header("Authorization", "Bearer " + accessToken) .build(); try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); return JsonHelper.fromJson(response.body(), userClass, null); } catch (IOException | InterruptedException | JsonParseException e) { throw new OAuthAuthenticationException(e.getMessage(), e); } } @Override public OAuthToken getOrRefreshToken(String userId) throws IOException { final OAuthToken token = super.getOrRefreshToken(userId); try { if (token == null || token.getToken() == null || token.getToken().isEmpty()) { return null; } GitLabUser user = getJson(gitlabUserEndpoint, token.getToken(), GitLabUser.class); if (user == null || isNullOrEmpty(user.getId())) { return null; } } catch (OAuthAuthenticationException e) { return null; } return token; } @Override public boolean invalidateToken(String token) { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri( URI.create( format( "%s/oauth/revoke?client_id=%s&client_secret=%s&token=%s", gitlabEndpoint, clientId, clientSecret, token))) .POST(HttpRequest.BodyPublishers.noBody()) .build(); try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); return response.statusCode() == 200; } catch (IOException | InterruptedException e) { return false; } } public String getEndpointUrl() { return gitlabEndpoint; } } ================================================ FILE: wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import org.eclipse.che.security.oauth.shared.User; /** * Represents GitLab user. * * @author Pavol Baran */ public class GitLabUser implements User { private String id; private String name; private String email; @Override public String getId() { return id; } @Override public void setId(String id) { this.id = id; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } @Override public String getEmail() { return email; } @Override public void setEmail(String email) { this.email = email; } @Override public String toString() { return "GitLabUser{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", email='" + email + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-auth-openshift/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-auth-openshift jar Che Core :: API :: Authentication OpenShift com.google.guava guava com.google.http-client google-http-client com.google.inject guice jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-shared org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang ================================================ FILE: wsmaster/che-core-api-auth-openshift/src/main/java/org/eclipse/che/security/oauth/OpenShiftOAuthAuthenticator.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.google.api.client.util.store.MemoryDataStoreFactory; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.commons.annotation.Nullable; /** * OAuth authentication for OpenShift. * * @author Igor Vinokur */ @Singleton public class OpenShiftOAuthAuthenticator extends OAuthAuthenticator { private final String oauthEndpoint; private final String verifyTokenUrl; @Inject public OpenShiftOAuthAuthenticator( @Nullable @Named("che.oauth.openshift.clientid") String clientId, @Nullable @Named("che.oauth.openshift.clientsecret") String clientSecret, @Nullable @Named("che.oauth.openshift.oauth_endpoint") String oauthEndpoint, @Nullable @Named("che.oauth.openshift.verify_token_url") String verifyTokenUrl, @Named("che.api") String apiEndpoint) throws IOException { this.oauthEndpoint = isNullOrEmpty(oauthEndpoint) ? "" : trimEnd(oauthEndpoint, '/'); this.verifyTokenUrl = verifyTokenUrl; String[] redirectUrl = {apiEndpoint + "/oauth/callback"}; if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret) && !isNullOrEmpty(oauthEndpoint)) { oauthEndpoint = oauthEndpoint.endsWith("/") ? oauthEndpoint : oauthEndpoint + "/"; configure( clientId, clientSecret, redirectUrl, oauthEndpoint + "oauth/authorize", oauthEndpoint + "oauth/token", new MemoryDataStoreFactory()); } } @Override public final String getOAuthProvider() { return "openshift"; } @Override public OAuthToken getOrRefreshToken(String userId) throws IOException { final OAuthToken token = super.getOrRefreshToken(userId); // Check if the token is valid for requests. if (!(token == null || token.getToken() == null || token.getToken().isEmpty())) { HttpURLConnection http = null; try { http = (HttpURLConnection) new URL(verifyTokenUrl).openConnection(); http.setInstanceFollowRedirects(false); http.setRequestMethod("GET"); http.setRequestProperty("Authorization", "Bearer " + token.getToken()); http.setRequestProperty("Accept", "application/json"); if (http.getResponseCode() == 401) { return null; } } finally { if (http != null) { http.disconnect(); } } return token; } return null; } @Override public String getEndpointUrl() { return oauthEndpoint; } } ================================================ FILE: wsmaster/che-core-api-auth-openshift/src/main/java/org/eclipse/che/security/oauth/OpenShiftOAuthModule.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; /** * Setup OpenShiftOAuthAuthenticator in guice container. * * @author Igor Vinokur */ public class OpenShiftOAuthModule extends AbstractModule { @Override protected void configure() { Multibinder oAuthAuthenticators = Multibinder.newSetBinder(binder(), OAuthAuthenticator.class); oAuthAuthenticators.addBinding().to(OpenShiftOAuthAuthenticator.class); } } ================================================ FILE: wsmaster/che-core-api-auth-shared/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-auth-shared jar Che Core :: API :: Authentication :: Shared ${project.build.directory}/generated-sources/dto/ false com.google.code.gson gson org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} maven-compiler-plugin pre-compile generate-sources compile org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} server process-sources generate org.eclipse.che.api.auth.shared.dto ${dto-generator-out-directory} org.eclipse.che.api.auth.shared.dto.server.DtoServerImpls server org.eclipse.che.core che-core-api-auth-shared ${project.version} org.eclipse.che.core che-core-api-model ${project.version} ================================================ FILE: wsmaster/che-core-api-auth-shared/src/main/java/org/eclipse/che/api/auth/shared/dto/OAuthToken.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.auth.shared.dto; import org.eclipse.che.dto.shared.DTO; /** * OAuth token. * * @author Sergii Kabashniuk */ @DTO public interface OAuthToken { /** Get OAuth token */ String getToken(); /** Set OAuth token */ void setToken(String token); OAuthToken withToken(String token); /** Get OAuth scope */ String getScope(); /** Set OAuth scope */ void setScope(String scope); OAuthToken withScope(String scope); } ================================================ FILE: wsmaster/che-core-api-auth-shared/src/main/java/org/eclipse/che/security/oauth/shared/OAuthAuthorizationHeaderProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth.shared; import java.util.Map; /** * Provides the Authorization header value from the OAuth 1 providers. * * @author Kevin Pollet * @author Igor Vinokur */ public interface OAuthAuthorizationHeaderProvider { /** * Returns the Authorization header value used to sign the request with OAuth. * * @param oauthProviderName the OAuth 1 provider name. * @param userId the user id. * @param requestMethod the HTTP request method. * @param requestUrl the HTTP request url with encoded query parameters. * @param requestParameters the HTTP request parameters. HTTP request parameters must include raw * values of application/x-www-form-urlencoded POST parameters. * @return the Authorization header value or {@code null} if it cannot be computed. */ String getAuthorizationHeader( String oauthProviderName, String userId, String requestMethod, String requestUrl, Map requestParameters); } ================================================ FILE: wsmaster/che-core-api-auth-shared/src/main/java/org/eclipse/che/security/oauth/shared/OAuthTokenProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth.shared; import java.io.IOException; import org.eclipse.che.api.auth.shared.dto.OAuthToken; /** Retrieves user token from OAuth providers. */ public interface OAuthTokenProvider { /** * Get oauth token. * * @param oauthProviderName - name of provider. * @param userId - user * @return oauth token or null * @throws IOException if i/o error occurs when try to refresh expired oauth token */ OAuthToken getToken(String oauthProviderName, String userId) throws IOException; } ================================================ FILE: wsmaster/che-core-api-auth-shared/src/main/java/org/eclipse/che/security/oauth/shared/User.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth.shared; /** * Represents an User with unique identifier. Have such interface to be able use GWT AutoBean * feature. Any interface that represents an User should extend this interface. */ public interface User { String getId(); void setId(String id); String getName(); void setName(String name); String getEmail(); void setEmail(String email); } ================================================ FILE: wsmaster/che-core-api-auth-shared/src/main/java/org/eclipse/che/security/oauth/shared/dto/OAuthAuthenticatorDescriptor.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.security.oauth.shared.dto; import java.util.List; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.dto.shared.DTO; /** * @author Max Shaposhnik (mshaposhnik@codenvy.com) */ @DTO public interface OAuthAuthenticatorDescriptor { String getName(); void setName(String name); OAuthAuthenticatorDescriptor withName(String name); String getEndpointUrl(); void setEndpointUrl(String endpointUrl); OAuthAuthenticatorDescriptor withEndpointUrl(String endpointUrl); List getLinks(); void setLinks(List links); OAuthAuthenticatorDescriptor withLinks(List links); } ================================================ FILE: wsmaster/che-core-api-devfile/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-devfile jar Che Core :: API :: Devfile com.fasterxml.jackson.core jackson-databind com.google.guava guava jakarta.inject jakarta.inject-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-account org.eclipse.che.core che-core-api-devfile-shared org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.core che-core-api-core provided org.eclipse.persistence jakarta.persistence provided ch.qos.logback logback-classic test com.google.inject guice test commons-fileupload commons-fileupload test io.rest-assured rest-assured test org.eclipse.che.core che-core-api-user test org.eclipse.che.core che-core-commons-json test org.eclipse.che.core che-core-commons-lang test org.eclipse.che.core che-core-commons-test test org.eclipse.che.core che-core-sql-schema test org.eclipse.persistence org.eclipse.persistence.core test org.eclipse.persistence org.eclipse.persistence.jpa test org.everrest everrest-assured test org.everrest everrest-core test org.hamcrest hamcrest-core test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test org.apache.maven.plugins maven-jar-plugin test-jar **/spi/tck/*.* **/devfile/server/TestObjectGenerator.* ================================================ FILE: wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/DtoConverter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.devfile.server; import static org.eclipse.che.dto.server.DtoFactory.newDto; import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; /** Helps to convert to/from DTOs related to user devfile. */ public class DtoConverter { public static UserDevfileDto asDto(UserDevfile userDevfile) { DevfileDto devfileDto = org.eclipse.che.api.workspace.server.DtoConverter.asDto(userDevfile.getDevfile()); return newDto(UserDevfileDto.class) .withId(userDevfile.getId()) .withDevfile(devfileDto) .withNamespace(userDevfile.getNamespace()) .withName(userDevfile.getName()) .withDescription(userDevfile.getDescription()); } } ================================================ FILE: wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileEntityProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.devfile.server; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Produces; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.MessageBodyReader; import jakarta.ws.rs.ext.MessageBodyWriter; import jakarta.ws.rs.ext.Provider; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.dto.server.DtoFactory; /** * Entity provider for {@link UserDevfileDto}. Performs schema validation of devfile part of the * user devfile before actual {@link UserDevfileDto} creation. */ @Singleton @Provider @Produces({APPLICATION_JSON}) @Consumes({APPLICATION_JSON}) public class UserDevfileEntityProvider implements MessageBodyReader, MessageBodyWriter { private final DevfileParser devfileParser; private final ObjectMapper mapper = new ObjectMapper(); @Inject public UserDevfileEntityProvider(DevfileParser devfileParser) { this.devfileParser = devfileParser; } @Override public boolean isReadable( Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return type == UserDevfileDto.class; } @Override public UserDevfileDto readFrom( Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { try { JsonNode wsNode = mapper.readTree(entityStream); JsonNode devfileNode = wsNode.path("devfile"); if (!devfileNode.isNull() && !devfileNode.isMissingNode()) { devfileParser.parseJson(devfileNode.toString()); } else { throw new BadRequestException("Mandatory field `devfile` is not defined."); } return DtoFactory.getInstance().createDtoFromJson(wsNode.toString(), UserDevfileDto.class); } catch (DevfileFormatException e) { throw new BadRequestException(e.getMessage()); } catch (IOException e) { throw new WebApplicationException(e.getMessage(), e); } } @Override public boolean isWriteable( Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return UserDevfileDto.class.isAssignableFrom(type); } @Override public long getSize( UserDevfileDto userDevfileDto, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } @Override public void writeTo( UserDevfileDto userDevfileDto, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { httpHeaders.putSingle(HttpHeaders.CACHE_CONTROL, "public, no-cache, no-store, no-transform"); try (Writer w = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8)) { w.write(DtoFactory.getInstance().toJson(userDevfileDto)); w.flush(); } } } ================================================ FILE: wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.devfile.server.model.impl; import com.google.common.annotations.Beta; import java.util.Objects; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToOne; import javax.persistence.Table; import org.eclipse.che.account.shared.model.Account; import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; @Entity(name = "UserDevfile") @Table(name = "userdevfile") @NamedQueries({ @NamedQuery( name = "UserDevfile.getByNamespace", query = "SELECT d FROM UserDevfile d WHERE d.account.name = :namespace"), @NamedQuery( name = "UserDevfile.getByNamespaceCount", query = "SELECT COUNT(d) FROM UserDevfile d WHERE d.account.name = :namespace "), @NamedQuery(name = "UserDevfile.getAll", query = "SELECT d FROM UserDevfile d ORDER BY d.id"), @NamedQuery(name = "UserDevfile.getTotalCount", query = "SELECT COUNT(d) FROM UserDevfile d"), }) @Beta public class UserDevfileImpl implements UserDevfile { /** * In {@MetadataImpl} name is mandatory and generateName is transient. That is not suitable for * UserDevfile because we need to handle situations when the name is not defined and generateName * is defined. To workaround that original name and generateName stored in individual fields * meta_name and meta_generated_name. But at the same time, we can't leave metadata filed null in * devfile because of database hard constrain. To replace that FAKE_META is used. */ private static final MetadataImpl FAKE_META = new MetadataImpl("name"); @Id @Column(name = "id", nullable = false) private String id; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "devfile_id") private DevfileImpl devfile; @Column(name = "meta_generated_name") private String metaGeneratedName; @Column(name = "meta_name") private String metaName; @Column(name = "name", nullable = false) private String name; @Column(name = "description") private String description; @ManyToOne @JoinColumn(name = "accountid", nullable = false) private AccountImpl account; public UserDevfileImpl() {} public UserDevfileImpl(String id, Account account, UserDevfile userDevfile) { this( id, account, userDevfile.getName(), userDevfile.getDescription(), userDevfile.getDevfile()); } public UserDevfileImpl(UserDevfile userDevfile, Account account) { this(userDevfile.getId(), account, userDevfile); } public UserDevfileImpl(UserDevfileImpl userDevfile) { this( userDevfile.id, userDevfile.account, userDevfile.getName(), userDevfile.getDescription(), userDevfile.getDevfile()); } public UserDevfileImpl( String id, Account account, String name, String description, Devfile devfile) { this.id = id; this.account = new AccountImpl(account); this.name = name; this.description = description; this.devfile = new DevfileImpl(devfile); syncMeta(); } @Override public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String getName() { return name; } @Override public String getNamespace() { return account.getName(); } public void setName(String name) { this.name = name; } @Override public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } @Override public Devfile getDevfile() { return new DevfileImpl( devfile.getApiVersion(), devfile.getProjects(), devfile.getComponents(), devfile.getCommands(), devfile.getAttributes(), new MetadataImpl(metaName, metaGeneratedName)); } public void setDevfile(DevfileImpl devfile) { this.devfile = devfile; syncMeta(); } public AccountImpl getAccount() { return account; } public void setAccount(AccountImpl account) { this.account = account; } private void syncMeta() { MetadataImpl metadata = devfile.getMetadata(); metaGeneratedName = metadata.getGenerateName(); metaName = metadata.getName(); devfile.setMetadata(FAKE_META); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserDevfileImpl that = (UserDevfileImpl) o; return Objects.equals(id, that.id) && Objects.equals(devfile, that.devfile) && Objects.equals(metaGeneratedName, that.metaGeneratedName) && Objects.equals(metaName, that.metaName) && Objects.equals(name, that.name) && Objects.equals(description, that.description) && Objects.equals(account, that.account); } @Override public int hashCode() { return Objects.hash(id, devfile, metaGeneratedName, metaName, name, description, account); } @Override public String toString() { return "UserDevfileImpl{" + "id='" + id + '\'' + ", devfile=" + devfile + ", metaGeneratedName='" + metaGeneratedName + '\'' + ", metaName='" + metaName + '\'' + ", name='" + name + '\'' + ", description='" + description + '\'' + ", account=" + account + '}'; } } ================================================ FILE: wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/TestObjectGenerator.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.devfile.server; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; import java.util.Collections; import org.eclipse.che.account.shared.model.Account; import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; public class TestObjectGenerator { public static final String TEST_CHE_NAMESPACE = "user"; public static final String CURRENT_USER_ID = NameGenerator.generate("usrid", 6); public static final Subject TEST_SUBJECT = new SubjectImpl(TEST_CHE_NAMESPACE, Collections.emptyList(), CURRENT_USER_ID, "token", false); public static final String USER_DEVFILE_ID = NameGenerator.generate("usrd", 16); public static final AccountImpl TEST_ACCOUNT = new AccountImpl("acc-id042u3ui3oi", TEST_CHE_NAMESPACE, "test"); public static UserDevfileDto createUserDevfileDto() { return DtoConverter.asDto(createUserDevfile(NameGenerator.generate("name", 6))); } public static UserDevfileImpl createUserDevfile() { return createUserDevfile(NameGenerator.generate("name", 6)); } public static UserDevfileImpl createUserDevfile(String name) { return createUserDevfile(NameGenerator.generate("id", 6), name); } public static UserDevfileImpl createUserDevfile(String id, String name) { return new UserDevfileImpl(id, TEST_ACCOUNT, name, "devfile description", createDevfile(name)); } public static UserDevfileImpl createUserDevfile(String id, Account account, String name) { return new UserDevfileImpl(id, account, name, "devfile description", createDevfile(name)); } public static UserDevfileImpl createUserDevfile(Account account) { return createUserDevfile( NameGenerator.generate("id", 6), account, NameGenerator.generate("name", 6)); } public static DevfileImpl createDevfile(String generatedName) { return createDevfile(null, generatedName); } public static DevfileImpl createDevfileWithName(String name) { return createDevfile(name, null); } private static DevfileImpl createDevfile(String name, String generatedName) { String effectiveName = MoreObjects.firstNonNull(name, generatedName); SourceImpl source1 = new SourceImpl( "type1", "http://location", "branch1", "point1", "tag1", "commit1", "sparseCheckoutDir1"); ProjectImpl project1 = new ProjectImpl("project1", source1, "path1"); SourceImpl source2 = new SourceImpl( "type2", "http://location", "branch2", "point2", "tag2", "commit2", "sparseCheckoutDir2"); ProjectImpl project2 = new ProjectImpl("project2", source2, "path2"); ActionImpl action1 = new ActionImpl("exec1", "component1", "run.sh", "/home/user/1", null, null); ActionImpl action2 = new ActionImpl("exec2", "component2", "run.sh", "/home/user/2", null, null); CommandImpl command1 = new CommandImpl( effectiveName + "-1", singletonList(action1), singletonMap("attr1", "value1"), null); CommandImpl command2 = new CommandImpl( effectiveName + "-2", singletonList(action2), singletonMap("attr2", "value2"), null); EntrypointImpl entrypoint1 = new EntrypointImpl( "parentName1", singletonMap("parent1", "selector1"), "containerName1", asList("command1", "command2"), asList("arg1", "arg2")); EntrypointImpl entrypoint2 = new EntrypointImpl( "parentName2", singletonMap("parent2", "selector2"), "containerName2", asList("command3", "command4"), asList("arg3", "arg4")); org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume1 = new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name1", "path1"); org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume2 = new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name2", "path2"); EnvImpl env1 = new EnvImpl("name1", "value1"); EnvImpl env2 = new EnvImpl("name2", "value2"); EndpointImpl endpoint1 = new EndpointImpl("name1", 1111, singletonMap("key1", "value1")); EndpointImpl endpoint2 = new EndpointImpl("name2", 2222, singletonMap("key2", "value2")); ComponentImpl component1 = new ComponentImpl( "kubernetes", "component1", null, null, null, null, "refcontent1", ImmutableMap.of("app.kubernetes.io/component", "db"), null, null, null, null, null, null, false, false, null, null, null, asList(env1, env2), null); component1.setSelector(singletonMap("key1", "value1")); ComponentImpl component2 = new ComponentImpl( "dockerimage", "component2", null, null, null, null, null, null, null, "image", "256G", null, "3", "180m", false, false, singletonList("command"), singletonList("arg"), asList(volume1, volume2), asList(env1, env2), asList(endpoint1, endpoint2)); ComponentImpl component3 = new ComponentImpl( "chePlugin", "check/terminal-sample/0.0.1", ImmutableMap.of( "java.home", "/home/user/jdk11aertwertert", "java.boolean", "true", "java.long", "123444L")); MetadataImpl metadata = new MetadataImpl(name); metadata.setGenerateName(generatedName); DevfileImpl devfile = new DevfileImpl( "1.0.0", asList(project1, project2), asList(component1, component2, component3), asList(command1, command2), singletonMap("attribute1", "value1"), metadata); return devfile; } } ================================================ FILE: wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileTckModule.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.devfile.server.jpa; import com.google.inject.TypeLiteral; import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.commons.test.tck.TckModule; import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; import org.eclipse.che.commons.test.tck.repository.TckRepository; /** Tck module for UserDevfile test. */ public class UserDevfileTckModule extends TckModule { @Override protected void configure() { bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(UserImpl.class)); bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(AccountImpl.class)); } } ================================================ FILE: wsmaster/che-core-api-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule ================================================ org.eclipse.che.api.devfile.server.jpa.UserDevfileTckModule ================================================ FILE: wsmaster/che-core-api-devfile/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: wsmaster/che-core-api-devfile-shared/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-devfile-shared jar Che Core :: API :: Devfile :: Shared ${project.build.directory}/generated-sources/dto/ com.google.code.gson gson com.google.guava guava org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} process-sources generate org.eclipse.che.core che-core-api-devfile-shared ${project.version} org.eclipse.che.core che-core-api-model ${project.version} org.eclipse.che.core che-core-api-workspace-shared ${project.version} org.eclipse.che.api.devfile.shared.dto ${dto-generator-out-directory} org.eclipse.che.api.devfile.server.dto.DtoServerImpls server maven-compiler-plugin pre-compile generate-sources compile org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} ================================================ FILE: wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/Constants.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.devfile.shared; import com.google.common.annotations.Beta; /** Constants for Devfile API */ @Beta public final class Constants { public static final String LINK_REL_SELF = "self"; private Constants() {} } ================================================ FILE: wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/dto/UserDevfileDto.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.devfile.shared.dto; import com.google.common.annotations.Beta; import java.util.List; import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.eclipse.che.dto.shared.DTO; @DTO @Beta public interface UserDevfileDto extends UserDevfile, Hyperlinks { void setId(String id); UserDevfileDto withId(String id); String getId(); void setName(String name); UserDevfileDto withName(String name); String getName(); void setDescription(String name); UserDevfileDto withDescription(String name); String getDescription(); @Override DevfileDto getDevfile(); void setDevfile(DevfileDto devfile); @Override String getNamespace(); void setNamespace(String namespace); UserDevfileDto withNamespace(String namespace); UserDevfileDto withDevfile(DevfileDto devfile); UserDevfileDto withLinks(List links); } ================================================ FILE: wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileCreatedEvent.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.devfile.shared.event; import com.google.common.annotations.Beta; import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; import org.eclipse.che.api.core.notification.EventOrigin; /** Informs about persisted devfile creation. */ @EventOrigin("devfile") @Beta public class DevfileCreatedEvent { private final UserDevfile userDevfile; public DevfileCreatedEvent(UserDevfile userDevfile) { this.userDevfile = userDevfile; } public UserDevfile getUserDevfile() { return userDevfile; } } ================================================ FILE: wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileDeletedEvent.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.devfile.shared.event; import com.google.common.annotations.Beta; import org.eclipse.che.api.core.notification.EventOrigin; /** Informs about persisted devfile removal. */ @EventOrigin("devfile") @Beta public class DevfileDeletedEvent { private final String id; public DevfileDeletedEvent(String id) { this.id = id; } public String getId() { return id; } } ================================================ FILE: wsmaster/che-core-api-devfile-shared/src/main/java/org/eclipse/che/api/devfile/shared/event/DevfileUpdatedEvent.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.devfile.shared.event; import com.google.common.annotations.Beta; import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; import org.eclipse.che.api.core.notification.EventOrigin; /** Informs about persisted devfile update. */ @EventOrigin("devfile") @Beta public class DevfileUpdatedEvent { private final UserDevfile userDevfile; public DevfileUpdatedEvent(UserDevfile userDevfile) { this.userDevfile = userDevfile; } public UserDevfile getUserDevfile() { return userDevfile; } } ================================================ FILE: wsmaster/che-core-api-factory/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-factory jar Che Core :: API :: Factory false com.fasterxml.jackson.core jackson-databind com.google.guava guava com.google.inject guice com.google.inject.extensions guice-persist io.swagger.core.v3 swagger-annotations-jakarta jakarta.inject jakarta.inject-api jakarta.validation jakarta.validation-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-factory-shared org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-api-user org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang org.eclipse.che.core che-core-commons-test org.eclipse.persistence jakarta.persistence org.slf4j slf4j-api jakarta.servlet jakarta.servlet-api provided ch.qos.logback logback-classic test commons-fileupload commons-fileupload test io.rest-assured rest-assured test org.eclipse.che.core che-core-api-account test org.eclipse.che.core che-core-commons-json test org.eclipse.che.core che-core-sql-schema test org.eclipse.persistence org.eclipse.persistence.core test org.eclipse.persistence org.eclipse.persistence.jpa test org.everrest everrest-assured test org.everrest everrest-core test org.hamcrest hamcrest-core test org.mockito mockito-core test org.mockito mockito-testng test org.slf4j jcl-over-slf4j test org.slf4j jul-to-slf4j test org.slf4j log4j-over-slf4j test org.testng testng test org.apache.maven.plugins maven-jar-plugin test-jar **/spi/tck/*.* ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/AdditionalFilenamesProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import com.google.common.base.Splitter; import java.util.List; import javax.inject.Inject; import javax.inject.Named; /** * Reads, parses, and provide handful access to property which describes a list of additional files * required for devfile v2. */ public class AdditionalFilenamesProvider { private final List filenames; @Inject public AdditionalFilenamesProvider( @Named("che.factory.devfile2_files_resolution_list") String additionalFilenamesString) { this.filenames = Splitter.on(",").splitToList(additionalFilenamesString); } public List get() { return filenames; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ApiExceptionMapper.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.dto.server.DtoFactory.newDto; import java.util.Collections; import java.util.Map; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.core.rest.shared.dto.ExtendedError; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** * Helps to convert {@link Exception}s with some specific causes into REST-friendly {@link * ApiException} */ public class ApiExceptionMapper { public static ApiException toApiException(DevfileException devfileException) { ApiException apiException = getApiException(devfileException.getCause()); return (apiException != null) ? apiException : new BadRequestException( "Error occurred during file content retrieval." + "Cause: " + devfileException.getMessage()); } public static ApiException toApiException(ScmUnauthorizedException scmUnauthorizedException) { ApiException apiException = getApiException(scmUnauthorizedException); return (apiException != null) ? apiException : new BadRequestException( "Error occurred during SCM authorisation." + "Cause: " + scmUnauthorizedException.getMessage()); } public static ApiException toApiException(ScmCommunicationException scmCommunicationException) { ApiException apiException = getApiException(scmCommunicationException); return (apiException != null) ? apiException : new ServerException( "Error occurred during SCM communication." + "Cause: " + scmCommunicationException.getMessage()); } public static ApiException toApiException( DevfileException devfileException, DevfileLocation location) { ApiException cause = getApiException(devfileException.getCause()); return (cause != null) ? cause : new BadRequestException( "Error occurred during creation a workspace from devfile located at `" + location.location() + "`. Cause: " + devfileException.getMessage()); } private static ApiException getApiException(Throwable throwable) { if (throwable instanceof ScmUnauthorizedException) { ScmUnauthorizedException scmCause = (ScmUnauthorizedException) throwable; return new UnauthorizedException( "SCM Authentication required", 401, Map.of( "oauth_version", scmCause.getOauthVersion(), "oauth_provider", scmCause.getOauthProvider(), "oauth_authentication_url", scmCause.getAuthenticateUrl())); } else { if (throwable instanceof UnknownScmProviderException) { return new ServerException( appendErrorMessage( "Provided location is unknown or misconfigured on the server side", throwable.getMessage())); } else if (throwable instanceof ScmCommunicationException) { return new ServerException( newDto(ExtendedError.class) .withMessage( appendErrorMessage( "Error occurred during SCM communication.", throwable.getMessage())) .withErrorCode(404) .withAttributes( Collections.singletonMap( "provider", ((ScmCommunicationException) throwable).getProvider()))); } } return null; } private static String appendErrorMessage(String message, String errorMessage) { return message + (isNullOrEmpty(errorMessage) ? "" : " Error message: " + errorMessage); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/BaseFactoryParameterResolver.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static java.util.stream.Collectors.toMap; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.DEFAULT_DEVFILE; import static org.eclipse.che.dto.server.DtoFactory.newDto; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; public class BaseFactoryParameterResolver { private final AuthorisationRequestManager authorisationRequestManager; private final URLFactoryBuilder urlFactoryBuilder; private final String providerName; public BaseFactoryParameterResolver( AuthorisationRequestManager authorisationRequestManager, URLFactoryBuilder urlFactoryBuilder, String providerName) { this.authorisationRequestManager = authorisationRequestManager; this.urlFactoryBuilder = urlFactoryBuilder; this.providerName = providerName; } protected FactoryMetaDto createFactory( Map factoryParameters, RemoteFactoryUrl factoryUrl, FactoryVisitor factoryVisitor, FileContentProvider contentProvider) throws ApiException { // create factory from the following location if location exists, else create default factory return urlFactoryBuilder .createFactoryFromDevfile( factoryUrl, contentProvider, extractOverrideParams(factoryParameters), getSkipAuthorisation(factoryParameters)) .orElseGet( () -> newDto(FactoryDevfileV2Dto.class) .withDevfile(DEFAULT_DEVFILE) .withV(CURRENT_VERSION) .withSource("repo")) .acceptVisitor(factoryVisitor); } protected boolean getSkipAuthorisation(Map factoryParameters) { String errorCode = "error_code"; boolean stored = authorisationRequestManager.isStored(providerName); boolean skipAuthentication = factoryParameters.get(errorCode) != null && factoryParameters.get(errorCode).equals("access_denied") || stored; if (skipAuthentication && !stored) { authorisationRequestManager.store(providerName); } return skipAuthentication; } /** * Returns priority of the resolver. Resolvers with higher priority will be used among matched * resolvers. */ public FactoryResolverPriority priority() { return FactoryResolverPriority.DEFAULT; } /** * Finds and returns devfile override parameters in general factory parameters map. * * @param factoryParameters map containing factory data parameters provided through URL * @return filtered devfile values override map */ protected Map extractOverrideParams(Map factoryParameters) { String overridePrefix = "override."; return factoryParameters.entrySet().stream() .filter(e -> e.getKey().startsWith(overridePrefix)) .collect(toMap(e -> e.getKey().substring(overridePrefix.length()), Map.Entry::getValue)); } /** * If devfile has no projects, put there one provided by given `projectSupplier`. Otherwise update * all projects with given `projectModifier`. * * @param devfile of the projects to update * @param projectSupplier provides default project * @param projectModifier updates existing projects */ protected void updateProjects( DevfileDto devfile, Supplier projectSupplier, Consumer projectModifier) { List projects = devfile.getProjects(); if (projects.isEmpty()) { devfile.setProjects(Collections.singletonList(projectSupplier.get())); } else { // update existing project with same repository, set current branch if needed projects.forEach(projectModifier); } } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DtoConverter.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static java.util.stream.Collectors.toList; import static org.eclipse.che.dto.server.DtoFactory.newDto; import java.util.List; import org.eclipse.che.api.core.model.factory.Action; import org.eclipse.che.api.core.model.factory.Author; import org.eclipse.che.api.core.model.factory.Ide; import org.eclipse.che.api.core.model.factory.OnAppClosed; import org.eclipse.che.api.core.model.factory.OnAppLoaded; import org.eclipse.che.api.core.model.factory.OnProjectsLoaded; import org.eclipse.che.api.core.model.factory.Policies; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.factory.shared.dto.AuthorDto; import org.eclipse.che.api.factory.shared.dto.IdeActionDto; import org.eclipse.che.api.factory.shared.dto.IdeDto; import org.eclipse.che.api.factory.shared.dto.OnAppClosedDto; import org.eclipse.che.api.factory.shared.dto.OnAppLoadedDto; import org.eclipse.che.api.factory.shared.dto.OnProjectsLoadedDto; import org.eclipse.che.api.factory.shared.dto.PoliciesDto; /** * Helps to convert to DTOs related to factory. * * @author Anton Korneta */ public final class DtoConverter { public static IdeDto asDto(Ide ide) { final IdeDto ideDto = newDto(IdeDto.class); final OnAppClosed onAppClosed = ide.getOnAppClosed(); final OnAppLoaded onAppLoaded = ide.getOnAppLoaded(); final OnProjectsLoaded onProjectsLoaded = ide.getOnProjectsLoaded(); if (onAppClosed != null) { ideDto.withOnAppClosed( newDto(OnAppClosedDto.class).withActions(asDto(onAppClosed.getActions()))); } if (onAppLoaded != null) { ideDto.withOnAppLoaded( newDto(OnAppLoadedDto.class).withActions(asDto(onAppLoaded.getActions()))); } if (onProjectsLoaded != null) { ideDto.withOnProjectsLoaded( newDto(OnProjectsLoadedDto.class).withActions(asDto(onProjectsLoaded.getActions()))); } return ideDto; } public static AuthorDto asDto(Author author, User user) { return newDto(AuthorDto.class) .withUserId(author.getUserId()) .withName(user.getName()) .withEmail(user.getEmail()) .withCreated(author.getCreated()); } public static IdeActionDto asDto(Action action) { return newDto(IdeActionDto.class).withId(action.getId()).withProperties(action.getProperties()); } public static List asDto(List actions) { return actions.stream().map(DtoConverter::asDto).collect(toList()); } public static PoliciesDto asDto(Policies policies) { return newDto(PoliciesDto.class) .withCreate(policies.getCreate()) .withReferer(policies.getReferer()) .withSince(policies.getSince()) .withUntil(policies.getUntil()); } private DtoConverter() {} } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryAcceptValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; /** Interface for validations of factory urls on accept stage. */ public interface FactoryAcceptValidator { /** * Validates factory object on accept stage. Implementation should throw {@link * BadRequestException} if factory object is invalid. * * @param factory factory object to validate * @throws BadRequestException in case if factory is not valid */ void validateOnAccept(FactoryMetaDto factory) throws BadRequestException; } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryConstants.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; /** Message constants for factory builder. */ public class FactoryConstants { public static final String INVALID_PARAMETER_MESSAGE = "Passed in an invalid parameter. You either provided a non-valid parameter, or that parameter is not " + "accepted for this Factory version. For more information, please visit " + "http://docs.codenvy.com/user/project-lifecycle/#configuration-reference"; public static final String INVALID_VERSION_MESSAGE = "You have provided an inaccurate or deprecated Factory Version. For more information, " + "please visit https://www.eclipse.org/che/docs/factories_json_reference.html"; public static final String UNPARSABLE_FACTORY_MESSAGE = "We cannot parse the provided factory. For more information, please visit https://www.eclipse.org/che/docs/factories_json_reference.html"; public static final String MISSING_MANDATORY_MESSAGE = "You are missing a mandatory parameter \"%s\". For more information, please visit https://www.eclipse.org/che/docs/factories_json_reference.html"; public static final String PARAMETRIZED_INVALID_PARAMETER_MESSAGE = "You have provided an invalid parameter \"%s\" for this version of Factory parameters \"%s\". For more " + "information, please visit https://www.eclipse.org/che/docs/factories_json_reference.html"; public static final String INVALID_SINCE_MESSAGE = "Since date cannot occur before the current date."; public static final String INVALID_UNTIL_MESSAGE = "Until date cannot occur before the current date."; public static final String INVALID_SINCEUNTIL_MESSAGE = "Until date should occur after the Since date."; public static final String INVALID_ACTION_SECTION = "The action %s is not allowed in this IDE event section."; public static final String INVALID_OPENFILE_ACTION = "The openFile action requires 'file' property to be set."; public static final String INVALID_RUNCOMMAND_ACTION = "The runCommand action requires 'name' property to be set."; public static final String INVALID_FIND_REPLACE_ACTION = "The findReplace action requires 'in', " + "'find' and 'replace' properties to be set."; public static final String INVALID_WELCOME_PAGE_ACTION = "The openWelcomePage action requires 'greetingContentUrl' property to be set."; public static final String ILLEGAL_FACTORY_BY_SINCE_MESSAGE = "This Factory is not yet valid due to time restrictions applied by its owner. Please, " + "contact owner for more information."; public static final String ILLEGAL_FACTORY_BY_UNTIL_MESSAGE = "This Factory has expired due to time restrictions applied by its owner. Please, " + "contact owner for more information."; public static final String PARAMETRIZED_ILLEGAL_PARAMETER_VALUE_MESSAGE = "The parameter %s has a value submitted %s with a value that is unexpected. For more information, " + "please visit https://www.eclipse.org/che/docs/workspace-data-model.html#projects"; private FactoryConstants() {} } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryLinksHelper.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static com.google.common.base.Strings.isNullOrEmpty; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static jakarta.ws.rs.core.MediaType.TEXT_HTML; import static org.eclipse.che.api.core.util.LinksHelper.createLink; import static org.eclipse.che.api.factory.shared.Constants.FACTORY_ACCEPTANCE_REL_ATT; import static org.eclipse.che.api.factory.shared.Constants.NAMED_FACTORY_ACCEPTANCE_REL_ATT; import static org.eclipse.che.api.factory.shared.Constants.RETRIEVE_FACTORY_REL_ATT; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.core.UriBuilder; import java.util.LinkedList; import java.util.List; import org.eclipse.che.api.core.rest.ServiceContext; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; /** * Helper class for creation links. * * @author Anton Korneta */ public class FactoryLinksHelper { private FactoryLinksHelper() {} /** * Creates factory links. * * @param serviceContext the context to retrieve factory service base URI * @return list of factory links */ public static List createLinks( FactoryMetaDto factory, ServiceContext serviceContext, AdditionalFilenamesProvider additionalFilenamesProvider, String userName, String repositoryUrl) { final List links = new LinkedList<>(); final UriBuilder uriBuilder = serviceContext.getServiceUriBuilder(); final String factoryId = factory.getId(); if (factoryId != null) { // creation of link to retrieve factory links.add( createLink( HttpMethod.GET, uriBuilder .clone() .path(FactoryService.class, "getFactory") .build(factoryId) .toString(), null, APPLICATION_JSON, RETRIEVE_FACTORY_REL_ATT)); // creation of accept factory link final Link createWorkspace = createLink( HttpMethod.GET, uriBuilder.clone().replacePath("f").queryParam("id", factoryId).build().toString(), null, TEXT_HTML, FACTORY_ACCEPTANCE_REL_ATT); links.add(createWorkspace); } if (!isNullOrEmpty(factory.getName()) && !isNullOrEmpty(userName)) { // creation of accept factory link by name and creator final Link createWorkspaceFromNamedFactory = createLink( HttpMethod.GET, uriBuilder .clone() .replacePath("f") .queryParam("name", factory.getName()) .queryParam("user", userName) .build() .toString(), null, TEXT_HTML, NAMED_FACTORY_ACCEPTANCE_REL_ATT); links.add(createWorkspaceFromNamedFactory); } if (factory instanceof FactoryDevfileV2Dto) { // link to devfile source if (!isNullOrEmpty(factory.getSource())) { links.add( createLink( HttpMethod.GET, uriBuilder .clone() .replacePath("api") .path(ScmService.class) .path(ScmService.class, "resolveFile") .queryParam("repository", repositoryUrl) .queryParam("file", factory.getSource()) .build(factoryId) .toString(), factory.getSource() + " content")); } if (((FactoryDevfileV2Dto) factory).getScmInfo() != null) { // additional files links for (String additionalFile : additionalFilenamesProvider.get()) { links.add( createLink( HttpMethod.GET, uriBuilder .clone() .replacePath("api") .path(ScmService.class) .path(ScmService.class, "resolveFile") .queryParam("repository", repositoryUrl) .queryParam("file", additionalFile) .build(factoryId) .toString(), additionalFile + " content")); } } } return links; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryManager.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static com.google.common.base.Strings.isNullOrEmpty; import static java.util.Objects.requireNonNull; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.factory.Factory; import org.eclipse.che.api.factory.server.model.impl.AuthorImpl; import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; import org.eclipse.che.api.factory.server.spi.FactoryDao; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.lang.Pair; /** * @author Anton Korneta */ @Singleton public class FactoryManager { private final FactoryDao factoryDao; @Inject public FactoryManager(FactoryDao factoryDao) { this.factoryDao = factoryDao; } /** * Stores {@link Factory} instance. * * @param factory instance of factory which would be stored * @return factory which has been stored * @throws NullPointerException when {@code factory} is null * @throws ConflictException when any conflict occurs (e.g Factory with given name already exists * for {@code creator}) * @throws ServerException when any server errors occurs */ public Factory saveFactory(Factory factory) throws ConflictException, ServerException { requireNonNull(factory); final FactoryImpl newFactory = new FactoryImpl(factory); newFactory.setId(NameGenerator.generate("factory", 16)); if (isNullOrEmpty(newFactory.getName())) { newFactory.setName(NameGenerator.generate("f", 9)); } return factoryDao.create(newFactory); } /** * Updates factory in accordance to the new configuration. * *

    Note: Updating uses replacement strategy, therefore existing factory would be replaced with * given update {@code update} * * @param update factory update * @return updated factory * @throws NullPointerException when {@code update} is null * @throws ConflictException when any conflict occurs (e.g Factory with given name already exists * for {@code creator}) * @throws NotFoundException when factory with given id not found * @throws ServerException when any server error occurs */ public Factory updateFactory(Factory update) throws ConflictException, NotFoundException, ServerException { requireNonNull(update); final AuthorImpl creator = factoryDao.getById(update.getId()).getCreator(); return factoryDao.update( FactoryImpl.builder() .from(new FactoryImpl(update)) .setCreator(new AuthorImpl(creator.getUserId(), creator.getCreated())) .build()); } /** * Removes stored {@link Factory} by given id. * * @param id factory identifier * @throws NullPointerException when {@code id} is null * @throws ServerException when any server errors occurs */ public void removeFactory(String id) throws ServerException { requireNonNull(id); factoryDao.remove(id); } /** * Gets factory by given id. * * @param id factory identifier * @return factory instance * @throws NullPointerException when {@code id} is null * @throws NotFoundException when factory with given id not found * @throws ServerException when any server errors occurs */ public Factory getById(String id) throws NotFoundException, ServerException { requireNonNull(id); return factoryDao.getById(id); } /** * Get list of factories which conform specified attributes. * * @param maxItems max number of items in response * @param skipCount skip items. Must be equals or greater then {@code 0} * @param attributes skip items. Must be equals or greater then {@code 0} * @return stored data, if specified attributes is correct * @throws ServerException when any server errors occurs */ public Page getByAttribute( int maxItems, int skipCount, List> attributes) throws ServerException { return factoryDao.getByAttributes(maxItems, skipCount, attributes); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryParameterValidator.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.factory.FactoryParameter; /** * @author Alexander Garagatyi */ public interface FactoryParameterValidator { void validate(T arg, FactoryParameter.Version version) throws ConflictException; } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryParametersResolver.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import jakarta.validation.constraints.NotNull; import java.util.Map; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; /** * Defines a resolver that will produce factories for some parameters * * @author Florent Benoit */ public interface FactoryParametersResolver { /** * Resolver acceptance based on the given parameters. * * @param factoryParameters map of parameters dedicated to factories * @return true if it will be accepted by the resolver implementation or false if it is not * accepted */ boolean accept(@NotNull Map factoryParameters); /** Returns the name of the provider */ String getProviderName(); /** * Create factory object based on provided parameters * * @param factoryParameters map containing factory data parameters provided through URL * @throws BadRequestException when data are invalid */ FactoryMetaDto createFactory(@NotNull Map factoryParameters) throws ApiException; /** * Parses a factory Url String to a {@link RemoteFactoryUrl} object * * @param factoryUrl the factory Url string * @return {@link RemoteFactoryUrl} representation of the factory URL * @throws ApiException when authentication required operations fail */ RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException; /** * Returns priority of the resolver. Resolvers with higher priority will be used among matched * resolvers. */ FactoryResolverPriority priority(); } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryResolverPriority.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; public enum FactoryResolverPriority { DEFAULT(1), HIGHEST(2), LOWEST(0); private final int value; FactoryResolverPriority(int value) { this.value = value; } public int getValue() { return value; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static java.util.Collections.singletonMap; import static java.util.Comparator.comparingInt; import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; import static org.eclipse.che.api.factory.server.FactoryLinksHelper.createLinks; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.inject.Inject; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; /** * Defines Factory REST API. * * @author Anton Korneta * @author Florent Benoit */ @Tag(name = "factory", description = "Factory manager") @Path("/factory") public class FactoryService extends Service { /** Error message if there is no plugged resolver. */ public static final String FACTORY_NOT_RESOLVABLE = "Cannot build factory with any of the provided parameters. Please check parameters correctness, and resend query."; /** Validate query parameter. If true, factory will be validated */ public static final String VALIDATE_QUERY_PARAMETER = "validate"; private final FactoryAcceptValidator acceptValidator; private final FactoryParametersResolverHolder factoryParametersResolverHolder; private final AdditionalFilenamesProvider additionalFilenamesProvider; private final PersonalAccessTokenManager personalAccessTokenManager; private final AuthorisationRequestManager authorisationRequestManager; @Inject public FactoryService( FactoryAcceptValidator acceptValidator, FactoryParametersResolverHolder factoryParametersResolverHolder, AdditionalFilenamesProvider additionalFilenamesProvider, PersonalAccessTokenManager personalAccessTokenManager, AuthorisationRequestManager authorisationRequestManager) { this.acceptValidator = acceptValidator; this.factoryParametersResolverHolder = factoryParametersResolverHolder; this.additionalFilenamesProvider = additionalFilenamesProvider; this.personalAccessTokenManager = personalAccessTokenManager; this.authorisationRequestManager = authorisationRequestManager; } @POST @Path("/resolver") @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) @Operation( summary = "Create factory by providing map of parameters. Get JSON with factory information", responses = { @ApiResponse( responseCode = "200", description = "Factory successfully built from parameters"), @ApiResponse( responseCode = "400", description = "Missed required parameters, failed to validate factory"), @ApiResponse(responseCode = "500", description = "Internal server error") }) public FactoryMetaDto resolveFactory( @Parameter(description = "Parameters provided to create factories") Map parameters, @Parameter( description = "Whether or not to validate values like it is done when accepting a Factory") @DefaultValue("false") @QueryParam(VALIDATE_QUERY_PARAMETER) Boolean validate) throws ApiException { // check parameter requiredNotNull(parameters, "Factory build parameters"); // search matching resolver and create factory from matching resolver FactoryMetaDto resolvedFactory = factoryParametersResolverHolder .getFactoryParametersResolver(parameters) .createFactory(parameters); if (resolvedFactory == null) { throw new BadRequestException(FACTORY_NOT_RESOLVABLE); } if (validate) { acceptValidator.validateOnAccept(resolvedFactory); } resolvedFactory = injectLinks(resolvedFactory, parameters); return resolvedFactory; } @POST @Path("/token/refresh") @Operation( summary = "Validate the the factory related OAuth token and update/create it if needed", responses = { @ApiResponse( responseCode = "200", description = "The factory related OAuth token is valid or has been updated successfully"), @ApiResponse( responseCode = "401", description = "Failed to update the factory related OAuth token"), @ApiResponse(responseCode = "500", description = "Internal server error") }) public void refreshToken(@Parameter(description = "Factory url") @QueryParam("url") String url) throws ApiException { // check parameter requiredNotNull(url, "Factory url"); try { FactoryParametersResolver factoryParametersResolver = factoryParametersResolverHolder.getFactoryParametersResolver( singletonMap(URL_PARAMETER_NAME, url)); if (!authorisationRequestManager.isStored(factoryParametersResolver.getProviderName())) { String scmServerUrl = factoryParametersResolver.parseFactoryUrl(url).getProviderUrl(); if (Boolean.parseBoolean(System.getenv("CHE_FORCE_REFRESH_PERSONAL_ACCESS_TOKEN"))) { personalAccessTokenManager.forceRefreshPersonalAccessToken(scmServerUrl); } else { personalAccessTokenManager.getAndStore(scmServerUrl); } } } catch (ScmConfigurationPersistenceException | UnsatisfiedScmPreconditionException e) { throw new ApiException(e); } catch (ScmUnauthorizedException e) { throw toApiException(e); } catch (ScmCommunicationException e) { throw toApiException(e); } catch (UnknownScmProviderException e) { // ignore the exception as it is not a problem if the provider from the given URL is unknown } } /** Injects factory links */ private FactoryMetaDto injectLinks(FactoryMetaDto factory, Map parameters) { return factory.withLinks( createLinks( factory, getServiceContext(), additionalFilenamesProvider, null, parameters.get(URL_PARAMETER_NAME))); } /** Usage of a dedicated class to manage the optional service-specific resolvers */ protected static class FactoryParametersResolverHolder { @Inject @SuppressWarnings("unused") private Set specificFactoryParametersResolvers; /** * Provides a suitable resolver for the given parameters. If there is no at least one resolver * able to process parameters,then {@link BadRequestException} will be thrown * * @return suitable service-specific resolver or default one */ public FactoryParametersResolver getFactoryParametersResolver(Map parameters) throws BadRequestException { Optional resolverOptional = specificFactoryParametersResolvers.stream() .filter( r -> { try { return r.accept(parameters); } catch (IllegalArgumentException e) { return false; } }) .max(comparingInt(r -> r.priority().getValue())); if (resolverOptional.isPresent()) { return resolverOptional.get(); } throw new BadRequestException(FACTORY_NOT_RESOLVABLE); } } /** * Checks object reference is not {@code null} * * @param object object reference to check * @param subject used as subject of exception message "{subject} required" * @throws BadRequestException when object reference is {@code null} */ private static void requiredNotNull(Object object, String subject) throws BadRequestException { if (object == null) { throw new BadRequestException(subject + " required"); } } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/LegacyConverter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.model.factory.Factory; /** * Convert legacy factory parameter to new the latest format * * @author Alexander Garagatyi */ public interface LegacyConverter { void convert(Factory factory) throws ApiException; } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static org.eclipse.che.api.factory.server.FactoryResolverPriority.HIGHEST; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import com.fasterxml.jackson.databind.JsonNode; import jakarta.validation.constraints.NotNull; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Map; import java.util.Optional; import javax.inject.Inject; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.URLFileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** * {@link FactoryParametersResolver} implementation to resolve factory based on url parameter as a * direct URL to a devfile content. Extracts and applies devfile values override parameters. */ public class RawDevfileUrlFactoryParameterResolver extends BaseFactoryParameterResolver implements FactoryParametersResolver { private static final String PROVIDER_NAME = "raw-devfile-url"; protected final URLFactoryBuilder urlFactoryBuilder; protected final URLFetcher urlFetcher; private final DevfileParser devfileParser; @Inject public RawDevfileUrlFactoryParameterResolver( URLFactoryBuilder urlFactoryBuilder, URLFetcher urlFetcher, DevfileParser devfileParser) { super(null, urlFactoryBuilder, PROVIDER_NAME); this.urlFactoryBuilder = urlFactoryBuilder; this.urlFetcher = urlFetcher; this.devfileParser = devfileParser; } /** * Check if this resolver can be used with the given parameters. * * @param factoryParameters map of parameters dedicated to factories * @return true if it will be accepted by the resolver implementation or false if it is not * accepted */ @Override public boolean accept(Map factoryParameters) { String url = factoryParameters.get(URL_PARAMETER_NAME); return !isNullOrEmpty(url) && containsYaml(url); } private boolean containsYaml(String requestURL) { try { Optional credentials = new DefaultFactoryUrl().withUrl(requestURL).getCredentials(); URLFileContentProvider urlFileContentProvider = new URLFileContentProvider(new URL(requestURL).toURI(), urlFetcher); String fetch = urlFileContentProvider.fetchContent(requestURL, credentials.orElse(null)); JsonNode parsedYaml = devfileParser.parseYamlRaw(fetch); return !parsedYaml.isEmpty(); } catch (IOException | URISyntaxException | DevfileException e) { return false; } } @Override public String getProviderName() { return PROVIDER_NAME; } /** * Creates factory based on provided parameters. Presumes url parameter as direct URL to a devfile * content. * * @param factoryParameters map containing factory data parameters provided through URL */ @Override public FactoryMetaDto createFactory(@NotNull final Map factoryParameters) throws ApiException { // This should never be null, because our contract in #accept prohibits that String devfileLocation = factoryParameters.get(URL_PARAMETER_NAME); URI devfileURI; try { devfileURI = new URL(devfileLocation).toURI(); } catch (MalformedURLException | URISyntaxException e) { throw new BadRequestException( format( "Unable to process provided factory URL. Please check its validity and try again. Parser message: %s", e.getMessage())); } return urlFactoryBuilder .createFactoryFromDevfile( new DefaultFactoryUrl() .withDevfileFileLocation(devfileLocation) .withUrl(devfileLocation), new URLFileContentProvider(devfileURI, urlFetcher), extractOverrideParams(factoryParameters), false) .orElse(null); } @Override public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException { throw new ApiException("Operation is not supported"); } @Override public FactoryResolverPriority priority() { return HIGHEST; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ScmFileResolver.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import org.eclipse.che.api.core.ApiException; /** Defines a resolver that will resolve particular file content in specified SCM repository. */ public interface ScmFileResolver { /** * Resolver acceptance based on the given repository URL. * * @param repository repository URL to resolve file * @return true if it will be accepted by the resolver implementation or false if it is not * accepted */ boolean accept(String repository); /** * Resolves particular file in the given repository. * * @param repository repository URL to resolve file * @param filePath path to the desired file * @return content of the file if it is present in repository * @throws ApiException if the given file is absent or other error occurs */ String fileContent(String repository, String filePath) throws ApiException; } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ScmService.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Response; import java.util.Set; import javax.inject.Inject; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.rest.Service; @Tag(name = "scm") @Path("/scm") public class ScmService extends Service { private final Set specificScmFileResolvers; @Inject public ScmService(Set specificScmFileResolvers) { this.specificScmFileResolvers = specificScmFileResolvers; } @GET @Path("/resolve") @Operation( summary = "Get file content by specific repository and filename.", responses = { @ApiResponse( responseCode = "200", description = "Factory successfully built from parameters"), @ApiResponse(responseCode = "400", description = "Missed required parameters."), @ApiResponse(responseCode = "404", description = "Requested file not found."), @ApiResponse(responseCode = "500", description = "Internal server error") }) public Response resolveFile( @Parameter(description = "Repository URL") @QueryParam("repository") String repository, @Parameter(description = "File name or path") @QueryParam("file") String filePath) throws ApiException { requireNonNull(repository, "Repository"); requireNonNull(repository, "File"); String content = getScmFileResolver(repository).fileContent(repository, filePath); return Response.ok().entity(content).build(); } /** * Checks object reference is not {@code null} * * @param object object reference to check * @param subject used as subject of exception message "{subject} required" * @throws BadRequestException when object reference is {@code null} */ private static void requireNonNull(Object object, String subject) throws BadRequestException { if (object == null) { throw new BadRequestException(subject + " parameter is required"); } } /** * Provides a suitable file resolver for the given parameters. If there is no at least one * resolver able to process parameters,then {@link BadRequestException} will be thrown * * @return suitable service-specific resolver or default one */ public ScmFileResolver getScmFileResolver(String repository) throws BadRequestException { for (ScmFileResolver scmFileResolver : specificScmFileResolvers) { if (scmFileResolver.accept(repository)) { return scmFileResolver; } } throw new BadRequestException("Cannot find suitable file resolver for the provided URL."); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ValueHelper.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static com.google.common.base.Strings.isNullOrEmpty; import java.util.Collection; import java.util.Map; /** * @author Sergii Kabashniuk */ public class ValueHelper { /** * Check that value wasn't set by json parser. * * @param value - value to check * @return - true if value is useless for factory (empty string, collection or map), false * otherwise */ public static boolean isEmpty(Object value) { return (null == value) || ((value.getClass().equals(String.class) && isNullOrEmpty((String) value)) || (Collection.class.isAssignableFrom(value.getClass()) && ((Collection) value).isEmpty()) || (Map.class.isAssignableFrom(value.getClass()) && ((Map) value).isEmpty())); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryAcceptValidatorImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.impl; import javax.inject.Singleton; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.FactoryAcceptValidator; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; /** Factory accept stage validator. */ @Singleton public class FactoryAcceptValidatorImpl extends FactoryBaseValidator implements FactoryAcceptValidator { @Override public void validateOnAccept(FactoryMetaDto factory) throws BadRequestException { validateCurrentTimeBetweenSinceUntil(factory); validateProjectActions(factory); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.impl; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.lang.System.currentTimeMillis; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.FactoryConstants; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.IdeActionDto; import org.eclipse.che.api.factory.shared.dto.IdeDto; import org.eclipse.che.api.factory.shared.dto.OnAppLoadedDto; import org.eclipse.che.api.factory.shared.dto.OnProjectsLoadedDto; import org.eclipse.che.api.factory.shared.dto.PoliciesDto; /** * Validates values of factory parameters. * * @author Alexander Garagatyi * @author Valeriy Svydenko */ public abstract class FactoryBaseValidator { private static final Pattern PROJECT_NAME_VALIDATOR = Pattern.compile("^[\\\\\\w\\\\\\d]+[\\\\\\w\\\\\\d_.-]*$"); /** * Validates that factory can be used at present time (used on accept) * * @param factory factory to validate * @throws BadRequestException if since date greater than current date
    * @throws BadRequestException if until date less than current date
    */ protected void validateCurrentTimeBetweenSinceUntil(FactoryMetaDto factory) throws BadRequestException { final PoliciesDto policies = factory.getPolicies(); if (policies == null) { return; } final Long since = policies.getSince() == null ? 0L : policies.getSince(); final Long until = policies.getUntil() == null ? 0L : policies.getUntil(); if (since != 0 && currentTimeMillis() < since) { throw new BadRequestException(FactoryConstants.ILLEGAL_FACTORY_BY_SINCE_MESSAGE); } if (until != 0 && currentTimeMillis() > until) { throw new BadRequestException(FactoryConstants.ILLEGAL_FACTORY_BY_UNTIL_MESSAGE); } } /** * Validates IDE actions * * @param factory factory to validate * @throws BadRequestException when factory actions is invalid */ protected void validateProjectActions(FactoryMetaDto factory) throws BadRequestException { final IdeDto ide = factory.getIde(); if (ide == null) { return; } final List applicationActions = new ArrayList<>(); if (ide.getOnAppClosed() != null) { applicationActions.addAll(ide.getOnAppClosed().getActions()); } if (ide.getOnAppLoaded() != null) { applicationActions.addAll(ide.getOnAppLoaded().getActions()); } for (IdeActionDto applicationAction : applicationActions) { String id = applicationAction.getId(); if ("openFile".equals(id) || "findReplace".equals(id) || "runCommand".equals(id) || "newTerminal".equals(id)) { throw new BadRequestException(format(FactoryConstants.INVALID_ACTION_SECTION, id)); } } final OnAppLoadedDto onAppLoaded = ide.getOnAppLoaded(); if (onAppLoaded != null) { for (IdeActionDto action : onAppLoaded.getActions()) { final Map properties = action.getProperties(); if ("openWelcomePage".equals(action.getId()) && isNullOrEmpty(properties.get("greetingContentUrl"))) { throw new BadRequestException(FactoryConstants.INVALID_WELCOME_PAGE_ACTION); } } } final OnProjectsLoadedDto onLoaded = ide.getOnProjectsLoaded(); if (onLoaded != null) { final List onProjectOpenedActions = onLoaded.getActions(); for (IdeActionDto applicationAction : onProjectOpenedActions) { final String id = applicationAction.getId(); final Map properties = applicationAction.getProperties(); switch (id) { case "openFile": if (isNullOrEmpty(properties.get("file"))) { throw new BadRequestException(FactoryConstants.INVALID_OPENFILE_ACTION); } break; case "runCommand": if (isNullOrEmpty(properties.get("name"))) { throw new BadRequestException(FactoryConstants.INVALID_RUNCOMMAND_ACTION); } break; case "findReplace": if (isNullOrEmpty(properties.get("in")) || isNullOrEmpty(properties.get("find")) || isNullOrEmpty(properties.get("replace"))) { throw new BadRequestException(FactoryConstants.INVALID_FIND_REPLACE_ACTION); } break; } } } } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/SourceStorageParametersValidator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.impl; import static java.lang.String.format; import static org.eclipse.che.api.factory.server.FactoryConstants.PARAMETRIZED_ILLEGAL_PARAMETER_VALUE_MESSAGE; import static org.eclipse.che.api.factory.server.FactoryConstants.PARAMETRIZED_INVALID_PARAMETER_MESSAGE; import java.util.Map; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.workspace.config.SourceStorage; import org.eclipse.che.api.factory.server.FactoryParameterValidator; /** * @author Alexander Garagatyi * @author Valeriy Svydenko */ public class SourceStorageParametersValidator implements FactoryParameterValidator { @Override public void validate(SourceStorage source, FactoryParameter.Version version) throws ConflictException { for (Map.Entry entry : source.getParameters().entrySet()) { switch (entry.getKey()) { case "keepVcs": final String keepVcs = entry.getValue(); if (!"true".equals(keepVcs) && !"false".equals(keepVcs)) { throw new ConflictException( format( PARAMETRIZED_ILLEGAL_PARAMETER_VALUE_MESSAGE, "source.project.parameters.keepVcs", entry.getValue())); } break; case "skipFirstLevel": final String skipFirstLevel = entry.getValue(); if (!"true".equals(skipFirstLevel) && !"false".equals(skipFirstLevel)) { throw new ConflictException( format( PARAMETRIZED_ILLEGAL_PARAMETER_VALUE_MESSAGE, "project.source.parameters.skipFirstLevel", entry.getValue())); } break; case "branch": case "startPoint": case "commitId": case "keepDir": case "fetch": case "branchMerge": break; default: throw new ConflictException( format( PARAMETRIZED_INVALID_PARAMETER_MESSAGE, "source.project.parameters." + entry.getKey(), version)); } } } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/FactoryJpaModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.jpa; import com.google.inject.AbstractModule; import org.eclipse.che.api.factory.server.spi.FactoryDao; /** * @author Yevhenii Voevodin */ public class FactoryJpaModule extends AbstractModule { @Override protected void configure() { bind(FactoryDao.class).to(JpaFactoryDao.class); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.jpa; import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import com.google.inject.persist.Transactional; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringJoiner; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; import org.eclipse.che.api.factory.server.spi.FactoryDao; import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; import org.eclipse.che.commons.lang.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Anton Korneta */ @Singleton public class JpaFactoryDao implements FactoryDao { private static final Logger LOG = LoggerFactory.getLogger(JpaFactoryDao.class); @Inject private Provider managerProvider; @Override public FactoryImpl create(FactoryImpl factory) throws ConflictException, ServerException { requireNonNull(factory); try { doCreate(factory); } catch (RuntimeException ex) { throw new ServerException(ex.getLocalizedMessage(), ex); } return new FactoryImpl(factory); } @Override public FactoryImpl update(FactoryImpl update) throws NotFoundException, ConflictException, ServerException { requireNonNull(update); try { return new FactoryImpl(doUpdate(update)); } catch (RuntimeException ex) { throw new ServerException(ex.getLocalizedMessage(), ex); } } @Override public void remove(String id) throws ServerException { requireNonNull(id); try { doRemove(id); } catch (RuntimeException ex) { throw new ServerException(ex.getLocalizedMessage(), ex); } } @Override @Transactional(rollbackOn = {ServerException.class}) public FactoryImpl getById(String id) throws NotFoundException, ServerException { requireNonNull(id); try { final FactoryImpl factory = managerProvider.get().find(FactoryImpl.class, id); if (factory == null) { throw new NotFoundException(format("Factory with id '%s' doesn't exist", id)); } return new FactoryImpl(factory); } catch (RuntimeException ex) { throw new ServerException(ex.getLocalizedMessage(), ex); } } @Override @Transactional(rollbackOn = {ServerException.class}) public Page getByAttributes( int maxItems, int skipCount, List> attributes) throws ServerException { checkArgument(maxItems >= 0, "The number of items to return can't be negative."); checkArgument( skipCount >= 0, "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE); try { LOG.debug( "FactoryDao#getByAttributes #maxItems: {} #skipCount: {}, #attributes: {}", maxItems, skipCount, attributes); final long count = countFactoriesByAttributes(attributes); if (count == 0) { return new Page<>(emptyList(), skipCount, maxItems, count); } List result = getFactoriesByAttributes(maxItems, skipCount, attributes); return new Page<>(result, skipCount, maxItems, count); } catch (RuntimeException ex) { throw new ServerException(ex.getLocalizedMessage(), ex); } } @Override @Transactional(rollbackOn = {ServerException.class}) public Page getByUser(String userId, int maxItems, long skipCount) throws ServerException { requireNonNull(userId); final Pair factoryCreator = Pair.of("creator.userId", userId); try { long totalCount = countFactoriesByAttributes(singletonList(factoryCreator)); return new Page<>( getFactoriesByAttributes(maxItems, skipCount, singletonList(factoryCreator)), skipCount, maxItems, totalCount); } catch (RuntimeException ex) { throw new ServerException(ex.getMessage(), ex); } } private List getFactoriesByAttributes( int maxItems, long skipCount, List> attributes) { final Map params = new HashMap<>(); StringBuilder query = new StringBuilder("SELECT factory FROM Factory factory"); if (!attributes.isEmpty()) { final StringJoiner matcher = new StringJoiner(" AND ", " WHERE ", " "); int i = 0; for (Pair attribute : attributes) { final String parameterName = "parameterName" + i++; params.put(parameterName, attribute.second); matcher.add("factory." + attribute.first + " = :" + parameterName); } query.append(matcher); } TypedQuery typedQuery = managerProvider .get() .createQuery(query.toString(), FactoryImpl.class) .setFirstResult((int) skipCount) .setMaxResults(maxItems); for (Map.Entry entry : params.entrySet()) { typedQuery.setParameter(entry.getKey(), entry.getValue()); } return typedQuery.getResultList().stream().map(FactoryImpl::new).collect(toList()); } private Long countFactoriesByAttributes(List> attributes) { final Map params = new HashMap<>(); StringBuilder query = new StringBuilder("SELECT COUNT(factory) FROM Factory factory"); if (!attributes.isEmpty()) { final StringJoiner matcher = new StringJoiner(" AND ", " WHERE ", " "); int i = 0; for (Pair attribute : attributes) { final String parameterName = "parameterName" + i++; params.put(parameterName, attribute.second); matcher.add("factory." + attribute.first + " = :" + parameterName); } query.append(matcher); } TypedQuery typedQuery = managerProvider.get().createQuery(query.toString(), Long.class); for (Map.Entry entry : params.entrySet()) { typedQuery.setParameter(entry.getKey(), entry.getValue()); } return typedQuery.getSingleResult(); } @Transactional protected void doCreate(FactoryImpl factory) { final EntityManager manager = managerProvider.get(); if (factory.getWorkspace() != null) { factory.getWorkspace().getProjects().forEach(ProjectConfigImpl::prePersistAttributes); } manager.persist(factory); manager.flush(); } @Transactional protected FactoryImpl doUpdate(FactoryImpl update) throws NotFoundException { final EntityManager manager = managerProvider.get(); if (manager.find(FactoryImpl.class, update.getId()) == null) { throw new NotFoundException( format("Could not update factory with id %s because it doesn't exist", update.getId())); } if (update.getWorkspace() != null) { update.getWorkspace().getProjects().forEach(ProjectConfigImpl::prePersistAttributes); } FactoryImpl merged = manager.merge(update); manager.flush(); return merged; } @Transactional protected void doRemove(String id) { final EntityManager manager = managerProvider.get(); final FactoryImpl factory = manager.find(FactoryImpl.class, id); if (factory != null) { manager.remove(factory); manager.flush(); } } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/ActionImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.model.impl; import java.util.HashMap; import java.util.Map; import java.util.Objects; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.Table; import org.eclipse.che.api.core.model.factory.Action; /** * Data object for {@link Action}. * * @author Anton Korneta */ @Entity(name = "Action") @Table(name = "che_factory_action") public class ActionImpl implements Action { @Id @GeneratedValue @Column(name = "entity_id") private Long entityId; @Column(name = "id") private String id; @ElementCollection @CollectionTable( name = "che_factory_action_properties", joinColumns = @JoinColumn(name = "action_entity_id")) @MapKeyColumn(name = "property_key") @Column(name = "property_value") private Map properties; public ActionImpl() {} public ActionImpl(String id, Map properties) { this.id = id; if (properties != null) { this.properties = new HashMap<>(properties); } } public ActionImpl(Action action) { this(action.getId(), action.getProperties()); } @Override public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public Map getProperties() { if (properties == null) { properties = new HashMap<>(); } return properties; } public void setProperties(Map properties) { this.properties = properties; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof ActionImpl)) { return false; } final ActionImpl that = (ActionImpl) obj; return Objects.equals(entityId, that.entityId) && Objects.equals(id, that.id) && getProperties().equals(that.getProperties()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(entityId); hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + getProperties().hashCode(); return hash; } @Override public String toString() { return "ActionImpl{" + "entityId=" + entityId + ", id='" + id + '\'' + ", properties=" + properties + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/AuthorImpl.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.model.impl; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Embeddable; import org.eclipse.che.api.core.model.factory.Author; /** * Data object for {@link Author}. * * @author Anton Korneta */ @Embeddable public class AuthorImpl implements Author { @Column(name = "created") private Long created; @Column(name = "user_id") private String userId; public AuthorImpl() {} public AuthorImpl(String userId, Long created) { this.created = created; this.userId = userId; } public AuthorImpl(Author creator) { this(creator.getUserId(), creator.getCreated()); } @Override public Long getCreated() { return created; } public void setCreated(Long created) { this.created = created; } @Override public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof AuthorImpl)) return false; final AuthorImpl other = (AuthorImpl) obj; return Objects.equals(userId, other.userId) && Objects.equals(created, other.created); } @Override public int hashCode() { int result = 7; result = 31 * result + Objects.hashCode(userId); result = 31 * result + Objects.hashCode(created); return result; } @Override public String toString() { return "AuthorImpl{" + "created=" + created + ", userId='" + userId + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/FactoryImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.model.impl; import java.util.Objects; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; import org.eclipse.che.api.core.model.factory.Author; import org.eclipse.che.api.core.model.factory.Factory; import org.eclipse.che.api.core.model.factory.Ide; import org.eclipse.che.api.core.model.factory.Policies; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.commons.lang.NameGenerator; /** * Data object for {@link Factory}. * * @author Anton Korneta */ @Entity(name = "Factory") @Table(name = "che_factory") // TODO fix after issue: https://github.com/eclipse/che/issues/2110 // (uniqueConstraints = {@UniqueConstraint(columnNames = {"name", "userId"})}) public class FactoryImpl implements Factory { public static FactoryImplBuilder builder() { return new FactoryImplBuilder(); } @Id @Column(name = "id") private String id; @Column(name = "name") private String name; @Column(name = "version", nullable = false) private String version; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, optional = false) @JoinColumn(name = "workspace_id") private WorkspaceConfigImpl workspace; @Embedded private AuthorImpl creator; // Mapping exists for explicit constraints which allows // jpa backend to perform operations in correct order @OneToOne(fetch = FetchType.LAZY) @JoinColumn(insertable = false, updatable = false, name = "user_id") private UserImpl userEntity; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "ide_id") private IdeImpl ide; @Embedded private PoliciesImpl policies; public FactoryImpl() {} public FactoryImpl( String id, String name, String version, WorkspaceConfig workspace, Author creator, Policies policies, Ide ide) { this.id = id; this.name = name; this.version = version; if (workspace != null) { this.workspace = new WorkspaceConfigImpl(workspace); } if (creator != null) { this.creator = new AuthorImpl(creator); } if (policies != null) { this.policies = new PoliciesImpl(policies); } if (ide != null) { this.ide = new IdeImpl(ide); } } public FactoryImpl(Factory factory) { this( factory.getId(), factory.getName(), factory.getV(), factory.getWorkspace(), factory.getCreator(), factory.getPolicies(), factory.getIde()); } @Override public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String getV() { return version; } public void setV(String version) { this.version = version; } @Override public WorkspaceConfigImpl getWorkspace() { return workspace; } public void setWorkspace(WorkspaceConfigImpl workspace) { this.workspace = workspace; } @Override public AuthorImpl getCreator() { return creator; } public void setCreator(AuthorImpl creator) { this.creator = creator; } @Override public PoliciesImpl getPolicies() { return policies; } public void setPolicies(PoliciesImpl policies) { this.policies = policies; } @Override public IdeImpl getIde() { return ide; } public void setIde(IdeImpl ide) { this.ide = ide; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof FactoryImpl)) return false; final FactoryImpl other = (FactoryImpl) obj; return Objects.equals(id, other.id) && Objects.equals(name, other.name) && Objects.equals(version, other.version) && Objects.equals(workspace, other.workspace) && Objects.equals(creator, other.creator) && Objects.equals(policies, other.policies) && Objects.equals(ide, other.ide); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(name); hash = 31 * hash + Objects.hashCode(version); hash = 31 * hash + Objects.hashCode(workspace); hash = 31 * hash + Objects.hashCode(creator); hash = 31 * hash + Objects.hashCode(policies); hash = 31 * hash + Objects.hashCode(ide); return hash; } @Override public String toString() { return "FactoryImpl{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", version='" + version + '\'' + ", workspace=" + workspace + ", creator=" + creator + ", policies=" + policies + ", ide=" + ide + '}'; } /** Helps to create the instance of {@link FactoryImpl}. */ public static class FactoryImplBuilder { private String id; private String name; private String version; private WorkspaceConfig workspace; private Author creator; private Policies policies; private Ide ide; private FactoryImplBuilder() {} public FactoryImpl build() { return new FactoryImpl(id, name, version, workspace, creator, policies, ide); } public FactoryImplBuilder from(FactoryImpl factory) { this.id = factory.getId(); this.name = factory.getName(); this.version = factory.getV(); this.workspace = factory.getWorkspace(); this.creator = factory.getCreator(); this.policies = factory.getPolicies(); this.ide = factory.getIde(); return this; } public FactoryImplBuilder generateId() { id = NameGenerator.generate("", 16); return this; } public FactoryImplBuilder setId(String id) { this.id = id; return this; } public FactoryImplBuilder setName(String name) { this.name = name; return this; } public FactoryImplBuilder setVersion(String version) { this.version = version; return this; } public FactoryImplBuilder setWorkspace(WorkspaceConfig workspace) { this.workspace = workspace; return this; } public FactoryImplBuilder setCreator(Author creator) { this.creator = creator; return this; } public FactoryImplBuilder setPolicies(Policies policies) { this.policies = policies; return this; } public FactoryImplBuilder setIde(Ide ide) { this.ide = ide; return this; } } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/IdeImpl.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.model.impl; import java.util.Objects; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; import org.eclipse.che.api.core.model.factory.Ide; import org.eclipse.che.api.core.model.factory.OnAppClosed; import org.eclipse.che.api.core.model.factory.OnAppLoaded; import org.eclipse.che.api.core.model.factory.OnProjectsLoaded; /** * Data object for {@link Ide}. * * @author Anton Korneta */ @Entity(name = "Ide") @Table(name = "che_factory_ide") public class IdeImpl implements Ide { @Id @GeneratedValue @Column(name = "id") private Long id; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "on_app_loaded_id") private OnAppLoadedImpl onAppLoaded; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "on_projects_loaded_id") private OnProjectsLoadedImpl onProjectsLoaded; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "on_app_closed_id") private OnAppClosedImpl onAppClosed; public IdeImpl() {} public IdeImpl( OnAppLoaded onAppLoaded, OnProjectsLoaded onProjectsLoaded, OnAppClosed onAppClosed) { if (onAppLoaded != null) { this.onAppLoaded = new OnAppLoadedImpl(onAppLoaded); } if (onProjectsLoaded != null) { this.onProjectsLoaded = new OnProjectsLoadedImpl(onProjectsLoaded); } if (onAppClosed != null) { this.onAppClosed = new OnAppClosedImpl(onAppClosed); } } public IdeImpl(Ide ide) { this(ide.getOnAppLoaded(), ide.getOnProjectsLoaded(), ide.getOnAppClosed()); } @Override public OnAppLoadedImpl getOnAppLoaded() { return onAppLoaded; } public void setOnAppLoaded(OnAppLoadedImpl onAppLoaded) { this.onAppLoaded = onAppLoaded; } @Override public OnProjectsLoadedImpl getOnProjectsLoaded() { return onProjectsLoaded; } public void setOnProjectsLoaded(OnProjectsLoadedImpl onProjectsLoaded) { this.onProjectsLoaded = onProjectsLoaded; } @Override public OnAppClosedImpl getOnAppClosed() { return onAppClosed; } public void setOnAppClosed(OnAppClosedImpl onAppClosed) { this.onAppClosed = onAppClosed; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof IdeImpl)) { return false; } final IdeImpl that = (IdeImpl) obj; return Objects.equals(id, that.id) && Objects.equals(onAppLoaded, that.onAppLoaded) && Objects.equals(onProjectsLoaded, that.onProjectsLoaded) && Objects.equals(onAppClosed, that.onAppClosed); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(onAppLoaded); hash = 31 * hash + Objects.hashCode(onProjectsLoaded); hash = 31 * hash + Objects.hashCode(onAppClosed); return hash; } @Override public String toString() { return "IdeImpl{" + "id=" + id + ", onAppLoaded=" + onAppLoaded + ", onProjectsLoaded=" + onProjectsLoaded + ", onAppClosed=" + onAppClosed + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnAppClosedImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.model.impl; import static java.util.stream.Collectors.toList; import static javax.persistence.CascadeType.ALL; import java.util.ArrayList; import java.util.List; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.OneToMany; import javax.persistence.Table; import org.eclipse.che.api.core.model.factory.Action; import org.eclipse.che.api.core.model.factory.OnAppClosed; /** * Data object for {@link OnAppClosed}. * * @author Anton Korneta */ @Entity(name = "OnAppClosed") @Table(name = "che_factory_on_app_closed_action") public class OnAppClosedImpl implements OnAppClosed { @Id @GeneratedValue @Column(name = "id") private Long id; @OneToMany(cascade = ALL, orphanRemoval = true) @JoinTable( name = "che_factory_on_app_closed_action_value", joinColumns = @JoinColumn(name = "on_app_closed_id"), inverseJoinColumns = @JoinColumn(name = "action_entity_id")) private List actions; public OnAppClosedImpl() {} public OnAppClosedImpl(List actions) { if (actions != null) { this.actions = actions.stream().map(ActionImpl::new).collect(toList()); } } public OnAppClosedImpl(OnAppClosed onAppClosed) { this(onAppClosed.getActions()); } @Override public List getActions() { if (actions == null) { actions = new ArrayList<>(); } return actions; } public void setActions(List actions) { this.actions = actions; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof OnAppClosedImpl)) { return false; } final OnAppClosedImpl that = (OnAppClosedImpl) obj; return Objects.equals(id, that.id) && getActions().equals(that.getActions()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + getActions().hashCode(); return hash; } @Override public String toString() { return "OnAppClosedImpl{" + "id=" + id + ", actions=" + actions + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnAppLoadedImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.model.impl; import static java.util.stream.Collectors.toList; import static javax.persistence.CascadeType.ALL; import java.util.ArrayList; import java.util.List; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.OneToMany; import javax.persistence.Table; import org.eclipse.che.api.core.model.factory.Action; import org.eclipse.che.api.core.model.factory.OnAppLoaded; /** * Data object for {@link OnAppLoaded}. * * @author Anton Korneta */ @Entity(name = "OnAppLoaded") @Table(name = "che_factory_on_app_loaded_action") public class OnAppLoadedImpl implements OnAppLoaded { @Id @GeneratedValue @Column(name = "id") private Long id; @OneToMany(cascade = ALL, orphanRemoval = true) @JoinTable( name = "che_factory_on_app_loaded_action_value", joinColumns = @JoinColumn(name = "on_app_loaded_id"), inverseJoinColumns = @JoinColumn(name = "action_entity_id")) private List actions; public OnAppLoadedImpl() {} public OnAppLoadedImpl(List actions) { if (actions != null) { this.actions = actions.stream().map(ActionImpl::new).collect(toList()); } } public OnAppLoadedImpl(OnAppLoaded onAppLoaded) { this(onAppLoaded.getActions()); } @Override public List getActions() { if (actions == null) { actions = new ArrayList<>(); } return actions; } public void setActions(List actions) { this.actions = actions; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof OnAppLoadedImpl)) { return false; } final OnAppLoadedImpl that = (OnAppLoadedImpl) obj; return Objects.equals(id, that.id) && getActions().equals(that.getActions()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + getActions().hashCode(); return hash; } @Override public String toString() { return "OnAppLoadedImpl{" + "id=" + id + ", actions=" + actions + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnProjectsLoadedImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.model.impl; import static java.util.stream.Collectors.toList; import java.util.ArrayList; import java.util.List; import java.util.Objects; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.OneToMany; import javax.persistence.Table; import org.eclipse.che.api.core.model.factory.Action; import org.eclipse.che.api.core.model.factory.OnProjectsLoaded; /** * Data object for {@link OnProjectsLoaded}. * * @author Anton Korneta */ @Entity(name = "OnProjectsLoaded") @Table(name = "che_factory_on_projects_loaded_action") public class OnProjectsLoadedImpl implements OnProjectsLoaded { @Id @GeneratedValue @Column(name = "id") private Long id; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinTable( name = "che_factory_on_projects_loaded_action_value", joinColumns = @JoinColumn(name = "on_projects_loaded_id"), inverseJoinColumns = @JoinColumn(name = "action_entity_id")) private List actions; public OnProjectsLoadedImpl() {} public OnProjectsLoadedImpl(List actions) { if (actions != null) { this.actions = actions.stream().map(ActionImpl::new).collect(toList()); } } public OnProjectsLoadedImpl(OnProjectsLoaded onProjectsLoaded) { this(onProjectsLoaded.getActions()); } @Override public List getActions() { if (actions == null) { actions = new ArrayList<>(); } return actions; } public void setActions(List actions) { this.actions = actions; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof OnProjectsLoadedImpl)) { return false; } final OnProjectsLoadedImpl that = (OnProjectsLoadedImpl) obj; return Objects.equals(id, that.id) && getActions().equals(that.getActions()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + getActions().hashCode(); return hash; } @Override public String toString() { return "OnProjectsLoadedImpl{" + "id=" + id + ", actions=" + actions + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/PoliciesImpl.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.model.impl; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Embeddable; import org.eclipse.che.api.core.model.factory.Policies; /** * Data object for {@link Policies}. * * @author Anton Korneta */ @Embeddable public class PoliciesImpl implements Policies { @Column(name = "referrer") private String referer; @Column(name = "creation_strategy") private String create; @Column(name = "until") private Long until; @Column(name = "since") private Long since; public PoliciesImpl() {} public PoliciesImpl(String referer, String create, Long until, Long since) { this.referer = referer; this.create = create; this.until = until; this.since = since; } public PoliciesImpl(Policies policies) { this(policies.getReferer(), policies.getCreate(), policies.getUntil(), policies.getSince()); } @Override public String getReferer() { return referer; } public void setReferer(String referer) { this.referer = referer; } @Override public String getCreate() { return create; } public void setCreate(String create) { this.create = create; } @Override public Long getUntil() { return until; } public void setUntil(Long until) { this.until = until; } @Override public Long getSince() { return since; } public void setSince(Long since) { this.since = since; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof PoliciesImpl)) return false; final PoliciesImpl other = (PoliciesImpl) obj; return Objects.equals(referer, other.referer) && Objects.equals(create, other.create) && Objects.equals(until, other.until) && Objects.equals(since, other.since); } @Override public int hashCode() { int result = 7; result = 31 * result + Objects.hashCode(referer); result = 31 * result + Objects.hashCode(create); result = 31 * result + Objects.hashCode(until); result = 31 * result + Objects.hashCode(since); return result; } @Override public String toString() { return "PoliciesImpl{" + "referer='" + referer + '\'' + ", create='" + create + '\'' + ", until=" + until + ", since=" + since + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AbstractGitUserDataFetcher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import java.util.Optional; import org.eclipse.che.api.factory.server.scm.exception.*; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; /** * Abstraction to fetch git user data from the specific git provider using OAuth 2.0 or personal * access. * * @author Anatolii Bazko */ public abstract class AbstractGitUserDataFetcher implements GitUserDataFetcher { protected final String oAuthProviderName; protected final String oAuthProviderUrl; protected final PersonalAccessTokenManager personalAccessTokenManager; public AbstractGitUserDataFetcher( String oAuthProviderName, String oAuthProviderUrl, PersonalAccessTokenManager personalAccessTokenManager) { this.oAuthProviderName = oAuthProviderName; this.oAuthProviderUrl = oAuthProviderUrl; this.personalAccessTokenManager = personalAccessTokenManager; } public GitUserData fetchGitUserData(String namespaceName) throws ScmUnauthorizedException, ScmCommunicationException, ScmConfigurationPersistenceException, ScmItemNotFoundException, ScmBadRequestException { Subject cheSubject = EnvironmentContext.getCurrent().getSubject(); Optional tokenOptional = personalAccessTokenManager.get(cheSubject, oAuthProviderName, null, namespaceName); if (tokenOptional.isPresent()) { return fetchGitUserDataWithPersonalAccessToken(tokenOptional.get()); } else { Optional oAuthTokenOptional = personalAccessTokenManager.get(cheSubject, null, oAuthProviderUrl, namespaceName); if (oAuthTokenOptional.isPresent()) { return fetchGitUserDataWithOAuthToken(oAuthTokenOptional.get().getToken()); } } throw new ScmCommunicationException( "There are no tokes for the user " + cheSubject.getUserId()); } protected abstract GitUserData fetchGitUserDataWithOAuthToken(String token) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException; protected abstract GitUserData fetchGitUserDataWithPersonalAccessToken( PersonalAccessToken personalAccessToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException; protected abstract String getLocalAuthenticateUrl(); } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorisationRequestManager.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import jakarta.ws.rs.core.UriInfo; import java.util.List; /** * Manager for storing and retrieving rejected authorisation requests. This is used to prevent from * asking the user to grant access to the SCM provider after the user has already rejected the * request. */ public interface AuthorisationRequestManager { /** * Store the reject flag for the given SCM provider name. * * @param scmProviderName the SCM provider name */ void store(String scmProviderName); /** * Remove the reject flag for the given SCM provider name. * * @param scmProviderName the SCM provider name */ void remove(String scmProviderName); /** * Check if the reject flag is stored for the given SCM provider name. * * @param scmProviderName the SCM provider name to check * @return {@code true} if the reject flag is stored, {@code false} otherwise */ boolean isStored(String scmProviderName); /** This method must be called on the Oauth callback from the SCM provider. */ void callback(UriInfo uriInfo, List errorValues); } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.api.factory.server.scm.exception.ExceptionMessages.getDevfileConnectionErrorMessage; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Base64; import javax.net.ssl.SSLException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.commons.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Common implementation of file content provider which is able to access content of private * repositories using personal access tokens from specially formatted secret in user's namespace. */ public class AuthorizingFileContentProvider implements FileContentProvider { private static final Logger LOG = LoggerFactory.getLogger(AuthorizingFileContentProvider.class); protected final T remoteFactoryUrl; protected final PersonalAccessTokenManager personalAccessTokenManager; protected final URLFetcher urlFetcher; public AuthorizingFileContentProvider( T remoteFactoryUrl, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { this.remoteFactoryUrl = remoteFactoryUrl; this.urlFetcher = urlFetcher; this.personalAccessTokenManager = personalAccessTokenManager; } @Override public String fetchContent(String fileURL) throws IOException, DevfileException { return fetchContent(fileURL, false, null); } @Override public String fetchContent(String fileURL, String credentials) throws IOException, DevfileException { return fetchContent(fileURL, false, credentials); } @Override public String fetchContentWithoutAuthentication(String fileURL) throws IOException, DevfileException { return fetchContent(fileURL, true, null); } private String fetchContent( String fileURL, boolean skipAuthentication, @Nullable String credentials) throws IOException, DevfileException { final String requestURL = formatUrl(fileURL); try { if (skipAuthentication) { return urlFetcher.fetch(requestURL); } else { // try to authenticate for the given URL String authorization; if (isNullOrEmpty(credentials)) { PersonalAccessToken token = personalAccessTokenManager.getAndStore(remoteFactoryUrl.getProviderUrl()); authorization = formatAuthorization( token.getToken(), token.getScmTokenName() == null || !token.getScmTokenName().startsWith(OAUTH_2_PREFIX)); } else { authorization = getCredentialsAuthorization(credentials); } return urlFetcher.fetch(requestURL, authorization); } } catch (UnknownScmProviderException | ScmConfigurationPersistenceException | UnsatisfiedScmPreconditionException e) { // No matching SCM provider or Kubernetes credentials unavailable (e.g. standalone mode). // Fall back to an unauthenticated fetch so public repositories still work. if (e instanceof ScmConfigurationPersistenceException || e instanceof UnsatisfiedScmPreconditionException) { LOG.error( "Kubernetes credentials unavailable (e.g. standalone mode): {}. " + "Falling back to unauthenticated fetch for {}", e.getMessage(), requestURL); } return fetchContentWithoutToken(requestURL); } catch (ScmCommunicationException e) { return toIOException(fileURL, e); } catch (ScmUnauthorizedException e) { throw new DevfileException(e.getMessage(), e); } } protected String fetchContentWithoutToken(String requestURL) throws DevfileException, IOException { // we don't have any provider matching this SCM provider // so try without secrets being configured try { return urlFetcher.fetch(requestURL); } catch (IOException exception) { if (exception instanceof SSLException) { ScmCommunicationException cause = new ScmCommunicationException( String.format( "Failed to fetch a content from URL %s due to TLS key misconfiguration. Please refer to the docs about how to correctly import it. ", requestURL)); throw new DevfileException(exception.getMessage(), cause); } else if (exception instanceof FileNotFoundException) { if (isPublicRepository(remoteFactoryUrl)) { // for public repo-s return 404 as-is throw exception; } } throw new DevfileException( getDevfileConnectionErrorMessage(exception.getMessage()), exception); } } protected String toIOException(String fileURL, ScmCommunicationException e) throws IOException { throw new IOException( String.format( "Failed to fetch a content from URL %s. Make sure the URL" + " is correct. For private repository, make sure authentication is configured." + " Additionally, if you're using " + " relative form, make sure the referenced file are actually stored" + " relative to the devfile on the same host," + " or try to specify URL in absolute form. The current attempt to authenticate" + " request, failed with the following error message: %s", fileURL, e.getMessage()), e); } protected boolean isPublicRepository(T remoteFactoryUrl) { return false; } protected String formatUrl(String fileURL) throws DevfileException { String requestURL; try { if (new URI(fileURL).isAbsolute()) { requestURL = fileURL; } else { // since files retrieved via REST, we cannot use path like '.' or one that starts with './' // so cut them off requestURL = remoteFactoryUrl.rawFileLocation(fileURL.replaceAll("^(?:\\.?\\/)|(?:\\.$)", "")); } } catch (URISyntaxException e) { throw new DevfileException(e.getMessage(), e); } return requestURL; } /** * Formats authorization header value. * * @param token personal access token * @param isPAT true if token is personal access token, false if it is OAuth token * @return formatted authorization header value */ protected String formatAuthorization(String token, boolean isPAT) { return "Bearer " + token; } private String getCredentialsAuthorization(String credentials) { return "Basic " + new String(Base64.getEncoder().encode(credentials.getBytes())); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/GitCredentialManager.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; public interface GitCredentialManager { /** * Propagates git credentials in format: "username:" if the token is Personal Access * Token or "oauth2: if oAuth token. * * @param personalAccessToken * @throws UnsatisfiedScmPreconditionException - some storage preconditions aren't met. * @throws ScmConfigurationPersistenceException */ void createOrReplace(PersonalAccessToken personalAccessToken) throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException; } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/GitUserData.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import java.util.Objects; /** Personal SCM user data such as `username` and `email`. Is used to sign git commits. */ public class GitUserData { private final String scmUsername; private final String scmUserEmail; public GitUserData(String scmUsername, String scmUserEmail) { this.scmUsername = scmUsername; this.scmUserEmail = scmUserEmail; } public String getScmUsername() { return scmUsername; } public String getScmUserEmail() { return scmUserEmail; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GitUserData that = (GitUserData) o; return Objects.equals(scmUsername, that.scmUsername) && Objects.equals(scmUserEmail, that.scmUserEmail); } @Override public int hashCode() { return Objects.hash(scmUsername, scmUserEmail); } @Override public String toString() { return "GitUserData{" + ", scmUsername='" + scmUsername + '\'' + ", scmUserEmail='" + scmUserEmail + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/GitUserDataFetcher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.annotation.Nullable; public interface GitUserDataFetcher { /** * Retrieve a {@link GitUserData} object from concrete scm provider. If OAuthProvider is not * configured, then personal access token should be taken into account. * * @param namespaceName - the user's namespace name. * @return - {@link GitUserData} object. * @throws ScmUnauthorizedException - in case if user is not authorized che server to create a new * token. Further user interaction is needed before calling this method next time. * @throws ScmCommunicationException - Some unexpected problem occurred during communication with * scm provider. * @throws ScmConfigurationPersistenceException - problem occurred during communication with * permanent storage. */ GitUserData fetchGitUserData(@Nullable String namespaceName) throws ScmUnauthorizedException, ScmCommunicationException, ScmConfigurationPersistenceException, ScmItemNotFoundException, ScmBadRequestException; } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessToken.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import com.google.common.base.Objects; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; /** * Personal access token that can be used to authorise scm operations like api calls, git clone or * git push. */ public class PersonalAccessToken { private final String scmProviderUrl; private final String scmProviderName; private final String scmUserName; /** Organization that user belongs to. Can be null if user is not a member of any organization. */ @Nullable private final String scmOrganization; private final String scmTokenName; private final String scmTokenId; private final String token; private final String cheUserId; public PersonalAccessToken( String scmProviderUrl, String scmProviderName, String cheUserId, String scmOrganization, String scmUserName, String scmTokenName, String scmTokenId, String token) { this.scmProviderUrl = scmProviderUrl; this.scmOrganization = scmOrganization; this.scmProviderName = scmProviderName; this.scmUserName = scmUserName; this.scmTokenName = scmTokenName; this.scmTokenId = scmTokenId; this.token = token; this.cheUserId = cheUserId; } public PersonalAccessToken( String scmProviderUrl, String scmProviderName, String cheUserId, String scmUserName, String scmTokenName, String scmTokenId, String token) { this( scmProviderUrl, scmProviderName, cheUserId, null, scmUserName, scmTokenName, scmTokenId, token); } public PersonalAccessToken( String scmProviderUrl, String scmProviderName, String scmUserName, String token) { this( scmProviderUrl, scmProviderName, EnvironmentContext.getCurrent().getSubject().getUserId(), null, scmUserName, null, null, token); } public String getScmProviderUrl() { return scmProviderUrl; } public String getScmTokenName() { return scmTokenName; } public String getScmTokenId() { return scmTokenId; } public String getScmUserName() { return scmUserName; } public String getToken() { return token; } public String getCheUserId() { return cheUserId; } @Nullable public String getScmOrganization() { return scmOrganization; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PersonalAccessToken that = (PersonalAccessToken) o; return Objects.equal(scmProviderUrl, that.scmProviderUrl) && Objects.equal(scmProviderName, that.scmProviderName) && Objects.equal(scmUserName, that.scmUserName) && Objects.equal(scmOrganization, that.scmOrganization) && Objects.equal(scmTokenName, that.scmTokenName) && Objects.equal(scmTokenId, that.scmTokenId) && Objects.equal(token, that.token) && Objects.equal(cheUserId, that.cheUserId); } @Override public int hashCode() { return Objects.hashCode( scmProviderUrl, scmUserName, scmOrganization, scmTokenName, scmTokenId, token, cheUserId); } @Override public String toString() { return "PersonalAccessToken{" + "scmProviderUrl='" + scmProviderUrl + '\'' + "scmProviderName='" + scmProviderName + '\'' + ", scmUserName='" + scmUserName + '\'' + ", scmOrganization='" + scmOrganization + '\'' + ", scmTokenName='" + scmTokenName + '\'' + ", scmTokenId='" + scmTokenId + '\'' + ", token='" + token + '\'' + ", cheUserId='" + cheUserId + '}'; } public String getScmProviderName() { return scmProviderName; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import java.util.Optional; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; public interface PersonalAccessTokenFetcher { /** Prefix for token names indication it is OAuth token (to differentiate from PAT-s) */ String OAUTH_2_PREFIX = "oauth2-"; /** * Retrieve new PersonalAccessToken from concrete scm provider * * @param cheUser * @param scmServerUrl * @return - personal access token. Must return {@code null} if scmServerUrl is not applicable for * the current fetcher. * @throws ScmUnauthorizedException - in case if user are not authorized che server to create new * token. Further user interaction is needed before calling next time this method. * @throws ScmCommunicationException - Some unexpected problem occurred during communication with * scm provider. */ PersonalAccessToken fetchPersonalAccessToken(Subject cheUser, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException; /** * Refresh a PersonalAccessToken. * * @throws ScmUnauthorizedException - in case if user are not authorized che server to create new * token. Further user interaction is needed before calling next time this method. * @throws ScmCommunicationException - Some unexpected problem occurred during communication with * scm provider. */ PersonalAccessToken refreshPersonalAccessToken(Subject cheUser, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException; /** * Checks whether the provided personal access token is valid and has expected scope of * permissions. * * @deprecated use {@link #isValid(PersonalAccessTokenParams)} instead. * @param personalAccessToken - personal access token to check. * @return - empty optional if {@link PersonalAccessTokenFetcher} is not able to confirm or deny * that token is valid or {@link Boolean} value if it can. * @throws ScmUnauthorizedException - in case if user did not authorized che server to create new * token. Further user interaction is needed before calling next time this method. * @throws ScmCommunicationException - Some unexpected problem occurred during communication with * scm provider. */ @Deprecated Optional isValid(PersonalAccessToken personalAccessToken) throws ScmCommunicationException, ScmUnauthorizedException; /** * Checks whether the provided personal access token is valid by fetching user info from the scm * provider. Also checks whether the token has expected scope of permissions if the provider API * supports such request. * * @return - Optional with a pair of boolean value and scm username. The boolean value is true if * the token has expected scope of permissions, false if the token scopes does not match the * expected ones. Empty optional if {@link PersonalAccessTokenFetcher} is not able to confirm * or deny that token is valid. * @throws ScmCommunicationException - problem occurred during communication with SCM server. */ Optional> isValid(PersonalAccessTokenParams params) throws ScmCommunicationException; } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenManager.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import java.util.Optional; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.subject.Subject; /** Manages {@link PersonalAccessToken}s in Che's permanent storage. */ public interface PersonalAccessTokenManager { /** * Fetches a new {@link PersonalAccessToken} token from scm provider and save it in permanent * storage for further usage. * * @param cheUser * @param scmServerUrl * @return personal access token * @throws UnsatisfiedScmPreconditionException - storage preconditions aren't met. * @throws ScmConfigurationPersistenceException - problem occurred during communication with * permanent storage. * @throws ScmUnauthorizedException - scm authorization required. * @throws ScmCommunicationException - problem occurred during communication with scm provider. * @throws UnknownScmProviderException - scm provider is unknown. */ PersonalAccessToken fetchAndSave(Subject cheUser, String scmServerUrl) throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException, ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException; /** * Gets {@link PersonalAccessToken} from permanent storage. * * @param scmServerUrl Git provider endpoint * @throws ScmConfigurationPersistenceException - problem occurred during communication with * permanent storage. * @throws ScmUnauthorizedException - scm authorization required. * @throws ScmCommunicationException - problem occurred during communication with scm provider. * @throws UnknownScmProviderException - scm provider is unknown. * @throws UnsatisfiedScmPreconditionException - storage preconditions aren't met. */ PersonalAccessToken get(String scmServerUrl) throws ScmConfigurationPersistenceException, ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException, UnsatisfiedScmPreconditionException; /** * Gets {@link PersonalAccessToken} from permanent storage for the given OAuth provider name. It * is useful when OAuth provider is not configured and Git provider endpoint is unknown. {@code * scmServerUrl} can be provided as an additional clause. * * @param cheUser Che user object * @param oAuthProviderName OAuth provider name to get token for * @param scmServerUrl Git provider endpoint * @param namespaceName The user's namespace name. * @return personal access token * @throws ScmConfigurationPersistenceException - problem occurred during communication with * permanent storage. * @throws ScmCommunicationException - problem occurred during communication with SCM server. */ Optional get( Subject cheUser, @Nullable String oAuthProviderName, @Nullable String scmServerUrl, @Nullable String namespaceName) throws ScmConfigurationPersistenceException, ScmCommunicationException; /** * Gets {@link PersonalAccessToken} from permanent storage. If the token is not found try to fetch * it from scm provider and save it in a permanent storage and set (update) git-credentials. * * @param scmServerUrl Git provider endpoint */ PersonalAccessToken getAndStore(String scmServerUrl) throws ScmCommunicationException, ScmConfigurationPersistenceException, UnknownScmProviderException, UnsatisfiedScmPreconditionException, ScmUnauthorizedException; /** Refresh a personal access token. */ void forceRefreshPersonalAccessToken(String scmServerUrl) throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException, ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException; /** * Set or update git-credentials with {@link PersonalAccessToken} from permanent storage. * * @param scmServerUrl Git provider endpoint * @throws UnsatisfiedScmPreconditionException - storage preconditions aren't met. * @throws ScmConfigurationPersistenceException - problem occurred during communication with * permanent storage. * @throws ScmCommunicationException - problem occurred during communication with scm provider. * @throws ScmUnauthorizedException - scm authorization required. */ void storeGitCredentials(String scmServerUrl) throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException, ScmCommunicationException, ScmUnauthorizedException; /** * Store {@link PersonalAccessToken} in permanent storage. * * @param token personal access token * @throws UnsatisfiedScmPreconditionException - storage preconditions aren't met. * @throws ScmConfigurationPersistenceException - problem occurred during communication with * permanent storage. */ void store(PersonalAccessToken token) throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException; /** * Remove {@link PersonalAccessToken} from permanent storage. * * @param scmUrl Git provider endpoint permanent storage. */ void remove(String scmUrl); } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenParams.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; /** An object to hold parameters for creating a personal access token. */ public class PersonalAccessTokenParams { private final String scmProviderUrl; private final String scmProviderName; private final String scmTokenName; private final String scmTokenId; private final String token; private final String organization; public PersonalAccessTokenParams( String scmProviderUrl, String scmProviderName, String scmTokenName, String scmTokenId, String token, String organization) { this.scmProviderUrl = scmProviderUrl; this.scmProviderName = scmProviderName; this.scmTokenName = scmTokenName; this.scmTokenId = scmTokenId; this.token = token; this.organization = organization; } public String getScmProviderUrl() { return scmProviderUrl; } /** * This method returns the provider name if the token is a Personal Access Token, and the token * name in format oauth2- if the token is an oauth token. Deprecated: * We need to add a new method to distinguish oauth tokens from personal access tokens. * * @return token name */ @Deprecated public String getScmTokenName() { return scmTokenName; } public String getScmTokenId() { return scmTokenId; } public String getToken() { return token; } public String getOrganization() { return organization; } public String getScmProviderName() { return scmProviderName; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/ScmPersonalAccessTokenFetcher.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import java.util.Optional; import java.util.Set; import javax.inject.Inject; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; /** * Iterate over configured list of PersonalAccessTokenFetcher with attempt to get * PersonalAccessToken. */ public class ScmPersonalAccessTokenFetcher { private final Set personalAccessTokenFetchers; @Inject public ScmPersonalAccessTokenFetcher( Set personalAccessTokenFetchers) { this.personalAccessTokenFetchers = personalAccessTokenFetchers; } public PersonalAccessToken refreshPersonalAccessToken(Subject cheUser, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { for (PersonalAccessTokenFetcher fetcher : personalAccessTokenFetchers) { PersonalAccessToken token = fetcher.refreshPersonalAccessToken(cheUser, scmServerUrl); if (token != null) { return token; } } throw new UnknownScmProviderException( "No PersonalAccessTokenFetcher configured for " + scmServerUrl, scmServerUrl); } /** * Iterate over the Set declared in container and sequentially invoke * {@link PersonalAccessTokenFetcher#fetchPersonalAccessToken(Subject, String)} method. * * @throws UnknownScmProviderException - if none of PersonalAccessTokenFetchers return a * meaningful result. */ public PersonalAccessToken fetchPersonalAccessToken(Subject cheUser, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { for (PersonalAccessTokenFetcher fetcher : personalAccessTokenFetchers) { PersonalAccessToken token = fetcher.fetchPersonalAccessToken(cheUser, scmServerUrl); if (token != null) { return token; } } throw new UnknownScmProviderException( "No PersonalAccessTokenFetcher configured for " + scmServerUrl, scmServerUrl); } /** * Iterate over the Set declared in container and sequentially invoke * {@link PersonalAccessTokenFetcher#isValid(PersonalAccessToken)} method. * * @deprecated use {@link #getScmUsername(PersonalAccessTokenParams)} instead. * @throws UnknownScmProviderException - if none of PersonalAccessTokenFetchers return a * meaningful result. */ @Deprecated public boolean isValid(PersonalAccessToken personalAccessToken) throws UnknownScmProviderException, ScmUnauthorizedException, ScmCommunicationException { for (PersonalAccessTokenFetcher fetcher : personalAccessTokenFetchers) { Optional isValid = fetcher.isValid(personalAccessToken); if (isValid.isPresent()) { return isValid.get(); } } throw new UnknownScmProviderException( "No PersonalAccessTokenFetcher configured for " + personalAccessToken.getScmProviderUrl(), personalAccessToken.getScmProviderUrl()); } /** * Iterate over the Set declared in container and sequentially invoke * {@link PersonalAccessTokenFetcher#isValid(PersonalAccessTokenParams)} method. If any of the * fetchers return an scm username, return it. Otherwise, return null. */ public Optional getScmUsername(PersonalAccessTokenParams params) throws UnknownScmProviderException, ScmCommunicationException { for (PersonalAccessTokenFetcher fetcher : personalAccessTokenFetchers) { Optional> isValid = fetcher.isValid(params); if (isValid.isPresent() && isValid.get().first) { return Optional.of(isValid.get().second); } } return Optional.empty(); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ExceptionMessages.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.exception; public class ExceptionMessages { public static String getDevfileConnectionErrorMessage(String location) { return String.format("Could not reach devfile at %s", location); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmBadRequestException.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.exception; /** Thrown when scm provider responded 400 on http request. */ public class ScmBadRequestException extends Exception { public ScmBadRequestException(String message) { super(message); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmCommunicationException.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.exception; /** Thrown when problem occurred during communication with scm provider */ public class ScmCommunicationException extends Exception { private int statusCode; private String provider; public ScmCommunicationException(String message) { super(message); } public ScmCommunicationException(String message, int statusCode, String provider) { super(message); this.statusCode = statusCode; this.provider = provider; } public ScmCommunicationException(String message, int statusCode) { super(message); this.statusCode = statusCode; } public ScmCommunicationException(String message, Throwable cause) { super(message, cause); } public ScmCommunicationException(String message, Throwable cause, String provider) { super(message, cause); this.provider = provider; } public int getStatusCode() { return statusCode; } public String getProvider() { return provider; } public void setProvider(String provider) { this.provider = provider; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmConfigurationPersistenceException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.exception; /** Thrown when problem occurred during communication with permanent storage. */ public class ScmConfigurationPersistenceException extends Exception { public ScmConfigurationPersistenceException(String message, Exception e) { super(message, e); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmItemNotFoundException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.exception; /** Thrown when scm provider responded 404 on http request. */ public class ScmItemNotFoundException extends Exception { public ScmItemNotFoundException(String message) { super(message); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmUnauthorizedException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.exception; /** In case if OAuth1 or Oauth2 token is missing and we cant make any authorised calls */ public class ScmUnauthorizedException extends Exception { private final String oauthProvider; private final String oauthVersion; private final String authenticateUrl; public ScmUnauthorizedException( String message, String oauthProvider, String oauthVersion, String authenticateUrl) { super(message); this.oauthProvider = oauthProvider; this.oauthVersion = oauthVersion; this.authenticateUrl = authenticateUrl; } public ScmUnauthorizedException( String message, String oauthProvider, Throwable cause, String oauthVersion, String authenticateUrl) { super(message, cause); this.oauthProvider = oauthProvider; this.oauthVersion = oauthVersion; this.authenticateUrl = authenticateUrl; } public String getOauthProvider() { return oauthProvider; } public String getAuthenticateUrl() { return authenticateUrl; } public String getOauthVersion() { return oauthVersion; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/UnknownScmProviderException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.exception; /** Thrown when scm provider is unknown */ public class UnknownScmProviderException extends Exception { private final String providerUrl; public UnknownScmProviderException(String message, String providerUrl) { super(message); this.providerUrl = providerUrl; } public String getProviderUrl() { return providerUrl; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/UnsatisfiedScmPreconditionException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm.exception; /** * Exception for the case when one of the precondition are not met. For example at least one k8s * namespace exists. */ public class UnsatisfiedScmPreconditionException extends Exception { public UnsatisfiedScmPreconditionException(String message) { super(message); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/spi/FactoryDao.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.spi; import java.util.List; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; import org.eclipse.che.commons.lang.Pair; /** * Defines data access object contract for {@code FactoryImpl}. * * @author Max Shaposhnik * @author Anton Korneta */ public interface FactoryDao { /** * Creates factory. * * @param factory factory to create * @return created factory * @throws NullPointerException when {@code factory} is null * @throws ConflictException when {@code factory} with given name and creator already exists * @throws ServerException when any other error occurs */ FactoryImpl create(FactoryImpl factory) throws ConflictException, ServerException; /** * Updates factory to the new entity, using replacement strategy. * * @param factory factory to update * @return updated factory * @throws NullPointerException when {@code factory} is null * @throws NotFoundException when given factory is not found * @throws ConflictException when {@code factory} with given name is already exist for creator * @throws ServerException when any other error occurs */ FactoryImpl update(FactoryImpl factory) throws NotFoundException, ConflictException, ServerException; /** * Removes factory. * * @param id factory identifier * @throws NullPointerException when {@code id} is null * @throws ServerException when any other error occurs */ void remove(String id) throws ServerException; /** * Gets factory by identifier. * * @param id factory identifier * @return factory instance, never null * @throws NullPointerException when {@code id} is null * @throws NotFoundException when factory with given {@code id} is not found * @throws ServerException when any other error occurs */ FactoryImpl getById(String id) throws NotFoundException, ServerException; /** * Gets all factories of specified user. * * @param userId user identifier * @return list factory instances, never null * @throws NullPointerException when {@code userId} is null * @throws ServerException when any other error occurs */ Page getByUser(String userId, int maxItems, long skipCount) throws ServerException; /** * Gets the factories for the list of attributes. * * @param maxItems the maximum count of items to fetch * @param skipCount count of items which should be skipped * @param attributes list of pairs of attributes to search for * @return list of the factories which contain the specified attributes * @throws IllegalArgumentException when {@code skipCount} or {@code maxItems} is negative * @throws ServerException when any other error occurs */ Page getByAttributes( int maxItems, int skipCount, List> attributes) throws ServerException; } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/DefaultFactoryUrl.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.urlfactory; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.Collections.singletonList; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.List; import java.util.Optional; /** * Default implementation of {@link RemoteFactoryUrl} which used with all factory URL's until there * is no specific implementation for given URL. */ public class DefaultFactoryUrl implements RemoteFactoryUrl { private String devfileFileLocation; private URL url; @Override public String getProviderName() { return "default"; } @Override public List devfileFileLocations() { return singletonList( new DevfileLocation() { @Override public Optional filename() { return Optional.empty(); } @Override public String location() { return devfileFileLocation; } }); } @Override public String rawFileLocation(String filename) { return URI.create(devfileFileLocation).resolve(filename).toString(); } @Override public String getHostName() { return URI.create(devfileFileLocation).getHost(); } @Override public String getProviderUrl() { return getHostName(); } @Override public String getBranch() { return null; } public URL getUrl() { return url; } @Override public Optional getCredentials() { if (url == null || isNullOrEmpty(url.getUserInfo())) { return Optional.empty(); } String userInfo = url.getUserInfo(); String[] credentials = userInfo.split(":"); String username = credentials[0]; String password = credentials.length == 2 ? credentials[1] : null; if (!isNullOrEmpty(username) || !isNullOrEmpty(password)) { return Optional.of( format( "%s:%s", isNullOrEmpty(username) ? "" : username, isNullOrEmpty(password) ? "" : password)); } return Optional.empty(); } public U withUrl(String url) { try { this.url = new URL(url); } catch (MalformedURLException e) { // Do nothing, wrong URL. } return (U) this; } public DefaultFactoryUrl withDevfileFileLocation(String devfileFileLocation) { this.devfileFileLocation = devfileFileLocation; return this; } @Override public void setDevfileFilename(String devfileName) { // do nothing as the devfile location is absolute } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/DevfileFilenamesProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.urlfactory; import com.google.common.base.Splitter; import java.util.List; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; /** Provides list of configured devfile filenames to look in repository-based factories. */ @Singleton public class DevfileFilenamesProvider { private final List configuredDevfileFilenames; @Inject public DevfileFilenamesProvider( @Named("che.factory.default_devfile_filenames") String devfileFilenames) { this.configuredDevfileFilenames = Splitter.on(",").splitToList(devfileFilenames); } public List getConfiguredDevfileFilenames() { return configuredDevfileFilenames; } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/RemoteFactoryUrl.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.urlfactory; import java.util.List; import java.util.Optional; /** * Provides basic information about the remote factory URL components. Vendor specific * implementations may provide wider range of details about the URL (like username, repository etc). */ public interface RemoteFactoryUrl { /** Provider name for given URL. */ String getProviderName(); /** Defines the devfile name */ void setDevfileFilename(String devfileName); /** * List of possible filenames and raw locations of devfile. * * @return devfile filenames and locations list */ List devfileFileLocations(); /** Address of raw file content in remote repository */ String rawFileLocation(String filename); /** Remote hostname */ String getHostName(); /** Remote provider URL */ String getProviderUrl(); /** Remote branch */ String getBranch(); /** Optional of credentials in format : or : */ Optional getCredentials(); /** Describes devfile location, including filename if any. */ interface DevfileLocation { Optional filename(); String location(); } } ================================================ FILE: wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.urlfactory; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; import static org.eclipse.che.api.factory.server.scm.exception.ExceptionMessages.getDevfileConnectionErrorMessage; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.dto.server.DtoFactory.newDto; import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; import java.util.Map; import java.util.Optional; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.commons.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Handle the creation of some elements used inside a {@link FactoryDto}. * * @author Florent Benoit * @author Max Shaposhnyk */ @Singleton public class URLFactoryBuilder { private static final Logger LOG = LoggerFactory.getLogger(URLFactoryBuilder.class); public static final String DEVFILE_FILENAME = "devfileFilename"; private final String defaultCheEditor; private final String defaultChePlugins; private final boolean devWorskspacesEnabled; private final DevfileParser devfileParser; private final DevfileVersionDetector devfileVersionDetector; @Inject public URLFactoryBuilder( @Named("che.factory.default_editor") String defaultCheEditor, @Nullable @Named("che.factory.default_plugins") String defaultChePlugins, @Named("che.devworkspaces.enabled") boolean devWorskspacesEnabled, DevfileParser devfileParser, DevfileVersionDetector devfileVersionDetector) { this.defaultCheEditor = defaultCheEditor; this.defaultChePlugins = defaultChePlugins; this.devWorskspacesEnabled = devWorskspacesEnabled; this.devfileParser = devfileParser; this.devfileVersionDetector = devfileVersionDetector; } /** * Build a factory using the provided devfile. Allows to override devfile properties using * specially constructed map {@see DevfileManager#parseYaml(String, Map)}. * *

    We want factory to never fail due to name collision. Taking `generateName` with precedence. *
    * If devfile has only `name`, we convert it to `generateName`.
    * If devfile has `name` and `generateName`, we remove `name` and use just `generateName`.
    * If devfile has `generateName`, we use that. * * @param remoteFactoryUrl parsed factory URL object * @param fileContentProvider service-specific devfile related file content provider * @param overrideProperties map of overridden properties to apply in devfile * @return a factory or null if devfile is not found */ public Optional createFactoryFromDevfile( RemoteFactoryUrl remoteFactoryUrl, FileContentProvider fileContentProvider, Map overrideProperties, boolean skipAuthentication) throws ApiException { String devfileYamlContent; // Apply the new devfile name to look for if (overrideProperties.containsKey(DEVFILE_FILENAME)) { remoteFactoryUrl.setDevfileFilename(overrideProperties.get(DEVFILE_FILENAME)); } for (DevfileLocation location : remoteFactoryUrl.devfileFileLocations()) { String devfileLocation = location.location(); try { Optional credentialsOptional = remoteFactoryUrl.getCredentials(); if (skipAuthentication) { devfileYamlContent = fileContentProvider.fetchContentWithoutAuthentication(devfileLocation); } else if (credentialsOptional.isPresent()) { devfileYamlContent = fileContentProvider.fetchContent(devfileLocation, credentialsOptional.get()); } else { devfileYamlContent = fileContentProvider.fetchContent(devfileLocation); } } catch (IOException ex) { // try next location LOG.debug( "Unreachable devfile location met: {}. Error is: {}", devfileLocation, ex.getMessage()); continue; } catch (DevfileException e) { LOG.debug("Unexpected devfile exception: {}", e.getMessage()); throw e.getCause() instanceof ScmUnauthorizedException ? toApiException(e, location) : new ApiException(e.getMessage()); } if (isNullOrEmpty(devfileYamlContent)) { return Optional.empty(); } try { JsonNode parsedDevfile = devfileParser.parseYamlRaw(devfileYamlContent); // We might have an html content in the parsed devfile, in case if the access is restricted, // or if the URL points to a wrong resource. try { devfileVersionDetector.devfileVersion(parsedDevfile); } catch (DevfileException e) { throw new ApiException(getDevfileConnectionErrorMessage(devfileLocation)); } return Optional.of(createFactory(parsedDevfile, location)); } catch (DevfileException e) { throw toApiException(e, location); } } return Optional.empty(); } /** * Converts given devfile json into factory. * * @param location devfile's location * @return new factory created from the given devfile */ private FactoryMetaDto createFactory(JsonNode devfileJson, DevfileLocation location) { return newDto(FactoryDevfileV2Dto.class) .withV(CURRENT_VERSION) .withDevfile(devfileParser.convertYamlToMap(devfileJson)) .withSource(location.filename().isPresent() ? location.filename().get() : null); } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/ApiExceptionMapperTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static org.testng.Assert.*; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.core.rest.shared.dto.ExtendedError; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.testng.annotations.Test; public class ApiExceptionMapperTest { @Test public void shouldReturnUnauthorizedExceptionIfCauseIsScmUnauthorized() { ScmUnauthorizedException scmUnauthorizedException = new ScmUnauthorizedException( "msg", "gitlab", "2.0", "http://gitlab.com/oauth/authenticate"); ApiException exception = ApiExceptionMapper.toApiException(new DevfileException("text", scmUnauthorizedException)); assertTrue(exception instanceof UnauthorizedException); assertEquals(((ExtendedError) exception.getServiceError()).getErrorCode(), 401); assertEquals(((ExtendedError) exception.getServiceError()).getAttributes().size(), 3); assertEquals( ((ExtendedError) exception.getServiceError()).getAttributes().get("oauth_version"), "2.0"); assertEquals( ((ExtendedError) exception.getServiceError()) .getAttributes() .get("oauth_authentication_url"), "http://gitlab.com/oauth/authenticate"); } @Test public void shouldReturnServerExceptionWhenCauseIsUnknownProvider() { UnknownScmProviderException scmProviderException = new UnknownScmProviderException("unknown", "http://gitlab.com/oauth/authenticate"); ApiException exception = ApiExceptionMapper.toApiException(new DevfileException("text", scmProviderException)); assertTrue(exception instanceof ServerException); } @Test public void shouldReturnServerExceptionWhenCauseIsCommunicationException() { ScmCommunicationException communicationException = new ScmCommunicationException("unknown"); ApiException exception = ApiExceptionMapper.toApiException(new DevfileException("text", communicationException)); assertTrue(exception instanceof ServerException); } @Test public void shouldReturnBadrequestExceptionWhenCauseIsOtherException() { ScmItemNotFoundException itemNotFoundException = new ScmItemNotFoundException("unknown"); ApiException exception = ApiExceptionMapper.toApiException(new DevfileException("text", itemNotFoundException)); assertTrue(exception instanceof BadRequestException); } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/BaseFactoryParameterResolverTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static java.util.Collections.emptyMap; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.util.Map; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(value = {MockitoTestNGListener.class}) public class BaseFactoryParameterResolverTest { @Mock private AuthorisationRequestManager authorisationRequestManager; @Mock private URLFactoryBuilder urlFactoryBuilder; @Mock private RemoteFactoryUrl remoteFactoryUrl; @Mock private FactoryVisitor factoryVisitor; @Mock private FileContentProvider contentProvider; private static final String PROVIDER_NAME = "test"; private BaseFactoryParameterResolver baseFactoryParameterResolver; @BeforeMethod protected void init() throws Exception { baseFactoryParameterResolver = new BaseFactoryParameterResolver( authorisationRequestManager, urlFactoryBuilder, PROVIDER_NAME); } @Test public void shouldReturnFalseOnGetSkipAuthorisation() { // given when(authorisationRequestManager.isStored(eq(PROVIDER_NAME))).thenReturn(false); // when boolean result = baseFactoryParameterResolver.getSkipAuthorisation(emptyMap()); // then assertFalse(result); } @Test public void shouldReturnTrueOnGetSkipAuthorisation() { // given when(authorisationRequestManager.isStored(eq(PROVIDER_NAME))).thenReturn(true); // when boolean result = baseFactoryParameterResolver.getSkipAuthorisation(emptyMap()); // then assertTrue(result); } @Test public void shouldReturnTrueOnGetSkipAuthorisationFromFactoryParams() { // given when(authorisationRequestManager.isStored(eq(PROVIDER_NAME))).thenReturn(false); // when boolean result = baseFactoryParameterResolver.getSkipAuthorisation(Map.of("error_code", "access_denied")); // then assertTrue(result); } @Test public void shouldReturnDevfileV2() throws Exception { // given when(authorisationRequestManager.isStored(eq(PROVIDER_NAME))).thenReturn(false); // when baseFactoryParameterResolver.createFactory( emptyMap(), remoteFactoryUrl, factoryVisitor, contentProvider); // then verify(factoryVisitor).visit(any(FactoryDevfileV2Dto.class)); } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryLinksHelperTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.Mockito.when; import static org.testng.Assert.*; import jakarta.ws.rs.core.UriBuilder; import java.util.Collections; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.rest.ServiceContext; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.factory.shared.dto.AuthorDto; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.everrest.core.impl.uri.UriBuilderImpl; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class FactoryLinksHelperTest { private static final String USER_ID = "user123"; private static final String URI_BASE = "http://localhost:8080"; public static final String TEST_REPO = "https://test.repo.com"; @Mock ServiceContext serviceContext; @Mock AdditionalFilenamesProvider additionalFilenamesProvider; @BeforeMethod public void setUp() { final UriBuilder uriBuilder = new UriBuilderImpl(); uriBuilder.uri(URI_BASE); when(serviceContext.getServiceUriBuilder()).thenReturn(uriBuilder); } @Test public void shouldContainDevfileLinkIfSourceIsPresent() { final String testRepo = "https://test.repo.com"; List links = FactoryLinksHelper.createLinks( createV2FactoryWithSource("factory1"), serviceContext, additionalFilenamesProvider, "user1", testRepo); assertTrue( links.stream() .anyMatch( l -> l.getMethod().equals("GET") && l.getRel().equals("devfile.yaml content") && l.getHref() .equals( URI_BASE + "/api/scm/resolve?repository=" + testRepo + "&file=devfile.yaml"))); } @Test public void shouldContainFilesLinksIfScmInfoIsPresent() { when(additionalFilenamesProvider.get()).thenReturn(Collections.singletonList("myfile.ext")); List links = FactoryLinksHelper.createLinks( createV2FactoryWithScmInfo("factory1", TEST_REPO), serviceContext, additionalFilenamesProvider, "user1", TEST_REPO); assertTrue( links.stream() .anyMatch( l -> l.getMethod().equals("GET") && l.getRel().equals("myfile.ext content") && l.getHref() .equals( URI_BASE + "/api/scm/resolve?repository=" + TEST_REPO + "&file=myfile.ext"))); } @Test public void shouldNotContainFilesLinksIfNoScmInfoIsPresent() { final String testRepo = "https://test.repo.com"; List links = FactoryLinksHelper.createLinks( createV2FactoryWithSource("factory1"), serviceContext, additionalFilenamesProvider, "user1", testRepo); assertTrue( links.stream() .noneMatch( l -> l.getMethod().equals("GET") && l.getRel().equals("myfile.ext content"))); } private FactoryDevfileV2Dto createV2FactoryWithScmInfo(String name, String testRepo) { return (FactoryDevfileV2Dto) newDto(FactoryDevfileV2Dto.class) .withV("4.0") .withDevfile(Map.of("a", "b")) .withScmInfo( newDto(ScmInfoDto.class) .withRepositoryUrl(testRepo) .withScmProviderName("prov1") .withBranch("branch")) .withCreator(newDto(AuthorDto.class).withUserId(USER_ID).withCreated(12L)) .withName(name); } private FactoryDevfileV2Dto createV2FactoryWithSource(String name) { return (FactoryDevfileV2Dto) newDto(FactoryDevfileV2Dto.class) .withV("4.0") .withDevfile(Map.of("a", "b")) .withSource("devfile.yaml") .withCreator(newDto(AuthorDto.class).withUserId(USER_ID).withCreated(12L)) .withName(name); } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryManagerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static com.google.common.base.Strings.isNullOrEmpty; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertFalse; import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; import org.eclipse.che.api.factory.server.spi.FactoryDao; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Max Shaposhnik (mshaposhnik@codenvy.com) on 3/20/17. */ @Listeners(value = {MockitoTestNGListener.class}) public class FactoryManagerTest { @Mock private FactoryDao factoryDao; @InjectMocks private FactoryManager factoryManager; @Captor private ArgumentCaptor factoryCaptor; @Test public void shouldGenerateNameOnFactoryCreation() throws Exception { final FactoryImpl factory = FactoryImpl.builder().generateId().build(); factoryManager.saveFactory(factory); verify(factoryDao).create(factoryCaptor.capture()); assertFalse(isNullOrEmpty(factoryCaptor.getValue().getName())); } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static io.restassured.RestAssured.given; import static java.lang.String.valueOf; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.server.FactoryResolverPriority.DEFAULT; import static org.eclipse.che.api.factory.server.FactoryResolverPriority.HIGHEST; import static org.eclipse.che.api.factory.server.FactoryResolverPriority.LOWEST; import static org.eclipse.che.api.factory.server.FactoryService.VALIDATE_QUERY_PARAMETER; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import io.restassured.http.ContentType; import io.restassured.response.Response; import java.lang.reflect.Field; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.rest.ApiExceptionMapper; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; import org.eclipse.che.api.factory.server.FactoryService.FactoryParametersResolverHolder; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.dto.server.DtoFactory; import org.everrest.assured.EverrestJetty; import org.everrest.core.Filter; import org.everrest.core.GenericContainerRequest; import org.everrest.core.RequestFilter; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests for {@link FactoryService}. * * @author Anton Korneta */ @Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class}) public class FactoryServiceTest { private static final String SERVICE_PATH = "/factory"; private static final String FACTORY_ID = "correctFactoryId"; private static final String FACTORY_NAME = "factory"; private static final String USER_ID = "userId"; private static final String USER_EMAIL = "email"; private static final String WORKSPACE_NAME = "workspace"; private static final String PROJECT_SOURCE_TYPE = "git"; private static final String PROJECT_SOURCE_LOCATION = "https://github.com/codenvy/platform-api.git"; private static final DtoFactory DTO = DtoFactory.getInstance(); private final String scmServerUrl = "https://hostName.com"; @Mock private FactoryAcceptValidator acceptValidator; @Mock private PreferenceManager preferenceManager; @Mock private UserManager userManager; @Mock private AdditionalFilenamesProvider additionalFilenamesProvider; @Mock private RawDevfileUrlFactoryParameterResolver rawDevfileUrlFactoryParameterResolver; @Mock private PersonalAccessTokenManager personalAccessTokenManager; @Mock private AuthorisationRequestManager authorisationRequestManager; @InjectMocks private FactoryParametersResolverHolder factoryParametersResolverHolder; private Set specificFactoryParametersResolvers; private User user; private FactoryService service; @SuppressWarnings("unused") private ApiExceptionMapper apiExceptionMapper; @SuppressWarnings("unused") private EnvironmentFilter environmentFilter; @BeforeMethod public void setUp() throws Exception { specificFactoryParametersResolvers = new HashSet<>(); Field parametersResolvers = FactoryParametersResolverHolder.class.getDeclaredField( "specificFactoryParametersResolvers"); parametersResolvers.setAccessible(true); parametersResolvers.set(factoryParametersResolverHolder, specificFactoryParametersResolvers); specificFactoryParametersResolvers.add(rawDevfileUrlFactoryParameterResolver); user = new UserImpl(USER_ID, USER_EMAIL, ADMIN_USER_NAME); lenient() .when(preferenceManager.find(USER_ID)) .thenReturn(ImmutableMap.of("preference", "value")); service = new FactoryService( acceptValidator, factoryParametersResolverHolder, additionalFilenamesProvider, personalAccessTokenManager, authorisationRequestManager); } @Filter public static class EnvironmentFilter implements RequestFilter { @Override public void doFilter(GenericContainerRequest request) { EnvironmentContext context = EnvironmentContext.getCurrent(); context.setSubject( new SubjectImpl( ADMIN_USER_NAME, Collections.emptyList(), USER_ID, ADMIN_USER_PASSWORD, false)); } } @Test public void shouldThrowBadRequestWhenNoURLParameterGiven() throws Exception { final FactoryParametersResolverHolder dummyHolder = spy(factoryParametersResolverHolder); doReturn(rawDevfileUrlFactoryParameterResolver) .when(dummyHolder) .getFactoryParametersResolver(anyMap()); // service instance with dummy holder service = new FactoryService( acceptValidator, dummyHolder, additionalFilenamesProvider, personalAccessTokenManager, authorisationRequestManager); // when final Map map = new HashMap<>(); final Response response = given() .contentType(ContentType.JSON) .when() .body(map) .queryParam(VALIDATE_QUERY_PARAMETER, valueOf(true)) .post(SERVICE_PATH + "/resolver"); assertEquals(response.getStatusCode(), 400); assertEquals( DTO.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), "Cannot build factory with any of the provided parameters. Please check parameters correctness, and resend query."); } @Test public void checkRefreshToken() throws Exception { // given final FactoryParametersResolverHolder dummyHolder = spy(factoryParametersResolverHolder); FactoryParametersResolver factoryParametersResolver = mock(FactoryParametersResolver.class); RemoteFactoryUrl remoteFactoryUrl = mock(RemoteFactoryUrl.class); when(factoryParametersResolver.parseFactoryUrl(eq("someUrl"))).thenReturn(remoteFactoryUrl); when(remoteFactoryUrl.getProviderUrl()).thenReturn(scmServerUrl); doReturn(factoryParametersResolver).when(dummyHolder).getFactoryParametersResolver(anyMap()); service = new FactoryService( acceptValidator, dummyHolder, additionalFilenamesProvider, personalAccessTokenManager, authorisationRequestManager); // when given() .contentType(ContentType.JSON) .when() .queryParam("url", "someUrl") .post(SERVICE_PATH + "/token/refresh"); // then verify(personalAccessTokenManager).getAndStore(eq(scmServerUrl)); } @Test public void shouldNotRefreshTokenIfAuthorisationRejected() throws Exception { // given final FactoryParametersResolverHolder dummyHolder = spy(factoryParametersResolverHolder); FactoryParametersResolver factoryParametersResolver = mock(FactoryParametersResolver.class); doReturn(factoryParametersResolver).when(dummyHolder).getFactoryParametersResolver(anyMap()); when(authorisationRequestManager.isStored(any())).thenReturn(true); service = new FactoryService( acceptValidator, dummyHolder, additionalFilenamesProvider, personalAccessTokenManager, authorisationRequestManager); // when given() .contentType(ContentType.JSON) .when() .queryParam("url", "someUrl") .post(SERVICE_PATH + "/token/refresh"); // then verify(personalAccessTokenManager, never()).getAndStore(eq(scmServerUrl)); } @Test public void shouldThrowBadRequestWhenRefreshTokenWithoutUrl() throws Exception { service = new FactoryService( acceptValidator, factoryParametersResolverHolder, additionalFilenamesProvider, personalAccessTokenManager, authorisationRequestManager); // when final Response response = given().contentType(ContentType.JSON).when().post(SERVICE_PATH + "/token/refresh"); assertEquals(response.getStatusCode(), 400); assertEquals( DTO.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), "Factory url required"); } @Test public void shouldReturnDefaultFactoryParameterResolver() throws Exception { // given Map params = singletonMap(URL_PARAMETER_NAME, "https://host/path/devfile.yaml"); when(rawDevfileUrlFactoryParameterResolver.accept(eq(params))).thenReturn(true); // when FactoryParametersResolver factoryParametersResolver = factoryParametersResolverHolder.getFactoryParametersResolver(params); // then assertTrue( factoryParametersResolver .getClass() .getName() .startsWith(RawDevfileUrlFactoryParameterResolver.class.getName())); } @Test public void shouldReturnTopPriorityFactoryParameterResolverOverLowPriority() throws Exception { // given Map params = singletonMap(URL_PARAMETER_NAME, "https://host/path/devfile.yaml"); specificFactoryParametersResolvers.clear(); FactoryParametersResolver topPriorityResolver = mock(FactoryParametersResolver.class); FactoryParametersResolver lowPriorityResolver = mock(FactoryParametersResolver.class); when(topPriorityResolver.accept(eq(params))).thenReturn(true); when(lowPriorityResolver.accept(eq(params))).thenReturn(true); when(topPriorityResolver.priority()).thenReturn(HIGHEST); when(lowPriorityResolver.priority()).thenReturn(LOWEST); specificFactoryParametersResolvers.add(topPriorityResolver); specificFactoryParametersResolvers.add(lowPriorityResolver); // when FactoryParametersResolver factoryParametersResolver = factoryParametersResolverHolder.getFactoryParametersResolver(params); // then assertEquals(factoryParametersResolver, topPriorityResolver); } @Test public void shouldReturnTopPriorityFactoryParameterResolverOverDefaultPriority() throws Exception { // given Map params = singletonMap(URL_PARAMETER_NAME, "https://host/path/devfile.yaml"); specificFactoryParametersResolvers.clear(); FactoryParametersResolver topPriorityResolver = mock(FactoryParametersResolver.class); FactoryParametersResolver defaultPriorityResolver = mock(FactoryParametersResolver.class); when(topPriorityResolver.accept(eq(params))).thenReturn(true); when(defaultPriorityResolver.accept(eq(params))).thenReturn(true); when(topPriorityResolver.priority()).thenReturn(HIGHEST); when(defaultPriorityResolver.priority()).thenReturn(DEFAULT); specificFactoryParametersResolvers.add(topPriorityResolver); specificFactoryParametersResolvers.add(defaultPriorityResolver); // when FactoryParametersResolver factoryParametersResolver = factoryParametersResolverHolder.getFactoryParametersResolver(params); // then assertEquals(factoryParametersResolver, topPriorityResolver); } @Test public void shouldReturnDefaultPriorityFactoryParameterResolverOverLowPriority() throws Exception { // given Map params = singletonMap(URL_PARAMETER_NAME, "https://host/path/devfile.yaml"); specificFactoryParametersResolvers.clear(); FactoryParametersResolver lowPriorityResolver = mock(FactoryParametersResolver.class); FactoryParametersResolver defaultPriorityResolver = mock(FactoryParametersResolver.class); when(lowPriorityResolver.accept(eq(params))).thenReturn(true); when(defaultPriorityResolver.accept(eq(params))).thenReturn(true); when(lowPriorityResolver.priority()).thenReturn(LOWEST); when(defaultPriorityResolver.priority()).thenReturn(DEFAULT); specificFactoryParametersResolvers.add(lowPriorityResolver); specificFactoryParametersResolvers.add(defaultPriorityResolver); // when FactoryParametersResolver factoryParametersResolver = factoryParametersResolverHolder.getFactoryParametersResolver(params); // then assertEquals(factoryParametersResolver, defaultPriorityResolver); } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolverTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server; import static java.lang.String.format; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertFalse; import static org.testng.Assert.fail; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import com.fasterxml.jackson.databind.JsonNode; import java.io.FileNotFoundException; import java.util.Base64; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.URLFileContentProvider; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(value = {MockitoTestNGListener.class}) public class RawDevfileUrlFactoryParameterResolverTest { private static final String DEVFILE = "" + "schemaVersion: 2.3.0\n" + "metadata:\n" + " name: test\n"; @Mock private URLFetcher urlFetcher; @Mock private DevfileParser devfileParser; @InjectMocks private RawDevfileUrlFactoryParameterResolver rawDevfileUrlFactoryParameterResolver; @Test @SuppressWarnings("unchecked") public void shouldFilterAndProvideOverrideParameters() throws Exception { URLFactoryBuilder urlFactoryBuilder = mock(URLFactoryBuilder.class); URLFetcher urlFetcher = mock(URLFetcher.class); RawDevfileUrlFactoryParameterResolver res = new RawDevfileUrlFactoryParameterResolver(urlFactoryBuilder, urlFetcher, devfileParser); Map factoryParameters = new HashMap<>(); factoryParameters.put(URL_PARAMETER_NAME, "http://myloc/devfile"); factoryParameters.put("override.param.foo", "bar"); factoryParameters.put("override.param.bar", "foo"); factoryParameters.put("ignored.non-override.property", "baz"); ArgumentCaptor> captor = ArgumentCaptor.forClass(Map.class); // when res.createFactory(factoryParameters); verify(urlFactoryBuilder) .createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(URLFileContentProvider.class), captor.capture(), anyBoolean()); Map filteredOverrides = captor.getValue(); assertEquals(2, filteredOverrides.size()); assertEquals("bar", filteredOverrides.get("param.foo")); assertEquals("foo", filteredOverrides.get("param.bar")); assertFalse(filteredOverrides.containsKey("ignored.non-override.property")); } @Test(dataProvider = "invalidURLsProvider") public void shouldThrowExceptionOnInvalidURL(String url, String message) throws Exception { URLFactoryBuilder urlFactoryBuilder = mock(URLFactoryBuilder.class); URLFetcher urlFetcher = mock(URLFetcher.class); RawDevfileUrlFactoryParameterResolver res = new RawDevfileUrlFactoryParameterResolver(urlFactoryBuilder, urlFetcher, devfileParser); Map factoryParameters = new HashMap<>(); factoryParameters.put(URL_PARAMETER_NAME, url); // when try { res.createFactory(factoryParameters); fail("Exception is expected"); } catch (BadRequestException e) { assertEquals( e.getMessage(), format( "Unable to process provided factory URL. Please check its validity and try again. Parser message: %s", message)); } } @Test(dataProvider = "devfileUrls") public void shouldAcceptRawDevfileUrl(String url) throws Exception { // given JsonNode jsonNode = mock(JsonNode.class); when(urlFetcher.fetch(eq(url), eq(null))).thenReturn(DEVFILE); when(devfileParser.parseYamlRaw(eq(DEVFILE))).thenReturn(jsonNode); when(jsonNode.isEmpty()).thenReturn(false); // when boolean result = rawDevfileUrlFactoryParameterResolver.accept(singletonMap(URL_PARAMETER_NAME, url)); // then assertTrue(result); } @Test(dataProvider = "devfileUrlsWithoutExtension") public void shouldAcceptRawDevfileUrlWithoutExtension(String url) throws Exception { // given JsonNode jsonNode = mock(JsonNode.class); when(urlFetcher.fetch(eq(url), eq(null))).thenReturn(DEVFILE); when(devfileParser.parseYamlRaw(eq(DEVFILE))).thenReturn(jsonNode); when(jsonNode.isEmpty()).thenReturn(false); // when boolean result = rawDevfileUrlFactoryParameterResolver.accept(singletonMap(URL_PARAMETER_NAME, url)); // then assertTrue(result); } @Test public void shouldAcceptRawDevfileUrlWithYaml() throws Exception { // given JsonNode jsonNode = mock(JsonNode.class); String url = "https://host/path/devfile"; when(urlFetcher.fetch(eq(url), eq(null))).thenReturn(DEVFILE); when(devfileParser.parseYamlRaw(eq(DEVFILE))).thenReturn(jsonNode); when(jsonNode.isEmpty()).thenReturn(false); // when boolean result = rawDevfileUrlFactoryParameterResolver.accept(singletonMap(URL_PARAMETER_NAME, url)); // then assertTrue(result); } @Test public void shouldAcceptRawDevfileUrlWithCredentials() throws Exception { // given JsonNode jsonNode = mock(JsonNode.class); String url = "https://credentials@host/path/devfile"; when(urlFetcher.fetch( eq(url), eq("Basic " + Base64.getEncoder().encodeToString("credentials:".getBytes())))) .thenReturn(DEVFILE); when(devfileParser.parseYamlRaw(eq(DEVFILE))).thenReturn(jsonNode); when(jsonNode.isEmpty()).thenReturn(false); // when boolean result = rawDevfileUrlFactoryParameterResolver.accept(singletonMap(URL_PARAMETER_NAME, url)); // then assertTrue(result); } @Test public void shouldNotAcceptPublicGitRepositoryUrl() throws Exception { // given JsonNode jsonNode = mock(JsonNode.class); String gitRepositoryUrl = "https://host/user/repo.git"; when(urlFetcher.fetch(eq(gitRepositoryUrl), eq(null))).thenReturn("unsupported content"); when(devfileParser.parseYamlRaw(eq("unsupported content"))).thenReturn(jsonNode); when(jsonNode.isEmpty()).thenReturn(true); // when boolean result = rawDevfileUrlFactoryParameterResolver.accept( singletonMap(URL_PARAMETER_NAME, gitRepositoryUrl)); // then assertFalse(result); } @Test public void shouldNotAcceptPrivateGitRepositoryUrl() throws Exception { // given String gitRepositoryUrl = "https://host/user/private-repo.git"; when(urlFetcher.fetch(eq(gitRepositoryUrl), eq(null))).thenThrow(new FileNotFoundException()); // when boolean result = rawDevfileUrlFactoryParameterResolver.accept( singletonMap(URL_PARAMETER_NAME, gitRepositoryUrl)); // then assertFalse(result); } @DataProvider(name = "invalidURLsProvider") private Object[][] invalidUrlsProvider() { return new Object[][] { {"C:\\Users\\aa\\bb\\XX\\", "unknown protocol: c"}, { "https://github.com/ .git", "Illegal character in path at index 19: https://github.com/ .git" }, {"unknown:///abc.dce", "unknown protocol: unknown"} }; } @DataProvider(name = "devfileUrls") private Object[] devfileUrls() { return new String[] { "https://host/path/devfile.yaml", "https://host/path/.devfile.yaml", "https://host/path/any-name.yaml", "https://host/path/any-name.yml", "https://host/path/devfile.yaml?token=TOKEN123", "https://host/path/.devfile.yaml?token=TOKEN123", "https://host/path/any-name.yaml?token=TOKEN123", "https://host/path/any-name.yml?token=TOKEN123", "https://host/path/devfile.yaml?at=refs/heads/branch", "https://host/path/.devfile.yaml?at=refs/heads/branch", "https://host/path/any-name.yaml?at=refs/heads/branch", "https://host/path/any-name.yml?at=refs/heads/branch" }; } @DataProvider(name = "devfileUrlsWithoutExtension") private Object[] devfileUrlsWithoutExtension() { return new String[] {"https://host/path/any-name", "https://host/path/any-name?token=TOKEN123"}; } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidatorTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.impl; import static java.util.Collections.singletonList; import static java.util.Objects.*; import static org.eclipse.che.dto.server.DtoFactory.*; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.Mockito.lenient; import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.net.URISyntaxException; import java.text.ParseException; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.factory.server.FactoryConstants; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.IdeActionDto; import org.eclipse.che.api.factory.shared.dto.IdeDto; import org.eclipse.che.api.factory.shared.dto.OnAppClosedDto; import org.eclipse.che.api.factory.shared.dto.OnAppLoadedDto; import org.eclipse.che.api.factory.shared.dto.OnProjectsLoadedDto; import org.eclipse.che.api.factory.shared.dto.PoliciesDto; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.user.server.spi.UserDao; import org.eclipse.che.dto.server.DtoFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(value = {MockitoTestNGListener.class}) public class FactoryBaseValidatorTest { private static final String VALID_REPOSITORY_URL = "https://github.com/codenvy/cloudide"; private static final String VALID_PROJECT_PATH = "/cloudide"; private static final String ID = "id"; @Mock private UserDao userDao; private TesterFactoryBaseValidator validator; private FactoryDevfileV2Dto factory; @BeforeMethod public void setUp() throws ParseException, NotFoundException, ServerException { factory = newDto(FactoryDevfileV2Dto.class).withV("4.0"); final UserImpl user = new UserImpl("userid", "email", "name"); lenient().when(userDao.getById("userid")).thenReturn(user); validator = new TesterFactoryBaseValidator(); } @DataProvider(name = "invalidProjectNamesProvider") public Object[][] invalidProjectNames() { return new Object[][] { {"-untitled"}, {"untitled->3"}, {"untitled__2%"}, {"untitled_!@#$%^&*()_+?><"} }; } @Test( expectedExceptions = ApiException.class, expectedExceptionsMessageRegExp = FactoryConstants.ILLEGAL_FACTORY_BY_UNTIL_MESSAGE) public void shouldNotValidateIfUntilBeforeCurrentTime() throws ApiException { Long currentTime = new Date().getTime(); factory.withPolicies(newDto(PoliciesDto.class).withUntil(currentTime - 10000L)); validator.validateCurrentTimeBetweenSinceUntil(factory); } @Test public void shouldValidateIfCurrentTimeBetweenUntilSince() throws ApiException { Long currentTime = new Date().getTime(); factory.withPolicies( newDto(PoliciesDto.class).withSince(currentTime - 10000L).withUntil(currentTime + 10000L)); validator.validateCurrentTimeBetweenSinceUntil(factory); } @Test( expectedExceptions = ApiException.class, expectedExceptionsMessageRegExp = FactoryConstants.ILLEGAL_FACTORY_BY_SINCE_MESSAGE) public void shouldNotValidateIfUntilSinceAfterCurrentTime() throws ApiException { Long currentTime = new Date().getTime(); factory.withPolicies(newDto(PoliciesDto.class).withSince(currentTime + 10000L)); validator.validateCurrentTimeBetweenSinceUntil(factory); } @Test public void shouldValidateTrackedParamsIfOrgIdIsMissingButOnPremisesTrue() throws Exception { final DtoFactory dtoFactory = getInstance(); FactoryDevfileV2Dto factory = dtoFactory.createDto(FactoryDevfileV2Dto.class); factory .withV("4.0") .withPolicies( dtoFactory .createDto(PoliciesDto.class) .withSince(System.currentTimeMillis() + 1_000_000) .withUntil(System.currentTimeMillis() + 10_000_000) .withReferer("codenvy.com")); validator = new TesterFactoryBaseValidator(); } @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateOpenfileActionIfInWrongSectionOnAppClosed() throws Exception { // given validator = new TesterFactoryBaseValidator(); List actions = singletonList(newDto(IdeActionDto.class).withId("openFile")); IdeDto ide = newDto(IdeDto.class).withOnAppClosed(newDto(OnAppClosedDto.class).withActions(actions)); FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateFindReplaceActionIfInWrongSectionOnAppLoaded() throws Exception { // given validator = new TesterFactoryBaseValidator(); List actions = singletonList(newDto(IdeActionDto.class).withId("findReplace")); IdeDto ide = newDto(IdeDto.class).withOnAppLoaded(newDto(OnAppLoadedDto.class).withActions(actions)); FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateIfOpenfileActionInsufficientParams() throws Exception { // given validator = new TesterFactoryBaseValidator(); List actions = singletonList(newDto(IdeActionDto.class).withId("openFile")); IdeDto ide = newDto(IdeDto.class) .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class).withActions(actions)); FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateIfrunCommandActionInsufficientParams() throws Exception { // given validator = new TesterFactoryBaseValidator(); List actions = singletonList(newDto(IdeActionDto.class).withId("openFile")); IdeDto ide = newDto(IdeDto.class) .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class).withActions(actions)); FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateIfOpenWelcomePageActionInsufficientParams() throws Exception { // given validator = new TesterFactoryBaseValidator(); List actions = singletonList(newDto(IdeActionDto.class).withId("openWelcomePage")); IdeDto ide = newDto(IdeDto.class).withOnAppLoaded((newDto(OnAppLoadedDto.class).withActions(actions))); FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateIfFindReplaceActionInsufficientParams() throws Exception { // given validator = new TesterFactoryBaseValidator(); Map params = new HashMap<>(); params.put("in", "pom.xml"); // find is missing! params.put("replace", "123"); List actions = singletonList(newDto(IdeActionDto.class).withId("findReplace").withProperties(params)); IdeDto ide = newDto(IdeDto.class) .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class).withActions(actions)); FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @Test public void shouldValidateFindReplaceAction() throws Exception { // given validator = new TesterFactoryBaseValidator(); Map params = new HashMap<>(); params.put("in", "pom.xml"); params.put("find", "123"); params.put("replace", "456"); List actions = singletonList(newDto(IdeActionDto.class).withId("findReplace").withProperties(params)); IdeDto ide = newDto(IdeDto.class) .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class).withActions(actions)); FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @Test public void shouldValidateOpenfileAction() throws Exception { // given validator = new TesterFactoryBaseValidator(); Map params = new HashMap<>(); params.put("file", "pom.xml"); List actions = singletonList(newDto(IdeActionDto.class).withId("openFile").withProperties(params)); IdeDto ide = newDto(IdeDto.class) .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class).withActions(actions)); FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @DataProvider(name = "trackedFactoryParameterWithoutValidAccountId") public Object[][] trackedFactoryParameterWithoutValidAccountId() throws URISyntaxException, IOException, NoSuchMethodException { return new Object[][] { { newDto(FactoryMetaDto.class) .withV("4.0") .withIde( newDto(IdeDto.class) .withOnAppLoaded( newDto(OnAppLoadedDto.class) .withActions( singletonList( newDto(IdeActionDto.class) .withId("openWelcomePage") .withProperties( ImmutableMap.builder() .put("authenticatedTitle", "title") .put("authenticatedIconUrl", "url") .put("authenticatedContentUrl", "url") .put("nonAuthenticatedTitle", "title") .put("nonAuthenticatedIconUrl", "url") .put("nonAuthenticatedContentUrl", "url") .build()))))) }, { newDto(FactoryMetaDto.class) .withV("4.0") .withPolicies(newDto(PoliciesDto.class).withSince(10000L)) }, { newDto(FactoryMetaDto.class) .withV("4.0") .withPolicies(newDto(PoliciesDto.class).withUntil(10000L)) }, { newDto(FactoryMetaDto.class) .withV("4.0") .withPolicies(newDto(PoliciesDto.class).withReferer("host")) } }; } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/SourceProjectParametersValidatorTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.impl; import com.google.common.collect.ImmutableMap; import java.util.HashMap; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.dto.server.DtoFactory; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class SourceProjectParametersValidatorTest { private SourceStorageParametersValidator validator; private SourceStorageDto sourceStorage; @BeforeMethod public void setUp() throws Exception { validator = new SourceStorageParametersValidator(); sourceStorage = DtoFactory.getInstance() .createDto(SourceStorageDto.class) .withLocation("location") .withType("git") .withParameters( new HashMap<>( ImmutableMap.of( "branch", "master", "commitId", "123456", "keepVcs", "true", "fetch", "12345", "keepDir", "/src"))); } @Test public void shouldBeAbleValidateGitSource() throws Exception { validator.validate(sourceStorage, FactoryParameter.Version.V4_0); } @Test public void shouldBeAbleValidateESBWSO2Source() throws Exception { sourceStorage.setType("esbwso2"); validator.validate(sourceStorage, FactoryParameter.Version.V4_0); } @Test( expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "You have provided an invalid parameter .* for this version of Factory parameters.*") public void shouldThrowExceptionIfUnknownParameterIsUsed() throws Exception { sourceStorage.getParameters().put("other", "value"); validator.validate(sourceStorage, FactoryParameter.Version.V4_0); } @Test( expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "The parameter .* has a value submitted .* with a value that is unexpected.*") public void shouldThrowExceptionIfKeepVcsIsNotTrueOrFalse() throws Exception { sourceStorage.getParameters().put("keepVcs", "qwerty"); validator.validate(sourceStorage, FactoryParameter.Version.V4_0); } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/TesterFactoryBaseValidator.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.impl; /** * @author Sergii Kabashniuk */ public class TesterFactoryBaseValidator extends FactoryBaseValidator { public TesterFactoryBaseValidator() {} } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/jpa/FactoryTckModule.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.jpa; import com.google.inject.TypeLiteral; import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; import org.eclipse.che.api.factory.server.spi.FactoryDao; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.commons.test.tck.TckModule; import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; import org.eclipse.che.commons.test.tck.repository.TckRepository; /** * Tck module for factory test. * * @author Yevhenii Voevodin */ public class FactoryTckModule extends TckModule { @Override protected void configure() { bind(FactoryDao.class).to(JpaFactoryDao.class); bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(FactoryImpl.class)); bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(UserImpl.class)); } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/scm/AuthorizingFactoryParameterResolverTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.scm; import static org.mockito.AdditionalAnswers.returnsFirstArg; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(value = {MockitoTestNGListener.class}) public class AuthorizingFactoryParameterResolverTest { @Mock private RemoteFactoryUrl remoteFactoryUrl; @Mock private URLFetcher urlFetcher; @Mock private PersonalAccessTokenManager personalAccessTokenManager; @Mock private PersonalAccessToken personalAccessToken; private AuthorizingFileContentProvider provider; @BeforeMethod public void setUp() throws Exception { provider = new AuthorizingFileContentProvider<>( remoteFactoryUrl, urlFetcher, personalAccessTokenManager); when(remoteFactoryUrl.rawFileLocation(anyString())).thenAnswer(returnsFirstArg()); } @Test public void shouldFetchContentWithAuthentication() throws Exception { // given when(remoteFactoryUrl.getProviderUrl()).thenReturn("https://provider.url"); when(urlFetcher.fetch(anyString(), anyString())).thenReturn("content"); when(personalAccessTokenManager.getAndStore(anyString())).thenReturn(personalAccessToken); // when provider.fetchContent("url"); // then verify(personalAccessTokenManager).getAndStore(anyString()); } @Test public void shouldFetchContentWithoutAuthentication() throws Exception { // given when(urlFetcher.fetch(anyString())).thenReturn("content"); // when provider.fetchContentWithoutAuthentication("url"); // then verify(personalAccessTokenManager, never()).getAndStore(anyString()); } @Test public void shouldOmitDotInTheResourceName() throws Exception { assertEquals(provider.formatUrl("./pom.xml"), "pom.xml"); } @Test public void shouldKeepResourceNameUnchanged() throws Exception { assertEquals(provider.formatUrl(".gitconfig"), ".gitconfig"); } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/DefaultFactoryUrlTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.urlfactory; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.util.Optional; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** Testing {@link DefaultFactoryUrl} */ @Listeners(MockitoTestNGListener.class) public class DefaultFactoryUrlTest { @Test(dataProvider = "urlsProvider") public void shouldGetCredentials(String url, String credentials) { // given DefaultFactoryUrl factoryUrl = new DefaultFactoryUrl().withUrl(url); // when Optional credentialsOptional = factoryUrl.getCredentials(); // then assertTrue(credentialsOptional.isPresent()); assertEquals(credentialsOptional.get(), credentials); } @DataProvider(name = "urlsProvider") private Object[][] urlsProvider() { return new Object[][] { {"https://username:password@hostname/path", "username:password"}, {"https://token@hostname/path/user/repo/", "token:"}, {"http://token@hostname/path/user/repo/", "token:"}, {"https://token@dev.azure.com/user/repo/", "token:"}, {"https://token@dev.azure.com/user/repo?a=&b=b&c=/.devfile.yaml&api-version=7.0", "token:"}, {"https://token@gitlub.com/user/repo/", "token:"}, {"https://token@bitbucket.org/user/repo/", "token:"}, { "https://personal-access-token@raw.githubusercontent.com/user/repo/branch/.devfile.yaml", "personal-access-token:" } }; } @Test public void shouldGetEmptyCredentials() { // given DefaultFactoryUrl factoryUrl = new DefaultFactoryUrl().withUrl("https://hostname/path"); // when Optional credentialsOptional = factoryUrl.getCredentials(); // then assertFalse(credentialsOptional.isPresent()); } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.urlfactory; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.core.rest.shared.dto.ExtendedError; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Testing {@link URLFactoryBuilder} * * @author Florent Benoit * @author Max Shaposhnyk */ @Listeners(MockitoTestNGListener.class) public class URLFactoryBuilderTest { private final String defaultEditor = "eclipse/che-theia/1.0.0"; private final String defaultPlugin = "eclipse/che-machine-exec-plugin/0.0.1"; /** Grab content of URLs */ @Mock private URLFetcher urlFetcher; @Mock private DevfileParser devfileParser; @Mock private DevfileVersionDetector devfileVersionDetector; @Mock private FileContentProvider fileContentProvider; /** Tested instance. */ private URLFactoryBuilder urlFactoryBuilder; @BeforeMethod public void setUp() throws IOException, DevfileException { this.urlFactoryBuilder = new URLFactoryBuilder( defaultEditor, defaultPlugin, true, devfileParser, devfileVersionDetector); } @Test public void checkWithCustomDevfileAndRecipe() throws Exception { DevfileImpl devfile = new DevfileImpl(); WorkspaceConfigImpl workspaceConfigImpl = new WorkspaceConfigImpl(); String myLocation = "http://foo-location/"; RecipeImpl expectedRecipe = new RecipeImpl(KUBERNETES_COMPONENT_TYPE, "application/x-yaml", "content", ""); EnvironmentImpl expectedEnv = new EnvironmentImpl(expectedRecipe, emptyMap()); workspaceConfigImpl.setEnvironments(singletonMap("name", expectedEnv)); workspaceConfigImpl.setDefaultEnv("name"); when(devfileParser.parseYamlRaw(anyString())) .thenReturn(new ObjectNode(JsonNodeFactory.instance)); when(fileContentProvider.fetchContent(anyString())).thenReturn("content"); FactoryMetaDto factory = urlFactoryBuilder .createFactoryFromDevfile( new DefaultFactoryUrl().withDevfileFileLocation(myLocation).withUrl("url"), fileContentProvider, emptyMap(), false) .get(); assertNotNull(factory); assertNull(factory.getSource()); assertTrue(factory instanceof FactoryDevfileV2Dto); } @Test public void testDevfileV2() throws ApiException, DevfileException, IOException { String myLocation = "http://foo-location/"; Map devfileAsMap = Map.of("hello", "there", "how", "are", "you", "?"); JsonNode devfile = new ObjectNode(JsonNodeFactory.instance); when(devfileParser.parseYamlRaw(anyString())).thenReturn(devfile); when(devfileParser.convertYamlToMap(devfile)).thenReturn(devfileAsMap); when(fileContentProvider.fetchContent(anyString())).thenReturn("content"); FactoryMetaDto factory = urlFactoryBuilder .createFactoryFromDevfile( new DefaultFactoryUrl().withDevfileFileLocation(myLocation).withUrl("url"), fileContentProvider, emptyMap(), false) .get(); assertNotNull(factory); assertNull(factory.getSource()); assertTrue(factory instanceof FactoryDevfileV2Dto); assertEquals(((FactoryDevfileV2Dto) factory).getDevfile(), devfileAsMap); } @Test public void testDevfileV2WithFilename() throws ApiException, DevfileException, IOException { String myLocation = "http://foo-location/"; Map devfileAsMap = Map.of("hello", "there", "how", "are", "you", "?"); JsonNode devfile = new ObjectNode(JsonNodeFactory.instance); when(devfileParser.parseYamlRaw(anyString())).thenReturn(devfile); when(devfileParser.convertYamlToMap(devfile)).thenReturn(devfileAsMap); when(fileContentProvider.fetchContent(anyString())).thenReturn("content"); RemoteFactoryUrl githubLikeRemoteUrl = new RemoteFactoryUrl() { @Override public String getProviderName() { return null; } @Override public List devfileFileLocations() { return Collections.singletonList( new DevfileLocation() { @Override public Optional filename() { return Optional.of("devfile.yaml"); } @Override public String location() { return myLocation; } }); } @Override public String rawFileLocation(String filename) { return null; } @Override public String getHostName() { return null; } @Override public String getProviderUrl() { return null; } @Override public String getBranch() { return null; } @Override public Optional getCredentials() { return Optional.empty(); } @Override public void setDevfileFilename(String devfileName) {} }; FactoryMetaDto factory = urlFactoryBuilder .createFactoryFromDevfile(githubLikeRemoteUrl, fileContentProvider, emptyMap(), false) .get(); assertNotNull(factory); assertEquals(factory.getSource(), "devfile.yaml"); assertTrue(factory instanceof FactoryDevfileV2Dto); assertEquals(((FactoryDevfileV2Dto) factory).getDevfile(), devfileAsMap); } @Test public void testDevfileSpecifyingFilename() throws ApiException, DevfileException, IOException { String myLocation = "http://foo-location/"; Map devfileAsMap = Map.of("hello", "there", "how", "are", "you", "?"); JsonNode devfile = new ObjectNode(JsonNodeFactory.instance); when(devfileParser.parseYamlRaw(anyString())).thenReturn(devfile); when(devfileParser.convertYamlToMap(devfile)).thenReturn(devfileAsMap); when(fileContentProvider.fetchContent(anyString())).thenReturn("content"); RemoteFactoryUrl githubLikeRemoteUrl = new RemoteFactoryUrl() { private String devfileName = "default-devfile.yaml"; @Override public String getProviderName() { return null; } @Override public List devfileFileLocations() { return Collections.singletonList( new DevfileLocation() { @Override public Optional filename() { return Optional.of(devfileName); } @Override public String location() { return myLocation; } }); } @Override public String rawFileLocation(String filename) { return null; } @Override public String getHostName() { return null; } @Override public String getProviderUrl() { return null; } @Override public String getBranch() { return null; } @Override public Optional getCredentials() { return Optional.empty(); } @Override public void setDevfileFilename(String devfileName) { this.devfileName = devfileName; } }; String pathToDevfile = "my-custom-devfile-path.yaml"; Map propertiesMap = singletonMap(URLFactoryBuilder.DEVFILE_FILENAME, pathToDevfile); FactoryMetaDto factory = urlFactoryBuilder .createFactoryFromDevfile( githubLikeRemoteUrl, fileContentProvider, propertiesMap, false) .get(); assertNotNull(factory); // Check that we didn't fetch from default files but from the parameter assertEquals(factory.getSource(), pathToDevfile); assertTrue(factory instanceof FactoryDevfileV2Dto); assertEquals(((FactoryDevfileV2Dto) factory).getDevfile(), devfileAsMap); } @Test public void testShouldReturnV2WithDevworkspacesDisabled() throws ApiException, DevfileException, IOException { String myLocation = "http://foo-location/"; Map devfileAsMap = Map.of("hello", "there", "how", "are", "you", "?"); JsonNode devfile = new ObjectNode(JsonNodeFactory.instance); when(devfileParser.parseYamlRaw(anyString())).thenReturn(devfile); when(devfileParser.convertYamlToMap(devfile)).thenReturn(devfileAsMap); when(fileContentProvider.fetchContent(anyString())).thenReturn("content"); URLFactoryBuilder localUrlFactoryBuilder = new URLFactoryBuilder( defaultEditor, defaultPlugin, false, devfileParser, devfileVersionDetector); FactoryMetaDto factory = localUrlFactoryBuilder .createFactoryFromDevfile( new DefaultFactoryUrl().withDevfileFileLocation(myLocation).withUrl("url"), fileContentProvider, emptyMap(), false) .get(); assertNotNull(factory); assertTrue(factory instanceof FactoryDevfileV2Dto); assertEquals(((FactoryDevfileV2Dto) factory).getDevfile(), devfileAsMap); } @DataProvider public Object[][] devfiles() { final String NAME = "name"; final String GEN_NAME = "genName"; DevfileImpl devfileTemplate = new DevfileImpl(); devfileTemplate.setApiVersion("1.0.0"); MetadataImpl metadataTemplate = new MetadataImpl(); metadataTemplate.setName(NAME); devfileTemplate.setMetadata(metadataTemplate); DevfileImpl justName = new DevfileImpl(devfileTemplate); metadataTemplate.setName(null); metadataTemplate.setGenerateName(GEN_NAME); devfileTemplate.setMetadata(metadataTemplate); DevfileImpl justGenerateName = new DevfileImpl(devfileTemplate); metadataTemplate.setName(NAME); metadataTemplate.setGenerateName(GEN_NAME); devfileTemplate.setMetadata(metadataTemplate); DevfileImpl bothNames = new DevfileImpl(devfileTemplate); return new Object[][] {{justName, NAME}, {justGenerateName, GEN_NAME}, {bothNames, GEN_NAME}}; } @Test(dataProvider = "devfileExceptions") public void checkCorrectExceptionThrownDependingOnCause( Throwable cause, Class expectedClass, String expectedMessage, Map expectedAttributes) throws IOException, DevfileException { DefaultFactoryUrl defaultFactoryUrl = mock(DefaultFactoryUrl.class); FileContentProvider fileContentProvider = mock(FileContentProvider.class); when(defaultFactoryUrl.devfileFileLocations()) .thenReturn( singletonList( new DevfileLocation() { @Override public Optional filename() { return Optional.empty(); } @Override public String location() { return "http://foo.bar/anything"; } })); when(fileContentProvider.fetchContent(anyString())) .thenThrow(new DevfileException(expectedMessage, cause)); // when try { urlFactoryBuilder.createFactoryFromDevfile( defaultFactoryUrl, fileContentProvider, emptyMap(), false); } catch (ApiException e) { assertTrue(e.getClass().isAssignableFrom(expectedClass)); assertEquals(e.getMessage(), expectedMessage); if ("SCM Authentication required".equals(e.getMessage())) assertEquals(((ExtendedError) e.getServiceError()).getAttributes(), expectedAttributes); } } @Test( expectedExceptions = ApiException.class, expectedExceptionsMessageRegExp = "Could not reach devfile at location") public void shouldThrowErrorOnUnsupportedDevfileContent() throws ApiException, DevfileException, IOException { JsonNode jsonNode = mock(JsonNode.class); when(fileContentProvider.fetchContent(eq("location"))).thenReturn("unsupported content"); when(devfileParser.parseYamlRaw(eq("unsupported content"))).thenReturn(jsonNode); when(devfileVersionDetector.devfileVersion(eq(jsonNode))).thenThrow(new DevfileException("")); urlFactoryBuilder.createFactoryFromDevfile( new DefaultFactoryUrl().withDevfileFileLocation("location"), fileContentProvider, emptyMap(), false); } @DataProvider public static Object[][] devfileExceptions() { return new Object[][] { { new ScmCommunicationException("foo"), ServerException.class, "There is an error happened when communicate with SCM server. Error message: foo", null }, { new UnknownScmProviderException("foo", "bar"), ServerException.class, "Provided location is unknown or misconfigured on the server side. Error message: foo", null }, { new ScmUnauthorizedException("foo", "bitbucket", "1.0", "http://foo.bar"), UnauthorizedException.class, "SCM Authentication required", Map.of( "oauth_version", "1.0", "oauth_authentication_url", "http://foo.bar", "oauth_provider", "bitbucket") } }; } } ================================================ FILE: wsmaster/che-core-api-factory/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule ================================================ org.eclipse.che.api.factory.server.jpa.FactoryTckModule ================================================ FILE: wsmaster/che-core-api-factory/src/test/resources/logback-test.xml ================================================ true %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n target/log/codenvy-factory-commons.log %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: wsmaster/che-core-api-factory/src/test/resources/logging.properties ================================================ handlers = org.apache.juli.logging.org.slf4j.bridge.SLF4JBridgeHandler ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-factory-azure-devops jar Che Core :: API :: Factory Resolver Azure DevOps true com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.core jackson-databind com.google.guava guava com.google.inject guice jakarta.inject jakarta.inject-api jakarta.validation jakarta.validation-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-shared org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-factory org.eclipse.che.core che-core-api-factory-shared org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang org.slf4j slf4j-api ch.qos.logback logback-classic test com.github.tomakehurst wiremock-jre8-standalone test jakarta.servlet jakarta.servlet-api test jakarta.ws.rs jakarta.ws.rs-api test org.eclipse.che.core che-core-commons-json test org.hamcrest hamcrest-core test org.mockito mockito-core test org.mockito mockito-testng test org.slf4j jcl-over-slf4j test org.testng testng test org.wiremock wiremock-standalone test ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOps.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import com.google.common.base.Joiner; import java.nio.charset.StandardCharsets; import java.util.Base64; /** * Utils for Azure DevOps OAuth. * * @author Anatolii Bazko */ public class AzureDevOps { /** Name of this OAuth provider as found in OAuthAPI. */ public static final String PROVIDER_NAME = "azure-devops"; /** Azure DevOps SAAS endpoint. */ public static final String SAAS_ENDPOINT = "https://dev.azure.com"; /** Azure DevOps Service API version calls. */ public static final String API_VERSION = "7.0"; public static String getAuthenticateUrlPath(String[] scopes) { return "/oauth/authenticate?oauth_provider=" + PROVIDER_NAME + "&scope=" + Joiner.on(" ").join(scopes); } /** The authorization request varies depending on the type of token. */ public static String formatAuthorizationHeader(String token, boolean isPAT) { return isPAT ? "Basic " + Base64.getEncoder().encodeToString((":" + token).getBytes(StandardCharsets.UTF_8)) : "Bearer " + token; } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.time.Duration.ofSeconds; import static org.eclipse.che.api.factory.server.azure.devops.AzureDevOps.formatAuthorizationHeader; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Charsets; import com.google.common.io.CharStreams; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.concurrent.Executors; import java.util.function.Function; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Azure DevOps Service API operations helper. */ @Singleton public class AzureDevOpsApiClient { private static final Logger LOG = LoggerFactory.getLogger(AzureDevOpsApiClient.class); private final HttpClient httpClient; private final String azureDevOpsApiEndpoint; private static final Duration DEFAULT_HTTP_TIMEOUT = ofSeconds(10); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @Inject public AzureDevOpsApiClient( @Named("che.integration.azure.devops.api_endpoint") String azureDevOpsApiEndpoint) { this.azureDevOpsApiEndpoint = trimEnd(azureDevOpsApiEndpoint, '/'); this.httpClient = HttpClient.newBuilder() .executor( Executors.newCachedThreadPool( new ThreadFactoryBuilder() .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setNameFormat(AzureDevOpsApiClient.class.getName() + "-%d") .setDaemon(true) .build())) .connectTimeout(DEFAULT_HTTP_TIMEOUT) .version(HttpClient.Version.HTTP_1_1) .build(); } /** * Returns the user associated with the provided OAuth access token. See Microsoft documentation * at: * *

    https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/users/get?view=azure-devops-rest-7.0&tabs=HTTP */ public AzureDevOpsUser getUserWithOAuthToken(String token) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { final String url = String.format( "%s/_apis/profile/profiles/me?api-version=%s", azureDevOpsApiEndpoint, AzureDevOps.API_VERSION); return getUser(url, formatAuthorizationHeader(token, false)); } /** * Returns the user associated with the provided PAT. The difference from {@code * getUserWithOAuthToken} is in authorization header and the fact that PAT is associated with * organization. */ public AzureDevOpsUser getUserWithPAT(String pat, String organization) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { final String url = String.format( "%s/%s/_apis/profile/profiles/me?api-version=%s", azureDevOpsApiEndpoint, organization, AzureDevOps.API_VERSION); return getUser(url, formatAuthorizationHeader(pat, true)); } private AzureDevOpsUser getUser(String url, String authorizationHeader) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { final HttpRequest userDataRequest; try { userDataRequest = HttpRequest.newBuilder(URI.create(url)) .headers("Authorization", authorizationHeader) .timeout(DEFAULT_HTTP_TIMEOUT) .build(); } catch (IllegalArgumentException e) { throw new ScmBadRequestException(e.getMessage()); } LOG.trace("executeRequest={}", userDataRequest); return executeRequest( httpClient, userDataRequest, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); return OBJECT_MAPPER.readValue(result, AzureDevOpsUser.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } private T executeRequest( HttpClient httpClient, HttpRequest request, Function, T> responseConverter) throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); LOG.trace("executeRequest={} response {}", request, response.statusCode()); if (response.statusCode() == HTTP_OK) { return responseConverter.apply(response); } else if (response.statusCode() == HTTP_NO_CONTENT) { return null; } else { String body = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); switch (response.statusCode()) { case HTTP_BAD_REQUEST: throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); case HTTP_UNAUTHORIZED: case HTTP_FORBIDDEN: // Azure DevOps tries to redirect to the login page if the user is not authorized. case HTTP_MOVED_TEMP: throw new ScmUnauthorizedException(body, "azure-devops", "v2", ""); default: throw new ScmCommunicationException( "Unexpected status code " + response.statusCode() + " " + response, response.statusCode(), "azure-devops"); } } } catch (IOException | InterruptedException | UncheckedIOException e) { throw new ScmCommunicationException(e.getMessage(), e); } } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsAuthorizingFileContentProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static org.eclipse.che.api.factory.server.azure.devops.AzureDevOps.formatAuthorizationHeader; import java.io.IOException; import org.eclipse.che.api.factory.server.scm.AuthorizingFileContentProvider; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** * AzureDevops specific authorizing file content provider. * * @author Anatolii Bazko */ class AzureDevOpsAuthorizingFileContentProvider extends AuthorizingFileContentProvider { AzureDevOpsAuthorizingFileContentProvider( AzureDevOpsUrl azureDevOpsUrl, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { super(azureDevOpsUrl, urlFetcher, personalAccessTokenManager); } @Override protected boolean isPublicRepository(AzureDevOpsUrl remoteFactoryUrl) { try { urlFetcher.fetch(remoteFactoryUrl.getRepositoryLocation()); return true; } catch (IOException e) { return false; } } @Override protected String formatAuthorization(String token, boolean isPAT) { return formatAuthorizationHeader(token, isPAT); } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsFactoryParametersResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static org.eclipse.che.api.factory.shared.Constants.REVISION_PARAMETER_NAME; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.eclipse.che.dto.server.DtoFactory.newDto; import com.google.common.base.Strings; import jakarta.validation.constraints.NotNull; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.factory.server.BaseFactoryParameterResolver; import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; /** * Provides Factory Parameters resolver for Azure DevOps repositories. * * @author Anatolii Bazko */ @Singleton public class AzureDevOpsFactoryParametersResolver extends BaseFactoryParameterResolver implements FactoryParametersResolver { private static final String PROVIDER_NAME = "azure-devops"; /** Parser which will allow to check validity of URLs and create objects. */ private final AzureDevOpsURLParser azureDevOpsURLParser; private final URLFetcher urlFetcher; private final URLFactoryBuilder urlFactoryBuilder; private final PersonalAccessTokenManager personalAccessTokenManager; @Inject public AzureDevOpsFactoryParametersResolver( AzureDevOpsURLParser azureDevOpsURLParser, URLFetcher urlFetcher, URLFactoryBuilder urlFactoryBuilder, PersonalAccessTokenManager personalAccessTokenManager, AuthorisationRequestManager authorisationRequestManager) { super(authorisationRequestManager, urlFactoryBuilder, PROVIDER_NAME); this.azureDevOpsURLParser = azureDevOpsURLParser; this.urlFetcher = urlFetcher; this.urlFactoryBuilder = urlFactoryBuilder; this.personalAccessTokenManager = personalAccessTokenManager; } @Override public boolean accept(@NotNull final Map factoryParameters) { return factoryParameters.containsKey(URL_PARAMETER_NAME) && azureDevOpsURLParser.isValid(factoryParameters.get(URL_PARAMETER_NAME)); } @Override public String getProviderName() { return PROVIDER_NAME; } @Override public FactoryMetaDto createFactory(@NotNull final Map factoryParameters) throws ApiException { // no need to check null value of url parameter as accept() method has performed the check final AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse( factoryParameters.get(URL_PARAMETER_NAME), factoryParameters.get(REVISION_PARAMETER_NAME)); // create factory from the following location if location exists, else create default factory return createFactory( factoryParameters, azureDevOpsUrl, new AzureDevOpsFactoryVisitor(azureDevOpsUrl), new AzureDevOpsAuthorizingFileContentProvider( azureDevOpsUrl, urlFetcher, personalAccessTokenManager)); } /** * Visitor that puts the default devfile or updates devfile projects into the Azure DevOps * Factory, if needed. */ private class AzureDevOpsFactoryVisitor implements FactoryVisitor { private final AzureDevOpsUrl azureDevOpsUrl; private AzureDevOpsFactoryVisitor(AzureDevOpsUrl azureDevOpsUrl) { this.azureDevOpsUrl = azureDevOpsUrl; } @Override public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { ScmInfoDto scmInfo = newDto(ScmInfoDto.class) .withScmProviderName(azureDevOpsUrl.getProviderName()) .withRepositoryUrl(azureDevOpsUrl.getRepositoryLocation()) .withBranch(azureDevOpsUrl.getBranch()); return factoryDto.withScmInfo(scmInfo); } } @Override public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException { return azureDevOpsURLParser.parse(factoryUrl, null); } private SourceStorageDto buildWorkspaceConfigSource(AzureDevOpsUrl azureDevOpsUrl) { Map parameters = new HashMap<>(1); if (!Strings.isNullOrEmpty(azureDevOpsUrl.getBranch())) { parameters.put("branch", azureDevOpsUrl.getBranch()); } if (!Strings.isNullOrEmpty(azureDevOpsUrl.getTag())) { parameters.put("tag", azureDevOpsUrl.getTag()); } return newDto(SourceStorageDto.class) .withLocation(azureDevOpsUrl.getRepositoryLocation()) .withType("git") .withParameters(parameters); } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsModule.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; public class AzureDevOpsModule extends AbstractModule { @Override protected void configure() { Multibinder tokenFetcherMultibinder = Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(AzureDevOpsPersonalAccessTokenFetcher.class); Multibinder gitUserDataMultibinder = Multibinder.newSetBinder(binder(), GitUserDataFetcher.class); gitUserDataMultibinder.addBinding().to(AzureDevOpsUserDataFetcher.class); } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static org.eclipse.che.api.factory.server.azure.devops.AzureDevOps.getAuthenticateUrlPath; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import java.util.Arrays; import java.util.Optional; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.security.oauth.OAuthAPI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Azure DevOps Service OAuth2 token fetcher. * * @author Anatolii Bazko */ public class AzureDevOpsPersonalAccessTokenFetcher implements PersonalAccessTokenFetcher { private static final Logger LOG = LoggerFactory.getLogger(AzureDevOpsPersonalAccessTokenFetcher.class); private static final String OAUTH_PROVIDER_NAME = "azure-devops"; private final String cheApiEndpoint; private final String azureDevOpsSAASApiEndpoint; private final OAuthAPI oAuthAPI; private final String[] scopes; private final AzureDevOpsApiClient azureDevOpsApiClient; @Inject public AzureDevOpsPersonalAccessTokenFetcher( @Named("che.api") String cheApiEndpoint, @Named("che.integration.azure.devops.scm.api_endpoint") String azureDevOpsSAASApiEndpoint, @Named("che.integration.azure.devops.application_scopes") String[] scopes, AzureDevOpsApiClient azureDevOpsApiClient, OAuthAPI oAuthAPI) { this.cheApiEndpoint = cheApiEndpoint; this.azureDevOpsSAASApiEndpoint = trimEnd(azureDevOpsSAASApiEndpoint, '/'); this.oAuthAPI = oAuthAPI; this.scopes = scopes; this.azureDevOpsApiClient = azureDevOpsApiClient; } @Override public PersonalAccessToken refreshPersonalAccessToken(Subject cheSubject, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, true); } @Override public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, false); } private PersonalAccessToken fetchOrRefreshPersonalAccessToken( Subject cheSubject, String scmServerUrl, boolean forceRefreshToken) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { OAuthToken oAuthToken; if (!isValidAzureDevOpsSAASUrl(scmServerUrl)) { LOG.debug("not a valid url {} for current fetcher ", scmServerUrl); return null; } try { oAuthToken = forceRefreshToken ? oAuthAPI.refreshToken(AzureDevOps.PROVIDER_NAME) : oAuthAPI.getOrRefreshToken(AzureDevOps.PROVIDER_NAME); String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5); String tokenId = NameGenerator.generate("id-", 5); Optional> valid = isValid( new PersonalAccessTokenParams( scmServerUrl, OAUTH_PROVIDER_NAME, tokenName, tokenId, oAuthToken.getToken(), null)); if (valid.isEmpty()) { throw buildScmUnauthorizedException(cheSubject); } else if (!valid.get().first) { throw new ScmCommunicationException( "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: " + Arrays.toString(scopes)); } return new PersonalAccessToken( scmServerUrl, OAUTH_PROVIDER_NAME, cheSubject.getUserId(), valid.get().second, tokenName, tokenId, oAuthToken.getToken()); } catch (UnauthorizedException e) { throw buildScmUnauthorizedException(cheSubject); } catch (NotFoundException nfe) { throw new UnknownScmProviderException(nfe.getMessage(), scmServerUrl); } catch (ServerException | ForbiddenException | BadRequestException | ConflictException e) { LOG.error(e.getMessage()); throw new ScmCommunicationException(e.getMessage(), e); } } private ScmUnauthorizedException buildScmUnauthorizedException(Subject cheSubject) { return new ScmUnauthorizedException( cheSubject.getUserName() + " is not authorized in " + AzureDevOps.PROVIDER_NAME + " OAuth provider.", AzureDevOps.PROVIDER_NAME, "2.0", getLocalAuthenticateUrl()); } @Override public Optional isValid(PersonalAccessToken personalAccessToken) { if (!isValidAzureDevOpsSAASUrl(personalAccessToken.getScmProviderUrl())) { LOG.debug("not a valid url {} for current fetcher ", personalAccessToken.getScmProviderUrl()); return Optional.empty(); } try { AzureDevOpsUser user; if (personalAccessToken.getScmTokenName() != null && personalAccessToken.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { user = azureDevOpsApiClient.getUserWithOAuthToken(personalAccessToken.getToken()); } else { user = azureDevOpsApiClient.getUserWithPAT( personalAccessToken.getToken(), personalAccessToken.getScmOrganization()); } return Optional.of(personalAccessToken.getScmUserName().equals(user.getEmailAddress())); } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } @Override public Optional> isValid(PersonalAccessTokenParams params) { if (!isValidAzureDevOpsSAASUrl(params.getScmProviderUrl())) { if (OAUTH_PROVIDER_NAME.equals(params.getScmProviderName())) { AzureDevOpsServerApiClient azureDevOpsServerApiClient = new AzureDevOpsServerApiClient(params.getScmProviderUrl(), params.getOrganization()); try { AzureDevOpsServerUserProfile user = azureDevOpsServerApiClient.getUser(params.getToken()); return Optional.of(Pair.of(Boolean.TRUE, user.getIdentity().getAccountName())); } catch (ScmItemNotFoundException | ScmBadRequestException | ScmCommunicationException e) { return Optional.empty(); } } else { LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); return Optional.empty(); } } try { AzureDevOpsUser user; if (params.getScmTokenName() != null && params.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { user = azureDevOpsApiClient.getUserWithOAuthToken(params.getToken()); } else { user = azureDevOpsApiClient.getUserWithPAT(params.getToken(), params.getOrganization()); } return Optional.of(Pair.of(Boolean.TRUE, user.getEmailAddress())); } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException | ScmCommunicationException e) { return Optional.empty(); } } private String getLocalAuthenticateUrl() { return cheApiEndpoint + getAuthenticateUrlPath(scopes); } private Boolean isValidAzureDevOpsSAASUrl(String url) { return azureDevOpsSAASApiEndpoint.equals(trimEnd(url, '/')); } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsScmFileResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; import jakarta.validation.constraints.NotNull; import java.io.IOException; import javax.inject.Inject; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.factory.server.ScmFileResolver; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** * Azure DevOps specific SCM file resolver. * * @author Anatolii Bazko */ public class AzureDevOpsScmFileResolver implements ScmFileResolver { private final AzureDevOpsURLParser azureDevOpsURLParser; private final URLFetcher urlFetcher; private final PersonalAccessTokenManager personalAccessTokenManager; @Inject public AzureDevOpsScmFileResolver( AzureDevOpsURLParser azureDevOpsURLParser, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { this.azureDevOpsURLParser = azureDevOpsURLParser; this.urlFetcher = urlFetcher; this.personalAccessTokenManager = personalAccessTokenManager; } @Override public boolean accept(@NotNull String repository) { return azureDevOpsURLParser.isValid(repository); } @Override public String fileContent(@NotNull String repository, @NotNull String filePath) throws ApiException { final AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse(repository, null); try { return fetchContent(azureDevOpsUrl, filePath, false); } catch (DevfileException exception) { // This catch might mean that the authentication was rejected by user, try to repeat the fetch // without authentication flow. try { return fetchContent(azureDevOpsUrl, filePath, true); } catch (DevfileException devfileException) { throw toApiException(devfileException); } } } private String fetchContent( AzureDevOpsUrl azureDevOpsUrl, String filePath, boolean skipAuthentication) throws DevfileException, NotFoundException { try { AzureDevOpsAuthorizingFileContentProvider contentProvider = new AzureDevOpsAuthorizingFileContentProvider( azureDevOpsUrl, urlFetcher, personalAccessTokenManager); return skipAuthentication ? contentProvider.fetchContentWithoutAuthentication(filePath) : contentProvider.fetchContent(filePath); } catch (IOException e) { throw new NotFoundException(e.getMessage()); } } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsServerApiClient.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; import static java.nio.charset.StandardCharsets.UTF_8; import static java.time.Duration.ofSeconds; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Charsets; import com.google.common.io.CharStreams; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.Base64; import java.util.concurrent.Executors; import java.util.function.Function; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Azure DevOps Server API operations helper. */ public class AzureDevOpsServerApiClient { private static final Logger LOG = LoggerFactory.getLogger(AzureDevOpsServerApiClient.class); private final HttpClient httpClient; private final String azureDevOpsServerApiEndpoint; private final String azureDevOpsServerCollection; private static final Duration DEFAULT_HTTP_TIMEOUT = ofSeconds(10); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public AzureDevOpsServerApiClient( String azureDevOpsServerApiEndpoint, String azureDevOpsServerCollection) { this.azureDevOpsServerApiEndpoint = trimEnd(azureDevOpsServerApiEndpoint, '/'); this.azureDevOpsServerCollection = azureDevOpsServerCollection; this.httpClient = HttpClient.newBuilder() .executor( Executors.newCachedThreadPool( new ThreadFactoryBuilder() .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setNameFormat(AzureDevOpsServerApiClient.class.getName() + "-%d") .setDaemon(true) .build())) .connectTimeout(DEFAULT_HTTP_TIMEOUT) .version(HttpClient.Version.HTTP_1_1) .build(); } /** * Returns the user associated with the provided PAT. * * @param token personal access token. */ public AzureDevOpsServerUserProfile getUser(String token) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { final String url = String.format( "%s/%s/_api/_common/GetUserProfile", azureDevOpsServerApiEndpoint, azureDevOpsServerCollection); return getUser(url, encodeAuthorizationHeader(token)); } private static String encodeAuthorizationHeader(String token) { return "Basic " + Base64.getEncoder().encodeToString((":" + token).getBytes(UTF_8)); } private AzureDevOpsServerUserProfile getUser(String url, String authorizationHeader) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { final HttpRequest userDataRequest = HttpRequest.newBuilder(URI.create(url)) .headers("Authorization", authorizationHeader) .timeout(DEFAULT_HTTP_TIMEOUT) .build(); LOG.trace("executeRequest={}", userDataRequest); return executeRequest( httpClient, userDataRequest, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); return OBJECT_MAPPER.readValue(result, AzureDevOpsServerUserProfile.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } private T executeRequest( HttpClient httpClient, HttpRequest request, Function, T> responseConverter) throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException { try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); LOG.trace("executeRequest={} response {}", request, response.statusCode()); if (response.statusCode() == HTTP_OK) { return responseConverter.apply(response); } else if (response.statusCode() == HTTP_NO_CONTENT) { return null; } else { String body = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); switch (response.statusCode()) { case HTTP_BAD_REQUEST: throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); default: throw new ScmCommunicationException( "Unexpected status code " + response.statusCode() + " " + response, response.statusCode(), "azure-devops"); } } } catch (IOException | InterruptedException | UncheckedIOException e) { throw new ScmCommunicationException(e.getMessage(), e); } } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsServerUserIdentity.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** Azure DevOps Server user's identity. */ @JsonIgnoreProperties(ignoreUnknown = true) public class AzureDevOpsServerUserIdentity { private String accountName; private String mailAddress; public String getAccountName() { return accountName; } @JsonProperty("AccountName") public void setAccountName(String accountName) { this.accountName = accountName; } public String getMailAddress() { return mailAddress; } @JsonProperty("MailAddress") public void setMailAddress(String mailAddress) { this.mailAddress = mailAddress; } @Override public String toString() { return "AzureDevOpsServerUserIdentity{" + "accountName='" + accountName + '\'' + ", mailAddress='" + mailAddress + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsServerUserPreferences.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** Azure DevOps Server user's preferences. */ @JsonIgnoreProperties(ignoreUnknown = true) public class AzureDevOpsServerUserPreferences { private String preferredEmail; public String getPreferredEmail() { return preferredEmail; } @JsonProperty("PreferredEmail") public void setPreferredEmail(String preferredEmail) { this.preferredEmail = preferredEmail; } @Override public String toString() { return "AzureDevOpsServerUserPreferences{" + "preferredEmail='" + preferredEmail + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsServerUserProfile.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** Azure DevOps Server user's profile. */ @JsonIgnoreProperties(ignoreUnknown = true) public class AzureDevOpsServerUserProfile { private AzureDevOpsServerUserIdentity identity; private AzureDevOpsServerUserPreferences userPreferences; private String defaultMailAddress; public AzureDevOpsServerUserIdentity getIdentity() { return identity; } public void setIdentity(AzureDevOpsServerUserIdentity identity) { this.identity = identity; } public String getDefaultMailAddress() { return defaultMailAddress; } public void setDefaultMailAddress(String defaultMailAddress) { this.defaultMailAddress = defaultMailAddress; } public AzureDevOpsServerUserPreferences getUserPreferences() { return userPreferences; } public void setUserPreferences(AzureDevOpsServerUserPreferences userPreferences) { this.userPreferences = userPreferences; } @Override public String toString() { return "AzureDevOpsServerUserProfile{" + "identity=" + identity + ", userPreferences=" + userPreferences + ", defaultMailAddress='" + defaultMailAddress + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsURLParser.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.regex.Pattern.compile; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import jakarta.validation.constraints.NotNull; import java.net.URI; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; /** * Parser of String Azure DevOps URLs and provide {@link AzureDevOpsUrl} objects. * * @author Anatolii Bazko */ @Singleton public class AzureDevOpsURLParser { private final DevfileFilenamesProvider devfileFilenamesProvider; private final PersonalAccessTokenManager tokenManager; private final String azureDevOpsScmApiEndpointHost; /** * Regexp to find repository details (repository name, organization name and branch or tag) * Examples of valid URLs are in the test class. */ private final Pattern azureDevOpsPattern; private final Pattern azureSSHDevOpsPattern; private final String azureSSHDevOpsPatternTemplate = "^git@ssh\\.%s:v3/(?.*)/(?.*)/(?.*)$"; private final String azureSSHDevOpsServerPatternTemplate = "^ssh://%s(:\\d*)?/(?.*)/(?.*)/_git/(?.*)$"; private final String azureDevOpsPatternTemplate = "^https?://(?[^@]++)?@?%s/(?[^/]++)/((?[^/]++)/)?_git/" + "(?[^?]++)" + "([?&]path=(?[^&]++))?" + "([?&]version=GT(?[^&]++))?" + "([?&]version=GB(?[^&]++))?" + "(.*)"; private static final String PROVIDER_NAME = "azure-devops"; @Inject public AzureDevOpsURLParser( DevfileFilenamesProvider devfileFilenamesProvider, PersonalAccessTokenManager tokenManager, @Named("che.integration.azure.devops.scm.api_endpoint") String azureDevOpsScmApiEndpoint) { this.devfileFilenamesProvider = devfileFilenamesProvider; this.tokenManager = tokenManager; this.azureDevOpsScmApiEndpointHost = trimEnd(azureDevOpsScmApiEndpoint, '/').replaceFirst("https?://", ""); this.azureDevOpsPattern = compile(format(azureDevOpsPatternTemplate, azureDevOpsScmApiEndpointHost)); this.azureSSHDevOpsPattern = compile(format(azureSSHDevOpsPatternTemplate, azureDevOpsScmApiEndpointHost)); } public boolean isValid(@NotNull String url) { String trimmedUrl = trimEnd(url, '/'); return azureDevOpsPattern.matcher(trimmedUrl).matches() || azureSSHDevOpsPattern.matcher(trimmedUrl).matches() // Check whether PAT is configured for the Azure Devops Server URL. It is sufficient to // confirm // that the URL is a valid Azure Devops Server URL. || isUserTokenPresent(trimmedUrl); } // Try to find the given url in a manually added user namespace token secret. private boolean isUserTokenPresent(String repositoryUrl) { Optional serverUrlOptional = getServerUrl(repositoryUrl); if (serverUrlOptional.isPresent()) { String serverUrl = serverUrlOptional.get(); try { PersonalAccessToken accessToken = tokenManager.get(serverUrl); return accessToken.getScmTokenName().equals(PROVIDER_NAME); } catch (ScmConfigurationPersistenceException | ScmCommunicationException | ScmUnauthorizedException | UnknownScmProviderException | UnsatisfiedScmPreconditionException exception) { return false; } } return false; } private Optional getServerUrl(String repositoryUrl) { // If the given repository url is an SSH url, generate the base url from the pattern: // https://. String substring = null; if (repositoryUrl.startsWith("git@ssh.")) { substring = repositoryUrl.substring(8); } else if (repositoryUrl.startsWith("ssh://")) { substring = repositoryUrl.substring(6); } if (!isNullOrEmpty(substring)) { // Handle IPv6 addresses in SSH URLs (e.g., ssh://[2001:db8::1]:port/...) if (substring.startsWith("[")) { int closingBracket = substring.indexOf(']'); if (closingBracket > 0) { return Optional.of("https://" + substring.substring(0, closingBracket + 1)); } } return Optional.of( "https://" + substring.substring( 0, substring.contains(":") ? substring.indexOf(":") : substring.indexOf("/"))); } // Use URI parsing to properly handle IPv6 addresses try { URI uri = URI.create(repositoryUrl); if (uri.getScheme() != null && uri.getHost() != null) { String authority = uri.getRawAuthority(); if (authority == null) { String host = uri.getHost(); boolean ipv6 = host != null && host.contains(":"); String hostForUrl = ipv6 ? "[" + host + "]" : host; int port = uri.getPort(); authority = port == -1 ? hostForUrl : hostForUrl + ":" + port; } if (authority != null) { String serverUrl = uri.getScheme() + "://" + authority; // Remove path and query from the server URL int authorityIdx = repositoryUrl.indexOf(authority); if (authorityIdx >= 0) { int pathIndex = authorityIdx + authority.length(); if (pathIndex < repositoryUrl.length() && repositoryUrl.charAt(pathIndex) == '/') { return Optional.of(serverUrl); } } } } } catch (IllegalArgumentException e) { // Fall through to old logic if URI parsing fails } // Otherwise, extract the base url from the given repository url by cutting the url after the // first slash. Matcher serverUrlMatcher = compile("[^/|:]/").matcher(repositoryUrl); if (serverUrlMatcher.find()) { return Optional.of( repositoryUrl.substring(0, repositoryUrl.indexOf(serverUrlMatcher.group()) + 1)); } return Optional.empty(); } private Optional getPatternMatcherByUrl(String url) { URI uri = URI.create(url); String host = uri.getHost(); // Handle IPv6 addresses: escape brackets for regex final String hostForRegex; if (host != null && host.startsWith("[") && host.endsWith("]")) { // IPv6 address - escape the brackets hostForRegex = "\\[" + Pattern.quote(host.substring(1, host.length() - 1)) + "\\]"; } else if (host != null) { // Regular hostname - escape special regex characters hostForRegex = Pattern.quote(host); } else { hostForRegex = ""; } Matcher matcher = compile(format(azureDevOpsPatternTemplate, hostForRegex)).matcher(url); if (matcher.matches()) { return Optional.of(matcher); } else { matcher = compile(format(azureSSHDevOpsPatternTemplate, hostForRegex)).matcher(url); if (matcher.matches()) { return Optional.of(matcher); } else { matcher = compile(format(azureSSHDevOpsServerPatternTemplate, hostForRegex)).matcher(url); } return matcher.matches() ? Optional.of(matcher) : Optional.empty(); } } private IllegalArgumentException buildIllegalArgumentException(String url) { return new IllegalArgumentException( format("The given url %s is not a valid Azure DevOps URL. ", url)); } public AzureDevOpsUrl parse(String url, @Nullable String revision) { Matcher matcher = azureDevOpsPattern.matcher(url); boolean isHTTPSUrl = matcher.matches(); if (!isHTTPSUrl) { matcher = azureSSHDevOpsPattern.matcher(url); if (!matcher.matches()) { matcher = getPatternMatcherByUrl(url).orElseThrow(() -> buildIllegalArgumentException(url)); isHTTPSUrl = url.startsWith("http"); } } String serverUrl = getServerUrl(url).orElseThrow(() -> buildIllegalArgumentException(url)); String repoName = matcher.group("repoName"); String project = matcher.group("project"); if (project == null) { // if project is not specified, repo name must be equal to project name // https://dev.azure.com///_git/ == // https://dev.azure.com//_git/ project = repoName; } String branchFromUrl = null; String tag = null; String organization = matcher.group("organization"); String urlToReturn = url; if (isHTTPSUrl) { branchFromUrl = matcher.group("branch"); tag = matcher.group("tag"); // The url might have the following formats: // - https://@///_git/ // - https://@///_git/ // For the first case we need to remove the `organization` from the url to distinguish it from // `credentials` // TODO: return empty credentials like the BitBucketUrl String organizationCanIgnore = matcher.group("organizationCanIgnore"); if (!isNullOrEmpty(organization) && organization.equals(organizationCanIgnore)) { urlToReturn = urlToReturn.replace(organizationCanIgnore + "@", ""); serverUrl = serverUrl.replace(organizationCanIgnore + "@", ""); } } return new AzureDevOpsUrl() .withHostName( url.startsWith("git@ssh.") ? azureDevOpsScmApiEndpointHost : URI.create(url).getHost()) .setIsHTTPSUrl(isHTTPSUrl) .withProject(project) .withRepository(repoName) .withOrganization(organization) .withBranch(isNullOrEmpty(branchFromUrl) ? revision : branchFromUrl) .withTag(tag) .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .withServerUrl(serverUrl) .withUrl(urlToReturn); } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsUrl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static com.google.common.base.Strings.isNullOrEmpty; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.StringJoiner; import java.util.stream.Collectors; import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; /** * Representation of Azure DevOps URL, allowing to get details from it. * *

    https://learn.microsoft.com/en-us/rest/api/azure/devops/git/items/get?view=azure-devops-rest-7.0&tabs=HTTP * Repository should allow OAUth requests TODO doc * * @author Anatolii Bazko */ public class AzureDevOpsUrl extends DefaultFactoryUrl { private boolean isHTTPSUrl; private String hostName; private String repository; private String project; private String organization; private String branch; private String tag; private String serverUrl; private final List devfileFilenames = new ArrayList<>(); protected AzureDevOpsUrl() {} @Override public String getProviderName() { return AzureDevOps.PROVIDER_NAME; } public String getProject() { return project; } public AzureDevOpsUrl withProject(String project) { this.project = project; return this; } public String getRepository() { return this.repository; } public AzureDevOpsUrl withRepository(String repository) { this.repository = repository; return this; } public String getOrganization() { return organization; } public AzureDevOpsUrl withOrganization(String organization) { this.organization = organization; return this; } public String getTag() { return tag; } public AzureDevOpsUrl withTag(String tag) { this.tag = tag; return this; } @Override public String getBranch() { return branch; } public AzureDevOpsUrl withBranch(String branch) { this.branch = branch; return this; } @Override public String getProviderUrl() { return isNullOrEmpty(serverUrl) ? "https://" + hostName : serverUrl; } protected AzureDevOpsUrl withDevfileFilenames(List devfileFilenames) { this.devfileFilenames.addAll(devfileFilenames); return this; } public AzureDevOpsUrl withServerUrl(String serverUrl) { this.serverUrl = serverUrl; return this; } @Override public void setDevfileFilename(String devfileName) { this.devfileFilenames.clear(); this.devfileFilenames.add(devfileName); } @Override public List devfileFileLocations() { return devfileFilenames.stream().map(this::createDevfileLocation).collect(Collectors.toList()); } private DevfileLocation createDevfileLocation(String devfileFilename) { return new DevfileLocation() { @Override public Optional filename() { return Optional.of(devfileFilename); } @Override public String location() { return rawFileLocation(devfileFilename); } }; } public String rawFileLocation(String fileName) { return getRepoPathJoiner() .add("_apis") .add("git") .add("repositories") .add(repository) .add( "items" + String.format("?path=/%s", fileName) + (!isNullOrEmpty(branch) ? String.format("&versionType=branch&version=%s", branch) : "") + (!isNullOrEmpty(tag) ? String.format("&versionType=tag&version=%s", tag) : "") + String.format("&api-version=%s", AzureDevOps.API_VERSION)) .toString(); } public String getRepositoryLocation() { if (isHTTPSUrl) { return getRepoPathJoiner().add("_git").add(repository).toString(); } if ("dev.azure.com".equals(hostName)) { return "git@ssh." + hostName + ":v3/" + organization + "/" + project + "/" + repository; } else { return "ssh://" + hostName + ":22/" + organization + "/" + project + "/_git/" + repository; } } private StringJoiner getRepoPathJoiner() { return new StringJoiner("/").add(getProviderUrl()).add(organization).add(project); } @Override public String getHostName() { return hostName; } public AzureDevOpsUrl setIsHTTPSUrl(boolean isHTTPSUrl) { this.isHTTPSUrl = isHTTPSUrl; return this; } public AzureDevOpsUrl withHostName(String hostName) { this.hostName = hostName; return this; } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsUser.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** * Azure DevOps user's profile. See more details at: * https://learn.microsoft.com/en-us/rest/api/azure/devops/profile/profiles/get?view=azure-devops-rest-7.0&tabs=HTTP#profile */ @JsonIgnoreProperties(ignoreUnknown = true) public class AzureDevOpsUser { private String displayName; private String emailAddress; private String id; public String getDisplayName() { return displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } public String getEmailAddress() { return emailAddress; } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String toString() { return "AzureDevOpsUser{" + "displayName='" + displayName + '\'' + ", emailAddress='" + emailAddress + '\'' + ", id='" + id + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsUserDataFetcher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.api.factory.server.azure.devops.AzureDevOps.SAAS_ENDPOINT; import static org.eclipse.che.api.factory.server.azure.devops.AzureDevOps.getAuthenticateUrlPath; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.factory.server.scm.AbstractGitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.GitUserData; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; /** * Azure DevOps user data fetcher. * * @author Anatolii Bazko */ public class AzureDevOpsUserDataFetcher extends AbstractGitUserDataFetcher { private final String cheApiEndpoint; private final String[] scopes; private final AzureDevOpsApiClient azureDevOpsApiClient; private static final String NO_USERNAME_AND_EMAIL_ERROR_MESSAGE = "User name and/or email is not found in the azure devops profile."; @Inject public AzureDevOpsUserDataFetcher( PersonalAccessTokenManager personalAccessTokenManager, AzureDevOpsApiClient azureDevOpsApiClient, @Named("che.api") String cheApiEndpoint, @Named("che.integration.azure.devops.scm.api_endpoint") String azureDevOpsScmApiEndpoint, @Named("che.integration.azure.devops.application_scopes") String[] scopes) { super(AzureDevOps.PROVIDER_NAME, azureDevOpsScmApiEndpoint, personalAccessTokenManager); this.scopes = scopes; this.cheApiEndpoint = cheApiEndpoint; this.azureDevOpsApiClient = azureDevOpsApiClient; } @Override protected GitUserData fetchGitUserDataWithOAuthToken(String token) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { AzureDevOpsUser user = azureDevOpsApiClient.getUserWithOAuthToken(token); return new GitUserData(user.getDisplayName(), user.getEmailAddress()); } @Override protected GitUserData fetchGitUserDataWithPersonalAccessToken( PersonalAccessToken personalAccessToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { if (SAAS_ENDPOINT.equals(personalAccessToken.getScmProviderUrl())) { AzureDevOpsUser user = azureDevOpsApiClient.getUserWithPAT( personalAccessToken.getToken(), personalAccessToken.getScmOrganization()); if (isNullOrEmpty(user.getDisplayName()) || isNullOrEmpty(user.getEmailAddress())) { throw new ScmItemNotFoundException(NO_USERNAME_AND_EMAIL_ERROR_MESSAGE); } else { return new GitUserData(user.getDisplayName(), user.getEmailAddress()); } } else { AzureDevOpsServerApiClient apiClient = new AzureDevOpsServerApiClient( personalAccessToken.getScmProviderUrl(), personalAccessToken.getScmOrganization()); AzureDevOpsServerUserProfile user = apiClient.getUser(personalAccessToken.getToken()); String defaultMailAddress = user.getDefaultMailAddress(); String identityMailAddress = user.getIdentity().getMailAddress(); String preferredEmail = user.getUserPreferences().getPreferredEmail(); String email = isNullOrEmpty(defaultMailAddress) ? (isNullOrEmpty(identityMailAddress) ? preferredEmail : identityMailAddress) : defaultMailAddress; String name = user.getIdentity().getAccountName(); if (isNullOrEmpty(name) || isNullOrEmpty(email)) { throw new ScmItemNotFoundException(NO_USERNAME_AND_EMAIL_ERROR_MESSAGE); } else { return new GitUserData(name, email); } } } protected String getLocalAuthenticateUrl() { return cheApiEndpoint + getAuthenticateUrlPath(scopes); } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcherTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.*; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.net.HttpHeaders; import java.util.Base64; import java.util.Collections; import java.util.Optional; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Anatalii Bazko */ @Listeners(MockitoTestNGListener.class) public class AzureDevOpsPersonalAccessTokenFetcherTest { @Mock private AzureDevOpsApiClient azureDevOpsApiClient; @Mock private OAuthAPI oAuthAPI; @Mock private OAuthToken oAuthToken; @Mock private AzureDevOpsUser azureDevOpsUser; private final String azureDevOpsToken = "token"; final int httpPort = 3301; WireMockServer wireMockServer; WireMock wireMock; final String azureOauthToken = "token"; private AzureDevOpsPersonalAccessTokenFetcher personalAccessTokenFetcher; @BeforeMethod protected void start() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).port(httpPort)); wireMockServer.start(); WireMock.configureFor("localhost", httpPort); wireMock = new WireMock("localhost", httpPort); personalAccessTokenFetcher = new AzureDevOpsPersonalAccessTokenFetcher( "localhost", "http://localhost:3301", new String[] {}, new AzureDevOpsApiClient(wireMockServer.url("/")), oAuthAPI); } @AfterMethod void stop() { wireMockServer.stop(); } @Test public void fetchPersonalAccessTokenShouldReturnNullIfScmServerUrlIsNotAzureDevOps() throws Exception { PersonalAccessToken personalAccessToken = personalAccessTokenFetcher.fetchPersonalAccessToken( mock(Subject.class), "https://eclipse.org"); assertNull(personalAccessToken); } @Test public void fetchPersonalAccessTokenShouldReturnToken() throws Exception { personalAccessTokenFetcher = new AzureDevOpsPersonalAccessTokenFetcher( "localhost", "https://dev.azure.com", new String[] {}, azureDevOpsApiClient, oAuthAPI); when(oAuthAPI.getOrRefreshToken(AzureDevOps.PROVIDER_NAME)).thenReturn(oAuthToken); when(azureDevOpsApiClient.getUserWithOAuthToken(any())).thenReturn(azureDevOpsUser); when(azureDevOpsUser.getEmailAddress()).thenReturn("user-email"); PersonalAccessToken personalAccessToken = personalAccessTokenFetcher.fetchPersonalAccessToken( mock(Subject.class), "https://dev.azure.com/"); assertNotNull(personalAccessToken); } @Test( expectedExceptions = ScmUnauthorizedException.class, expectedExceptionsMessageRegExp = "Username is not authorized in azure-devops OAuth provider.") public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(azureOauthToken).withScope(""); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/_apis/profile/profiles/me?api-version=7.0")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + azureOauthToken)) .willReturn(aResponse().withStatus(HTTP_FORBIDDEN))); personalAccessTokenFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @Test(expectedExceptions = ScmUnauthorizedException.class) public void shouldThrowUnauthorizedExceptionOnRedirectResponse() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(azureOauthToken).withScope(""); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/_apis/profile/profiles/me?api-version=7.0")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + azureOauthToken)) .willReturn(aResponse().withStatus(HTTP_MOVED_TEMP))); personalAccessTokenFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @Test public void shouldValidateSAASPersonalAccessToken() throws Exception { stubFor( get(urlEqualTo("/organization/_apis/profile/profiles/me?api-version=7.0")) .withHeader( HttpHeaders.AUTHORIZATION, equalTo( "Basic " + Base64.getEncoder().encodeToString((":" + azureDevOpsToken).getBytes()))) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("azure-devops/rest/user/response.json"))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( wireMockServer.url("/"), "azure-devops", "token-name", "tid-23434", azureDevOpsToken, "organization"); Optional> valid = personalAccessTokenFetcher.isValid(params); assertTrue(valid.isPresent()); assertTrue(valid.get().first); } @Test public void shouldNotValidateSAASPersonalAccessToken() throws Exception { PersonalAccessTokenParams params = new PersonalAccessTokenParams( wireMockServer.url("/"), "azure-devops", "token-name", "tid-23434", azureDevOpsToken, "invalid organization"); Optional> valid = personalAccessTokenFetcher.isValid(params); assertTrue(valid.isEmpty()); } @Test public void shouldValidateServerPersonalAccessToken() throws Exception { personalAccessTokenFetcher = new AzureDevOpsPersonalAccessTokenFetcher( "localhost", "https://dev.azure-server.com", new String[] {}, new AzureDevOpsApiClient(wireMockServer.url("/")), oAuthAPI); stubFor( get(urlEqualTo("/organization/_api/_common/GetUserProfile")) .withHeader( HttpHeaders.AUTHORIZATION, equalTo( "Basic " + Base64.getEncoder().encodeToString((":" + azureDevOpsToken).getBytes()))) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("azure-devops-server/rest/user/response.json"))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( wireMockServer.url("/"), "azure-devops", "token-name", "tid-23434", azureDevOpsToken, "organization"); Optional> valid = personalAccessTokenFetcher.isValid(params); assertTrue(valid.isPresent()); assertTrue(valid.get().first); } @Test public void shouldValidateOauthToken() throws Exception { stubFor( get(urlEqualTo("/_apis/profile/profiles/me?api-version=7.0")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + azureDevOpsToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("azure-devops/rest/user/response.json"))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( wireMockServer.url("/"), "dev-azure", OAUTH_2_PREFIX + "-token-name", "tid-23434", azureDevOpsToken, "organization"); Optional> valid = personalAccessTokenFetcher.isValid(params); assertTrue(valid.isPresent()); assertTrue(valid.get().first); } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsURLParserTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; import java.util.Optional; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Anatalii Bazko */ @Listeners(MockitoTestNGListener.class) public class AzureDevOpsURLParserTest { private AzureDevOpsURLParser azureDevOpsURLParser; @BeforeMethod protected void start() { azureDevOpsURLParser = new AzureDevOpsURLParser( mock(DevfileFilenamesProvider.class), mock(PersonalAccessTokenManager.class), "https://dev.azure.com/"); } @Test(expectedExceptions = IllegalArgumentException.class) public void testParseInvalidUrl() { azureDevOpsURLParser.parse("http://www.eclipse.org", null); } @Test public void shouldParseWithBranch() { AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse("https://dev.azure.com/MyOrg/MyProject/_git/MyRepo", "branch"); assertEquals(azureDevOpsUrl.getBranch(), "branch"); } @Test public void shouldParseWithUrlBranch() { AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse( "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?version=GBmain", "branch"); assertEquals(azureDevOpsUrl.getBranch(), "main"); } @Test public void shouldParseServerUrlWithIpv6Host() { // given azureDevOpsURLParser = new AzureDevOpsURLParser( mock(DevfileFilenamesProvider.class), mock(PersonalAccessTokenManager.class), "https://[2001:db8::1]/"); // when AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse("https://[2001:db8::1]/MyOrg/MyProject/_git/MyRepo", null); // then assertEquals(azureDevOpsUrl.getOrganization(), "MyOrg"); assertEquals(azureDevOpsUrl.getProject(), "MyProject"); assertEquals(azureDevOpsUrl.getRepository(), "MyRepo"); } @Test public void shouldParseIpv6UrlWithBranch() { // given azureDevOpsURLParser = new AzureDevOpsURLParser( mock(DevfileFilenamesProvider.class), mock(PersonalAccessTokenManager.class), "https://[2001:db8::1]/"); // when AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse( "https://[2001:db8::1]/MyOrg/MyProject/_git/MyRepo?version=GBfeature-branch", null); // then assertEquals(azureDevOpsUrl.getOrganization(), "MyOrg"); assertEquals(azureDevOpsUrl.getProject(), "MyProject"); assertEquals(azureDevOpsUrl.getRepository(), "MyRepo"); assertEquals(azureDevOpsUrl.getBranch(), "feature-branch"); } @Test public void shouldParseIpv6UrlWithTag() { // given azureDevOpsURLParser = new AzureDevOpsURLParser( mock(DevfileFilenamesProvider.class), mock(PersonalAccessTokenManager.class), "https://[2001:db8::1]/"); // when AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse( "https://[2001:db8::1]/MyOrg/MyProject/_git/MyRepo?version=GTv1.0", null); // then assertEquals(azureDevOpsUrl.getOrganization(), "MyOrg"); assertEquals(azureDevOpsUrl.getProject(), "MyProject"); assertEquals(azureDevOpsUrl.getRepository(), "MyRepo"); assertEquals(azureDevOpsUrl.getTag(), "v1.0"); } @Test public void shouldParseIpv6UrlWithRevisionParam() { // given azureDevOpsURLParser = new AzureDevOpsURLParser( mock(DevfileFilenamesProvider.class), mock(PersonalAccessTokenManager.class), "https://[2001:db8::1]/"); // when AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse( "https://[2001:db8::1]/MyOrg/MyProject/_git/MyRepo", "my-branch"); // then assertEquals(azureDevOpsUrl.getOrganization(), "MyOrg"); assertEquals(azureDevOpsUrl.getProject(), "MyProject"); assertEquals(azureDevOpsUrl.getRepository(), "MyRepo"); assertEquals(azureDevOpsUrl.getBranch(), "my-branch"); } @Test public void shouldParseIpv6LoopbackAddress() { // given azureDevOpsURLParser = new AzureDevOpsURLParser( mock(DevfileFilenamesProvider.class), mock(PersonalAccessTokenManager.class), "https://[::1]/"); // when AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse("https://[::1]/MyOrg/MyProject/_git/MyRepo", null); // then assertEquals(azureDevOpsUrl.getOrganization(), "MyOrg"); assertEquals(azureDevOpsUrl.getProject(), "MyProject"); assertEquals(azureDevOpsUrl.getRepository(), "MyRepo"); } @Test public void shouldParseIpv6FullFormAddress() { // given azureDevOpsURLParser = new AzureDevOpsURLParser( mock(DevfileFilenamesProvider.class), mock(PersonalAccessTokenManager.class), "https://[2001:0db8:0000:0000:0000:0000:0000:0001]/"); // when AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse( "https://[2001:0db8:0000:0000:0000:0000:0000:0001]/MyOrg/MyProject/_git/MyRepo", null); // then assertEquals(azureDevOpsUrl.getOrganization(), "MyOrg"); assertEquals(azureDevOpsUrl.getProject(), "MyProject"); assertEquals(azureDevOpsUrl.getRepository(), "MyRepo"); } @Test public void shouldParseIpv6WithCredentials() { // given azureDevOpsURLParser = new AzureDevOpsURLParser( mock(DevfileFilenamesProvider.class), mock(PersonalAccessTokenManager.class), "https://[2001:db8::1]/"); // when AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse( "https://user:pwd@[2001:db8::1]/MyOrg/MyProject/_git/MyRepo", null); // then assertEquals(azureDevOpsUrl.getOrganization(), "MyOrg"); assertEquals(azureDevOpsUrl.getProject(), "MyProject"); assertEquals(azureDevOpsUrl.getRepository(), "MyRepo"); assertEquals(azureDevOpsUrl.getCredentials(), Optional.of("user:pwd")); } @Test public void shouldParseIpv6UrlViaDynamicPatternMatching() { // The parser is configured for dev.azure.com, but getPatternMatcherByUrl() dynamically // creates a pattern from the URL itself, so IPv6 URLs can be parsed even when the // constructor was configured with a different host. AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse("https://[2001:db8::1]/MyOrg/MyProject/_git/MyRepo", null); assertEquals(azureDevOpsUrl.getOrganization(), "MyOrg"); assertEquals(azureDevOpsUrl.getProject(), "MyProject"); assertEquals(azureDevOpsUrl.getRepository(), "MyRepo"); } @Test(dataProvider = "parsing") public void testParse( String url, String organization, String project, String repository, String branch, String tag) { AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse(url, null); assertEquals(azureDevOpsUrl.getOrganization(), organization); assertEquals(azureDevOpsUrl.getProject(), project); assertEquals(azureDevOpsUrl.getRepository(), repository); assertEquals(azureDevOpsUrl.getBranch(), branch); assertEquals(azureDevOpsUrl.getTag(), tag); } @Test(dataProvider = "parsingServer") public void testParseServer( String url, String organization, String project, String repository, String branch, String tag) { AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse(url, null); assertEquals(azureDevOpsUrl.getOrganization(), organization); assertEquals(azureDevOpsUrl.getProject(), project); assertEquals(azureDevOpsUrl.getRepository(), repository); assertEquals(azureDevOpsUrl.getBranch(), branch); assertEquals(azureDevOpsUrl.getTag(), tag); } @DataProvider(name = "parsing") public Object[][] expectedParsing() { return new Object[][] { { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo", "MyOrg", "MyProject", "MyRepo", null, null }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo.git", "MyOrg", "MyProject", "MyRepo.git", null, null }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo.dot.git", "MyOrg", "MyProject", "MyRepo.dot.git", null, null }, { "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo", "MyOrg", "MyProject", "MyRepo", null, null }, { "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo-with-hypen", "MyOrg", "MyProject", "MyRepo-with-hypen", null, null }, {"https://dev.azure.com/MyOrg/MyProject/_git/-", "MyOrg", "MyProject", "-", null, null}, { "https://dev.azure.com/MyOrg/MyProject/_git/-j.git", "MyOrg", "MyProject", "-j.git", null, null }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain&_a=contents", "MyOrg", "MyProject", "MyRepo", "main", null }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo", "MyOrg", "MyProject", "MyRepo", null, null }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo.git", "MyOrg", "MyProject", "MyRepo.git", null, null }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo.dot.git", "MyOrg", "MyProject", "MyRepo.dot.git", null, null }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo", "MyOrg", "MyProject", "MyRepo", null, null }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo-with-hypen", "MyOrg", "MyProject", "MyRepo-with-hypen", null, null }, {"git@ssh.dev.azure.com:v3/MyOrg/MyProject/-", "MyOrg", "MyProject", "-", null, null}, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/-j.git", "MyOrg", "MyProject", "-j.git", null, null }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain", "MyOrg", "MyProject", "MyRepo", "main", null }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag&_a=contents", "MyOrg", "MyProject", "MyRepo", null, "MyTag" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag", "MyOrg", "MyProject", "MyRepo", null, "MyTag" }, {"https://MyOrg@dev.azure.com/MyOrg/_git/MyRepo", "MyOrg", "MyRepo", "MyRepo", null, null}, }; } @DataProvider(name = "parsingServer") public Object[][] expectedServerParsing() { return new Object[][] { { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo", "MyOrg", "MyProject", "MyRepo", null, null }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo.git", "MyOrg", "MyProject", "MyRepo.git", null, null }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo.dot.git", "MyOrg", "MyProject", "MyRepo.dot.git", null, null }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo-with-hypen", "MyOrg", "MyProject", "MyRepo-with-hypen", null, null }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/-", "MyOrg", "MyProject", "-", null, null }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/-j.git", "MyOrg", "MyProject", "-j.git", null, null }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain&_a=contents", "MyOrg", "MyProject", "MyRepo", "main", null }, { "ssh://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo", "MyOrg", "MyProject", "MyRepo", null, null }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo", "MyOrg", "MyProject", "MyRepo", null, null }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo.git", "MyOrg", "MyProject", "MyRepo.git", null, null }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo.dot.git", "MyOrg", "MyProject", "MyRepo.dot.git", null, null }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo", "MyOrg", "MyProject", "MyRepo", null, null }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo-with-hypen", "MyOrg", "MyProject", "MyRepo-with-hypen", null, null }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/-", "MyOrg", "MyProject", "-", null, null }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/-j.git", "MyOrg", "MyProject", "-j.git", null, null }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain", "MyOrg", "MyProject", "MyRepo", "main", null }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag&_a=contents", "MyOrg", "MyProject", "MyRepo", null, "MyTag" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag", "MyOrg", "MyProject", "MyRepo", null, "MyTag" }, {"https://dev.azure-server.com/MyOrg/_git/MyRepo", "MyOrg", "MyRepo", "MyRepo", null, null}, }; } @Test(dataProvider = "url") public void testCredentials(String url, String organization, Optional credentials) { AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse(url, null); assertEquals(azureDevOpsUrl.getOrganization(), organization); assertEquals(azureDevOpsUrl.getCredentials(), credentials); } @DataProvider(name = "url") public Object[][] url() { return new Object[][] { {"https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo", "MyOrg", Optional.empty()}, { "https://user:pwd@dev.azure.com/MyOrg/MyProject/_git/MyRepo", "MyOrg", Optional.of("user:pwd") }, }; } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsURLTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.azure.devops; import static java.lang.String.format; import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; import java.util.Arrays; import java.util.Iterator; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class AzureDevOpsURLTest { private AzureDevOpsURLParser azureDevOpsURLParser; @BeforeMethod protected void init() { azureDevOpsURLParser = new AzureDevOpsURLParser( mock(DevfileFilenamesProvider.class), mock(PersonalAccessTokenManager.class), "https://dev.azure.com/"); } @Test(dataProvider = "urlsProvider") public void checkDevfileLocation(String repoUrl, String fileUrl) { AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser .parse(repoUrl, null) .withDevfileFilenames(Arrays.asList("devfile.yaml", "foo.bar")); assertEquals(azureDevOpsUrl.devfileFileLocations().size(), 2); Iterator iterator = azureDevOpsUrl.devfileFileLocations().iterator(); String location = iterator.next().location(); assertEquals(location, format(fileUrl, "devfile.yaml")); assertEquals(location, format(fileUrl, "foo.bar")); } @Test(dataProvider = "urlsProviderServer") public void checkDevfileLocationServer(String repoUrl, String fileUrl) { AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser .parse(repoUrl, null) .withDevfileFilenames(Arrays.asList("devfile.yaml", "foo.bar")); assertEquals(azureDevOpsUrl.devfileFileLocations().size(), 2); Iterator iterator = azureDevOpsUrl.devfileFileLocations().iterator(); String location = iterator.next().location(); assertEquals(location, format(fileUrl, "devfile.yaml")); assertEquals(location, format(fileUrl, "foo.bar")); } @DataProvider public static Object[][] urlsProvider() { return new Object[][] { { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&api-version=7.0" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo.git", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo.git/items?path=/devfile.yaml&api-version=7.0" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo.dot.git", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo.dot.git/items?path=/devfile.yaml&api-version=7.0" }, { "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&api-version=7.0" }, { "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo-with-hypen", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo-with-hypen/items?path=/devfile.yaml&api-version=7.0" }, { "https://dev.azure.com/MyOrg/MyProject/_git/-", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/-/items?path=/devfile.yaml&api-version=7.0" }, { "https://dev.azure.com/MyOrg/MyProject/_git/-j.git", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/-j.git/items?path=/devfile.yaml&api-version=7.0" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&api-version=7.0" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo.git", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo.git/items?path=/devfile.yaml&api-version=7.0" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo.dot.git", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo.dot.git/items?path=/devfile.yaml&api-version=7.0" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&api-version=7.0" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo-with-hypen", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo-with-hypen/items?path=/devfile.yaml&api-version=7.0" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/-", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/-/items?path=/devfile.yaml&api-version=7.0" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/-j.git", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/-j.git/items?path=/devfile.yaml&api-version=7.0" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain&_a=contents", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&versionType=branch&version=main&api-version=7.0" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&versionType=branch&version=main&api-version=7.0" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag&_a=contents", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&versionType=tag&version=MyTag&api-version=7.0" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag", "https://dev.azure.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&versionType=tag&version=MyTag&api-version=7.0" }, { "https://MyOrg@dev.azure.com/MyOrg/_git/MyRepo", "https://dev.azure.com/MyOrg/MyRepo/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&api-version=7.0" } }; } @DataProvider public static Object[][] urlsProviderServer() { return new Object[][] { { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&api-version=7.0" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo.git", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo.git/items?path=/devfile.yaml&api-version=7.0" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo.dot.git", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo.dot.git/items?path=/devfile.yaml&api-version=7.0" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo-with-hypen", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo-with-hypen/items?path=/devfile.yaml&api-version=7.0" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/-", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/-/items?path=/devfile.yaml&api-version=7.0" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/-j.git", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/-j.git/items?path=/devfile.yaml&api-version=7.0" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&api-version=7.0" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo.git", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo.git/items?path=/devfile.yaml&api-version=7.0" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo.dot.git", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo.dot.git/items?path=/devfile.yaml&api-version=7.0" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo-with-hypen", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo-with-hypen/items?path=/devfile.yaml&api-version=7.0" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/-", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/-/items?path=/devfile.yaml&api-version=7.0" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/-j.git", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/-j.git/items?path=/devfile.yaml&api-version=7.0" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain&_a=contents", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&versionType=branch&version=main&api-version=7.0" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&versionType=branch&version=main&api-version=7.0" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag&_a=contents", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&versionType=tag&version=MyTag&api-version=7.0" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag", "https://dev.azure-server.com/MyOrg/MyProject/_apis/git/repositories/MyRepo/items?path=/devfile.yaml&versionType=tag&version=MyTag&api-version=7.0" }, }; } @Test(dataProvider = "repoProvider") public void checkRepositoryLocation(String rawUrl, String repoUrl) { AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse(rawUrl, null); assertEquals(azureDevOpsUrl.getRepositoryLocation(), repoUrl); } @Test(dataProvider = "repoProviderServer") public void checkRepositoryLocationServer(String rawUrl, String repoUrl) { AzureDevOpsUrl azureDevOpsUrl = azureDevOpsURLParser.parse(rawUrl, null); assertEquals(azureDevOpsUrl.getRepositoryLocation(), repoUrl); } @DataProvider public static Object[][] repoProvider() { return new Object[][] { { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo", "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo.git", "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo.git" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo.dot.git", "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo.dot.git" }, { "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo", "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo" }, { "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo-with-hypen", "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo-with-hypen" }, { "https://dev.azure.com/MyOrg/MyProject/_git/-", "https://dev.azure.com/MyOrg/MyProject/_git/-" }, { "https://dev.azure.com/MyOrg/MyProject/_git/-j.git", "https://dev.azure.com/MyOrg/MyProject/_git/-j.git" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo", "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo.git", "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo.git" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo.dot.git", "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo.dot.git" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo", "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo" }, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo-with-hypen", "git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo-with-hypen" }, {"git@ssh.dev.azure.com:v3/MyOrg/MyProject/-", "git@ssh.dev.azure.com:v3/MyOrg/MyProject/-"}, { "git@ssh.dev.azure.com:v3/MyOrg/MyProject/-j.git", "git@ssh.dev.azure.com:v3/MyOrg/MyProject/-j.git" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain&_a=contents", "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain", "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag&_a=contents", "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo" }, { "https://MyOrg@dev.azure.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag", "https://dev.azure.com/MyOrg/MyProject/_git/MyRepo" }, { "https://MyOrg@dev.azure.com/MyOrg/_git/MyRepo", "https://dev.azure.com/MyOrg/MyRepo/_git/MyRepo" } }; } @DataProvider public static Object[][] repoProviderServer() { return new Object[][] { { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo", "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo.git", "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo.git" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo.dot.git", "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo.dot.git" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo-with-hypen", "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo-with-hypen" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/-", "https://dev.azure-server.com/MyOrg/MyProject/_git/-" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/-j.git", "https://dev.azure-server.com/MyOrg/MyProject/_git/-j.git" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo", "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo.git", "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo.git" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo.dot.git", "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo.dot.git" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo-with-hypen", "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/MyRepo-with-hypen" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/-", "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/-" }, { "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/-j.git", "ssh://dev.azure-server.com:22/MyOrg/MyProject/_git/-j.git" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain&_a=contents", "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GBmain", "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag&_a=contents", "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo" }, { "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo?path=MyFile&version=GTMyTag", "https://dev.azure-server.com/MyOrg/MyProject/_git/MyRepo" } }; } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/test/resources/__files/azure-devops/rest/user/email/response.json ================================================ { "values": [{"email" : "azure-user@mail.com"}] } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/test/resources/__files/azure-devops/rest/user/response.json ================================================ { "displayName": "Display Name", "emailAddress": "user@mail.com" } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/test/resources/__files/azure-devops-server/rest/user/response.json ================================================ { "identity": { "AccountName": "Azure DevOps" } } ================================================ FILE: wsmaster/che-core-api-factory-azure-devops/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-factory-bitbucket jar Che Core :: API :: Factory Resolver Bitbucket true com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.core jackson-databind com.google.guava guava com.google.inject guice jakarta.inject jakarta.inject-api jakarta.validation jakarta.validation-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-shared org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-factory org.eclipse.che.core che-core-api-factory-shared org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang org.slf4j slf4j-api org.eclipse.che.core che-core-api-model provided ch.qos.logback logback-classic test com.github.tomakehurst wiremock-jre8-standalone test jakarta.servlet jakarta.servlet-api test jakarta.ws.rs jakarta.ws.rs-api test org.eclipse.che.core che-core-commons-json test org.hamcrest hamcrest-core test org.mockito mockito-core test org.mockito mockito-testng test org.slf4j jcl-over-slf4j test org.testng testng test org.wiremock wiremock-standalone test ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClient.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.time.Duration.ofSeconds; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Charsets; import com.google.common.base.Splitter; import com.google.common.io.CharStreams; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.Optional; import java.util.concurrent.Executors; import java.util.function.Function; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Bitbucket API operations helper. */ public class BitbucketApiClient { private static final Logger LOG = LoggerFactory.getLogger(BitbucketApiClient.class); /** Bitbucket API endpoint URL. */ public static final String BITBUCKET_API_SERVER = "https://api.bitbucket.org/2.0/"; /** Bitbucket endpoint URL. */ public static final String BITBUCKET_SERVER = "https://bitbucket.org"; /** Bitbucket HTTP header containing OAuth scopes. */ public static final String BITBUCKET_OAUTH_SCOPES_HEADER = "X-OAuth-Scopes"; private final HttpClient httpClient; private final URI apiServerUrl; private final URI scmServerUrl; private static final Duration DEFAULT_HTTP_TIMEOUT = ofSeconds(10); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); /** Default constructor, binds http client to https://api.bitbucket.org */ public BitbucketApiClient() { this(BITBUCKET_API_SERVER); } /** * Used for URL injection in testing. * * @param apiServerUrl the Bitbucket API url */ BitbucketApiClient(final String apiServerUrl) { this.apiServerUrl = URI.create(apiServerUrl); this.scmServerUrl = URI.create(BITBUCKET_SERVER); this.httpClient = HttpClient.newBuilder() .executor( Executors.newCachedThreadPool( new ThreadFactoryBuilder() .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setNameFormat(BitbucketApiClient.class.getName() + "-%d") .setDaemon(true) .build())) .connectTimeout(DEFAULT_HTTP_TIMEOUT) .version(HttpClient.Version.HTTP_1_1) .build(); } /** * Returns the user associated with the provided OAuth access token. * * @param authenticationToken OAuth access token used by the user. * @return Information about the user associated with the token * @throws ScmItemNotFoundException * @throws ScmCommunicationException * @throws ScmBadRequestException */ public BitbucketUser getUser(String authenticationToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("user"); HttpRequest request = buildBitbucketApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); return executeRequest( httpClient, request, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); return OBJECT_MAPPER.readValue(result, BitbucketUser.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } public String getFileContent( String workspace, String repository, String source, String path, String authenticationToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { final URI uri = apiServerUrl.resolve( String.format("repositories/%s/%s/src/%s/%s", workspace, repository, source, path)); HttpRequest request = buildBitbucketApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); return executeRequest( httpClient, request, response -> { try { return CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); } catch (IOException e) { throw new UncheckedIOException(e); } }); } /** * Returns email of the user, associated with the provided OAuth access token. * * @param authenticationToken OAuth access token used by the user. * @return Information about email of the user, associated with the token * @throws ScmItemNotFoundException * @throws ScmCommunicationException * @throws ScmBadRequestException */ public BitbucketUserEmail getEmail(String authenticationToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("user/emails"); HttpRequest request = buildBitbucketApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); return executeRequest( httpClient, request, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); return OBJECT_MAPPER.readValue(result, BitbucketUserEmail.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } /** * Returns a pair of the username and array of scopes of the OAuth token. * * @param authenticationToken The OAuth token to inspect. * @return A pair of the username and array of scopes from the supplied token, empty array if no * scopes. */ public Pair getTokenScopes(String authenticationToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("user"); HttpRequest request = buildBitbucketApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); return executeRequest( httpClient, request, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); BitbucketUser user = OBJECT_MAPPER.readValue(result, BitbucketUser.class); Optional responseScopes = response.headers().firstValue(BITBUCKET_OAUTH_SCOPES_HEADER); String[] scopes = Splitter.on(',') .trimResults() .omitEmptyStrings() .splitToList(responseScopes.orElse("")) .toArray(String[]::new); return Pair.of(user.getName(), scopes); } catch (IOException e) { throw new UncheckedIOException(e); } }); } private HttpRequest buildBitbucketApiRequest(URI uri, String authenticationToken) { return HttpRequest.newBuilder(uri) .headers("Authorization", "Bearer " + authenticationToken, "Accept", "application/json") .timeout(DEFAULT_HTTP_TIMEOUT) .build(); } private T executeRequest( HttpClient httpClient, HttpRequest request, Function, T> responseConverter) throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); LOG.trace("executeRequest={} response {}", request, response.statusCode()); if (response.statusCode() == HTTP_OK) { return responseConverter.apply(response); } else if (response.statusCode() == HTTP_NO_CONTENT) { return null; } else { String body = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); switch (response.statusCode()) { case HTTP_BAD_REQUEST: throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); case HTTP_UNAUTHORIZED: throw new ScmUnauthorizedException(body, "bitbucket", "v2", ""); default: throw new ScmCommunicationException( "Unexpected status code " + response.statusCode() + " " + response, response.statusCode(), "bitbucket"); } } } catch (IOException | InterruptedException | UncheckedIOException e) { throw new ScmCommunicationException(e.getMessage(), e, "bitbucket"); } } /** * Checks if the provided url belongs to this client (Bitbucket) * * @param scmServerUrl the SCM url to verify * @return {@code true} If the provided url is recognized by the current client */ public boolean isConnected(String scmServerUrl) { return this.scmServerUrl.equals(URI.create(scmServerUrl)); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketAuthorizingFileContentProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import java.io.FileNotFoundException; import java.io.IOException; import org.eclipse.che.api.factory.server.scm.AuthorizingFileContentProvider; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** Bitbucket specific authorizing file content provider. */ class BitbucketAuthorizingFileContentProvider extends AuthorizingFileContentProvider { private final BitbucketApiClient apiClient; BitbucketAuthorizingFileContentProvider( BitbucketUrl bitbucketUrl, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager, BitbucketApiClient apiClient) { super(bitbucketUrl, urlFetcher, personalAccessTokenManager); this.apiClient = apiClient; } /** Formats OAuth token as HTTP Authorization header. */ @Override protected String formatAuthorization(String token, boolean isPAT) { return "Bearer " + token; } @Override public String fetchContent(String fileURL) throws IOException, DevfileException { final String requestURL = formatUrl(fileURL); try { // try to authenticate for the given URL PersonalAccessToken token = personalAccessTokenManager.getAndStore(remoteFactoryUrl.getHostName()); String[] split = requestURL.split("/"); return apiClient.getFileContent( split[3], split[4], split[6], requestURL.substring(requestURL.indexOf(split[6]) + split[6].length() + 1), token.getToken()); } catch (UnknownScmProviderException e) { return fetchContentWithoutToken(requestURL); } catch (ScmCommunicationException e) { return toIOException(fileURL, e); } catch (ScmUnauthorizedException | ScmConfigurationPersistenceException | UnsatisfiedScmPreconditionException | ScmBadRequestException e) { throw new DevfileException(e.getMessage(), e); } catch (ScmItemNotFoundException e) { throw new FileNotFoundException(e.getMessage()); } } @Override protected boolean isPublicRepository(BitbucketUrl remoteFactoryUrl) { try { urlFetcher.fetch( remoteFactoryUrl.getHostName() + '/' + remoteFactoryUrl.getWorkspaceId() + '/' + remoteFactoryUrl.getRepository()); return true; } catch (IOException e) { return false; } } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.eclipse.che.api.factory.shared.Constants.REVISION_PARAMETER_NAME; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.eclipse.che.dto.server.DtoFactory.newDto; import jakarta.validation.constraints.NotNull; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.BaseFactoryParameterResolver; import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** Provides Factory Parameters resolver for bitbucket repositories. */ @Singleton public class BitbucketFactoryParametersResolver extends BaseFactoryParameterResolver implements FactoryParametersResolver { private static final String PROVIDER_NAME = "bitbucket"; /** Parser which will allow to check validity of URLs and create objects. */ private final BitbucketURLParser bitbucketURLParser; private final URLFetcher urlFetcher; /** Builder allowing to build objects from bitbucket URL. */ private final BitbucketSourceStorageBuilder bitbucketSourceStorageBuilder; private final URLFactoryBuilder urlFactoryBuilder; /** Personal Access Token manager used when fetching protected content. */ private final PersonalAccessTokenManager personalAccessTokenManager; private final BitbucketApiClient bitbucketApiClient; @Inject public BitbucketFactoryParametersResolver( BitbucketURLParser bitbucketURLParser, URLFetcher urlFetcher, BitbucketSourceStorageBuilder bitbucketSourceStorageBuilder, URLFactoryBuilder urlFactoryBuilder, PersonalAccessTokenManager personalAccessTokenManager, BitbucketApiClient bitbucketApiClient, AuthorisationRequestManager authorisationRequestManager) { super(authorisationRequestManager, urlFactoryBuilder, PROVIDER_NAME); this.bitbucketURLParser = bitbucketURLParser; this.urlFetcher = urlFetcher; this.bitbucketSourceStorageBuilder = bitbucketSourceStorageBuilder; this.urlFactoryBuilder = urlFactoryBuilder; this.personalAccessTokenManager = personalAccessTokenManager; this.bitbucketApiClient = bitbucketApiClient; } /** * Check if this resolver can be used with the given parameters. * * @param factoryParameters map of parameters dedicated to factories * @return true if it will be accepted by the resolver implementation or false if it is not * accepted */ @Override public boolean accept(@NotNull final Map factoryParameters) { // Check if url parameter is a bitbucket URL return factoryParameters.containsKey(URL_PARAMETER_NAME) && bitbucketURLParser.isValid(factoryParameters.get(URL_PARAMETER_NAME)); } @Override public String getProviderName() { return PROVIDER_NAME; } /** * Create factory object based on provided parameters * * @param factoryParameters map containing factory data parameters provided through URL * @throws BadRequestException when data are invalid */ @Override public FactoryMetaDto createFactory(@NotNull final Map factoryParameters) throws ApiException { // no need to check null value of url parameter as accept() method has performed the check final BitbucketUrl bitbucketUrl = bitbucketURLParser.parse( factoryParameters.get(URL_PARAMETER_NAME), factoryParameters.get(REVISION_PARAMETER_NAME)); // create factory from the following location if location exists, else create default factory return createFactory( factoryParameters, bitbucketUrl, new BitbucketFactoryVisitor(bitbucketUrl), new BitbucketAuthorizingFileContentProvider( bitbucketUrl, urlFetcher, personalAccessTokenManager, bitbucketApiClient)); } /** * Visitor that puts the default devfile or updates devfile projects into the Bitbucket Factory, * if needed. */ private class BitbucketFactoryVisitor implements FactoryVisitor { private final BitbucketUrl bitbucketUrl; private BitbucketFactoryVisitor(BitbucketUrl bitbucketUrl) { this.bitbucketUrl = bitbucketUrl; } @Override public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { ScmInfoDto scmInfo = newDto(ScmInfoDto.class) .withScmProviderName(bitbucketUrl.getProviderName()) .withRepositoryUrl(bitbucketUrl.repositoryLocation()); if (bitbucketUrl.getBranch() != null) { scmInfo.withBranch(bitbucketUrl.getBranch()); } return factoryDto.withScmInfo(scmInfo); } } @Override public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) { return bitbucketURLParser.parse(factoryUrl, null); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketModule.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; public class BitbucketModule extends AbstractModule { @Override protected void configure() { Multibinder tokenFetcherMultibinder = Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(BitbucketPersonalAccessTokenFetcher.class); Multibinder gitUserDataMultibinder = Multibinder.newSetBinder(binder(), GitUserDataFetcher.class); gitUserDataMultibinder.addBinding().to(BitbucketUserDataFetcher.class); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import com.google.common.collect.Sets; import java.util.Optional; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.security.oauth.OAuthAPI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Bitbucket OAuth token retriever. */ public class BitbucketPersonalAccessTokenFetcher implements PersonalAccessTokenFetcher { private static final Logger LOG = LoggerFactory.getLogger(BitbucketPersonalAccessTokenFetcher.class); private final String apiEndpoint; private final OAuthAPI oAuthAPI; /** Bitbucket API client. */ private final BitbucketApiClient bitbucketApiClient; /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "bitbucket"; /** OAuth scope required to make integration with Bitbucket work. */ public static final String DEFAULT_REPOSITORY_WRITE_TOKEN_SCOPE = "repository:write"; public static final String DEFAULT_PULLREQUEST_WRITE_TOKEN_SCOPE = "pullrequest:write"; public static final String DEFAULT_ACCOUNT_READ_TOKEN_SCOPE = "account"; public static final String DEFAULT_ACCOUNT_WRITE_TOKEN_SCOPE = "account:write"; @Inject public BitbucketPersonalAccessTokenFetcher( @Named("che.api") String apiEndpoint, OAuthAPI oAuthAPI) { this(apiEndpoint, oAuthAPI, new BitbucketApiClient()); } /** * Constructor used for testing only. * * @param apiEndpoint * @param oAuthAPI * @param bitbucketApiClient */ BitbucketPersonalAccessTokenFetcher( String apiEndpoint, OAuthAPI oAuthAPI, BitbucketApiClient bitbucketApiClient) { this.apiEndpoint = apiEndpoint; this.oAuthAPI = oAuthAPI; this.bitbucketApiClient = bitbucketApiClient; } @Override public PersonalAccessToken refreshPersonalAccessToken(Subject cheSubject, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, true); } @Override public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, false); } private PersonalAccessToken fetchOrRefreshPersonalAccessToken( Subject cheSubject, String scmServerUrl, boolean forceRefreshToken) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { OAuthToken oAuthToken; if (bitbucketApiClient == null || !bitbucketApiClient.isConnected(scmServerUrl)) { LOG.debug("not a valid url {} for current fetcher ", scmServerUrl); return null; } try { oAuthToken = forceRefreshToken ? oAuthAPI.refreshToken(OAUTH_PROVIDER_NAME) : oAuthAPI.getOrRefreshToken(OAUTH_PROVIDER_NAME); String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5); String tokenId = NameGenerator.generate("id-", 5); Optional> valid = isValid( new PersonalAccessTokenParams( scmServerUrl, OAUTH_PROVIDER_NAME, tokenName, tokenId, oAuthToken.getToken(), null)); if (valid.isEmpty()) { throw buildScmUnauthorizedException(cheSubject); } else if (!valid.get().first) { throw new ScmCommunicationException( "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: " + DEFAULT_REPOSITORY_WRITE_TOKEN_SCOPE + " and " + DEFAULT_ACCOUNT_READ_TOKEN_SCOPE); } return new PersonalAccessToken( scmServerUrl, OAUTH_PROVIDER_NAME, cheSubject.getUserId(), valid.get().second, tokenName, tokenId, oAuthToken.getToken()); } catch (UnauthorizedException e) { throw buildScmUnauthorizedException(cheSubject); } catch (NotFoundException nfe) { throw new UnknownScmProviderException(nfe.getMessage(), scmServerUrl); } catch (ServerException | ForbiddenException | BadRequestException | ConflictException e) { LOG.error(e.getMessage()); throw new ScmCommunicationException(e.getMessage(), e); } } private ScmUnauthorizedException buildScmUnauthorizedException(Subject cheSubject) { return new ScmUnauthorizedException( cheSubject.getUserName() + " is not authorized in " + OAUTH_PROVIDER_NAME + " OAuth provider.", OAUTH_PROVIDER_NAME, "2.0", getLocalAuthenticateUrl()); } @Override public Optional isValid(PersonalAccessToken personalAccessToken) { if (!bitbucketApiClient.isConnected(personalAccessToken.getScmProviderUrl())) { LOG.debug("not a valid url {} for current fetcher ", personalAccessToken.getScmProviderUrl()); return Optional.empty(); } try { String[] scopes = bitbucketApiClient.getTokenScopes(personalAccessToken.getToken()).second; return Optional.of(isValidScope(Sets.newHashSet(scopes))); } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } @Override public Optional> isValid(PersonalAccessTokenParams params) throws ScmCommunicationException { if (!bitbucketApiClient.isConnected(params.getScmProviderUrl())) { LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); return Optional.empty(); } try { Pair pair = bitbucketApiClient.getTokenScopes(params.getToken()); return Optional.of( Pair.of( isValidScope(Sets.newHashSet(pair.second)) ? Boolean.TRUE : Boolean.FALSE, pair.first)); } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.empty(); } } private String getLocalAuthenticateUrl() { return apiEndpoint + "/oauth/authenticate?oauth_provider=" + OAUTH_PROVIDER_NAME + "&scope=repository&request_method=POST&signature_method=rsa"; } /** * Checks if the given scopes are valid for Bitbucket. Note: that pullrequest:write is a wider * scope than repository:write, and account:write is a wider scope than account. */ private boolean isValidScope(Set scopes) { return (scopes.contains(DEFAULT_REPOSITORY_WRITE_TOKEN_SCOPE) || scopes.contains(DEFAULT_PULLREQUEST_WRITE_TOKEN_SCOPE)) && (scopes.contains(DEFAULT_ACCOUNT_READ_TOKEN_SCOPE) || scopes.contains(DEFAULT_ACCOUNT_WRITE_TOKEN_SCOPE)); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketScmFileResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; import jakarta.validation.constraints.NotNull; import java.io.IOException; import javax.inject.Inject; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.factory.server.ScmFileResolver; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** Bitbucket specific SCM file resolver. */ public class BitbucketScmFileResolver implements ScmFileResolver { private final BitbucketURLParser bitbucketUrlParser; private final URLFetcher urlFetcher; private final PersonalAccessTokenManager personalAccessTokenManager; private final BitbucketApiClient bitbucketApiClient; @Inject public BitbucketScmFileResolver( BitbucketURLParser bitbucketUrlParser, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager, BitbucketApiClient bitbucketApiClient) { this.bitbucketUrlParser = bitbucketUrlParser; this.urlFetcher = urlFetcher; this.personalAccessTokenManager = personalAccessTokenManager; this.bitbucketApiClient = bitbucketApiClient; } @Override public boolean accept(@NotNull String repository) { // Check if repository parameter is a bitbucket URL return bitbucketUrlParser.isValid(repository); } @Override public String fileContent(@NotNull String repository, @NotNull String filePath) throws ApiException { final BitbucketUrl bitbucketUrl = bitbucketUrlParser.parse(repository, null); try { return fetchContent(bitbucketUrl, filePath, false); } catch (DevfileException exception) { // This catch might mean that the authentication was rejected by user, try to repeat the fetch // without authentication flow. try { return fetchContent(bitbucketUrl, filePath, true); } catch (DevfileException devfileException) { throw toApiException(devfileException); } } } private String fetchContent( BitbucketUrl bitbucketUrl, String filePath, boolean skipAuthentication) throws DevfileException, NotFoundException { try { BitbucketAuthorizingFileContentProvider contentProvider = new BitbucketAuthorizingFileContentProvider( bitbucketUrl, urlFetcher, personalAccessTokenManager, bitbucketApiClient); return skipAuthentication ? contentProvider.fetchContentWithoutAuthentication(filePath) : contentProvider.fetchContent(filePath); } catch (IOException e) { throw new NotFoundException(e.getMessage()); } } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketSourceStorageBuilder.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.eclipse.che.dto.server.DtoFactory.newDto; import com.google.common.base.Strings; import java.util.HashMap; import java.util.Map; import javax.inject.Singleton; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; /** Create {@link ProjectConfigDto} object from objects */ @Singleton public class BitbucketSourceStorageBuilder { /** * Create SourceStorageDto DTO by using data of a bitbucket url * * @param bitbucketUrl an instance of {@link BitbucketUrl} * @return newly created source storage DTO object */ public SourceStorageDto buildWorkspaceConfigSource(BitbucketUrl bitbucketUrl) { // Create map for source storage dto Map parameters = new HashMap<>(1); if (!Strings.isNullOrEmpty(bitbucketUrl.getBranch())) { parameters.put("branch", bitbucketUrl.getBranch()); } return newDto(SourceStorageDto.class) .withLocation(bitbucketUrl.repositoryLocation()) .withType("bitbucket") .withParameters(parameters); } /** * Create SourceStorageDto DTO by using data of a bitbucket url * * @param bitbucketUrl an instance of {@link BitbucketUrl} * @return newly created source DTO object */ public SourceDto buildDevfileSource(BitbucketUrl bitbucketUrl) { return newDto(SourceDto.class) .withLocation(bitbucketUrl.repositoryLocation()) .withType("bitbucket") .withBranch(bitbucketUrl.getBranch()); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketURLParser.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static com.google.common.base.Strings.isNullOrEmpty; import jakarta.validation.constraints.NotNull; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; /** Parser of String Bitbucket SAAS URLs and provides {@link BitbucketUrl} objects. */ @Singleton public class BitbucketURLParser { private final DevfileFilenamesProvider devfileFilenamesProvider; @Inject public BitbucketURLParser(DevfileFilenamesProvider devfileFilenamesProvider) { this.devfileFilenamesProvider = devfileFilenamesProvider; } /** * Regexp to find repository details (repository name, workspace id, project name and branch) * Examples of valid URLs are in the test class. */ protected static final Pattern BITBUCKET_PATTERN = Pattern.compile( "^https?://(?[^/@]+)?@?bitbucket\\.org/(?[^/]+)/(?[^/]+)/?(\\.git)?(/(src|branch)/(?[^/]+)/?)?$"); protected static final Pattern BITBUCKET_SSH_PATTERN = Pattern.compile("^git@bitbucket.org:(?.*)/(?.*)$"); public boolean isValid(@NotNull String url) { return BITBUCKET_PATTERN.matcher(url).matches() || BITBUCKET_SSH_PATTERN.matcher(url).matches(); } public BitbucketUrl parse(String url, @Nullable String revision) { // Apply bitbucket url to the regexp boolean isHTTPSUrl = BITBUCKET_PATTERN.matcher(url).matches(); Matcher matcher = isHTTPSUrl ? BITBUCKET_PATTERN.matcher(url) : BITBUCKET_SSH_PATTERN.matcher(url); if (!matcher.matches()) { throw new IllegalArgumentException( String.format("The given bitbucket url %s is not a valid URL bitbucket url. ", url)); } String workspaceId = matcher.group("workspaceId"); String repoName = matcher.group("repoName"); if (repoName.matches("^[\\w-][\\w.-]*?\\.git$")) { repoName = repoName.substring(0, repoName.length() - 4); } String username = null; String branchFromUrl = null; if (isHTTPSUrl) { username = matcher.group("username"); branchFromUrl = matcher.group("branchName"); } return new BitbucketUrl() .withUsername(username) .withRepository(repoName) .setIsHTTPSUrl(isHTTPSUrl) .withBranch(isNullOrEmpty(branchFromUrl) ? revision : branchFromUrl) .withWorkspaceId(workspaceId) .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .withUrl(url); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketUrl.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static com.google.common.base.MoreObjects.firstNonNull; import com.google.common.base.Strings; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.StringJoiner; import java.util.stream.Collectors; import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; /** * Representation of a bitbucket URL, allowing to get details from it. * *

    like https://@bitbucket.org//.git */ public class BitbucketUrl extends DefaultFactoryUrl { private final String NAME = "bitbucket"; private static final String HOSTNAME = "bitbucket.org"; /** Username part of the bitbucket URL */ private String username; /** workspace part of the bitbucket URL */ private String workspaceId; /** Repository part of the URL. */ private String repository; private boolean isHTTPSUrl; /** Branch name */ private String branch; /** Devfile filenames list */ private final List devfileFilenames = new ArrayList<>(); /** * Creation of this instance is made by the parser so user may not need to create a new instance * directly */ protected BitbucketUrl() {} @Override public String getProviderName() { return NAME; } /** * Gets username of this bitbucket url * * @return the username part */ public String getUsername() { return this.username; } public BitbucketUrl withUsername(String userName) { this.username = userName; return this; } public String getRepository() { return this.repository; } protected BitbucketUrl withRepository(String repository) { this.repository = repository; return this; } public BitbucketUrl setIsHTTPSUrl(boolean isHTTPSUrl) { this.isHTTPSUrl = isHTTPSUrl; return this; } public String getWorkspaceId() { return workspaceId; } protected BitbucketUrl withWorkspaceId(String workspaceId) { this.workspaceId = workspaceId; return this; } protected BitbucketUrl withDevfileFilenames(List devfileFilenames) { this.devfileFilenames.addAll(devfileFilenames); return this; } @Override public void setDevfileFilename(String devfileName) { this.devfileFilenames.clear(); this.devfileFilenames.add(devfileName); } public String getBranch() { return this.branch; } protected BitbucketUrl withBranch(String branch) { if (!Strings.isNullOrEmpty(branch)) { this.branch = branch; } return this; } @Override public List devfileFileLocations() { return devfileFilenames.stream().map(this::createDevfileLocation).collect(Collectors.toList()); } private DevfileLocation createDevfileLocation(String devfileFilename) { return new DevfileLocation() { @Override public Optional filename() { return Optional.of(devfileFilename); } @Override public String location() { return rawFileLocation(devfileFilename); } }; } public String rawFileLocation(String fileName) { return new StringJoiner("/") .add("https://bitbucket.org") .add(workspaceId) .add(repository) .add("raw") .add(firstNonNull(branch, "HEAD")) .add(fileName) .toString(); } @Override public String getHostName() { return "https://" + HOSTNAME; } @Override public Optional getCredentials() { // Bitbucket repository URL may contain username e.g. // https://@bitbucket.org//.git. If username is present, it // can not be used as credentials. Moreover, we skip credentials for Bitbucket repository URl at // all, because we do not support credentials in a repository URL. We only support credentials // in a devfile URL, which is handled by the DefaultFactoryUrl class. // Todo: add a new abstraction for divfile URL to be able to retrieve credentials separately. return Optional.empty(); } /** * Provides location to the repository part of the full bitbucket URL. * * @return location of the repository. */ protected String repositoryLocation() { if (isHTTPSUrl) { return "https://" + (this.username != null ? this.username + '@' : "") + HOSTNAME + "/" + workspaceId + "/" + repository + ".git"; } return String.format("git@%s:%s/%s.git", HOSTNAME, workspaceId, repository); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketUser.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Objects; @JsonIgnoreProperties(ignoreUnknown = true) public class BitbucketUser { private String name; private String id; @JsonProperty("display_name") private String displayName; public String getName() { return name; } @JsonProperty("username") public void setName(String name) { this.name = name; } public String getId() { return id; } @JsonProperty("account_id") public void setId(String id) { this.id = id; } public String getDisplayName() { return displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BitbucketUser that = (BitbucketUser) o; return Objects.equal(name, that.name) && Objects.equal(id, that.id) && Objects.equal(displayName, that.displayName); } @Override public int hashCode() { return Objects.hashCode(name, id, displayName); } @Override public String toString() { return "BitbucketUser{" + "name='" + name + '\'' + ", id='" + id + '\'' + ", displayName='" + displayName + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketUserDataFetcher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.scm.GitUserData; import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.security.oauth.OAuthAPI; /** * Bitbucket user data retriever. TODO: extends {@code AbstractGitUserDataFetcher} when we support * personal access tokens for BitBucket. */ public class BitbucketUserDataFetcher implements GitUserDataFetcher { private final String apiEndpoint; private final OAuthAPI oAuthAPI; /** Bitbucket API client. */ private final BitbucketApiClient bitbucketApiClient; /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "bitbucket"; /** Collection of OAuth scopes required to make integration with Bitbucket work. */ public static final Set DEFAULT_TOKEN_SCOPES = ImmutableSet.of("repo"); @Inject public BitbucketUserDataFetcher(@Named("che.api") String apiEndpoint, OAuthAPI oAuthAPI) { this(apiEndpoint, oAuthAPI, new BitbucketApiClient()); } /** Constructor used for testing only. */ public BitbucketUserDataFetcher( String apiEndpoint, OAuthAPI oAuthAPI, BitbucketApiClient bitbucketApiClient) { this.apiEndpoint = apiEndpoint; this.oAuthAPI = oAuthAPI; this.bitbucketApiClient = bitbucketApiClient; } @Override public GitUserData fetchGitUserData(String namespaceName) throws ScmUnauthorizedException, ScmCommunicationException { OAuthToken oAuthToken; try { oAuthToken = oAuthAPI.getOrRefreshToken(OAUTH_PROVIDER_NAME); // Find the user associated to the OAuth token by querying the Bitbucket API. BitbucketUser user = bitbucketApiClient.getUser(oAuthToken.getToken()); BitbucketUserEmail emailResponse = bitbucketApiClient.getEmail(oAuthToken.getToken()); String email = emailResponse.getValues().length > 0 ? emailResponse.getValues()[0].getEmail() : ""; return new GitUserData(user.getDisplayName(), email); } catch (UnauthorizedException e) { Subject cheSubject = EnvironmentContext.getCurrent().getSubject(); throw new ScmUnauthorizedException( cheSubject.getUserName() + " is not authorized in " + OAUTH_PROVIDER_NAME + " OAuth provider.", OAUTH_PROVIDER_NAME, "2.0", getLocalAuthenticateUrl()); } catch (NotFoundException | ServerException | ForbiddenException | BadRequestException | ScmItemNotFoundException | ScmBadRequestException | ConflictException e) { throw new ScmCommunicationException(e.getMessage(), e); } } private String getLocalAuthenticateUrl() { return apiEndpoint + "/oauth/authenticate?oauth_provider=" + OAUTH_PROVIDER_NAME + "&scope=" + Joiner.on(',').join(DEFAULT_TOKEN_SCOPES) + "&request_method=POST&signature_method=rsa"; } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketUserEmail.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) class Value { private String email; public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } @JsonIgnoreProperties(ignoreUnknown = true) public class BitbucketUserEmail { private Value[] values; public Value[] getValues() { return values; } public void setValues(Value[] values) { this.values = values; } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClientTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEqualsNoOrder; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.net.HttpHeaders; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketApiClientTest { private BitbucketApiClient client; WireMockServer wireMockServer; WireMock wireMock; @BeforeMethod void start() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); client = new BitbucketApiClient(wireMockServer.url("/")); } @AfterMethod void stop() { wireMockServer.stop(); } @Test public void testGetUser() throws Exception { stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/user/response.json"))); BitbucketUser user = client.getUser("token1"); assertNotNull(user, "Bitbucket API should have returned a non-null user object"); assertEquals( user.getId(), "bitbucket-user_id_123", "Bitbucket user id was not parsed properly by client"); assertEquals(user.getName(), "user", "Bitbucket user name was not parsed properly by client"); assertEquals( user.getDisplayName(), "Bitbucket User", "Bitbucket user display name was not parsed properly by client"); } @Test public void testGetTokenScopes() throws Exception { stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader( BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, "repository:write,account") .withBodyFile("bitbucket/rest/user/response.json"))); String[] scopes = client.getTokenScopes("token1").second; String[] expectedScopes = {"repository:write", "account"}; assertNotNull(scopes, "Bitbucket API should have returned a non-null scope array"); assertEqualsNoOrder( scopes, expectedScopes, "Returned scope array does not match expected values"); } @Test public void testGetTokenScopesWithNoScopeHeader() throws Exception { stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/user/response.json"))); String[] scopes = client.getTokenScopes("token1").second; assertNotNull(scopes, "Bitbucket API should have returned a non-null scope array"); assertEquals( scopes.length, 0, "A response with no " + BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER + " header should return an empty array"); } @Test public void testGetTokenScopesWithNoScope() throws Exception { stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader(BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, "") .withBodyFile("bitbucket/rest/user/response.json"))); String[] scopes = client.getTokenScopes("token1").second; assertNotNull(scopes, "Bitbucket API should have returned a non-null scope array"); assertEquals( scopes.length, 0, "A response with empty " + BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER + " header should return an empty array"); } @Test public void shouldReturnFalseOnConnectedToOtherHost() { assertFalse(client.isConnected("https://other.com")); } @Test public void shouldReturnTrueWhenConnectedToBitbucket() { assertTrue(client.isConnected("https://bitbucket.org")); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketAuthorizingFileContentProviderTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import java.io.FileNotFoundException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketAuthorizingFileContentProviderTest { @Mock private PersonalAccessTokenManager personalAccessTokenManager; @Mock private BitbucketApiClient bitbucketApiClient; @Test public void shouldExpandRelativePaths() throws Exception { URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); BitbucketUrl bitbucketUrl = new BitbucketUrl().withWorkspaceId("eclipse").withRepository("che"); FileContentProvider fileContentProvider = new BitbucketAuthorizingFileContentProvider( bitbucketUrl, urlFetcher, personalAccessTokenManager, bitbucketApiClient); when(personalAccessTokenManager.getAndStore(anyString())) .thenThrow(UnknownScmProviderException.class); fileContentProvider.fetchContent("devfile.yaml"); verify(urlFetcher).fetch(eq("https://bitbucket.org/eclipse/che/raw/HEAD/devfile.yaml")); } @Test public void shouldPreserveAbsolutePaths() throws Exception { URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); BitbucketUrl bitbucketUrl = new BitbucketUrl().withUsername("eclipse").withRepository("che"); FileContentProvider fileContentProvider = new BitbucketAuthorizingFileContentProvider( bitbucketUrl, urlFetcher, personalAccessTokenManager, bitbucketApiClient); String url = "https://api.bitbucket.org/2.0/repositories/foo/bar/devfile.yaml"; when(personalAccessTokenManager.getAndStore(anyString())) .thenThrow(UnknownScmProviderException.class); fileContentProvider.fetchContent(url); verify(urlFetcher).fetch(eq(url)); } @Test(expectedExceptions = FileNotFoundException.class) public void shouldThrowNotFoundForPublicRepos() throws Exception { URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); String url = "https://bitbucket.org/foo/bar/raw/HEAD/devfile.yaml"; when(personalAccessTokenManager.getAndStore(anyString())) .thenThrow(UnknownScmProviderException.class); when(urlFetcher.fetch(eq(url))).thenThrow(FileNotFoundException.class); when(urlFetcher.fetch(eq("https://bitbucket.org/eclipse/che"))).thenReturn("OK"); BitbucketUrl bitbucketUrl = new BitbucketUrl().withUsername("eclipse").withWorkspaceId("eclipse").withRepository("che"); FileContentProvider fileContentProvider = new BitbucketAuthorizingFileContentProvider( bitbucketUrl, urlFetcher, personalAccessTokenManager, bitbucketApiClient); fileContentProvider.fetchContent(url); } @Test public void shouldFetchContent() throws Exception { // given URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); String url = "https://bitbucket.org/workspace/repository/raw/HEAD/devfile.yaml"; PersonalAccessToken personalAccessToken = new PersonalAccessToken(url, "provider", "che", "my-token"); when(personalAccessTokenManager.getAndStore(anyString())).thenReturn(personalAccessToken); when(bitbucketApiClient.getFileContent( eq("workspace"), eq("repository"), eq("HEAD"), eq("devfile.yaml"), eq("my-token"))) .thenReturn("content"); BitbucketUrl bitbucketUrl = new BitbucketUrl().withUsername("eclipse").withWorkspaceId("eclipse").withRepository("che"); FileContentProvider fileContentProvider = new BitbucketAuthorizingFileContentProvider( bitbucketUrl, urlFetcher, personalAccessTokenManager, bitbucketApiClient); // when String content = fileContentProvider.fetchContent(url); // then assertEquals(content, "content"); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolverTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.eclipse.che.security.oauth1.OAuthAuthenticationService.ERROR_QUERY_NAME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.model.factory.ScmInfo; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** Validate operations performed by the Bitbucket Factory service */ @Listeners(MockitoTestNGListener.class) public class BitbucketFactoryParametersResolverTest { @Mock private URLFetcher urlFetcher; @Mock private DevfileFilenamesProvider devfileFilenamesProvider; @Mock private AuthorisationRequestManager authorisationRequestManager; /** Parser which will allow to check validity of URLs and create objects. */ private BitbucketURLParser bitbucketURLParser; /** Converter allowing to convert bitbucket URL to other objects. */ @Spy private BitbucketSourceStorageBuilder bitbucketSourceStorageBuilder = new BitbucketSourceStorageBuilder(); /** Parser which will allow to check validity of URLs and create objects. */ @Mock private URLFactoryBuilder urlFactoryBuilder; @Mock private PersonalAccessTokenManager personalAccessTokenManager; @Mock private BitbucketApiClient bitbucketApiClient; /** * Capturing the location parameter when calling {@link * URLFactoryBuilder#createFactoryFromDevfile(RemoteFactoryUrl, FileContentProvider, Map, * boolean)} */ @Captor private ArgumentCaptor factoryUrlArgumentCaptor; /** Instance of resolver that will be tested. */ private BitbucketFactoryParametersResolver bitbucketFactoryParametersResolver; @BeforeMethod protected void init() { bitbucketURLParser = new BitbucketURLParser(devfileFilenamesProvider); assertNotNull(this.bitbucketURLParser); bitbucketFactoryParametersResolver = new BitbucketFactoryParametersResolver( bitbucketURLParser, urlFetcher, bitbucketSourceStorageBuilder, urlFactoryBuilder, personalAccessTokenManager, bitbucketApiClient, authorisationRequestManager); assertNotNull(this.bitbucketFactoryParametersResolver); } /** Check missing parameter name can't be accepted by this resolver */ @Test public void checkMissingParameter() { Map parameters = singletonMap("foo", "this is a foo bar"); boolean accept = bitbucketFactoryParametersResolver.accept(parameters); // shouldn't be accepted assertFalse(accept); } /** Check url which is not a bitbucket url can't be accepted by this resolver */ @Test public void checkInvalidAcceptUrl() { Map parameters = singletonMap(URL_PARAMETER_NAME, "http://www.eclipse.org/che"); boolean accept = bitbucketFactoryParametersResolver.accept(parameters); // shouldn't be accepted assertFalse(accept); } /** Check bitbucket url will be accepted by this resolver */ @Test public void checkValidAcceptUrl() { Map parameters = singletonMap(URL_PARAMETER_NAME, "https://bitbucket.org/eclipse/che.git"); boolean accept = bitbucketFactoryParametersResolver.accept(parameters); // shouldn't be accepted assertTrue(accept); } @Test public void shouldGenerateDevfileForFactoryWithNoDevfile() throws Exception { String bitbucketUrl = "https://bitbucket.org/eclipse/che"; when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.empty()); Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) bitbucketFactoryParametersResolver.createFactory(params); // then ScmInfoDto scmInfo = factory.getScmInfo(); assertEquals(scmInfo.getRepositoryUrl(), bitbucketUrl + ".git"); assertEquals(scmInfo.getBranch(), null); } @Test public void shouldSetScmInfoIntoDevfileV2() throws Exception { String bitbucketUrl = "https://bitbucket.org/eclipse/che/src/foobar"; FactoryDevfileV2Dto computedFactory = generateDevfileV2Factory(); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.of(computedFactory)); Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) bitbucketFactoryParametersResolver.createFactory(params); // then ScmInfo scmInfo = factory.getScmInfo(); assertNotNull(scmInfo); assertEquals(scmInfo.getScmProviderName(), "bitbucket"); assertEquals(scmInfo.getRepositoryUrl(), "https://bitbucket.org/eclipse/che.git"); assertEquals(scmInfo.getBranch(), "foobar"); } private FactoryDevfileV2Dto generateDevfileV2Factory() { return newDto(FactoryDevfileV2Dto.class) .withV(CURRENT_VERSION) .withSource("repo") .withDevfile(Map.of("schemaVersion", "2.0.0")); } @Test public void shouldCreateFactoryWithoutAuthentication() throws ApiException { // given String bitbucketUrl = "https://bitbucket.org/user/repo.git"; Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl, ERROR_QUERY_NAME, "access_denied"); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.of(generateDevfileV2Factory())); // when bitbucketFactoryParametersResolver.createFactory(params); // then verify(urlFactoryBuilder) .createFactoryFromDevfile( any(BitbucketUrl.class), any(BitbucketAuthorizingFileContentProvider.class), anyMap(), eq(true)); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketGitUserDataFetcherTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.net.HttpHeaders; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.factory.server.scm.GitUserData; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketGitUserDataFetcherTest { @Mock OAuthAPI oAuthAPI; BitbucketUserDataFetcher bitbucketUserDataFetcher; final int httpPort = 3301; WireMockServer wireMockServer; WireMock wireMock; final String bitbucketOauthToken = "bitbucket_token"; @BeforeMethod void start() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).port(httpPort)); wireMockServer.start(); WireMock.configureFor("localhost", httpPort); wireMock = new WireMock("localhost", httpPort); bitbucketUserDataFetcher = new BitbucketUserDataFetcher( "http://che.api", oAuthAPI, new BitbucketApiClient(wireMockServer.url("/"))); stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + bitbucketOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/user/response.json"))); stubFor( get(urlEqualTo("/user/emails")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + bitbucketOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/user/email/response.json"))); } @AfterMethod void stop() { wireMockServer.stop(); } @Test public void shouldFetchGitUserData() throws Exception { OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(bitbucketOauthToken).withScope("repo"); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); GitUserData gitUserData = bitbucketUserDataFetcher.fetchGitUserData(null); assertEquals(gitUserData.getScmUsername(), "Bitbucket User"); assertEquals(gitUserData.getScmUserEmail(), "bitbucketuser@email.com"); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.testng.Assert.*; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.net.HttpHeaders; import java.util.Collections; import java.util.Optional; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketPersonalAccessTokenFetcherTest { @Mock OAuthAPI oAuthAPI; BitbucketPersonalAccessTokenFetcher bitbucketPersonalAccessTokenFetcher; final int httpPort = 3301; WireMockServer wireMockServer; WireMock wireMock; final String bitbucketOauthToken = "token"; @BeforeMethod void start() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).port(httpPort)); wireMockServer.start(); WireMock.configureFor("localhost", httpPort); wireMock = new WireMock("localhost", httpPort); bitbucketPersonalAccessTokenFetcher = new BitbucketPersonalAccessTokenFetcher( "http://che.api", oAuthAPI, new BitbucketApiClient(wireMockServer.url("/"))); } @AfterMethod void stop() { wireMockServer.stop(); } @Test public void shouldNotValidateSCMServerWithTrailingSlash() throws Exception { stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + bitbucketOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/user/response.json"))); PersonalAccessTokenParams personalAccessTokenParams = new PersonalAccessTokenParams( "https://bitbucket.org/", "provider", "scmTokenName", "scmTokenId", bitbucketOauthToken, null); assertTrue( bitbucketPersonalAccessTokenFetcher.isValid(personalAccessTokenParams).isEmpty(), "Should not validate SCM server with trailing /"); } @Test( expectedExceptions = ScmCommunicationException.class, expectedExceptionsMessageRegExp = "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: repository:write and account") public void shouldThrowExceptionOnInsufficientTokenScopes() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(bitbucketOauthToken).withScope(""); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + bitbucketOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader(BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, "") .withBodyFile("bitbucket/rest/user/response.json"))); bitbucketPersonalAccessTokenFetcher.fetchPersonalAccessToken( subject, BitbucketApiClient.BITBUCKET_SERVER); } @Test( expectedExceptions = ScmUnauthorizedException.class, expectedExceptionsMessageRegExp = "Username is not authorized in bitbucket OAuth provider.") public void shouldThrowUnauthorizedExceptionWhenUserNotLoggedIn() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); when(oAuthAPI.getOrRefreshToken(anyString())).thenThrow(UnauthorizedException.class); bitbucketPersonalAccessTokenFetcher.fetchPersonalAccessToken( subject, BitbucketApiClient.BITBUCKET_SERVER); } @Test public void shouldReturnToken() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(bitbucketOauthToken).withScope("repo"); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + bitbucketOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader( BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, "repository:write,account") .withBodyFile("bitbucket/rest/user/response.json"))); PersonalAccessToken token = bitbucketPersonalAccessTokenFetcher.fetchPersonalAccessToken( subject, BitbucketApiClient.BITBUCKET_SERVER); assertNotNull(token); assertTrue(token.getScmTokenName().startsWith(OAUTH_2_PREFIX)); } @Test public void shouldValidatePersonalToken() throws Exception { stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + bitbucketOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader( BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, "repository:write,account") .withBodyFile("bitbucket/rest/user/response.json"))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( "https://bitbucket.org", "provider", "params-name", "tid-23434", bitbucketOauthToken, null); Optional> valid = bitbucketPersonalAccessTokenFetcher.isValid(params); assertTrue(valid.isPresent()); assertTrue(valid.get().first); } @Test public void shouldValidateOauthToken() throws Exception { stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + bitbucketOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader( BitbucketApiClient.BITBUCKET_OAUTH_SCOPES_HEADER, "repository:write,account") .withBodyFile("bitbucket/rest/user/response.json"))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( "https://bitbucket.org", "provider", OAUTH_2_PREFIX + "-params-name", "tid-23434", bitbucketOauthToken, null); Optional> valid = bitbucketPersonalAccessTokenFetcher.isValid(params); assertTrue(valid.isPresent()); assertTrue(valid.get().first); } @Test public void shouldNotValidateExpiredOauthToken() throws Exception { stubFor(get(urlEqualTo("/user")).willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( "https://bitbucket.org", "provider", OAUTH_2_PREFIX + "-token-name", "tid-23434", bitbucketOauthToken, null); assertFalse(bitbucketPersonalAccessTokenFetcher.isValid(params).isPresent()); } @Test( expectedExceptions = ScmUnauthorizedException.class, expectedExceptionsMessageRegExp = "Username is not authorized in bitbucket OAuth provider.") public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(bitbucketOauthToken).withScope(""); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + bitbucketOauthToken)) .willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); bitbucketPersonalAccessTokenFetcher.fetchPersonalAccessToken( subject, BitbucketApiClient.BITBUCKET_SERVER); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketScmFileResolverTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertTrue; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketScmFileResolverTest { BitbucketURLParser bitbucketURLParser; @Mock private URLFetcher urlFetcher; @Mock private DevfileFilenamesProvider devfileFilenamesProvider; @Mock private PersonalAccessTokenManager personalAccessTokenManager; @Mock private BitbucketApiClient bitbucketApiClient; private BitbucketScmFileResolver bitbucketScmFileResolver; @BeforeMethod protected void init() { bitbucketURLParser = new BitbucketURLParser(devfileFilenamesProvider); assertNotNull(this.bitbucketURLParser); bitbucketScmFileResolver = new BitbucketScmFileResolver( bitbucketURLParser, urlFetcher, personalAccessTokenManager, bitbucketApiClient); assertNotNull(this.bitbucketScmFileResolver); } /** Check url which is not a Gitlab url can't be accepted by this resolver */ @Test public void checkInvalidAcceptUrl() { // shouldn't be accepted assertFalse(bitbucketScmFileResolver.accept("http://foobar.com")); } /** Check url will be be accepted by this resolver */ @Test public void checkValidAcceptUrl() { // should be accepted assertTrue(bitbucketScmFileResolver.accept("http://bitbucket.org/test/repo.git")); } @Test public void shouldReturnContentFromUrlFetcher() throws Exception { final String rawContent = "raw_content"; final String filename = "devfile.yaml"; when(bitbucketApiClient.getFileContent( eq("test"), eq("repo"), eq("HEAD"), eq("devfile.yaml"), eq("my-token"))) .thenReturn(rawContent); var personalAccessToken = new PersonalAccessToken("foo", "provider", "che", "my-token"); when(personalAccessTokenManager.getAndStore(anyString())).thenReturn(personalAccessToken); String content = bitbucketScmFileResolver.fileContent("http://bitbucket.org/test/repo.git", filename); assertEquals(content, rawContent); } @Test public void shouldFetchContentWithoutAuthentication() throws Exception { // given when(personalAccessTokenManager.getAndStore(anyString())) .thenThrow(new ScmUnauthorizedException("message", "bitbucket", "v1", "url")); // when bitbucketScmFileResolver.fileContent("https://bitbucket.org/username/repo.git", "devfile.yaml"); // then verify(urlFetcher).fetch(anyString()); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketURLParserTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** Validate operations performed by the Bitbucket parser */ @Listeners(MockitoTestNGListener.class) public class BitbucketURLParserTest { @Mock private URLFetcher urlFetcher; @Mock private DevfileFilenamesProvider devfileFilenamesProvider; /** Instance of component that will be tested. */ @InjectMocks private BitbucketURLParser bitbucketURLParser; @BeforeMethod public void init() { lenient().when(urlFetcher.fetchSafely(any(String.class))).thenReturn(""); } /** Check URLs are valid with regexp */ @Test(dataProvider = "UrlsProvider") public void checkRegexp(String url) { assertTrue(bitbucketURLParser.isValid(url), "url " + url + " is invalid"); } @Test public void shouldParseWithBranch() { BitbucketUrl bitbucketUrl = bitbucketURLParser.parse("https://bitbucket.org/eclipse/che", "branch"); assertEquals(bitbucketUrl.getBranch(), "branch"); } @Test public void shouldParseWithUrlBranch() { BitbucketUrl bitbucketUrl = bitbucketURLParser.parse("https://bitbucket.org/eclipse/che/src/master/", "branch"); assertEquals(bitbucketUrl.getBranch(), "master"); } /** Compare parsing */ @Test(dataProvider = "parsing") public void checkParsing(String url, String username, String repository, String branch) { BitbucketUrl bitbucketUrl = bitbucketURLParser.parse(url, null); assertEquals(bitbucketUrl.getWorkspaceId(), username); assertEquals(bitbucketUrl.getRepository(), repository); assertEquals(bitbucketUrl.getBranch(), branch); } /** Compare parsing */ @Test(dataProvider = "parsingBadRepository") public void checkParsingBadRepositoryDoNotModifiesInitialInput(String url, String repository) { BitbucketUrl bitbucketUrl = bitbucketURLParser.parse(url, null); assertEquals(bitbucketUrl.getRepository(), repository); } @DataProvider(name = "UrlsProvider") public Object[][] urls() { return new Object[][] { {"https://bitbucket.org/eclipse/che"}, {"https://bitbucket.org/eclipse/che123"}, {"https://bitbucket.org/eclipse/che/"}, {"https://bitbucket.org/eclipse/che/src/4.2.x"}, {"https://bitbucket.org/eclipse/che/src/master/"}, {"https://bitbucket.org/eclipse/che.git"}, {"https://bitbucket.org/eclipse/che.with.dots.git"}, {"https://bitbucket.org/eclipse/che-with-hyphen"}, {"https://bitbucket.org/eclipse/che-with-hyphen.git"}, {"git@bitbucket.org:eclipse/che"}, {"git@bitbucket.org:eclipse/che123"}, {"git@bitbucket.org:eclipse/che/"}, {"git@bitbucket.org:eclipse/che/src/4.2.x"}, {"git@bitbucket.org:eclipse/che/src/master/"}, {"git@bitbucket.org:eclipse/che.git"}, {"git@bitbucket.org:eclipse/che.with.dots.git"}, {"git@bitbucket.org:eclipse/che-with-hyphen"}, {"git@bitbucket.org:eclipse/che-with-hyphen.git"}, {"https://username@bitbucket.org/eclipse/che"}, {"https://username@bitbucket.org/eclipse/che123"}, {"https://username@bitbucket.org/eclipse/che/"}, {"https://username@bitbucket.org/eclipse/che/src/4.2.x"}, {"https://username@bitbucket.org/eclipse/che/src/master/"}, {"https://username@bitbucket.org/eclipse/che.git"}, {"https://username@bitbucket.org/eclipse/che.with.dots.git"}, {"https://username@bitbucket.org/eclipse/che-with-hyphen"}, {"https://username@bitbucket.org/eclipse/che-with-hyphen.git"} }; } @DataProvider(name = "parsing") public Object[][] expectedParsing() { return new Object[][] { {"https://bitbucket.org/eclipse/che", "eclipse", "che", null}, {"https://bitbucket.org/eclipse/che123", "eclipse", "che123", null}, {"https://bitbucket.org/eclipse/che.git", "eclipse", "che", null}, {"https://bitbucket.org/eclipse/che.with.dot.git", "eclipse", "che.with.dot", null}, {"https://bitbucket.org/eclipse/-.git", "eclipse", "-", null}, {"https://bitbucket.org/eclipse/-j.git", "eclipse", "-j", null}, {"https://bitbucket.org/eclipse/-", "eclipse", "-", null}, {"https://bitbucket.org/eclipse/che-with-hyphen", "eclipse", "che-with-hyphen", null}, {"https://bitbucket.org/eclipse/che-with-hyphen.git", "eclipse", "che-with-hyphen", null}, {"https://bitbucket.org/eclipse/che/", "eclipse", "che", null}, {"https://bitbucket.org/eclipse/repositorygit", "eclipse", "repositorygit", null}, {"git@bitbucket.org:eclipse/che", "eclipse", "che", null}, {"git@bitbucket.org:eclipse/che123", "eclipse", "che123", null}, {"git@bitbucket.org:eclipse/che.git", "eclipse", "che", null}, {"git@bitbucket.org:eclipse/che.with.dot.git", "eclipse", "che.with.dot", null}, {"git@bitbucket.org:eclipse/-.git", "eclipse", "-", null}, {"git@bitbucket.org:eclipse/-j.git", "eclipse", "-j", null}, {"git@bitbucket.org:eclipse/-", "eclipse", "-", null}, {"git@bitbucket.org:eclipse/che-with-hyphen", "eclipse", "che-with-hyphen", null}, {"git@bitbucket.org:eclipse/che-with-hyphen.git", "eclipse", "che-with-hyphen", null}, {"git@bitbucket.org:eclipse/repositorygit", "eclipse", "repositorygit", null}, {"https://bitbucket.org/eclipse/che/src/4.2.x", "eclipse", "che", "4.2.x"}, {"https://bitbucket.org/eclipse/che/src/master/", "eclipse", "che", "master"} }; } @DataProvider(name = "parsingBadRepository") public Object[][] parsingBadRepository() { return new Object[][] { {"https://bitbucket.org/eclipse/che .git", "che .git"}, {"https://bitbucket.org/eclipse/.git", ".git"}, {"https://bitbucket.org/eclipse/myB@dR&pository.git", "myB@dR&pository.git"}, {"https://bitbucket.org/eclipse/.", "."}, {"https://bitbucket.org/eclipse/івапівап.git", "івапівап.git"}, {"https://bitbucket.org/eclipse/ ", " "}, {"https://bitbucket.org/eclipse/.", "."}, {"https://bitbucket.org/eclipse/ .git", " .git"}, {"git@bitbucket.org:eclipse/che .git", "che .git"}, {"git@bitbucket.org:eclipse/.git", ".git"}, {"git@bitbucket.org:eclipse/myB@dR&pository.git", "myB@dR&pository.git"}, {"git@bitbucket.org:eclipse/.", "."}, {"git@bitbucket.org:eclipse/івапівап.git", "івапівап.git"}, {"git@bitbucket.org:eclipse/ ", " "}, {"git@bitbucket.org:eclipse/.", "."}, {"git@bitbucket.org:eclipse/ .git", " .git"} }; } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketUrlTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import java.util.Arrays; import java.util.Iterator; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** Test of {@Link BitbucketUrl} Note: The parser is also testing the object */ @Listeners(MockitoTestNGListener.class) public class BitbucketUrlTest { @Mock private DevfileFilenamesProvider devfileFilenamesProvider; /** Parser used to create the url. */ @InjectMocks private BitbucketURLParser bitbucketURLParser; /** Instance of the url created */ private BitbucketUrl bitbucketUrl; /** Setup objects/ */ @BeforeMethod protected void init() { when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); this.bitbucketUrl = this.bitbucketURLParser.parse("https://bitbucket.org/eclipse/che", null); assertNotNull(this.bitbucketUrl); } /** Check when there is devfile in the repository */ @Test public void checkDevfileLocation() { lenient() .when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); assertEquals(bitbucketUrl.devfileFileLocations().size(), 2); Iterator iterator = bitbucketUrl.devfileFileLocations().iterator(); assertEquals( iterator.next().location(), "https://bitbucket.org/eclipse/che/raw/HEAD/devfile.yaml"); assertEquals(iterator.next().location(), "https://bitbucket.org/eclipse/che/raw/HEAD/foo.bar"); } @Test public void shouldReturnDevfileLocationBySSHUrl() { bitbucketUrl = bitbucketURLParser.parse("git@bitbucket.org:eclipse/che", null); lenient() .when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); assertEquals(bitbucketUrl.devfileFileLocations().size(), 2); Iterator iterator = bitbucketUrl.devfileFileLocations().iterator(); assertEquals( iterator.next().location(), "https://bitbucket.org/eclipse/che/raw/HEAD/devfile.yaml"); assertEquals(iterator.next().location(), "https://bitbucket.org/eclipse/che/raw/HEAD/foo.bar"); } @Test public void shouldReturnEmptyCredentials() { // when BitbucketUrl url = this.bitbucketURLParser.parse("https://user@bitbucket.org/eclipse/che", null); // then assertTrue(url.getCredentials().isEmpty()); } /** Check the original repository */ @Test public void checkRepositoryLocation() { assertEquals(bitbucketUrl.repositoryLocation(), "https://bitbucket.org/eclipse/che.git"); } @Test public void shouldReturnRepositoryLocationBySSHUrl() { bitbucketUrl = bitbucketURLParser.parse("git@bitbucket.org:eclipse/che", null); assertEquals(bitbucketUrl.repositoryLocation(), "git@bitbucket.org:eclipse/che.git"); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/test/resources/__files/bitbucket/rest/user/email/response.json ================================================ { "values": [{"email" : "bitbucketuser@email.com"}] } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/test/resources/__files/bitbucket/rest/user/response.json ================================================ { "username": "user", "display_name": "Bitbucket User", "account_id": "bitbucket-user_id_123" } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-factory-bitbucket-server jar Che Core :: API :: Factory Resolver Bitbucket Server false com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.core jackson-core com.fasterxml.jackson.core jackson-databind com.google.guava guava com.google.inject guice jakarta.inject jakarta.inject-api jakarta.validation jakarta.validation-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-bitbucket org.eclipse.che.core che-core-api-auth-shared org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-factory org.eclipse.che.core che-core-api-factory-shared org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-inject org.eclipse.che.core che-core-commons-lang org.slf4j slf4j-api org.eclipse.che.core che-core-api-model provided ch.qos.logback logback-classic test com.github.tomakehurst wiremock-jre8-standalone test jakarta.servlet jakarta.servlet-api test org.eclipse.che.core che-core-commons-json test org.mockito mockito-core test org.mockito mockito-testng test org.slf4j jcl-over-slf4j test org.testng testng test org.wiremock wiremock-standalone test ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerApiProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.base.Splitter; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.NoopBitbucketServerApiClient; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.StringUtils; import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.security.oauth.OAuthAPI; import org.eclipse.che.security.oauth1.NoopOAuthAuthenticator; import org.eclipse.che.security.oauth1.OAuthAuthenticator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Singleton public class BitbucketServerApiProvider implements Provider { private static final Logger LOG = LoggerFactory.getLogger(BitbucketServerApiProvider.class); private final BitbucketServerApiClient bitbucketServerApiClient; private final String apiEndpoint; private final OAuthAPI oAuthAPI; @Inject public BitbucketServerApiProvider( @Nullable @Named("che.integration.bitbucket.server_endpoints") String bitbucketEndpoints, @Named("che.oauth.bitbucket.endpoint") String bitbucketOauthEndpoint, @Named("che.api") String apiEndpoint, OAuthAPI oAuthAPI, Set authenticators) { this.apiEndpoint = apiEndpoint; this.oAuthAPI = oAuthAPI; bitbucketServerApiClient = doGet(bitbucketEndpoints, bitbucketOauthEndpoint, authenticators); LOG.debug("Bitbucket server api is used {}", bitbucketServerApiClient); } @Override public BitbucketServerApiClient get() { return bitbucketServerApiClient; } private BitbucketServerApiClient doGet( String rawBitbucketEndpoints, String bitbucketOauthEndpoint, Set authenticators) { boolean isBitbucketCloud = bitbucketOauthEndpoint.equals("https://bitbucket.org"); if (isBitbucketCloud && isNullOrEmpty(rawBitbucketEndpoints)) { return new NoopBitbucketServerApiClient(); } else if (!isBitbucketCloud && isNullOrEmpty(rawBitbucketEndpoints)) { throw new ConfigurationException( "`che.integration.bitbucket.server_endpoints` bitbucket configuration is missing." + " It should contain values from 'che.oauth.bitbucket.endpoint'"); } else if (isBitbucketCloud && !isNullOrEmpty(rawBitbucketEndpoints)) { return new HttpBitbucketServerApiClient( sanitizedEndpoints(rawBitbucketEndpoints).get(0), new NoopOAuthAuthenticator(), oAuthAPI, apiEndpoint); } else { bitbucketOauthEndpoint = StringUtils.trimEnd(bitbucketOauthEndpoint, '/'); if (!sanitizedEndpoints(rawBitbucketEndpoints).contains(bitbucketOauthEndpoint)) { throw new ConfigurationException( "`che.integration.bitbucket.server_endpoints` must contain `" + bitbucketOauthEndpoint + "` value"); } else { Optional authenticator = authenticators.stream().findFirst(); if (authenticator.isEmpty()) { throw new ConfigurationException( "'che.oauth.bitbucket.endpoint' is set but BitbucketServerOAuthAuthenticator is not deployed correctly"); } return new HttpBitbucketServerApiClient( bitbucketOauthEndpoint, authenticator.get(), oAuthAPI, apiEndpoint); } } } private static List sanitizedEndpoints(String rawBitbucketEndpoints) { return Splitter.on(",").splitToList(rawBitbucketEndpoints).stream() .map(s -> StringUtils.trimEnd(s, '/')) .collect(Collectors.toList()); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.eclipse.che.api.factory.shared.Constants.REVISION_PARAMETER_NAME; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.eclipse.che.dto.server.DtoFactory.newDto; import jakarta.validation.constraints.NotNull; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.BaseFactoryParameterResolver; import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** * Provides Factory Parameters resolver for both public and private bitbucket repositories. * * @author Max Shaposhnyk */ @Singleton public class BitbucketServerAuthorizingFactoryParametersResolver extends BaseFactoryParameterResolver implements FactoryParametersResolver { private static final String PROVIDER_NAME = "bitbucket-server"; private final URLFactoryBuilder urlFactoryBuilder; private final URLFetcher urlFetcher; /** Parser which will allow to check validity of URLs and create objects. */ private final BitbucketServerURLParser bitbucketURLParser; private final PersonalAccessTokenManager personalAccessTokenManager; @Inject public BitbucketServerAuthorizingFactoryParametersResolver( URLFactoryBuilder urlFactoryBuilder, URLFetcher urlFetcher, BitbucketServerURLParser bitbucketURLParser, PersonalAccessTokenManager personalAccessTokenManager, AuthorisationRequestManager authorisationRequestManager) { super(authorisationRequestManager, urlFactoryBuilder, PROVIDER_NAME); this.urlFactoryBuilder = urlFactoryBuilder; this.urlFetcher = urlFetcher; this.bitbucketURLParser = bitbucketURLParser; this.personalAccessTokenManager = personalAccessTokenManager; } /** * Check if this resolver can be used with the given parameters. * * @param factoryParameters map of parameters dedicated to factories * @return true if it will be accepted by the resolver implementation or false if it is not * accepted */ @Override public boolean accept(@NotNull final Map factoryParameters) { return factoryParameters.containsKey(URL_PARAMETER_NAME) && bitbucketURLParser.isValid(factoryParameters.get(URL_PARAMETER_NAME)); } @Override public String getProviderName() { return PROVIDER_NAME; } /** * Create factory object based on provided parameters * * @param factoryParameters map containing factory data parameters provided through URL * @throws BadRequestException when data are invalid */ @Override public FactoryMetaDto createFactory(@NotNull final Map factoryParameters) throws ApiException { // no need to check null value of url parameter as accept() method has performed the check final BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse( factoryParameters.get(URL_PARAMETER_NAME), factoryParameters.get(REVISION_PARAMETER_NAME)); // create factory from the following location if location exists, else create default factory return createFactory( factoryParameters, bitbucketServerUrl, new BitbucketFactoryVisitor(bitbucketServerUrl), new BitbucketServerAuthorizingFileContentProvider( bitbucketServerUrl, urlFetcher, personalAccessTokenManager)); } /** * Visitor that puts the default devfile or updates devfile projects into the Bitbucket Factory, * if needed. */ private class BitbucketFactoryVisitor implements FactoryVisitor { private final BitbucketServerUrl bitbucketServerUrl; private BitbucketFactoryVisitor(BitbucketServerUrl bitbucketServerUrl) { this.bitbucketServerUrl = bitbucketServerUrl; } @Override public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { ScmInfoDto scmInfo = newDto(ScmInfoDto.class) .withScmProviderName(bitbucketServerUrl.getProviderName()) .withRepositoryUrl(bitbucketServerUrl.repositoryLocation()); if (bitbucketServerUrl.getBranch() != null) { String branch = bitbucketServerUrl.getBranch(); scmInfo.withBranch( branch.startsWith("refs%2Fheads%2F") ? branch.substring(branch.lastIndexOf("%2F") + 3) : branch); } return factoryDto.withScmInfo(scmInfo); } } @Override public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException { return bitbucketURLParser.parse(factoryUrl, null); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFileContentProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import org.eclipse.che.api.factory.server.scm.AuthorizingFileContentProvider; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** * Bitbucket Server specific file content provider. Files are retrieved using bitbucket Server REST * API and personal access token based authentication is performed during requests. */ public class BitbucketServerAuthorizingFileContentProvider extends AuthorizingFileContentProvider { public BitbucketServerAuthorizingFileContentProvider( BitbucketServerUrl bitbucketServerUrl, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { super(bitbucketServerUrl, urlFetcher, personalAccessTokenManager); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerModule.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; public class BitbucketServerModule extends AbstractModule { @Override protected void configure() { Multibinder tokenFetcherMultibinder = Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(BitbucketServerPersonalAccessTokenFetcher.class); bind(BitbucketServerApiClient.class).toProvider(BitbucketServerApiProvider.class); Multibinder gitUserDataMultibinder = Multibinder.newSetBinder(binder(), GitUserDataFetcher.class); gitUserDataMultibinder.addBinding().to(BitbucketServerUserDataFetcher.class); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static java.lang.String.format; import static java.lang.String.valueOf; import com.google.common.collect.ImmutableSet; import java.net.URL; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketPersonalAccessToken; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.security.oauth.OAuthAPI; import org.eclipse.che.security.oauth1.NoopOAuthAuthenticator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Bitbucket implementation for {@link PersonalAccessTokenFetcher}. Right now returns {@code null} * for all possible SCM URL-s (which is valid value) but later will be extended to fully featured * class. */ public class BitbucketServerPersonalAccessTokenFetcher implements PersonalAccessTokenFetcher { private static final Logger LOG = LoggerFactory.getLogger(BitbucketServerPersonalAccessTokenFetcher.class); private static final String OAUTH_PROVIDER_NAME = "bitbucket-server"; private static final String TOKEN_NAME_TEMPLATE = "che-token-<%s>-<%s>"; public static final Set DEFAULT_TOKEN_SCOPE = ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE"); private final BitbucketServerApiClient bitbucketServerApiClient; private final URL apiEndpoint; private final OAuthAPI oAuthAPI; @Inject public BitbucketServerPersonalAccessTokenFetcher( BitbucketServerApiClient bitbucketServerApiClient, @Named("che.api") URL apiEndpoint, OAuthAPI oAuthAPI) { this.bitbucketServerApiClient = bitbucketServerApiClient; this.apiEndpoint = apiEndpoint; this.oAuthAPI = oAuthAPI; } @Override public PersonalAccessToken refreshPersonalAccessToken(Subject cheUser, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException { // #fetchPersonalAccessToken does the same thing as #refreshPersonalAccessToken return fetchOrRefreshPersonalAccessToken(cheUser, scmServerUrl); } @Override public PersonalAccessToken fetchPersonalAccessToken(Subject cheUser, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException { return fetchOrRefreshPersonalAccessToken(cheUser, scmServerUrl); } private PersonalAccessToken fetchOrRefreshPersonalAccessToken( Subject cheUser, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException { if (!bitbucketServerApiClient.isConnected(scmServerUrl)) { LOG.debug("not a valid url {} for current fetcher ", scmServerUrl); return null; } final String tokenName = format(TOKEN_NAME_TEMPLATE, cheUser.getUserId(), apiEndpoint.getHost()); try { BitbucketUser user = bitbucketServerApiClient.getUser(); LOG.debug("Current bitbucket user {} ", user); // cleanup existed List existingTokens = bitbucketServerApiClient.getPersonalAccessTokens().stream() .filter(p -> p.getName().equals(tokenName)) .collect(Collectors.toList()); for (BitbucketPersonalAccessToken existedToken : existingTokens) { LOG.debug("Deleting existed che token {} {}", existedToken.getId(), existedToken.getName()); bitbucketServerApiClient.deletePersonalAccessTokens(existedToken.getId()); } BitbucketPersonalAccessToken token = bitbucketServerApiClient.createPersonalAccessTokens(tokenName, DEFAULT_TOKEN_SCOPE); LOG.debug("Token created = {} for {}", token.getId(), token.getUser()); return new PersonalAccessToken( scmServerUrl, OAUTH_PROVIDER_NAME, EnvironmentContext.getCurrent().getSubject().getUserId(), null, user.getSlug(), token.getName(), valueOf(token.getId()), token.getToken()); } catch (ScmBadRequestException | ScmItemNotFoundException e) { throw new ScmCommunicationException(e.getMessage(), e); } } @Override public Optional isValid(PersonalAccessToken accessToken) throws ScmCommunicationException, ScmUnauthorizedException { if (!bitbucketServerApiClient.isConnected(accessToken.getScmProviderUrl())) { // If BitBucket oAuth is not configured check the manually added user namespace token. HttpBitbucketServerApiClient apiClient = new HttpBitbucketServerApiClient( accessToken.getScmProviderUrl(), new NoopOAuthAuthenticator(), oAuthAPI, apiEndpoint.toString()); try { apiClient.getUser(accessToken.getToken()); return Optional.of(Boolean.TRUE); } catch (ScmItemNotFoundException | ScmUnauthorizedException | ScmCommunicationException exception) { LOG.debug("not a valid url {} for current fetcher ", accessToken.getScmProviderUrl()); return Optional.empty(); } } try { BitbucketPersonalAccessToken bitbucketPersonalAccessToken = bitbucketServerApiClient.getPersonalAccessToken( accessToken.getScmTokenId(), // Pass oauth token to fetch personal access token // TODO: rename the PersonalAccessToken interface to more generic name, so both OAuth // and personal access token implementations would be suitable. accessToken.getToken()); return Optional.of(DEFAULT_TOKEN_SCOPE.equals(bitbucketPersonalAccessToken.getPermissions())); } catch (ScmItemNotFoundException e) { return Optional.of(Boolean.FALSE); } } @Override public Optional> isValid(PersonalAccessTokenParams params) throws ScmCommunicationException { if (!bitbucketServerApiClient.isConnected(params.getScmProviderUrl())) { // If BitBucket oAuth is not configured check the manually added user namespace token. HttpBitbucketServerApiClient apiClient = new HttpBitbucketServerApiClient( params.getScmProviderUrl(), new NoopOAuthAuthenticator(), oAuthAPI, apiEndpoint.toString()); try { BitbucketUser user = apiClient.getUser(params.getToken()); return Optional.of(Pair.of(Boolean.TRUE, user.getName())); } catch (ScmItemNotFoundException | ScmUnauthorizedException | ScmCommunicationException exception) { if (exception instanceof ScmCommunicationException) { ScmCommunicationException scmCommunicationException = (ScmCommunicationException) exception; if (scmCommunicationException.getStatusCode() == 495) { throw scmCommunicationException; } } LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); return Optional.empty(); } } try { BitbucketUser user = bitbucketServerApiClient.getUser(params.getToken()); return Optional.of(Pair.of(Boolean.TRUE, user.getName())); } catch (ScmItemNotFoundException | ScmUnauthorizedException e) { return Optional.empty(); } } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerScmFileResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; import java.io.IOException; import javax.inject.Inject; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.factory.server.ScmFileResolver; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** Bitbucket Server specific SCM file resolver. */ public class BitbucketServerScmFileResolver implements ScmFileResolver { private final URLFetcher urlFetcher; private final BitbucketServerURLParser bitbucketURLParser; private final PersonalAccessTokenManager personalAccessTokenManager; @Inject public BitbucketServerScmFileResolver( BitbucketServerURLParser bitbucketURLParser, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { this.urlFetcher = urlFetcher; this.bitbucketURLParser = bitbucketURLParser; this.personalAccessTokenManager = personalAccessTokenManager; } @Override public boolean accept(String repository) { return bitbucketURLParser.isValid(repository); } @Override public String fileContent(String repository, String filePath) throws ApiException { BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse(repository, null); try { return fetchContent(bitbucketServerUrl, filePath, false); } catch (DevfileException exception) { // This catch might mean that the authentication was rejected by user, try to repeat the fetch // without authentication flow. try { return fetchContent(bitbucketServerUrl, filePath, true); } catch (DevfileException devfileException) { throw toApiException(devfileException); } } } private String fetchContent( BitbucketServerUrl bitbucketServerUrl, String filePath, boolean skipAuthentication) throws DevfileException, NotFoundException { try { BitbucketServerAuthorizingFileContentProvider contentProvider = new BitbucketServerAuthorizingFileContentProvider( bitbucketServerUrl, urlFetcher, personalAccessTokenManager); return skipAuthentication ? contentProvider.fetchContentWithoutAuthentication(filePath) : contentProvider.fetchContent(filePath); } catch (IOException e) { throw new NotFoundException(e.getMessage()); } } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParser.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.regex.Pattern.compile; import com.google.common.base.Splitter; import jakarta.validation.constraints.NotNull; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.StringUtils; import org.eclipse.che.security.oauth.OAuthAPI; import org.eclipse.che.security.oauth1.BitbucketServerOAuthAuthenticator; /** * Parser of String Bitbucket Server URLs and provide {@link BitbucketServerUrl} objects. * * @author Max Shaposhnyk */ @Singleton public class BitbucketServerURLParser { private final DevfileFilenamesProvider devfileFilenamesProvider; private final OAuthAPI oAuthAPI; private final PersonalAccessTokenManager personalAccessTokenManager; private static final List bitbucketUrlPatternTemplates = List.of( "^(?%s)://(?%s)/scm/~(?[^/]+)/(?.*).git$", "^(?%s)://(?%s)/users/(?[^/]+)/repos/(?[^/]+)/browse(\\?at=(?.*))?", "^(?%s)://(?%s)/users/(?[^/]+)/repos/(?[^/]+)/?", "^(?%s)://(?%s)/scm/(?[^/~]+)/(?[^/]+).git", "^(?%s)://(?%s)/projects/(?[^/]+)/repos/(?[^/]+)/browse(\\?at=(?.*))?", "^(?%s)://git@(?%s):?(?\\d*)?/~(?[^/]+)/(?.*).git$", "^(?%s)://git@(?%s):?(?\\d*)?/(?[^/]+)/(?.*).git$"); private final List bitbucketUrlPatterns = new ArrayList<>(); private static final String OAUTH_PROVIDER_NAME = "bitbucket-server"; @Inject public BitbucketServerURLParser( @Nullable @Named("che.integration.bitbucket.server_endpoints") String bitbucketEndpoints, DevfileFilenamesProvider devfileFilenamesProvider, OAuthAPI oAuthAPI, PersonalAccessTokenManager personalAccessTokenManager) { this.devfileFilenamesProvider = devfileFilenamesProvider; this.oAuthAPI = oAuthAPI; this.personalAccessTokenManager = personalAccessTokenManager; if (!isNullOrEmpty(bitbucketEndpoints)) { for (String bitbucketEndpoint : Splitter.on(",").split(bitbucketEndpoints)) { String trimmedEndpoint = StringUtils.trimEnd(bitbucketEndpoint, '/'); bitbucketUrlPatterns.addAll(getUrlPatterns(trimmedEndpoint)); } } } private boolean isUserTokenPresent(String repositoryUrl) { String serverUrl = getServerUrl(repositoryUrl); URI uri = URI.create(repositoryUrl); String schema = uri.getScheme(); String host = uri.getHost(); // Handle IPv6 addresses: escape brackets for regex final String hostForRegex; if (host != null && host.startsWith("[") && host.endsWith("]")) { hostForRegex = "\\[" + Pattern.quote(host.substring(1, host.length() - 1)) + "\\]"; } else if (host != null) { hostForRegex = Pattern.quote(host); } else { hostForRegex = ""; } // Escape scheme for regex to prevent injection final String schemeForRegex = schema != null ? Pattern.quote(schema) : ""; if (bitbucketUrlPatternTemplates.stream() .anyMatch( t -> Pattern.compile(format(t, schemeForRegex, hostForRegex)) .matcher(repositoryUrl) .matches())) { try { Optional token = personalAccessTokenManager.get( EnvironmentContext.getCurrent().getSubject(), null, serverUrl, null); return token.isPresent() && token.get().getScmTokenName().equals(OAUTH_PROVIDER_NAME); } catch (ScmConfigurationPersistenceException | ScmCommunicationException exception) { return false; } } return false; } public boolean isValid(@NotNull String url) { if (!url.contains("://")) { return false; } else if (!bitbucketUrlPatterns.isEmpty()) { return bitbucketUrlPatterns.stream().anyMatch(pattern -> pattern.matcher(url).matches()); } else { return // If Bitbucket server URL is not configured try to find it in a manually added user namespace // token. isUserTokenPresent(url) // Try to call an API request to see if the URL matches Bitbucket Server. || (isApiRequestRelevant(url) // Also check if the URL matches the Bitbucket Server url pattern. && getUrlPatterns(getServerUrl(url)).stream() .anyMatch(pattern -> pattern.matcher(url).matches())); } } private List getUrlPatterns(String url) { URI uri = URI.create(url); ArrayList patterns = new ArrayList<>(); String rawHost = uri.getHost(); // Handle IPv6 addresses: escape brackets for regex final String hostForRegex; if (rawHost != null && rawHost.startsWith("[") && rawHost.endsWith("]")) { hostForRegex = "\\[" + Pattern.quote(rawHost.substring(1, rawHost.length() - 1)) + "\\]"; } else if (rawHost != null) { hostForRegex = Pattern.quote(rawHost); } else { hostForRegex = ""; } String portSuffix = uri.getPort() > 0 ? ":" + uri.getPort() : ""; String pathSuffix = uri.getPath() != null ? Pattern.quote(uri.getPath()) : ""; bitbucketUrlPatternTemplates.forEach( t -> { String scheme = t.contains("git@") ? "ssh" : uri.getScheme(); // Escape scheme for regex to prevent injection String schemeForRegex = scheme != null ? Pattern.quote(scheme) : ""; String host = hostForRegex + portSuffix + pathSuffix; patterns.add(Pattern.compile(format(t, schemeForRegex, host))); }); return patterns; } private boolean isApiRequestRelevant(String repositoryUrl) { try { HttpBitbucketServerApiClient bitbucketServerApiClient = new HttpBitbucketServerApiClient( getServerUrl(repositoryUrl), new BitbucketServerOAuthAuthenticator("", "", "", ""), oAuthAPI, ""); // If the user request catches the unauthorised error, it means that the provided url // belongs to Bitbucket. bitbucketServerApiClient.getUser(); } catch (ScmItemNotFoundException | ScmCommunicationException e) { return false; } catch (ScmUnauthorizedException e) { return true; } return false; } private String getServerUrl(String repositoryUrl) { if (repositoryUrl.startsWith("ssh://git@")) { String substring = repositoryUrl.substring(10); // Handle IPv6 addresses in SSH URLs (e.g., ssh://git@[2001:db8::1]:port/...) if (substring.startsWith("[")) { int closingBracket = substring.indexOf(']'); if (closingBracket > 0) { return "https://" + substring.substring(0, closingBracket + 1); } } return "https://" + substring.substring( 0, substring.contains(":") ? substring.indexOf(":") : substring.indexOf("/")); } // Use URI parsing to properly handle IPv6 addresses try { URI uri = URI.create(repositoryUrl); if (uri.getScheme() != null && uri.getHost() != null) { String authority = uri.getRawAuthority(); if (authority == null) { String host = uri.getHost(); boolean ipv6 = host != null && host.contains(":"); String hostForUrl = ipv6 ? "[" + host + "]" : host; int port = uri.getPort(); authority = port == -1 ? hostForUrl : hostForUrl + ":" + port; } if (authority != null) { String serverUrl = uri.getScheme() + "://" + authority; int authorityIdx = repositoryUrl.indexOf(authority); if (authorityIdx >= 0) { int pathIndex = authorityIdx + authority.length(); if (pathIndex < repositoryUrl.length() && repositoryUrl.charAt(pathIndex) == '/') { return serverUrl; } } } } } catch (IllegalArgumentException e) { // Fall through to old logic if URI parsing fails } return repositoryUrl.substring( 0, repositoryUrl.indexOf("/scm") > 0 ? repositoryUrl.indexOf("/scm") : repositoryUrl.indexOf("/users") > 0 ? repositoryUrl.indexOf("/users") : repositoryUrl.indexOf("/projects") > 0 ? repositoryUrl.indexOf("/projects") : repositoryUrl.length()); } private Optional getPatternMatcherByUrl(String url) { URI uri = URI.create(url); String scheme = uri.getScheme(); String host = uri.getHost(); // Handle IPv6 addresses: escape brackets for regex final String hostForRegex; if (host != null && host.startsWith("[") && host.endsWith("]")) { // IPv6 address - strip brackets, quote the address, re-add escaped brackets hostForRegex = "\\[" + Pattern.quote(host.substring(1, host.length() - 1)) + "\\]"; } else if (host != null) { hostForRegex = Pattern.quote(host); } else { hostForRegex = ""; } // Append port if present String hostWithPort = hostForRegex; if (uri.getPort() > 0) { hostWithPort += ":" + uri.getPort(); } final String finalHostWithPort = hostWithPort; // Escape scheme for regex to prevent injection final String schemeForRegex = scheme != null ? Pattern.quote(scheme) : ""; return bitbucketUrlPatternTemplates.stream() .map(t -> compile(format(t, schemeForRegex, finalHostWithPort)).matcher(url)) .filter(Matcher::matches) .findAny(); } /** * Parses url-s like * https://bitbucket.apps.cluster-cb82.cb82.example.opentlc.com/scm/test/test1.git into * BitbucketUrl objects. */ public BitbucketServerUrl parse(String url, @Nullable String revision) { if (bitbucketUrlPatterns.isEmpty()) { Optional matcherOptional = getPatternMatcherByUrl(url); if (matcherOptional.isPresent()) { return parse(matcherOptional.get(), revision); } throw new UnsupportedOperationException( "The Bitbucket integration is not configured properly and cannot be used at this moment." + "Please refer to docs to check the Bitbucket integration instructions"); } Matcher matcher = bitbucketUrlPatterns.stream() .map(pattern -> pattern.matcher(url)) .filter(Matcher::matches) .findFirst() .orElseThrow( () -> new IllegalArgumentException( format( "The given url %s is not a valid Bitbucket server URL. Check either URL or server configuration.", url))); return parse(matcher, revision).withUrl(url); } private BitbucketServerUrl parse(Matcher matcher, @Nullable String revision) { String scheme = matcher.group("scheme"); String host = matcher.group("host"); String port = null; try { port = matcher.group("port"); } catch (IllegalArgumentException e) { // keep port with null, as the pattern doesn't have the port group } String user = null; String project = null; try { user = matcher.group("user"); } catch (IllegalArgumentException e) { project = matcher.group("project"); } String repoName = matcher.group("repo"); String branch = null; try { String branchFromUrl = matcher.group("branch"); if (!isNullOrEmpty(branchFromUrl)) { branch = branchFromUrl.startsWith("refs/heads/") ? branchFromUrl.substring(11) : (branchFromUrl.startsWith("refs%2Fheads%2F") ? branchFromUrl.substring(15) : branchFromUrl); } } catch (IllegalArgumentException e) { // keep branch with null, as the pattern doesn't have the branch group } return new BitbucketServerUrl() .withScheme(scheme) .withHostName(host) .withPort(port) .withProject(project) .withUser(user) .withRepository(repoName) .withBranch(isNullOrEmpty(branch) ? revision : branch) .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUrl.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Strings.isNullOrEmpty; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.StringJoiner; import java.util.stream.Collectors; import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; /** Representation of a bitbucket Server URL, allowing to get details from it. */ public class BitbucketServerUrl extends DefaultFactoryUrl { private final String NAME = "bitbucket"; /** Hostname of bitbucket URL */ private String hostName; private String scheme; private String port; /** Project part of bitbucket URL */ private String project; private String user; /** Repository part of the URL. */ private String repository; /** Branch name */ private String branch; /** Devfile filenames list */ private final List devfileFilenames = new ArrayList<>(); /** * Creation of this instance is made by the parser so user may not need to create a new instance * directly */ protected BitbucketServerUrl() {} @Override public String getProviderName() { return NAME; } @Override public String getProviderUrl() { return (scheme.equals("ssh") ? "https" : scheme) + "://" + hostName; } /** * Gets hostname of this bitbucket server url * * @return the project part */ public String getHostName() { return this.hostName; } public BitbucketServerUrl withHostName(String hostName) { this.hostName = hostName; return this; } public BitbucketServerUrl withScheme(String scheme) { this.scheme = scheme; return this; } public BitbucketServerUrl withPort(String port) { this.port = port; return this; } /** * Gets project of this bitbucket server url * * @return the project part */ public String getProject() { return this.project; } public BitbucketServerUrl withProject(String project) { this.project = project; return this; } /** * Gets repository of this bitbucket url * * @return the repository part */ public String getRepository() { return this.repository; } protected BitbucketServerUrl withRepository(String repository) { this.repository = repository; return this; } @Override public void setDevfileFilename(String devfileName) { this.devfileFilenames.clear(); this.devfileFilenames.add(devfileName); } protected BitbucketServerUrl withDevfileFilenames(List devfileFilenames) { this.devfileFilenames.addAll(devfileFilenames); return this; } /** * Gets branch of this bitbucket url * * @return the branch part */ public String getBranch() { return this.branch; } protected BitbucketServerUrl withBranch(String branch) { if (!isNullOrEmpty(branch)) { this.branch = branch; } return this; } /** * Gets user of this bitbucket server url * * @return the user part */ public String getUser() { return this.user; } protected BitbucketServerUrl withUser(String user) { if (!isNullOrEmpty(user)) { this.user = user; } return this; } /** * Provides list of configured devfile filenames with locations * * @return list of devfile filenames and locations */ @Override public List devfileFileLocations() { return devfileFilenames.stream().map(this::createDevfileLocation).collect(Collectors.toList()); } private DevfileLocation createDevfileLocation(String devfileFilename) { return new DevfileLocation() { @Override public Optional filename() { return Optional.of(devfileFilename); } @Override public String location() { return rawFileLocation(devfileFilename); } }; } /** * Provides location to raw content of specified file * * @return location of specified file in a repository */ public String rawFileLocation(String fileName) { StringJoiner joiner = new StringJoiner("/") .add((scheme.equals("ssh") ? "https" : scheme) + "://" + hostName) .add("rest/api/1.0") .add(!isNullOrEmpty(user) && isNullOrEmpty(project) ? "users" : "projects") .add(firstNonNull(user, project)) .add("repos") .add(repository) .add("raw") .add(fileName); String resultUrl = joiner.toString(); if (branch != null) { resultUrl = resultUrl + "?at=" + branch; } return resultUrl; } /** * Provides location to the repository part of the full bitbucket URL. * * @return location of the repository. */ protected String repositoryLocation() { if (scheme.equals("ssh")) { return String.format( "%s://git@%s:%s/%s/%s.git", scheme, hostName, port, (isNullOrEmpty(user) ? project : "~" + user), repository); } return scheme + "://" + hostName + "/scm/" + (isNullOrEmpty(user) ? project : "~" + user) + "/" + this.repository + ".git"; } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static java.util.stream.Collectors.toList; import com.google.common.base.Splitter; import java.util.Collections; import java.util.List; import java.util.Optional; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; import org.eclipse.che.api.factory.server.scm.GitUserData; import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.StringUtils; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.security.oauth.OAuthAPI; import org.eclipse.che.security.oauth1.NoopOAuthAuthenticator; /** Bitbucket git user data retriever. */ public class BitbucketServerUserDataFetcher implements GitUserDataFetcher { private final String OAUTH_PROVIDER_NAME = "bitbucket-server"; private final String apiEndpoint; /** Bitbucket API client. */ private final BitbucketServerApiClient bitbucketServerApiClient; private final PersonalAccessTokenManager personalAccessTokenManager; private final OAuthAPI oAuthAPI; private final List registeredBitbucketEndpoints; @Inject public BitbucketServerUserDataFetcher( @Named("che.api") String apiEndpoint, @Nullable @Named("che.integration.bitbucket.server_endpoints") String bitbucketEndpoints, BitbucketServerApiClient bitbucketServerApiClient, OAuthAPI oAuthAPI, PersonalAccessTokenManager personalAccessTokenManager) { this.oAuthAPI = oAuthAPI; this.apiEndpoint = apiEndpoint; this.bitbucketServerApiClient = bitbucketServerApiClient; this.personalAccessTokenManager = personalAccessTokenManager; if (bitbucketEndpoints != null) { this.registeredBitbucketEndpoints = Splitter.on(",") .splitToStream(bitbucketEndpoints) .map(e -> StringUtils.trimEnd(e, '/')) .collect(toList()); } else { this.registeredBitbucketEndpoints = Collections.emptyList(); } } @Override public GitUserData fetchGitUserData(String namespaceName) throws ScmUnauthorizedException, ScmCommunicationException, ScmConfigurationPersistenceException, ScmItemNotFoundException { Subject cheSubject = EnvironmentContext.getCurrent().getSubject(); for (String bitbucketServerEndpoint : this.registeredBitbucketEndpoints) { if (bitbucketServerApiClient.isConnected(bitbucketServerEndpoint)) { try { BitbucketUser user = bitbucketServerApiClient.getUser(); return new GitUserData(user.getDisplayName(), user.getEmailAddress()); } catch (ScmItemNotFoundException e) { throw new ScmCommunicationException(e.getMessage(), e); } } } // Try go get user data using personal access token Optional personalAccessToken = this.personalAccessTokenManager.get(cheSubject, OAUTH_PROVIDER_NAME, null, namespaceName); if (personalAccessToken.isPresent()) { PersonalAccessToken token = personalAccessToken.get(); HttpBitbucketServerApiClient httpBitbucketServerApiClient = new HttpBitbucketServerApiClient( StringUtils.trimEnd(token.getScmProviderUrl(), '/'), new NoopOAuthAuthenticator(), oAuthAPI, this.apiEndpoint); BitbucketUser user = httpBitbucketServerApiClient.getUser(token.getToken()); return new GitUserData(user.getDisplayName(), user.getEmailAddress()); } throw new ScmCommunicationException("Failed to retrieve git user data from Bitbucket"); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static com.google.common.base.Strings.isNullOrEmpty; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.nio.charset.StandardCharsets.UTF_8; import static java.time.Duration.ofSeconds; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import com.google.common.base.Charsets; import com.google.common.io.CharStreams; import com.google.common.net.HttpHeaders; import com.google.common.util.concurrent.ThreadFactoryBuilder; import jakarta.ws.rs.core.MediaType; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.net.URI; import java.net.URLDecoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executors; import java.util.function.Function; import javax.net.ssl.SSLHandshakeException; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketApplicationProperties; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketPersonalAccessToken; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; import org.eclipse.che.api.factory.server.bitbucket.server.Page; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.security.oauth.OAuthAPI; import org.eclipse.che.security.oauth1.NoopOAuthAuthenticator; import org.eclipse.che.security.oauth1.OAuthAuthenticationException; import org.eclipse.che.security.oauth1.OAuthAuthenticator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of @{@link BitbucketServerApiClient} that is using @{@link HttpClient} to * communicate with Bitbucket Server. */ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { private static final ObjectMapper OM = new ObjectMapper(); private static final Logger LOG = LoggerFactory.getLogger(HttpBitbucketServerApiClient.class); private static final Duration DEFAULT_HTTP_TIMEOUT = ofSeconds(10); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public static final String USERNAME_HEADER = "x-ausername"; private final URI serverUri; private final OAuthAuthenticator authenticator; private final OAuthAPI oAuthAPI; private final String apiEndpoint; private final HttpClient httpClient; private static final int SSL_ERROR_CODE = 495; public HttpBitbucketServerApiClient( String serverUrl, OAuthAuthenticator authenticator, OAuthAPI oAuthAPI, String apiEndpoint) { this.serverUri = URI.create(serverUrl.endsWith("/") ? serverUrl : serverUrl + "/"); this.authenticator = authenticator; this.oAuthAPI = oAuthAPI; this.apiEndpoint = apiEndpoint; this.httpClient = HttpClient.newBuilder() .executor( Executors.newCachedThreadPool( new ThreadFactoryBuilder() .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setNameFormat(HttpBitbucketServerApiClient.class.getName() + "-%d") .setDaemon(true) .build())) .connectTimeout(DEFAULT_HTTP_TIMEOUT) .version(HttpClient.Version.HTTP_1_1) .build(); } @Override public boolean isConnected(String bitbucketServerUrl) { return serverUri.equals( URI.create( bitbucketServerUrl.endsWith("/") ? bitbucketServerUrl : bitbucketServerUrl + "/")); } @Override public BitbucketUser getUser(String token) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { return getUser(Optional.of(token)); } @Override public BitbucketUser getUser() throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { return getUser(Optional.empty()); } @Override public List getUsers() throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { try { return doGetItems(Optional.empty(), BitbucketUser.class, "./rest/api/1.0/users", null); } catch (ScmItemNotFoundException e) { throw new ScmCommunicationException(e.getMessage(), e); } } @Override public List getUsers(String filter) throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { try { return doGetItems(Optional.empty(), BitbucketUser.class, "./rest/api/1.0/users", filter); } catch (ScmItemNotFoundException e) { throw new ScmCommunicationException(e.getMessage(), e); } } @Override public void deletePersonalAccessTokens(String tokenId) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { URI uri = serverUri.resolve( "./rest/access-tokens/1.0/users/" + getUserSlug(Optional.empty()) + "/" + tokenId); HttpRequest request = HttpRequest.newBuilder(uri) .DELETE() .headers( HttpHeaders.AUTHORIZATION, computeAuthorizationHeader("DELETE", uri.toString()), HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON, HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .timeout(DEFAULT_HTTP_TIMEOUT) .build(); try { LOG.trace("executeRequest={}", request); executeRequest( httpClient, request, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); return OM.readValue(result, String.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } catch (ScmBadRequestException e) { throw new ScmCommunicationException(e.getMessage(), e); } } @Override public BitbucketPersonalAccessToken createPersonalAccessTokens( String tokenName, Set permissions) throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException { BitbucketPersonalAccessToken token = new BitbucketPersonalAccessToken(tokenName, permissions, 90); URI uri = serverUri.resolve("./rest/access-tokens/1.0/users/" + getUserSlug(Optional.empty())); try { HttpRequest request = HttpRequest.newBuilder(uri) .PUT( HttpRequest.BodyPublishers.ofString( OM.writeValueAsString( // set maximum allowed expiryDays to 90 token))) .headers( HttpHeaders.AUTHORIZATION, computeAuthorizationHeader("PUT", uri.toString()), HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON, HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .timeout(DEFAULT_HTTP_TIMEOUT) .build(); LOG.trace("executeRequest={}", request); return executeRequest( httpClient, request, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); return OM.readValue(result, BitbucketPersonalAccessToken.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } catch (ScmItemNotFoundException | JsonProcessingException e) { throw new ScmCommunicationException(e.getMessage(), e); } } @Override public List getPersonalAccessTokens() throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { try { return doGetItems( Optional.empty(), BitbucketPersonalAccessToken.class, "./rest/access-tokens/1.0/users/" + getUserSlug(Optional.empty()), null); } catch (ScmBadRequestException e) { throw new ScmCommunicationException(e.getMessage(), e); } } @Override public BitbucketPersonalAccessToken getPersonalAccessToken(String tokenId, String oauthToken) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { URI uri = serverUri.resolve( "./rest/access-tokens/1.0/users/" + getUserSlug(Optional.of(oauthToken)) + "/" + tokenId); HttpRequest request = HttpRequest.newBuilder(uri) .headers( "Authorization", "Bearer " + oauthToken, HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) .timeout(DEFAULT_HTTP_TIMEOUT) .build(); try { LOG.trace("executeRequest={}", request); return executeRequest( httpClient, request, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); return OM.readValue(result, BitbucketPersonalAccessToken.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } catch (ScmBadRequestException e) { throw new ScmCommunicationException(e.getMessage(), e); } } private String getUserSlug(Optional token) throws ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { return getUser(token).getSlug(); } private BitbucketUser getUser(Optional token) throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException { // We use the application-properties request to obtain the authenticated username from the // response headers. The request does not fail if no authentication is passed, see: // https://developer.atlassian.com/server/bitbucket/rest/v906/api-group-system-maintenance/#api-api-latest-application-properties-get URI uri = serverUri.resolve("/rest/api/1.0/application-properties"); HttpRequest request = HttpRequest.newBuilder(uri) .headers( "Authorization", token.isPresent() ? "Bearer " + token.get() : computeAuthorizationHeader("GET", uri.toString())) .timeout(DEFAULT_HTTP_TIMEOUT) .build(); HttpResponse response; try { LOG.trace("executeRequest={}", request); response = executeRequest(httpClient, request, r -> r); } catch (ScmBadRequestException e) { throw new ScmCommunicationException(e.getMessage(), e); } String username = URLDecoder.decode(getUsername(response), UTF_8); try { List users = doGetItems(token, BitbucketUser.class, "./rest/api/1.0/users", username); // The result can contain multiple users, but we only want the one with the correct username // See https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp434 return users.stream() .filter(user -> user.getName().equals(username)) .findFirst() .orElseThrow( () -> new ScmItemNotFoundException("User '" + username + "' not found in Bitbucket")); } catch (ScmBadRequestException e) { throw new ScmCommunicationException(e.getMessage(), e); } } private String getUsername(HttpResponse response) throws ScmCommunicationException, ScmUnauthorizedException { try { // Try to get the username from the response header. if (response.headers().firstValue(USERNAME_HEADER).isPresent()) { return response.headers().firstValue(USERNAME_HEADER).get(); } else { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); // Convert the response data to the Bitbucket Server info object. OBJECT_MAPPER.readValue(result, BitbucketApplicationProperties.class); // Throw the unauthorized exception if the response contains the Bitbucket info. throw buildScmUnauthorizedException(); } } catch (IOException e) { // The response does not contain the Bitbucket Server info throw new ScmCommunicationException("Bad request"); } } private List doGetItems(Optional token, Class tClass, String api, String filter) throws ScmUnauthorizedException, ScmCommunicationException, ScmBadRequestException, ScmItemNotFoundException { Page currentPage = doGetPage(token, tClass, api, 0, 25, filter); List result = new ArrayList<>(currentPage.getValues()); while (!currentPage.isLastPage()) { currentPage = doGetPage(token, tClass, api, currentPage.getNextPageStart(), 25, filter); result.addAll(currentPage.getValues()); } return result; } private Page doGetPage( Optional token, Class tClass, String api, int start, int limit, String filter) throws ScmUnauthorizedException, ScmBadRequestException, ScmCommunicationException, ScmItemNotFoundException { String suffix = api + "?start=" + start + "&limit=" + limit; if (!isNullOrEmpty(filter)) { suffix += "&filter=" + filter; } URI uri = serverUri.resolve(suffix); HttpRequest request = HttpRequest.newBuilder(uri) .headers( "Authorization", token.isPresent() ? "Bearer " + token.get() : computeAuthorizationHeader("GET", uri.toString())) .timeout(DEFAULT_HTTP_TIMEOUT) .build(); LOG.trace("executeRequest={}", request); final JavaType typeReference = TypeFactory.defaultInstance().constructParametricType(Page.class, tClass); return executeRequest( httpClient, request, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); return OM.readValue(result, typeReference); } catch (IOException e) { throw new UncheckedIOException(e); } }); } private T executeRequest( HttpClient httpClient, HttpRequest request, Function, T> bodyConverter) throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); LOG.trace("executeRequest={} response {}", request, response.statusCode()); if (response.statusCode() == 200) { return bodyConverter.apply(response); } else if (response.statusCode() == 204) { return null; } else { String body = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); switch (response.statusCode()) { case HTTP_UNAUTHORIZED: throw buildScmUnauthorizedException(); case HTTP_BAD_REQUEST: throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); default: throw new ScmCommunicationException( "Unexpected status code " + response.statusCode() + " " + response, response.statusCode(), "bitbucket"); } } } catch (IOException | InterruptedException | UncheckedIOException e) { if (e instanceof SSLHandshakeException) { throw new ScmCommunicationException( "The required SSL certificate is missing or not trusted by the system. Please contact your administrator.", SSL_ERROR_CODE); } throw new ScmCommunicationException(e.getMessage(), e); } } private @Nullable String getToken() throws ScmUnauthorizedException { try { OAuthToken token = oAuthAPI.getOrRefreshToken("bitbucket-server"); return token.getToken(); } catch (NotFoundException | ServerException | ForbiddenException | BadRequestException | ConflictException e) { LOG.error(e.getMessage()); return null; } catch (UnauthorizedException e) { throw buildScmUnauthorizedException(); } } private String computeAuthorizationHeader(String requestMethod, String requestUrl) throws ScmUnauthorizedException, ScmCommunicationException { if (authenticator instanceof NoopOAuthAuthenticator) { String token = getToken(); if (!isNullOrEmpty(token)) { return "Bearer " + token; } } try { Subject subject = EnvironmentContext.getCurrent().getSubject(); return authenticator.computeAuthorizationHeader( subject.getUserId(), requestMethod, requestUrl); } catch (OAuthAuthenticationException e) { throw new ScmCommunicationException(e.getMessage(), e); } } private ScmUnauthorizedException buildScmUnauthorizedException() { return new ScmUnauthorizedException( EnvironmentContext.getCurrent().getSubject().getUserName() + " is not authorized in bitbucket OAuth provider", "bitbucket", authenticator instanceof NoopOAuthAuthenticator ? "2.0" : "1.0", authenticator instanceof NoopOAuthAuthenticator ? apiEndpoint + "/oauth/authenticate?oauth_provider=bitbucket-server&scope=ADMIN_WRITE" : authenticator.getLocalAuthenticateUrl()); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketApplicationProperties.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket.server; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Objects; @JsonIgnoreProperties(ignoreUnknown = true) public class BitbucketApplicationProperties { private String version; private String buildNumber; private String buildDate; private String displayName; public BitbucketApplicationProperties() {} public BitbucketApplicationProperties( String version, String buildNumber, String buildDate, String displayName) { this.version = version; this.buildNumber = buildNumber; this.buildDate = buildDate; this.displayName = displayName; } public String getVersion() { return version; } public String getBuildNumber() { return buildNumber; } public String getBuildDate() { return buildDate; } public String getDisplayName() { return displayName; } @Override public String toString() { return "BitbucketApplicationProperties{" + "version='" + version + '\'' + ", buildNumber=" + buildNumber + ", buildDate=" + buildDate + ", displayName='" + displayName + '\'' + '}'; } @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } BitbucketApplicationProperties that = (BitbucketApplicationProperties) o; return buildNumber == that.buildNumber && buildDate == that.buildDate && Objects.equals(version, that.version) && Objects.equals(displayName, that.displayName); } @Override public int hashCode() { return Objects.hash(version, buildNumber, buildDate, displayName); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketPersonalAccessToken.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket.server; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Objects; import java.util.Set; @JsonIgnoreProperties(ignoreUnknown = true) public class BitbucketPersonalAccessToken { private String id; private long createdDate; private long lastAuthenticated; private int expiryDays; private String name; private String token; private BitbucketUser user; private Set permissions; public BitbucketPersonalAccessToken(String name, Set permissions, int expiryDays) { this.name = name; this.permissions = permissions; this.expiryDays = expiryDays; } public BitbucketPersonalAccessToken() {} public BitbucketPersonalAccessToken( String id, long createdDate, long lastAuthenticated, int expiryDays, String name, String token, BitbucketUser user, Set permissions) { this.id = id; this.createdDate = createdDate; this.lastAuthenticated = lastAuthenticated; this.expiryDays = expiryDays; this.name = name; this.token = token; this.user = user; this.permissions = permissions; } public String getId() { return id; } public void setId(String id) { this.id = id; } public long getCreatedDate() { return createdDate; } public void setCreatedDate(long createdDate) { this.createdDate = createdDate; } public String getName() { return name; } public void setName(String name) { this.name = name; } public BitbucketUser getUser() { return user; } public void setUser(BitbucketUser user) { this.user = user; } public Set getPermissions() { return permissions; } public void setPermissions(Set permissions) { this.permissions = permissions; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } public long getLastAuthenticated() { return lastAuthenticated; } public void setLastAuthenticated(long lastAuthenticated) { this.lastAuthenticated = lastAuthenticated; } public long getExpiryDays() { return expiryDays; } public void setExpiryDays(int expiryDays) { this.expiryDays = expiryDays; } @Override public String toString() { return "BitbucketPersonalAccessToken{" + "id=" + id + ", createdDate=" + createdDate + ", lastAuthenticated=" + lastAuthenticated + ", expiryDate=" + expiryDays + ", name='" + name + '\'' + ", token='" + token + '\'' + ", user=" + user + ", permissions=" + permissions + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BitbucketPersonalAccessToken that = (BitbucketPersonalAccessToken) o; return id.equals(that.id) && createdDate == that.createdDate && lastAuthenticated == that.lastAuthenticated && expiryDays == that.expiryDays && Objects.equals(name, that.name) && Objects.equals(token, that.token) && Objects.equals(user, that.user) && Objects.equals(permissions, that.permissions); } @Override public int hashCode() { return Objects.hash( id, createdDate, lastAuthenticated, expiryDays, name, token, user, permissions); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketServerApiClient.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket.server; import java.util.List; import java.util.Set; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; /** Bitbucket Server API client. */ public interface BitbucketServerApiClient { /** * @param bitbucketServerUrl * @return - true if client is connected to the given bitbucket server. */ boolean isConnected(String bitbucketServerUrl); /** * @param token token to authorise the user request. * @return - authenticated {@link BitbucketUser}. */ BitbucketUser getUser(String token) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException; /** * @return Retrieve the authenticated {@link BitbucketUser} using an OAuth token. * @return - authenticated {@link BitbucketUser}. */ BitbucketUser getUser() throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException; /** * @return Retrieve a list of {@link BitbucketUser}. Only authenticated users may call this * resource. * @throws ScmBadRequestException * @throws ScmUnauthorizedException * @throws ScmCommunicationException */ List getUsers() throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException; /** * @return Retrieve a list of {@link BitbucketUser}, optionally run through provided filters. Only * authenticated users may call this resource. * @throws ScmBadRequestException * @throws ScmUnauthorizedException * @throws ScmCommunicationException */ List getUsers(String filter) throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException; /** * Modify an access token for the user according to the given request. Any fields not specified * will not be altered * * @param tokenId - the token id * @throws ScmItemNotFoundException * @throws ScmUnauthorizedException * @throws ScmCommunicationException */ void deletePersonalAccessTokens(String tokenId) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException; /** * Create an access token for the user according to the given request. * * @param tokenName * @param permissions * @return * @throws ScmBadRequestException * @throws ScmUnauthorizedException * @throws ScmCommunicationException */ BitbucketPersonalAccessToken createPersonalAccessTokens(String tokenName, Set permissions) throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException; /** * Get all personal access tokens associated with the given user * * @return * @throws ScmItemNotFoundException * @throws ScmUnauthorizedException * @throws ScmBadRequestException * @throws ScmCommunicationException */ List getPersonalAccessTokens() throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException; /** * @param tokenId - bitbucket personal access token id. * @param oauthToken - bitbucket oauth token. * @return - Bitbucket personal access token. * @throws ScmCommunicationException */ BitbucketPersonalAccessToken getPersonalAccessToken(String tokenId, String oauthToken) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException; } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketUser.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket.server; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Objects; @JsonIgnoreProperties(value = "links") public class BitbucketUser { private String displayName; private String name; private long id; private String type; private boolean isActive; private String slug; private String emailAddress; public BitbucketUser( String displayName, String name, long id, String type, boolean isActive, String slug, String emailAddress) { this.displayName = displayName; this.name = name; this.id = id; this.type = type; this.isActive = isActive; this.slug = slug; this.emailAddress = emailAddress; } public BitbucketUser() {} public String getDisplayName() { return displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } public String getName() { return name; } public void setName(String name) { this.name = name; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getType() { return type; } public void setType(String type) { this.type = type; } public boolean isActive() { return isActive; } public void setActive(boolean active) { isActive = active; } public String getSlug() { return slug; } public void setSlug(String slug) { this.slug = slug; } public String getEmailAddress() { return emailAddress; } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BitbucketUser that = (BitbucketUser) o; return id == that.id && isActive == that.isActive && Objects.equals(displayName, that.displayName) && Objects.equals(name, that.name) && Objects.equals(type, that.type) && Objects.equals(slug, that.slug) && Objects.equals(emailAddress, that.emailAddress); } @Override public int hashCode() { return Objects.hash(displayName, name, id, type, isActive, slug, emailAddress); } @Override public String toString() { return "BitbucketUser{" + "displayName='" + displayName + '\'' + ", name='" + name + '\'' + ", id=" + id + ", type='" + type + '\'' + ", isActive=" + isActive + ", slug='" + slug + '\'' + ", emailAddress='" + emailAddress + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/NoopBitbucketServerApiClient.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket.server; import java.util.List; import java.util.Set; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; /** * Implementation of @{@link BitbucketServerApiClient} that is going to be deployed in container in * case if no integration with Bitbucket server is needed. */ public class NoopBitbucketServerApiClient implements BitbucketServerApiClient { @Override public boolean isConnected(String bitbucketServerUrl) { return false; } @Override public BitbucketUser getUser(String token) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { throw new RuntimeException( "The fallback noop api client cannot be used for real operation. Make sure Bitbucket OAuth1 is properly configured."); } @Override public BitbucketUser getUser() throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { throw new RuntimeException( "The fallback noop api client cannot be used for real operation. Make sure Bitbucket OAuth1 is properly configured."); } @Override public List getUsers() throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { throw new RuntimeException( "The fallback noop api client cannot be used for real operation. Make sure Bitbucket OAuth1 is properly configured."); } @Override public List getUsers(String filter) throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { throw new RuntimeException( "The fallback noop api client cannot be used for real operation. Make sure Bitbucket OAuth1 is properly configured."); } @Override public void deletePersonalAccessTokens(String tokenId) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { throw new RuntimeException( "The fallback noop api client cannot be used for real operation. Make sure Bitbucket OAuth1 is properly configured."); } @Override public BitbucketPersonalAccessToken createPersonalAccessTokens( String tokenName, Set permissions) throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { throw new RuntimeException("Invalid usage of BitbucketServerApi"); } @Override public List getPersonalAccessTokens() throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { throw new RuntimeException("Invalid usage of BitbucketServerApi"); } @Override public BitbucketPersonalAccessToken getPersonalAccessToken(String tokenId, String oauthToken) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { throw new RuntimeException("Invalid usage of BitbucketServerApi"); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/Page.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket.server; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import java.util.Objects; /** * Bitbucket's paging object. Combines collections of items with some metadata. * *

    See more * *

    https://docs.atlassian.com/bitbucket-server/rest/5.6.1/bitbucket-rest.html * * @param */ public class Page { private int start; private int size; private int limit; @JsonProperty(value = "isLastPage") private boolean isLastPage; private int nextPageStart; List values; public int getStart() { return start; } public void setStart(int start) { this.start = start; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public int getLimit() { return limit; } public void setLimit(int limit) { this.limit = limit; } public boolean isLastPage() { return isLastPage; } public void setLastPage(boolean lastPage) { isLastPage = lastPage; } public List getValues() { return values; } public void setValues(List values) { this.values = values; } public int getNextPageStart() { return nextPageStart; } public void setNextPageStart(int nextPageStart) { this.nextPageStart = nextPageStart; } @Override public String toString() { return "Page{" + "start=" + start + ", size=" + size + ", limit=" + limit + ", isLastPage=" + isLastPage + ", nextPageStart=" + nextPageStart + ", values=" + values + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Page page = (Page) o; return start == page.start && size == page.size && limit == page.limit && isLastPage == page.isLastPage && nextPageStart == page.nextPageStart && Objects.equals(values, page.values); } @Override public int hashCode() { return Objects.hash(start, size, limit, isLastPage, nextPageStart, values); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerApiClientProviderTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.util.Collections; import java.util.Set; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.NoopBitbucketServerApiClient; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.security.oauth.OAuthAPI; import org.eclipse.che.security.oauth1.BitbucketServerOAuthAuthenticator; import org.eclipse.che.security.oauth1.OAuthAuthenticator; import org.mockito.Mock; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class BitbucketServerApiClientProviderTest { BitbucketServerOAuthAuthenticator oAuthAuthenticator; @Mock OAuthAPI oAuthAPI; @BeforeClass public void setUp() { oAuthAuthenticator = new BitbucketServerOAuthAuthenticator( "df", "private", " https://bitbucket2.server.com", " https://che.server.com"); } @Test public void shouldBeAbleToCreateBitbucketServerApi() { // given BitbucketServerApiProvider bitbucketServerApiProvider = new BitbucketServerApiProvider( "https://bitbucket.server.com, https://bitbucket2.server.com", "https://bitbucket.server.com", "https://bitbucket.server.com", oAuthAPI, ImmutableSet.of(oAuthAuthenticator)); // when BitbucketServerApiClient actual = bitbucketServerApiProvider.get(); // then assertNotNull(actual); assertTrue(HttpBitbucketServerApiClient.class.isAssignableFrom(actual.getClass())); } @Test public void shouldNormalizeURLsBeforeCreateBitbucketServerApi() { // given BitbucketServerApiProvider bitbucketServerApiProvider = new BitbucketServerApiProvider( "https://bitbucket.server.com, https://bitbucket2.server.com", "https://bitbucket.server.com", "https://bitbucket.server.com", oAuthAPI, ImmutableSet.of(oAuthAuthenticator)); // when BitbucketServerApiClient actual = bitbucketServerApiProvider.get(); // then assertNotNull(actual); // internal representation always w/out slashes assertTrue(actual.isConnected("https://bitbucket.server.com/")); } @Test(dataProvider = "noopConfig") public void shouldProvideNoopOAuthAuthenticatorIfSomeConfigurationIsNotSet( @Nullable String bitbucketEndpoints, Set authenticators) throws IOException { // given BitbucketServerApiProvider bitbucketServerApiProvider = new BitbucketServerApiProvider( bitbucketEndpoints, "https://bitbucket.org", "", oAuthAPI, authenticators); // when BitbucketServerApiClient actual = bitbucketServerApiProvider.get(); // then assertNotNull(actual); assertTrue(NoopBitbucketServerApiClient.class.isAssignableFrom(actual.getClass())); } @Test(dataProvider = "httpOnlyConfig") public void shouldProvideHttpAuthenticatorIfOauthConfigurationIsNotSet( @Nullable String bitbucketEndpoints, Set authenticators) throws IOException { // given BitbucketServerApiProvider bitbucketServerApiProvider = new BitbucketServerApiProvider( bitbucketEndpoints, "https://bitbucket.org", "", oAuthAPI, authenticators); // when BitbucketServerApiClient actual = bitbucketServerApiProvider.get(); // then assertNotNull(actual); assertTrue(HttpBitbucketServerApiClient.class.isAssignableFrom(actual.getClass())); } @Test( expectedExceptions = ConfigurationException.class, expectedExceptionsMessageRegExp = "`che.integration.bitbucket.server_endpoints` bitbucket configuration is missing. It should contain values from 'che.oauth.bitbucket.endpoint'") public void shouldFailToBuildIfEndpointsAreMisconfigured() { // given // when BitbucketServerApiProvider bitbucketServerApiProvider = new BitbucketServerApiProvider( "", "https://bitbucket.server.com", "https://bitbucket.server.com", oAuthAPI, ImmutableSet.of(oAuthAuthenticator)); } @Test( expectedExceptions = ConfigurationException.class, expectedExceptionsMessageRegExp = "'che.oauth.bitbucket.endpoint' is set but BitbucketServerOAuthAuthenticator is not deployed correctly") public void shouldFailToBuildIfEndpointsAreMisconfigured2() { // given // when BitbucketServerApiProvider bitbucketServerApiProvider = new BitbucketServerApiProvider( "https://bitbucket.server.com, https://bitbucket2.server.com", "https://bitbucket.server.com", "https://bitbucket.server.com", oAuthAPI, Collections.emptySet()); } @Test( expectedExceptions = ConfigurationException.class, expectedExceptionsMessageRegExp = "`che.integration.bitbucket.server_endpoints` must contain `https://bitbucket.server.com` value") public void shouldFailToBuildIfEndpointsAreMisconfigured3() { // given // when BitbucketServerApiProvider bitbucketServerApiProvider = new BitbucketServerApiProvider( "https://bitbucket3.server.com, https://bitbucket2.server.com", "https://bitbucket.server.com", "https://bitbucket.server.com", oAuthAPI, ImmutableSet.of(oAuthAuthenticator)); } @DataProvider(name = "noopConfig") public Object[][] noopConfig() { return new Object[][] { {null, null}, {"", null} }; } @DataProvider(name = "httpOnlyConfig") public Object[][] httpOnlyConfig() { return new Object[][] { {"https://bitbucket.server.com", null}, {"https://bitbucket.server.com, https://bitbucket2.server.com", null}, { "https://bitbucket.server.com, https://bitbucket2.server.com", ImmutableSet.of(oAuthAuthenticator) } }; } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.eclipse.che.security.oauth1.OAuthAuthenticationService.ERROR_QUERY_NAME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.model.factory.ScmInfo; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketServerAuthorizingFactoryParametersResolverTest { @Mock private OAuthAPI oAuthAPI; @Mock private URLFactoryBuilder urlFactoryBuilder; @Mock private URLFetcher urlFetcher; @Mock private DevfileFilenamesProvider devfileFilenamesProvider; @Mock private AuthorisationRequestManager authorisationRequestManager; BitbucketServerURLParser bitbucketURLParser; @Mock private PersonalAccessTokenManager personalAccessTokenManager; private BitbucketServerAuthorizingFactoryParametersResolver bitbucketServerFactoryParametersResolver; @BeforeMethod protected void init() { bitbucketURLParser = new BitbucketServerURLParser( "http://bitbucket.2mcl.com", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); assertNotNull(this.bitbucketURLParser); bitbucketServerFactoryParametersResolver = new BitbucketServerAuthorizingFactoryParametersResolver( urlFactoryBuilder, urlFetcher, bitbucketURLParser, personalAccessTokenManager, authorisationRequestManager); assertNotNull(this.bitbucketServerFactoryParametersResolver); } /** Check url which is not a bitbucket url can't be accepted by this resolver */ @Test public void checkInvalidAcceptUrl() { Map parameters = singletonMap(URL_PARAMETER_NAME, "http://github.com"); // shouldn't be accepted assertFalse(bitbucketServerFactoryParametersResolver.accept(parameters)); } /** Check bitbucket url will be be accepted by this resolver */ @Test public void checkValidAcceptUrl() { Map parameters = singletonMap(URL_PARAMETER_NAME, "http://bitbucket.2mcl.com/scm/test/repo.git"); // should be accepted assertTrue(bitbucketServerFactoryParametersResolver.accept(parameters)); } @Test public void shouldGenerateDevfileForFactoryWithNoDevfileOrJson() throws Exception { String bitbucketUrl = "http://bitbucket.2mcl.com/scm/test/repo.git"; when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.empty()); Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) bitbucketServerFactoryParametersResolver.createFactory(params); // then ScmInfoDto scmInfo = factory.getScmInfo(); assertEquals(scmInfo.getRepositoryUrl(), bitbucketUrl); assertEquals(scmInfo.getBranch(), null); } @Test public void shouldSetDefaultProjectIntoDevfileIfNotSpecified() throws Exception { String bitbucketUrl = "http://bitbucket.2mcl.com/users/test/repos/repo/browse?at=refs%2Fheads%2Ffoobar"; FactoryDevfileV2Dto factoryDevfileV2Dto = generateDevfileV2Factory(); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.of(factoryDevfileV2Dto)); Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) bitbucketServerFactoryParametersResolver.createFactory(params); // then assertNotNull(factory.getDevfile()); ScmInfoDto source = factory.getScmInfo(); assertEquals(source.getRepositoryUrl(), "http://bitbucket.2mcl.com/scm/~test/repo.git"); assertEquals(source.getBranch(), "foobar"); } @Test public void shouldSetScmInfoIntoDevfileV2() throws Exception { String bitbucketUrl = "http://bitbucket.2mcl.com/users/test/repos/repo/browse?at=refs%2Fheads%2Ffoobar"; FactoryDevfileV2Dto computedFactory = generateDevfileV2Factory(); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.of(computedFactory)); Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) bitbucketServerFactoryParametersResolver.createFactory(params); // then ScmInfo scmInfo = factory.getScmInfo(); assertNotNull(scmInfo); assertEquals(scmInfo.getScmProviderName(), "bitbucket"); assertEquals(scmInfo.getRepositoryUrl(), "http://bitbucket.2mcl.com/scm/~test/repo.git"); assertEquals(scmInfo.getBranch(), "foobar"); } private FactoryDevfileV2Dto generateDevfileV2Factory() { return newDto(FactoryDevfileV2Dto.class) .withV(CURRENT_VERSION) .withSource("repo") .withDevfile(Map.of("schemaVersion", "2.0.0")); } @Test public void shouldCreateFactoryWithoutAuthentication() throws ApiException { // given String bitbucketServerUrl = "http://bitbucket.2mcl.com/scm/~user/repo.git"; Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketServerUrl, ERROR_QUERY_NAME, "access_denied"); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.of(generateDevfileV2Factory())); // when bitbucketServerFactoryParametersResolver.createFactory(params); // then verify(urlFactoryBuilder) .createFactoryFromDevfile( any(BitbucketServerUrl.class), any(BitbucketServerAuthorizingFileContentProvider.class), anyMap(), eq(true)); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFileContentProviderTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Collections; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketServerAuthorizingFileContentProviderTest { public static final String TEST_HOSTNAME = "foo.bar"; public static final String TEST_SCHEME = "https"; @Mock private URLFetcher urlFetcher; @Mock private PersonalAccessTokenManager personalAccessTokenManager; @Test public void shouldFetchContentWithTokenIfPresent() throws Exception { BitbucketServerUrl url = new BitbucketServerUrl().withHostName(TEST_HOSTNAME).withScheme(TEST_SCHEME); BitbucketServerAuthorizingFileContentProvider fileContentProvider = new BitbucketServerAuthorizingFileContentProvider( url, urlFetcher, personalAccessTokenManager); PersonalAccessToken token = new PersonalAccessToken(TEST_SCHEME + "://" + TEST_HOSTNAME, "provider", "user1", "token"); when(personalAccessTokenManager.getAndStore(anyString())).thenReturn(token); String fileURL = "https://foo.bar/scm/repo/.devfile"; // when fileContentProvider.fetchContent(fileURL); // then verify(urlFetcher).fetch(eq(fileURL), eq("Bearer token")); } @Test public void shouldFetchTokenIfNotYetPresent() throws Exception { BitbucketServerUrl url = new BitbucketServerUrl().withHostName(TEST_HOSTNAME).withScheme(TEST_SCHEME); BitbucketServerAuthorizingFileContentProvider fileContentProvider = new BitbucketServerAuthorizingFileContentProvider( url, urlFetcher, personalAccessTokenManager); PersonalAccessToken token = new PersonalAccessToken(TEST_SCHEME + "://" + TEST_HOSTNAME, "provider", "user1", "token"); when(personalAccessTokenManager.getAndStore(eq(TEST_SCHEME + "://" + TEST_HOSTNAME))) .thenReturn(token); String fileURL = "https://foo.bar/scm/repo/.devfile"; // when fileContentProvider.fetchContent(fileURL); // then verify(personalAccessTokenManager).getAndStore(eq(TEST_SCHEME + "://" + TEST_HOSTNAME)); verify(urlFetcher).fetch(eq(fileURL), eq("Bearer token")); } @Test(dataProvider = "relativePathsProvider") public void shouldResolveRelativePaths(String relative, String expected, String branch) throws Exception { BitbucketServerUrl url = new BitbucketServerUrl() .withHostName(TEST_HOSTNAME) .withScheme(TEST_SCHEME) .withProject("proj") .withRepository("repo") .withDevfileFilenames(Collections.singletonList(".devfile")); if (branch != null) { url.withBranch(branch); } BitbucketServerAuthorizingFileContentProvider fileContentProvider = new BitbucketServerAuthorizingFileContentProvider( url, urlFetcher, personalAccessTokenManager); PersonalAccessToken token = new PersonalAccessToken(TEST_SCHEME + "://" + TEST_HOSTNAME, "provider", "user1", "token"); when(personalAccessTokenManager.getAndStore(anyString())).thenReturn(token); // when fileContentProvider.fetchContent(relative); // then verify(urlFetcher).fetch(eq(expected), eq("Bearer token")); } @DataProvider public static Object[][] relativePathsProvider() { return new Object[][] { {"./file.txt", "https://foo.bar/rest/api/1.0/projects/proj/repos/repo/raw/file.txt", null}, {"/file.txt", "https://foo.bar/rest/api/1.0/projects/proj/repos/repo/raw/file.txt", null}, {"file.txt", "https://foo.bar/rest/api/1.0/projects/proj/repos/repo/raw/file.txt", null}, { "foo/file.txt", "https://foo.bar/rest/api/1.0/projects/proj/repos/repo/raw/foo/file.txt", null }, { "file.txt", "https://foo.bar/rest/api/1.0/projects/proj/repos/repo/raw/file.txt?at=foo", "foo" } }; } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcherTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static java.lang.String.valueOf; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.Optional; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketPersonalAccessToken; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketServerPersonalAccessTokenFetcherTest { String someNotBitbucketURL = "https://notabitbucket.com"; String someBitbucketURL = "https://some.bitbucketserver.com"; Subject subject; @Mock BitbucketServerApiClient bitbucketServerApiClient; @Mock PersonalAccessTokenParams personalAccessTokenParams; @Mock OAuthAPI oAuthAPI; BitbucketUser bitbucketUser; BitbucketServerPersonalAccessTokenFetcher fetcher; BitbucketPersonalAccessToken bitbucketPersonalAccessToken; BitbucketPersonalAccessToken bitbucketPersonalAccessToken2; BitbucketPersonalAccessToken bitbucketPersonalAccessToken3; @BeforeMethod public void setup() throws MalformedURLException { URL apiEndpoint = new URL("https://che.server.com"); subject = new SubjectImpl("another_user", Collections.emptyList(), "user987", "token111", false); bitbucketUser = new BitbucketUser( "User User", "user-name", 32423523, "NORMAL", true, "user-slug", "user@users.com"); bitbucketPersonalAccessToken = new BitbucketPersonalAccessToken( "234234", 234345345, 23534534, 90, "che-token--", "2340590skdf3<0>945i0923i4jasoidfj934ui50", bitbucketUser, ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE")); bitbucketPersonalAccessToken2 = new BitbucketPersonalAccessToken( "3647456", 234345345, 23534534, 90, "che-token--", "34545<0>945i0923i4jasoidfj934ui50", bitbucketUser, ImmutableSet.of("REPO_READ")); bitbucketPersonalAccessToken3 = new BitbucketPersonalAccessToken( "132423", 234345345, 23534534, 90, "che-token--", "3456\\<0>945//i0923i4jasoidfj934ui50", bitbucketUser, ImmutableSet.of("PROJECT_READ", "REPO_READ")); fetcher = new BitbucketServerPersonalAccessTokenFetcher( bitbucketServerApiClient, apiEndpoint, oAuthAPI); EnvironmentContext context = new EnvironmentContext(); context.setSubject(subject); EnvironmentContext.setCurrent(context); } @Test public void shouldSkipToFetchUnknownUrls() throws ScmUnauthorizedException, ScmCommunicationException { // given when(bitbucketServerApiClient.isConnected(eq(someNotBitbucketURL))).thenReturn(false); // when PersonalAccessToken result = fetcher.fetchPersonalAccessToken(subject, someNotBitbucketURL); // then assertNull(result); } @Test( dataProvider = "expectedExceptions", expectedExceptions = {ScmUnauthorizedException.class, ScmCommunicationException.class}) public void shouldRethrowBasicExceptionsOnGetUserStep(Class exception) throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException { // given when(bitbucketServerApiClient.isConnected(eq(someNotBitbucketURL))).thenReturn(true); doThrow(exception).when(bitbucketServerApiClient).getUser(); // when fetcher.fetchPersonalAccessToken(subject, someNotBitbucketURL); } @Test public void shouldBeAbleToFetchPersonalAccessToken() throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException, ScmBadRequestException { // given when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true); when(bitbucketServerApiClient.getUser()).thenReturn(bitbucketUser); when(bitbucketServerApiClient.getPersonalAccessTokens()).thenReturn(Collections.emptyList()); when(bitbucketServerApiClient.createPersonalAccessTokens( eq("che-token--"), eq(ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE")))) .thenReturn(bitbucketPersonalAccessToken); // when PersonalAccessToken result = fetcher.fetchPersonalAccessToken(subject, someBitbucketURL); // then assertNotNull(result); assertEquals(result.getScmProviderUrl(), someBitbucketURL); assertEquals(result.getCheUserId(), subject.getUserId()); assertNull(result.getScmOrganization(), bitbucketUser.getName()); assertEquals(result.getScmTokenId(), valueOf(bitbucketPersonalAccessToken.getId())); assertEquals(result.getToken(), bitbucketPersonalAccessToken.getToken()); } @Test public void shouldDeleteExistedCheTokenBeforeCreatingNew() throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException, ScmBadRequestException { when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true); when(bitbucketServerApiClient.getUser()).thenReturn(bitbucketUser); when(bitbucketServerApiClient.getPersonalAccessTokens()) .thenReturn(ImmutableList.of(bitbucketPersonalAccessToken, bitbucketPersonalAccessToken2)); when(bitbucketServerApiClient.createPersonalAccessTokens( eq("che-token--"), eq(ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE")))) .thenReturn(bitbucketPersonalAccessToken3); // when PersonalAccessToken result = fetcher.fetchPersonalAccessToken(subject, someBitbucketURL); // then assertNotNull(result); verify(bitbucketServerApiClient) .deletePersonalAccessTokens(eq(bitbucketPersonalAccessToken.getId())); verify(bitbucketServerApiClient) .deletePersonalAccessTokens(eq(bitbucketPersonalAccessToken2.getId())); } @Test(expectedExceptions = {ScmCommunicationException.class}) public void shouldRethrowUnExceptionsOnCreatePersonalAccessTokens() throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException, ScmBadRequestException { // given when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true); when(bitbucketServerApiClient.getUser()).thenReturn(bitbucketUser); when(bitbucketServerApiClient.getPersonalAccessTokens()).thenReturn(Collections.emptyList()); doThrow(ScmBadRequestException.class) .when(bitbucketServerApiClient) .createPersonalAccessTokens( eq("che-token--"), eq(ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE"))); // when fetcher.fetchPersonalAccessToken(subject, someBitbucketURL); } @Test public void shouldSkipToValidateTokensWithUnknownUrls() throws ScmUnauthorizedException, ScmCommunicationException, ForbiddenException, ServerException, ConflictException, UnauthorizedException, NotFoundException, BadRequestException { // given when(personalAccessTokenParams.getToken()).thenReturn("token"); when(personalAccessTokenParams.getScmProviderUrl()).thenReturn(someNotBitbucketURL); when(bitbucketServerApiClient.isConnected(eq(someNotBitbucketURL))).thenReturn(false); // when Optional> result = fetcher.isValid(personalAccessTokenParams); // then assertTrue(result.isEmpty()); } @Test public void shouldBeAbleToValidateToken() throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException { // given when(personalAccessTokenParams.getScmProviderUrl()).thenReturn(someBitbucketURL); when(personalAccessTokenParams.getToken()).thenReturn(bitbucketPersonalAccessToken.getToken()); when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true); when(bitbucketServerApiClient.getUser(anyString())).thenReturn(bitbucketUser); // when Optional> result = fetcher.isValid(personalAccessTokenParams); // then assertFalse(result.isEmpty()); assertTrue(result.get().first); assertEquals(result.get().second, bitbucketUser.getName()); } @Test public void shouldValidateTokenWithoutId() throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException { // given when(personalAccessTokenParams.getScmProviderUrl()).thenReturn(someBitbucketURL); when(personalAccessTokenParams.getToken()).thenReturn("token"); when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true); when(bitbucketServerApiClient.getUser(eq("token"))).thenReturn(bitbucketUser); // when Optional> result = fetcher.isValid(personalAccessTokenParams); // then assertFalse(result.isEmpty()); assertTrue(result.get().first); assertEquals(result.get().second, bitbucketUser.getName()); } @DataProvider public static Object[][] expectedExceptions() { return new Object[][] {{ScmUnauthorizedException.class}, {ScmCommunicationException.class}}; } @DataProvider public static Object[][] unExpectedExceptions() { return new Object[][] {{ScmBadRequestException.class}, {ScmItemNotFoundException.class}}; } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerScmFileResolverTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.*; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketServerScmFileResolverTest { public static final String SCM_URL = "https://foo.bar"; BitbucketServerURLParser bitbucketURLParser; @Mock private OAuthAPI oAuthAPI; @Mock private URLFetcher urlFetcher; @Mock private DevfileFilenamesProvider devfileFilenamesProvider; @Mock private PersonalAccessTokenManager personalAccessTokenManager; private BitbucketServerScmFileResolver serverScmFileResolver; @BeforeMethod protected void init() { bitbucketURLParser = new BitbucketServerURLParser( SCM_URL, devfileFilenamesProvider, oAuthAPI, personalAccessTokenManager); assertNotNull(this.bitbucketURLParser); serverScmFileResolver = new BitbucketServerScmFileResolver( bitbucketURLParser, urlFetcher, personalAccessTokenManager); assertNotNull(this.serverScmFileResolver); } /** Check url which is not a bitbucket url can't be accepted by this resolver */ @Test public void checkInvalidAcceptUrl() { // shouldn't be accepted assertFalse(serverScmFileResolver.accept("http://github.com")); } /** Check bitbucket url will be be accepted by this resolver */ @Test public void checkValidAcceptUrl() { // should be accepted assertTrue(serverScmFileResolver.accept("https://foo.bar/scm/test/repo.git")); } @Test public void shouldReturnContentFromUrlFetcher() throws Exception { final String rawContent = "raw_content"; final String filename = "devfile.yaml"; when(personalAccessTokenManager.getAndStore(anyString())) .thenReturn(new PersonalAccessToken(SCM_URL, "provider", "root", "token123")); when(urlFetcher.fetch(anyString(), eq("Bearer token123"))).thenReturn(rawContent); String content = serverScmFileResolver.fileContent("https://foo.bar/scm/test/repo.git", filename); assertEquals(content, rawContent); } @Test public void shouldFetchContentWithoutAuthentication() throws Exception { // given when(personalAccessTokenManager.getAndStore(anyString())) .thenThrow(new ScmUnauthorizedException("message", "bitbucket-server", "v1", "url")); // when serverScmFileResolver.fileContent("https://foo.bar/scm/~username/repo.git", "devfile.yaml"); // then verify(urlFetcher).fetch(anyString()); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParserTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketServerURLParserTest { @Mock private DevfileFilenamesProvider devfileFilenamesProvider; @Mock private OAuthAPI oAuthAPI; /** Instance of component that will be tested. */ private BitbucketServerURLParser bitbucketURLParser; private WireMockServer wireMockServer; @BeforeClass public void prepare() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); new WireMock("localhost", wireMockServer.port()); } @BeforeMethod public void setUp() { bitbucketURLParser = new BitbucketServerURLParser( "https://bitbucket.2mcl.com,https://bbkt.com,https://my-bitbucket.org/bitbucket", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); } /** Check URLs are valid with regexp */ @Test(dataProvider = "UrlsProvider") public void checkRegexp(String url) { assertTrue(bitbucketURLParser.isValid(url), "url " + url + " is invalid"); } @Test public void shouldParseWithBranch() { BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse("https://my-bitbucket.org/bitbucket/scm/proj/repo.git", "branch"); assertEquals(bitbucketServerUrl.getBranch(), "branch"); } @Test public void shouldParseWithUrlBranch() { BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse( "https://my-bitbucket.org/bitbucket/projects/proj/repos/repo/browse?at=master", "branch"); assertEquals(bitbucketServerUrl.getBranch(), "master"); } /** Compare parsing */ @Test(dataProvider = "parsing") public void checkParsing( String url, String user, String project, String repository, String branch) { BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse(url, null); assertEquals(bitbucketServerUrl.getUser(), user); assertEquals(bitbucketServerUrl.getProject(), project); assertEquals(bitbucketServerUrl.getRepository(), repository); assertEquals(bitbucketServerUrl.getBranch(), branch); } @Test(dataProvider = "parsing") public void shouldParseWithoutPredefinedEndpoint( String url, String user, String project, String repository, String branch) { // given bitbucketURLParser = new BitbucketServerURLParser( null, devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse(url, null); // then assertEquals(bitbucketServerUrl.getUser(), user); assertEquals(bitbucketServerUrl.getProject(), project); assertEquals(bitbucketServerUrl.getRepository(), repository); assertEquals(bitbucketServerUrl.getBranch(), branch); } @Test( expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "The given url https://github.com/org/repo is not a valid Bitbucket server URL. Check either URL or server configuration.") public void shouldThrowExceptionWhenURLDintMatchAnyConfiguredServer() { bitbucketURLParser.parse("https://github.com/org/repo", null); } @Test public void shouldValidateUrlByApiRequest() { // given bitbucketURLParser = new BitbucketServerURLParser( null, devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); String url = wireMockServer.url("/users/user/repos/repo"); stubFor( get(urlEqualTo("/rest/api/1.0/application-properties")) .willReturn( aResponse() .withBodyFile("bitbucket/rest/api.1.0.application-properties/response.json"))); // when boolean result = bitbucketURLParser.isValid(url); // then assertTrue(result); } @Test public void shouldValidateUrlByApiRequestButFailOnPatternCheck() { // given bitbucketURLParser = new BitbucketServerURLParser( null, devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); String url = wireMockServer.url("/user/repo"); stubFor( get(urlEqualTo("/rest/api/1.0/application-properties")) .willReturn( aResponse() .withBodyFile("bitbucket/rest/api.1.0.application-properties/response.json"))); // when boolean result = bitbucketURLParser.isValid(url); // then assertFalse(result); } @Test public void shouldNotValidateUrlByApiRequestWithBadRequest() { // given bitbucketURLParser = new BitbucketServerURLParser( null, devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); String url = wireMockServer.url("/users/user/repos/repo"); stubFor( get(urlEqualTo("/rest/api/1.0/application-properties")) .willReturn(aResponse().withStatus(400))); // when boolean result = bitbucketURLParser.isValid(url); // then assertFalse(result); } @Test public void shouldNotValidateUrlByApiRequestWithEmptyData() { // given bitbucketURLParser = new BitbucketServerURLParser( null, devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); String url = wireMockServer.url("/users/user/repos/repo"); stubFor( get(urlEqualTo("/rest/api/1.0/application-properties")) .willReturn(aResponse().withBody(""))); // when boolean result = bitbucketURLParser.isValid(url); // then assertFalse(result); } @Test public void shouldNotValidateUrlByApiRequestWithEmptyHeader() { // given bitbucketURLParser = new BitbucketServerURLParser( null, devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); String url = wireMockServer.url("/users/user/repos/repo"); stubFor( get(urlEqualTo("/rest/api/1.0/application-properties")) .willReturn(aResponse().withStatus(200))); // when boolean result = bitbucketURLParser.isValid(url); // then assertFalse(result); } @Test public void shouldParseIpv6HttpsUrl() { // given bitbucketURLParser = new BitbucketServerURLParser( "https://[2001:db8::1]", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse("https://[2001:db8::1]/scm/project/repo.git", null); // then assertEquals(bitbucketServerUrl.getProject(), "project"); assertEquals(bitbucketServerUrl.getRepository(), "repo"); } @Test public void shouldParseIpv6HttpsUrlWithUserRepo() { // given bitbucketURLParser = new BitbucketServerURLParser( "https://[2001:db8::1]", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse("https://[2001:db8::1]/scm/~user/repo.git", null); // then assertEquals(bitbucketServerUrl.getUser(), "user"); assertEquals(bitbucketServerUrl.getRepository(), "repo"); } @Test public void shouldParseIpv6UrlWithBranch() { // given bitbucketURLParser = new BitbucketServerURLParser( "https://[2001:db8::1]", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse( "https://[2001:db8::1]/projects/project/repos/repo/browse?at=refs%2Fheads%2Ffeature", null); // then assertEquals(bitbucketServerUrl.getProject(), "project"); assertEquals(bitbucketServerUrl.getRepository(), "repo"); assertEquals(bitbucketServerUrl.getBranch(), "feature"); } @Test public void shouldParseIpv6UrlWithRevisionParam() { // given bitbucketURLParser = new BitbucketServerURLParser( "https://[2001:db8::1]", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse("https://[2001:db8::1]/scm/project/repo.git", "my-branch"); // then assertEquals(bitbucketServerUrl.getProject(), "project"); assertEquals(bitbucketServerUrl.getRepository(), "repo"); assertEquals(bitbucketServerUrl.getBranch(), "my-branch"); } @Test public void shouldParseIpv6UrlWithUserBrowse() { // given bitbucketURLParser = new BitbucketServerURLParser( "https://[2001:db8::1]", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse( "https://[2001:db8::1]/users/user/repos/repo/browse?at=refs/heads/main", null); // then assertEquals(bitbucketServerUrl.getUser(), "user"); assertEquals(bitbucketServerUrl.getRepository(), "repo"); assertEquals(bitbucketServerUrl.getBranch(), "main"); } @Test public void shouldParseIpv6LoopbackAddress() { // given bitbucketURLParser = new BitbucketServerURLParser( "https://[::1]", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse("https://[::1]/scm/project/repo.git", null); // then assertEquals(bitbucketServerUrl.getProject(), "project"); assertEquals(bitbucketServerUrl.getRepository(), "repo"); } @Test public void shouldParseIpv6FullFormAddress() { // given bitbucketURLParser = new BitbucketServerURLParser( "https://[2001:0db8:0000:0000:0000:0000:0000:0001]", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse( "https://[2001:0db8:0000:0000:0000:0000:0000:0001]/scm/project/repo.git", null); // then assertEquals(bitbucketServerUrl.getProject(), "project"); assertEquals(bitbucketServerUrl.getRepository(), "repo"); } @Test public void shouldValidateIpv6Url() { // given bitbucketURLParser = new BitbucketServerURLParser( "https://[2001:db8::1]", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when/then assertTrue(bitbucketURLParser.isValid("https://[2001:db8::1]/scm/project/repo.git")); } @Test public void shouldValidateIpv6UrlWithProjectsBrowse() { // given bitbucketURLParser = new BitbucketServerURLParser( "https://[2001:db8::1]", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when/then assertTrue( bitbucketURLParser.isValid("https://[2001:db8::1]/projects/project/repos/repo/browse")); } @Test public void shouldParseIpv6SshUrl() { // given bitbucketURLParser = new BitbucketServerURLParser( "https://[2001:db8::1]", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse("ssh://git@[2001:db8::1]:7999/project/repo.git", null); // then assertEquals(bitbucketServerUrl.getProject(), "project"); assertEquals(bitbucketServerUrl.getRepository(), "repo"); } @Test public void shouldParseIpv6SshUrlWithUserRepo() { // given bitbucketURLParser = new BitbucketServerURLParser( "https://[2001:db8::1]", devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); // when BitbucketServerUrl bitbucketServerUrl = bitbucketURLParser.parse("ssh://git@[2001:db8::1]:7999/~user/repo.git", null); // then assertEquals(bitbucketServerUrl.getUser(), "user"); assertEquals(bitbucketServerUrl.getRepository(), "repo"); } @DataProvider(name = "UrlsProvider") public Object[][] urls() { return new Object[][] { {"https://my-bitbucket.org/bitbucket/scm/proj/repo.git"}, {"https://bitbucket.2mcl.com/scm/~user/repo.git"}, {"https://bitbucket.2mcl.com/scm/project/test1.git"}, {"https://bitbucket.2mcl.com/projects/project/repos/test1/browse?at=refs%2Fheads%2Fbranch"}, {"https://bitbucket.2mcl.com/projects/project/repos/test1/browse"}, {"https://bitbucket.2mcl.com/users/user/repos/repo"}, {"https://bitbucket.2mcl.com/users/user/repos/repo/"}, {"https://bbkt.com/scm/project/test1.git"}, {"ssh://git@bitbucket.2mcl.com:12345/~user/repo.git"}, {"ssh://git@bitbucket.2mcl.com:12345/project/test1.git"}, {"ssh://git@bitbucket.2mcl.com/~user/repo.git"}, {"ssh://git@bitbucket.2mcl.com/project/test1.git"} }; } @DataProvider(name = "parsing") public Object[][] expectedParsing() { return new Object[][] { {"https://bitbucket.2mcl.com/scm/project/test1.git", null, "project", "test1", null}, {"ssh://git@bitbucket.2mcl.com:12345/project/test1.git", null, "project", "test1", null}, {"ssh://git@bitbucket.2mcl.com:12345/~user/test1.git", "user", null, "test1", null}, { "https://bitbucket.2mcl.com/projects/project/repos/test1/browse?at=refs%2Fheads%2Fbranch", null, "project", "test1", "branch" }, { "https://bbkt.com/projects/project/repos/test1/browse?at=refs%2Fheads%2Fbranch", null, "project", "test1", "branch" }, { "https://bitbucket.2mcl.com/users/user/repos/repo/browse?at=refs/heads/branch", "user", null, "repo", "branch" }, {"https://bitbucket.2mcl.com/users/user/repos/repo/browse", "user", null, "repo", null}, {"https://bbkt.com/users/user/repos/repo/", "user", null, "repo", null} }; } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static java.lang.String.format; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import java.util.Arrays; import java.util.Iterator; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketServerURLTest { private BitbucketServerURLParser bitbucketServerURLParser; @Mock private DevfileFilenamesProvider devfileFilenamesProvider; @BeforeMethod protected void init() { when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); bitbucketServerURLParser = new BitbucketServerURLParser( "https://bitbucket.net", devfileFilenamesProvider, mock(OAuthAPI.class), mock(PersonalAccessTokenManager.class)); } @Test(dataProvider = "urlsProvider") public void checkDevfileLocation(String repoUrl, String fileUrl) { lenient() .when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); BitbucketServerUrl gitlabUrl = bitbucketServerURLParser.parse(repoUrl, null); assertEquals(gitlabUrl.devfileFileLocations().size(), 2); Iterator iterator = gitlabUrl.devfileFileLocations().iterator(); assertEquals(iterator.next().location(), format(fileUrl, "devfile.yaml")); assertEquals(iterator.next().location(), format(fileUrl, "foo.bar")); } @DataProvider public static Object[][] urlsProvider() { return new Object[][] { { "https://bitbucket.net/scm/~user/repo.git", "https://bitbucket.net/rest/api/1.0/users/user/repos/repo/raw/%s" }, { "https://bitbucket.net/users/user/repos/repo/browse?at=branch", "https://bitbucket.net/rest/api/1.0/users/user/repos/repo/raw/%s?at=branch" }, { "https://bitbucket.net/users/user/repos/repo", "https://bitbucket.net/rest/api/1.0/users/user/repos/repo/raw/%s" }, { "https://bitbucket.net/scm/project/repo.git", "https://bitbucket.net/rest/api/1.0/projects/project/repos/repo/raw/%s" }, { "https://bitbucket.net/projects/project/repos/repo/browse?at=branch", "https://bitbucket.net/rest/api/1.0/projects/project/repos/repo/raw/%s?at=branch" }, { "ssh://git@bitbucket.net:12345/project/repo.git", "https://bitbucket.net/rest/api/1.0/projects/project/repos/repo/raw/%s" }, { "ssh://git@bitbucket.net:12345/~user/repo.git", "https://bitbucket.net/rest/api/1.0/users/user/repos/repo/raw/%s" } }; } @Test(dataProvider = "repoProvider") public void checkRepositoryLocation(String rawUrl, String repoUrl) { BitbucketServerUrl bitbucketServerUrl = bitbucketServerURLParser.parse(rawUrl, null); assertEquals(bitbucketServerUrl.repositoryLocation(), repoUrl); } @Test(dataProvider = "urlsProvider") public void shouldReturnProviderUrl(String repoUrl, String ignored) { // when BitbucketServerUrl bitbucketServerUrl = bitbucketServerURLParser.parse(repoUrl, null); // then assertEquals(bitbucketServerUrl.getProviderUrl(), "https://bitbucket.net"); } @DataProvider public static Object[][] repoProvider() { return new Object[][] { {"https://bitbucket.net/scm/~user/repo.git", "https://bitbucket.net/scm/~user/repo.git"}, { "https://bitbucket.net/users/user/repos/repo/browse?at=branch", "https://bitbucket.net/scm/~user/repo.git" }, {"https://bitbucket.net/users/user/repos/repo", "https://bitbucket.net/scm/~user/repo.git"}, {"https://bitbucket.net/scm/project/repo.git", "https://bitbucket.net/scm/project/repo.git"}, { "https://bitbucket.net/projects/project/repos/repo/browse?at=branch", "https://bitbucket.net/scm/project/repo.git" }, { "ssh://git@bitbucket.net:12345/project/repo.git", "ssh://git@bitbucket.net:12345/project/repo.git" }, { "ssh://git@bitbucket.net:12345/~user/repo.git", "ssh://git@bitbucket.net:12345/~user/repo.git" }, }; } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcherTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import java.net.MalformedURLException; import java.util.Collections; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; import org.eclipse.che.api.factory.server.scm.GitUserData; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.*; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketServerUserDataFetcherTest { String someBitbucketURL = "https://some.bitbucketserver.com"; Subject subject; @Mock BitbucketServerApiClient bitbucketServerApiClient; @Mock PersonalAccessTokenManager personalAccessTokenManager; @Mock OAuthAPI oAuthAPI; BitbucketUser bitbucketUser; BitbucketServerUserDataFetcher fetcher; @BeforeMethod public void setup() throws MalformedURLException { subject = new SubjectImpl("another_user", Collections.emptyList(), "user987", "token111", false); bitbucketUser = new BitbucketUser("User", "user", 32423523, "NORMAL", true, "user", "user@users.com"); fetcher = new BitbucketServerUserDataFetcher( "api.local", someBitbucketURL, bitbucketServerApiClient, oAuthAPI, personalAccessTokenManager); EnvironmentContext context = new EnvironmentContext(); context.setSubject(subject); EnvironmentContext.setCurrent(context); } @Test public void shouldBeAbleToFetchPersonalAccessToken() throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException, ScmBadRequestException, ScmConfigurationPersistenceException { // given when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true); when(bitbucketServerApiClient.getUser()).thenReturn(bitbucketUser); // when GitUserData gitUserData = fetcher.fetchGitUserData(null); // then assertEquals(gitUserData.getScmUsername(), "User"); assertEquals(gitUserData.getScmUserEmail(), "user@users.com"); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.bitbucket; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.delete; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.notFound; import static com.github.tomakehurst.wiremock.client.WireMock.ok; import static com.github.tomakehurst.wiremock.client.WireMock.put; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.unauthorized; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.net.HttpHeaders; import jakarta.ws.rs.core.MediaType; import java.util.List; import java.util.stream.Collectors; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketPersonalAccessToken; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.security.oauth.OAuthAPI; import org.eclipse.che.security.oauth1.BitbucketServerOAuthAuthenticator; import org.eclipse.che.security.oauth1.NoopOAuthAuthenticator; import org.eclipse.che.security.oauth1.OAuthAuthenticationException; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class HttpBitbucketServerApiClientTest { private final String AUTHORIZATION_TOKEN = "OAuth oauth_consumer_key=\"key123321\", oauth_nonce=\"nonce\"," + " oauth_signature=\"signature\", " + "oauth_signature_method=\"RSA-SHA1\", oauth_timestamp=\"1609250025\", " + "oauth_token=\"token\", oauth_version=\"1.0\""; WireMockServer wireMockServer; WireMock wireMock; BitbucketServerApiClient bitbucketServer; @Mock OAuthAPI oAuthAPI; String apiEndpoint; @BeforeMethod void start() { oAuthAPI = mock(OAuthAPI.class); apiEndpoint = "apiEndpoint"; wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); bitbucketServer = new HttpBitbucketServerApiClient( wireMockServer.url("/"), new BitbucketServerOAuthAuthenticator("", "", "", "") { @Override public String computeAuthorizationHeader( String userId, String requestMethod, String requestUrl) throws OAuthAuthenticationException { return AUTHORIZATION_TOKEN; } }, oAuthAPI, apiEndpoint); stubFor( get(urlEqualTo("/rest/api/1.0/application-properties")) .willReturn(aResponse().withHeader("x-ausername", "ksmster"))); } @AfterMethod void stop() { wireMockServer.stop(); } @Test public void testGetUser() throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { stubFor( get(urlEqualTo("/rest/api/1.0/users/ksmster")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/ksmster/response.json"))); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/filtered/response.json"))); BitbucketUser user = bitbucketServer.getUser(); assertNotNull(user); } @Test public void shouldGetUserWithSpecialCharacters() throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { stubFor( get(urlEqualTo("/rest/api/1.0/application-properties")) .willReturn(aResponse().withHeader("x-ausername", "user%40email.com"))); stubFor( get(urlEqualTo("/rest/api/1.0/users?start=0&limit=25&filter=user@email.com")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .withQueryParam("filter", equalTo("user@email.com")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/email-user/response.json"))); BitbucketUser user = bitbucketServer.getUser(); assertNotNull(user); } @Test public void testGetUsers() throws ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/response_s0_l25.json"))); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withQueryParam("start", equalTo("3")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/response_s3_l25.json"))); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withQueryParam("start", equalTo("6")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/response_s6_l25.json"))); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withQueryParam("start", equalTo("9")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/response_s9_l25.json"))); List page = bitbucketServer.getUsers().stream() .map(BitbucketUser::getSlug) .collect(Collectors.toList()); assertEquals( page, ImmutableList.of( "admin", "ksmster", "skabashn", "user1", "user2", "user3", "user4", "user5", "user6", "user7")); } @Test public void testGetUsersFiltered() throws ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/filtered/response.json"))); List page = bitbucketServer.getUsers("ksmster").stream() .map(BitbucketUser::getSlug) .collect(Collectors.toList()); assertEquals(page, ImmutableList.of("admin", "ksmster")); } @Test public void testGetPersonalAccessTokens() throws ScmCommunicationException, ScmBadRequestException, ScmItemNotFoundException, ScmUnauthorizedException { stubFor( get(urlPathEqualTo("/rest/access-tokens/1.0/users/ksmster")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/access-tokens/1.0/users/ksmster/response.json"))); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/filtered/response.json"))); List page = bitbucketServer.getPersonalAccessTokens().stream() .map(BitbucketPersonalAccessToken::getName) .collect(Collectors.toList()); assertEquals(page, ImmutableList.of("che", "t2")); } @Test public void shouldBeAbleToCreatePAT() throws ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException, ScmItemNotFoundException { // given stubFor( put(urlPathEqualTo("/rest/access-tokens/1.0/users/ksmster")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON)) .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON)) .withHeader(HttpHeaders.CONTENT_LENGTH, equalTo("152")) .willReturn( ok().withBodyFile("bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json"))); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/filtered/response.json"))); // when BitbucketPersonalAccessToken result = bitbucketServer.createPersonalAccessTokens( "myToKen", ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE")); // then assertNotNull(result); assertEquals(result.getToken(), "token"); } @Test public void shouldBeAbleToDeletePAT() throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException { // given stubFor( delete(urlPathEqualTo("/rest/access-tokens/1.0/users/ksmster/5")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON)) .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON)) .willReturn(aResponse().withStatus(204))); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/filtered/response.json"))); // when bitbucketServer.deletePersonalAccessTokens("5"); } @Test public void shouldBeAbleToGetExistedPAT() throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException { // given stubFor( get(urlPathEqualTo("/rest/access-tokens/1.0/users/ksmster/5")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON)) .willReturn( ok().withBodyFile("bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json"))); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/filtered/response.json"))); // when BitbucketPersonalAccessToken result = bitbucketServer.getPersonalAccessToken("5", "token"); // then assertNotNull(result); assertEquals(result.getToken(), "token"); } @Test(expectedExceptions = ScmItemNotFoundException.class) public void shouldBeAbleToThrowNotFoundOnGePAT() throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException { // given stubFor( get(urlPathEqualTo("/rest/access-tokens/1.0/users/ksmster/5")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON)) .willReturn(notFound())); // when bitbucketServer.getPersonalAccessToken("5", "token"); } @Test(expectedExceptions = ScmUnauthorizedException.class) public void shouldBeAbleToThrowScmUnauthorizedExceptionOnGetUser() throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException { // given stubFor( get(urlEqualTo("/rest/api/1.0/application-properties")) .willReturn( aResponse() .withBodyFile("bitbucket/rest/api.1.0.application-properties/response.json"))); // when bitbucketServer.getUser(); } @Test(expectedExceptions = ScmCommunicationException.class) public void shouldBeAbleToThrowScmCommunicationExceptionOnGetUser() throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException { // given stubFor(get(urlEqualTo("/rest/api/1.0/application-properties")).willReturn(aResponse())); // when bitbucketServer.getUser(); } @Test(expectedExceptions = ScmUnauthorizedException.class) public void shouldBeAbleToThrowScmUnauthorizedExceptionOnGetPAT() throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException { // given stubFor( get(urlPathEqualTo("/rest/access-tokens/1.0/users/ksmster/5")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON)) .willReturn(unauthorized())); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/filtered/response.json"))); // when bitbucketServer.getPersonalAccessToken("5", "token"); } @Test( expectedExceptions = ScmCommunicationException.class, expectedExceptionsMessageRegExp = "The fallback noop authenticator cannot be used for authentication. Make sure OAuth is properly configured.") public void shouldThrowScmCommunicationExceptionInNoOauthAuthenticator() throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException, ForbiddenException, ServerException, ConflictException, UnauthorizedException, NotFoundException, BadRequestException { // given when(oAuthAPI.getOrRefreshToken(eq("bitbucket-server"))).thenReturn(mock(OAuthToken.class)); HttpBitbucketServerApiClient localServer = new HttpBitbucketServerApiClient( wireMockServer.url("/"), new NoopOAuthAuthenticator(), oAuthAPI, apiEndpoint); // when localServer.getUser(); } @Test public void shouldGetOauth2Token() throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException, ForbiddenException, ServerException, ConflictException, UnauthorizedException, NotFoundException, BadRequestException { // given OAuthToken token = mock(OAuthToken.class); when(token.getToken()).thenReturn("token"); when(oAuthAPI.getOrRefreshToken(eq("bitbucket-server"))).thenReturn(token); bitbucketServer = new HttpBitbucketServerApiClient( wireMockServer.url("/"), new NoopOAuthAuthenticator(), oAuthAPI, apiEndpoint); stubFor( get(urlEqualTo("/rest/api/1.0/users/ksmster")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/ksmster/response.json"))); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("bitbucket/rest/api/1.0/users/filtered/response.json"))); // when bitbucketServer.getUser(); // then verify(oAuthAPI, times(2)).getOrRefreshToken(eq("bitbucket-server")); } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json ================================================ { "id": "158910532909", "createdDate": 1609249808751, "expiryDays": 90, "name": "che5", "permissions": [ "PROJECT_WRITE", "REPO_WRITE" ], "user": { "name": "ksmster", "emailAddress": "ksmster@gmail.com", "id": 2, "displayName": "ksmster", "active": true, "slug": "ksmster", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c5ac.devtools-c5ac.example.opentlc.com/users/ksmster" } ] } }, "token": "token" } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/response.json ================================================ { "size": 2, "limit": 25, "isLastPage": true, "values": [ { "id": "898123953680", "createdDate": 1609227270831, "name": "che", "permissions": [ "PROJECT_WRITE", "REPO_WRITE" ], "user": { "name": "ksmster", "emailAddress": "ksmster@gmail.com", "id": 2, "displayName": "ksmster", "active": true, "slug": "ksmster", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c5ac.devtools-c5ac.example.opentlc.com/users/ksmster" } ] } } }, { "id": "080920112506", "createdDate": 1609227263410, "name": "t2", "permissions": [ "REPO_ADMIN", "PROJECT_WRITE" ], "user": { "name": "ksmster", "emailAddress": "ksmster@gmail.com", "id": 2, "displayName": "ksmster", "active": true, "slug": "ksmster", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c5ac.devtools-c5ac.example.opentlc.com/users/ksmster" } ] } } } ], "start": 0 } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/email-user/response.json ================================================ { "size": 1, "limit": 3, "isLastPage": true, "values": [ { "name": "user@email.com", "emailAddress": "user@email.com", "id": 58, "displayName": "user", "active": true, "slug": "user@email.com", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user%40email.com" } ] } } ], "start": 9 } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/filtered/response.json ================================================ { "size": 2, "limit": 25, "isLastPage": true, "values": [ { "name": "admin", "emailAddress": "admin@ksmster.com", "id": 1, "displayName": "Admin", "active": true, "slug": "admin", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c5ac.devtools-c5ac.example.opentlc.com/users/admin" } ] } }, { "name": "ksmster", "emailAddress": "ksmster@gmail.com", "id": 2, "displayName": "ksmster", "active": true, "slug": "ksmster", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c5ac.devtools-c5ac.example.opentlc.com/users/ksmster" } ] } } ], "start": 0 } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/ksmster/response.json ================================================ { "name": "ksmster", "emailAddress": "ksmster@gmail.com", "id": 2, "displayName": "Sergii Kabashniuk", "active": true, "slug": "ksmster", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/ksmster" } ] } } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response.json ================================================ { "size": 3, "limit": 25, "isLastPage": true, "values": [ { "name": "admin", "emailAddress": "admin@ksmster.com", "id": 1, "displayName": "Admin", "active": true, "slug": "admin", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/admin" } ] } }, { "name": "ksmster", "emailAddress": "ksmster@gmail.com", "id": 2, "displayName": "Sergii Kabashniuk", "active": true, "slug": "ksmster", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/ksmster" } ] } }, { "name": "skabashn", "emailAddress": "skabashniuk@redhat.com", "id": 3, "displayName": "Kabashn", "active": true, "slug": "skabashn", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/skabashn" } ] } } ], "start": 0 } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s0_l25.json ================================================ { "size": 3, "limit": 3, "isLastPage": false, "values": [ { "name": "admin", "emailAddress": "admin@ksmster.com", "id": 1, "displayName": "Admin", "active": true, "slug": "admin", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/admin" } ] } }, { "name": "ksmster", "emailAddress": "ksmster@gmail.com", "id": 2, "displayName": "Sergii Kabashniuk", "active": true, "slug": "ksmster", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/ksmster" } ] } }, { "name": "skabashn", "emailAddress": "skabashniuk@redhat.com", "id": 3, "displayName": "Kabashn", "active": true, "slug": "skabashn", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/skabashn" } ] } } ], "start": 0, "nextPageStart": 3 } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s3_l25.json ================================================ { "size": 3, "limit": 3, "isLastPage": false, "values": [ { "name": "user1", "emailAddress": "user1@gmail.com", "id": 52, "displayName": "User1", "active": true, "slug": "user1", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user1" } ] } }, { "name": "user2", "emailAddress": "user2@gmail.com", "id": 53, "displayName": "user2", "active": true, "slug": "user2", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user2" } ] } }, { "name": "user3@gmail.com", "emailAddress": "user3@gmail.com", "id": 54, "displayName": "user3", "active": true, "slug": "user3", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user3_gmail.com" } ] } } ], "start": 3, "nextPageStart": 6 } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s6_l25.json ================================================ { "size": 3, "limit": 3, "isLastPage": false, "values": [ { "name": "user4", "emailAddress": "user4@gmail.com", "id": 55, "displayName": "user4", "active": true, "slug": "user4", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user4" } ] } }, { "name": "user5", "emailAddress": "user5@gmail.com", "id": 56, "displayName": "user5", "active": true, "slug": "user5", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user5" } ] } }, { "name": "user6", "emailAddress": "user6@gmail.com", "id": 57, "displayName": "user6", "active": true, "slug": "user6", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user6" } ] } } ], "start": 6, "nextPageStart": 9 } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s9_l25.json ================================================ { "size": 1, "limit": 3, "isLastPage": true, "values": [ { "name": "user7", "emailAddress": "user7@gmail.com", "id": 58, "displayName": "user7", "active": true, "slug": "user7", "type": "NORMAL", "links": { "self": [ { "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user7" } ] } } ], "start": 9 } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api.1.0.application-properties/response.json ================================================ { "version": "8.7.0", "buildNumber": 8007000, "buildDate": 1672975062662, "displayName": "Bitbucket" } ================================================ FILE: wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex ================================================ FILE: wsmaster/che-core-api-factory-git-ssh/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-factory-git-ssh jar Che Core :: API :: Factory Resolver Git Ssh true jakarta.inject jakarta.inject-api jakarta.validation jakarta.validation-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-factory org.eclipse.che.core che-core-api-factory-shared org.eclipse.che.core che-core-api-workspace ch.qos.logback logback-classic test com.github.tomakehurst wiremock-jre8-standalone test com.google.guava guava test jakarta.servlet jakarta.servlet-api test jakarta.ws.rs jakarta.ws.rs-api test org.eclipse.che.core che-core-commons-json test org.hamcrest hamcrest-core test org.mockito mockito-core test org.mockito mockito-testng test org.slf4j jcl-over-slf4j test org.testng testng test ================================================ FILE: wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshAuthorizingFileContentProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.git.ssh; import org.eclipse.che.api.factory.server.scm.AuthorizingFileContentProvider; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** * Git Ssh specific authorizing file content provider. * * @author Anatolii Bazko */ class GitSshAuthorizingFileContentProvider extends AuthorizingFileContentProvider { GitSshAuthorizingFileContentProvider( GitSshUrl gitSshUrl, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { super(gitSshUrl, urlFetcher, personalAccessTokenManager); } } ================================================ FILE: wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshFactoryParametersResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.git.ssh; import static org.eclipse.che.api.factory.server.FactoryResolverPriority.LOWEST; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.eclipse.che.dto.server.DtoFactory.newDto; import jakarta.validation.constraints.NotNull; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.factory.server.BaseFactoryParameterResolver; import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.FactoryResolverPriority; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** * Provides Factory Parameters resolver for SSH urls of unsupported Git providers. * * @author Anatolii Bazko */ @Singleton public class GitSshFactoryParametersResolver extends BaseFactoryParameterResolver implements FactoryParametersResolver { private static final String PROVIDER_NAME = "git-ssh"; private final GitSshURLParser gitSshURLParser; private final URLFetcher urlFetcher; private final URLFactoryBuilder urlFactoryBuilder; private final PersonalAccessTokenManager personalAccessTokenManager; @Inject public GitSshFactoryParametersResolver( GitSshURLParser gitSshURLParser, URLFetcher urlFetcher, URLFactoryBuilder urlFactoryBuilder, PersonalAccessTokenManager personalAccessTokenManager, AuthorisationRequestManager authorisationRequestManager) { super(authorisationRequestManager, urlFactoryBuilder, PROVIDER_NAME); this.gitSshURLParser = gitSshURLParser; this.urlFetcher = urlFetcher; this.urlFactoryBuilder = urlFactoryBuilder; this.personalAccessTokenManager = personalAccessTokenManager; } @Override public boolean accept(@NotNull final Map factoryParameters) { return factoryParameters.containsKey(URL_PARAMETER_NAME) && gitSshURLParser.isValid(factoryParameters.get(URL_PARAMETER_NAME)); } @Override public String getProviderName() { return PROVIDER_NAME; } @Override public FactoryMetaDto createFactory(@NotNull final Map factoryParameters) throws ApiException { // no need to check null value of url parameter as accept() method has performed the check final GitSshUrl gitSshUrl = gitSshURLParser.parse(factoryParameters.get(URL_PARAMETER_NAME)); // create factory from the following location if location exists, else create default factory return urlFactoryBuilder .createFactoryFromDevfile( gitSshUrl, new GitSshAuthorizingFileContentProvider( gitSshUrl, urlFetcher, personalAccessTokenManager), extractOverrideParams(factoryParameters), true) .orElseThrow(() -> new ApiException("Failed to fetch devfile")) .acceptVisitor(new GitSshFactoryVisitor(gitSshUrl)); } /** Visitor that updates factory dto with git ssh information. */ private class GitSshFactoryVisitor implements FactoryVisitor { private final GitSshUrl gitSshUrl; private GitSshFactoryVisitor(GitSshUrl gitSshUrl) { this.gitSshUrl = gitSshUrl; } @Override public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { ScmInfoDto scmInfo = newDto(ScmInfoDto.class) .withScmProviderName(gitSshUrl.getProviderName()) .withRepositoryUrl(gitSshUrl.getRepositoryLocation()); return factoryDto.withScmInfo(scmInfo); } } @Override public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) { return gitSshURLParser.parse(factoryUrl); } @Override public FactoryResolverPriority priority() { return LOWEST; } } ================================================ FILE: wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshScmFileResolver.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.git.ssh; import jakarta.validation.constraints.NotNull; import javax.inject.Inject; import org.eclipse.che.api.factory.server.ScmFileResolver; /** * Git Ssh specific SCM file resolver. * * @author Anatolii Bazko */ public class GitSshScmFileResolver implements ScmFileResolver { private final GitSshURLParser gitSshURLParser; @Inject public GitSshScmFileResolver(GitSshURLParser gitSshURLParser) { this.gitSshURLParser = gitSshURLParser; } @Override public boolean accept(@NotNull String repository) { return gitSshURLParser.isValid(repository); } /** * There is no way to get a file content from a git repository via ssh protocol. So this method * always returns an empty string. It allows to start a workspace from an empty devfile. */ @Override public String fileContent(@NotNull String repository, @NotNull String filePath) { return ""; } } ================================================ FILE: wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshURLParser.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.git.ssh; import static java.lang.String.format; import static java.util.regex.Pattern.compile; import jakarta.validation.constraints.NotNull; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; /** * Parser of String Git Ssh URLs and provide {@link GitSshUrl} objects. * * @author Anatolii Bazko */ @Singleton public class GitSshURLParser { private final Pattern gitSshPattern; private final DevfileFilenamesProvider devfileFilenamesProvider; @Inject public GitSshURLParser(DevfileFilenamesProvider devfileFilenamesProvider) { this.devfileFilenamesProvider = devfileFilenamesProvider; this.gitSshPattern = compile("^git@(?[^:]++):(.*)/(?[^/]++)$"); } public boolean isValid(@NotNull String url) { return gitSshPattern.matcher(url).matches(); } public GitSshUrl parse(String url) { Matcher matcher = gitSshPattern.matcher(url); if (!matcher.matches()) { throw new IllegalArgumentException( format("The given url %s is not a valid. It should start with git@", url)); } String hostName = matcher.group("hostName"); String repoName = matcher.group("repoName"); if (repoName.endsWith(".git")) { repoName = repoName.substring(0, repoName.length() - 4); } return new GitSshUrl() .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .withHostName(hostName) .withRepository(repoName) .withRepositoryLocation(url) .withUrl(url); } } ================================================ FILE: wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshUrl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.git.ssh; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; /** * Representation of Git Ssh URL, allowing to get details from it. * * @author Anatolii Bazko */ public class GitSshUrl extends DefaultFactoryUrl { private String repository; private String hostName; private String repositoryLocation; private final List devfileFilenames = new ArrayList<>(); protected GitSshUrl() {} @Override public String getProviderName() { return "git-ssh"; } @Override public String getBranch() { return null; } public GitSshUrl withDevfileFilenames(List devfileFilenames) { this.devfileFilenames.addAll(devfileFilenames); return this; } @Override public void setDevfileFilename(String devfileName) { this.devfileFilenames.clear(); this.devfileFilenames.add(devfileName); } @Override public List devfileFileLocations() { return devfileFilenames.stream().map(this::createDevfileLocation).collect(Collectors.toList()); } @Override public String rawFileLocation(String filename) { return filename; } private DevfileLocation createDevfileLocation(String devfileFilename) { return new DevfileLocation() { @Override public Optional filename() { return Optional.of(devfileFilename); } @Override public String location() { // Since we do not know the location from an SSH URL, we return the filename instead. The // devfile content fetcher will always fail to fetch the devfile in this case. // TODO: throw an error in order to avoid http request to fetch the devfile content. return devfileFilename; } }; } @Override public String getHostName() { return hostName; } public GitSshUrl withHostName(String hostName) { this.hostName = hostName; return this; } public String getRepositoryLocation() { return repositoryLocation; } public GitSshUrl withRepositoryLocation(String repositoryLocation) { this.repositoryLocation = repositoryLocation; return this; } public String getRepository() { return repository; } public GitSshUrl withRepository(String repository) { this.repository = repository; return this; } } ================================================ FILE: wsmaster/che-core-api-factory-git-ssh/src/test/java/org/eclipse/che/api/factory/server/git/ssh/GitSshFactoryParametersResolverTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.git.ssh; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitSshFactoryParametersResolverTest { @Mock private DevfileFilenamesProvider devfileFilenamesProvider; @Mock private URLFetcher urlFetcher; @Mock private URLFactoryBuilder urlFactoryBuilder; @Mock private PersonalAccessTokenManager personalAccessTokenManager; @Mock private AuthorisationRequestManager authorisationRequestManager; @Mock private GitSshURLParser gitSshURLParser; @Mock private GitSshUrl gitSshUrl; private GitSshFactoryParametersResolver gitSshFactoryParametersResolver; @BeforeMethod protected void init() { gitSshFactoryParametersResolver = new GitSshFactoryParametersResolver( gitSshURLParser, urlFetcher, urlFactoryBuilder, personalAccessTokenManager, authorisationRequestManager); } @Test public void ShouldNotAcceptMissingParameter() { // given Map parameters = singletonMap("foo", "this is a foo bar"); // when boolean accept = gitSshFactoryParametersResolver.accept(parameters); // then assertFalse(accept); } @Test public void ShouldNotAcceptInvalidUrl() { // given String url = "https://provider.com/user/repo.git"; when(gitSshURLParser.isValid(eq(url))).thenReturn(false); Map parameters = singletonMap(URL_PARAMETER_NAME, url); // when boolean accept = gitSshFactoryParametersResolver.accept(parameters); // then assertFalse(accept); } @Test public void shouldAcceptValidUrl() { // given String url = "git@provider.com:user/repo.git"; when(gitSshURLParser.isValid(eq(url))).thenReturn(true); Map parameters = singletonMap(URL_PARAMETER_NAME, url); // when boolean accept = gitSshFactoryParametersResolver.accept(parameters); // then assertTrue(accept); } @Test public void shouldCreateFactoryWithDevfile() throws Exception { // given String url = "git@provider.com:user/repo.git"; when(gitSshUrl.getProviderName()).thenReturn("git-ssh"); when(gitSshUrl.getRepositoryLocation()).thenReturn("repository-location"); ImmutableMap params = ImmutableMap.of(URL_PARAMETER_NAME, url); when(gitSshURLParser.parse(eq(url))).thenReturn(gitSshUrl); when(urlFactoryBuilder.createFactoryFromDevfile( eq(gitSshUrl), any(FileContentProvider.class), eq(Collections.emptyMap()), eq(true))) .thenReturn(Optional.of(generateDevfileV2Factory())); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) gitSshFactoryParametersResolver.createFactory(params); // then ScmInfoDto scmInfo = factory.getScmInfo(); assertEquals(scmInfo.getScmProviderName(), "git-ssh"); assertEquals(scmInfo.getRepositoryUrl(), "repository-location"); } @Test( expectedExceptions = ApiException.class, expectedExceptionsMessageRegExp = "Failed to fetch devfile") public void shouldThrowException() throws Exception { // given String url = "git@provider.com:user/repo.git"; ImmutableMap params = ImmutableMap.of(URL_PARAMETER_NAME, url); when(gitSshURLParser.parse(eq(url))).thenReturn(gitSshUrl); when(urlFactoryBuilder.createFactoryFromDevfile( eq(gitSshUrl), any(FileContentProvider.class), eq(Collections.emptyMap()), eq(true))) .thenReturn(Optional.empty()); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) gitSshFactoryParametersResolver.createFactory(params); } private FactoryDevfileV2Dto generateDevfileV2Factory() { return newDto(FactoryDevfileV2Dto.class) .withV(CURRENT_VERSION) .withSource("repo") .withDevfile(Map.of("schemaVersion", "2.0.0")); } } ================================================ FILE: wsmaster/che-core-api-factory-git-ssh/src/test/java/org/eclipse/che/api/factory/server/git/ssh/GitSshURLParserTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.git.ssh; import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Anatalii Bazko */ @Listeners(MockitoTestNGListener.class) public class GitSshURLParserTest { private GitSshURLParser gitSshURLParser; @BeforeMethod protected void start() { gitSshURLParser = new GitSshURLParser(mock(DevfileFilenamesProvider.class)); } @Test(dataProvider = "parsing") public void testParse(String url, String hostName, String repository) { GitSshUrl gitSshUrl = gitSshURLParser.parse(url); assertEquals(gitSshUrl.getHostName(), hostName); assertEquals(gitSshUrl.getRepository(), repository); } @DataProvider(name = "parsing") public Object[][] expectedParsing() { return new Object[][] { {"git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepo", "ssh.dev.azure.com", "MyRepo"}, {"git@github.com:MyOrg/MyRepo.git", "github.com", "MyRepo"}, }; } } ================================================ FILE: wsmaster/che-core-api-factory-git-ssh/src/test/java/org/eclipse/che/api/factory/server/git/ssh/GitSshUrlTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.git.ssh; import static org.testng.Assert.assertEquals; import java.util.Arrays; import java.util.List; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitSshUrlTest { @Test public void shouldReturnDevfileLocations() throws Exception { String[] devfileNames = {"devfile.yaml", ".devfile.yaml"}; GitSshUrl sshUrl = new GitSshUrl() .withRepository("repository") .withHostName("hostname") .withDevfileFilenames(Arrays.asList(devfileNames)); List devfileLocations = sshUrl.devfileFileLocations(); assertEquals(devfileLocations.size(), 2); assertEquals(devfileLocations.get(0).location(), "devfile.yaml"); assertEquals(devfileLocations.get(1).location(), ".devfile.yaml"); } } ================================================ FILE: wsmaster/che-core-api-factory-git-ssh/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex ================================================ FILE: wsmaster/che-core-api-factory-github/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-factory-github jar Che Core :: API :: Factory Resolver Github true com.google.inject guice jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-factory org.eclipse.che.core che-core-api-factory-github-common org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-api-core provided org.eclipse.che.core che-core-commons-lang provided ch.qos.logback logback-classic test com.github.tomakehurst wiremock-jre8-standalone test com.google.guava guava test jakarta.servlet jakarta.servlet-api test jakarta.ws.rs jakarta.ws.rs-api test org.eclipse.che.core che-core-api-auth-shared test org.eclipse.che.core che-core-api-dto test org.eclipse.che.core che-core-api-factory-shared test org.eclipse.che.core che-core-api-model test org.eclipse.che.core che-core-commons-json test org.hamcrest hamcrest-core test org.mockito mockito-core test org.mockito mockito-testng test org.slf4j jcl-over-slf4j test org.testng testng test org.wiremock wiremock-standalone test ================================================ FILE: wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** * Provides Factory Parameters resolver for github repositories. * * @author Florent Benoit */ @Singleton public class GithubFactoryParametersResolver extends AbstractGithubFactoryParametersResolver { private static final String PROVIDER_NAME = "github"; @Inject public GithubFactoryParametersResolver( GithubURLParser githubUrlParser, URLFetcher urlFetcher, GithubSourceStorageBuilder githubSourceStorageBuilder, AuthorisationRequestManager authorisationRequestManager, URLFactoryBuilder urlFactoryBuilder, PersonalAccessTokenManager personalAccessTokenManager) { super( githubUrlParser, urlFetcher, githubSourceStorageBuilder, authorisationRequestManager, urlFactoryBuilder, personalAccessTokenManager, PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverSecond.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** * Provides Factory Parameters resolver for github repositories. * * @author Florent Benoit */ @Singleton public class GithubFactoryParametersResolverSecond extends AbstractGithubFactoryParametersResolver { private static final String PROVIDER_NAME = "github_2"; @Inject public GithubFactoryParametersResolverSecond( GithubURLParserSecond githubUrlParser, URLFetcher urlFetcher, GithubSourceStorageBuilder githubSourceStorageBuilder, AuthorisationRequestManager authorisationRequestManager, URLFactoryBuilder urlFactoryBuilder, PersonalAccessTokenManager personalAccessTokenManager) { super( githubUrlParser, urlFetcher, githubSourceStorageBuilder, authorisationRequestManager, urlFactoryBuilder, personalAccessTokenManager, PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubModule.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; public class GithubModule extends AbstractModule { @Override protected void configure() { Multibinder tokenFetcherMultibinder = Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(GithubPersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(GithubPersonalAccessTokenFetcherSecond.class); Multibinder gitUserDataMultibinder = Multibinder.newSetBinder(binder(), GitUserDataFetcher.class); gitUserDataMultibinder.addBinding().to(GithubUserDataFetcher.class); gitUserDataMultibinder.addBinding().to(GithubUserDataFetcherSecond.class); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcher.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.security.oauth.OAuthAPI; /** GitHub OAuth token retriever. */ public class GithubPersonalAccessTokenFetcher extends AbstractGithubPersonalAccessTokenFetcher { /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "github"; @Inject public GithubPersonalAccessTokenFetcher( @Named("che.api") String apiEndpoint, @Nullable @Named("che.integration.github.oauth_endpoint") String oauthEndpoint, OAuthAPI oAuthAPI) { super(apiEndpoint, oAuthAPI, new GithubApiClient(oauthEndpoint), OAUTH_PROVIDER_NAME); } GithubPersonalAccessTokenFetcher( @Named("che.api") String apiEndpoint, OAuthAPI oAuthAPI, GithubApiClient githubApiClient) { super(apiEndpoint, oAuthAPI, githubApiClient, OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherSecond.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.security.oauth.OAuthAPI; /** GitHub OAuth token retriever. */ public class GithubPersonalAccessTokenFetcherSecond extends AbstractGithubPersonalAccessTokenFetcher { /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "github_2"; @Inject public GithubPersonalAccessTokenFetcherSecond( @Named("che.api") String apiEndpoint, @Nullable @Named("che.integration.github.oauth_endpoint_2") String oauthEndpoint, OAuthAPI oAuthAPI) { super(apiEndpoint, oAuthAPI, new GithubApiClient(oauthEndpoint), OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubScmFileResolver.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import javax.inject.Inject; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** Github specific SCM file resolver. */ public class GithubScmFileResolver extends AbstractGithubScmFileResolver { @Inject public GithubScmFileResolver( GithubURLParser githubUrlParser, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { super(githubUrlParser, urlFetcher, personalAccessTokenManager); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubScmFileResolverSecond.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import javax.inject.Inject; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** Github specific SCM file resolver. */ public class GithubScmFileResolverSecond extends AbstractGithubScmFileResolver { @Inject public GithubScmFileResolverSecond( GithubURLParserSecond githubUrlParser, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { super(githubUrlParser, urlFetcher, personalAccessTokenManager); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubURLParser.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; /** * Parser of String Github URLs and provide {@link GithubUrl} objects. * * @author Florent Benoit */ @Singleton public class GithubURLParser extends AbstractGithubURLParser { /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "github"; @Inject public GithubURLParser( PersonalAccessTokenManager tokenManager, DevfileFilenamesProvider devfileFilenamesProvider, @Nullable @Named("che.integration.github.oauth_endpoint") String oauthEndpoint, @Named("che.integration.github.disable_subdomain_isolation") boolean disableSubdomainIsolation) { super( tokenManager, devfileFilenamesProvider, new GithubApiClient(oauthEndpoint), oauthEndpoint, disableSubdomainIsolation, OAUTH_PROVIDER_NAME); } GithubURLParser( PersonalAccessTokenManager tokenManager, DevfileFilenamesProvider devfileFilenamesProvider, GithubApiClient githubApiClient, String oauthEndpoint, boolean disableSubdomainIsolation) { super( tokenManager, devfileFilenamesProvider, githubApiClient, oauthEndpoint, disableSubdomainIsolation, OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubURLParserSecond.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; /** * Parser of String Github URLs and provide {@link GithubUrl} objects. * * @author Florent Benoit */ @Singleton public class GithubURLParserSecond extends AbstractGithubURLParser { /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "github_2"; @Inject public GithubURLParserSecond( PersonalAccessTokenManager tokenManager, DevfileFilenamesProvider devfileFilenamesProvider, @Nullable @Named("che.integration.github.oauth_endpoint_2") String oauthEndpoint, @Named("che.integration.github.disable_subdomain_isolation_2") boolean disableSubdomainIsolation) { super( tokenManager, devfileFilenamesProvider, new GithubApiClient(oauthEndpoint), oauthEndpoint, disableSubdomainIsolation, OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.commons.annotation.Nullable; /** GitHub user data retriever. */ public class GithubUserDataFetcher extends AbstractGithubUserDataFetcher { /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "github"; @Inject public GithubUserDataFetcher( @Named("che.api") String apiEndpoint, @Nullable @Named("che.integration.github.oauth_endpoint") String oauthEndpoint, PersonalAccessTokenManager personalAccessTokenManager) { super( apiEndpoint, personalAccessTokenManager, new GithubApiClient(oauthEndpoint), OAUTH_PROVIDER_NAME); } GithubUserDataFetcher( String apiEndpoint, PersonalAccessTokenManager personalAccessTokenManager, GithubApiClient githubApiClient) { super(apiEndpoint, personalAccessTokenManager, githubApiClient, OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcherSecond.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.commons.annotation.Nullable; /** GitHub user data retriever. */ public class GithubUserDataFetcherSecond extends AbstractGithubUserDataFetcher { /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "github_2"; @Inject public GithubUserDataFetcherSecond( @Named("che.api") String apiEndpoint, @Nullable @Named("che.integration.github.oauth_endpoint_2") String oauthEndpoint, PersonalAccessTokenManager personalAccessTokenManager) { super( apiEndpoint, personalAccessTokenManager, new GithubApiClient(oauthEndpoint), OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubApiClientTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static java.net.HttpURLConnection.HTTP_BAD_GATEWAY; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEqualsNoOrder; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.net.HttpHeaders; import java.lang.reflect.Field; import java.net.http.HttpClient; import java.net.http.HttpResponse; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.commons.lang.Pair; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GithubApiClientTest { private GithubApiClient client; WireMockServer wireMockServer; WireMock wireMock; @BeforeMethod void start() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); client = new GithubApiClient(wireMockServer.url("/")); } @AfterMethod void stop() { wireMockServer.stop(); } @Test public void shouldUseDefaultApiUrl() throws Exception { // given client = new GithubApiClient("https://github.com"); Field serverUrl = client.getClass().getDeclaredField("apiServerUrl"); serverUrl.setAccessible(true); // then assertEquals(serverUrl.get(client).toString(), "https://api.github.com/"); } @Test public void shouldUseDefaultApiUrlWithNull() throws Exception { // given client = new GithubApiClient(null); Field serverUrl = client.getClass().getDeclaredField("apiServerUrl"); serverUrl.setAccessible(true); // then assertEquals(serverUrl.get(client).toString(), "https://api.github.com/"); } @Test public void shouldUseDefaultApiUrlWithEmpty() throws Exception { // given client = new GithubApiClient(""); Field serverUrl = client.getClass().getDeclaredField("apiServerUrl"); serverUrl.setAccessible(true); // then assertEquals(serverUrl.get(client).toString(), "https://api.github.com/"); } @Test(expectedExceptions = ScmCommunicationException.class) public void shouldThrowExceptionOnUserParseError() throws Exception { // given stubFor(get(urlEqualTo("/api/v3/user")).willReturn(aResponse().withBody("invalid value"))); // when client.getUser("token"); } @Test public void testGetUser() throws Exception { stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("github/rest/user/response.json"))); GithubUser user = client.getUser("token1"); assertNotNull(user, "GitHub API should have returned a non-null user object"); assertEquals(user.getId(), 123456789, "GitHub user id was not parsed properly by client"); assertEquals( user.getLogin(), "github-user", "GitHub user login was not parsed properly by client"); assertEquals( user.getEmail(), "github-user@acme.com", "GitHub user email was not parsed properly by client"); assertEquals( user.getName(), "Github User", "GitHub user name was not parsed properly by client"); } @Test public void testGetPullRequest() throws Exception { // given stubFor( get(urlEqualTo("/api/v3/repos/user/repo/pulls/id")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("github/rest/pullRequest/response.json"))); // when GithubPullRequest pullRequest = client.getPullRequest("id", "user", "repo", "token1"); // then assertNotNull(pullRequest); assertNotNull(pullRequest.getHead()); assertEquals(pullRequest.getState(), "open"); } @Test(expectedExceptions = ScmCommunicationException.class) public void shouldThrowExceptionOnPullRequestParseError() throws Exception { // given stubFor( get(urlEqualTo("/api/v3/repos/user/repo/pulls/id")) .willReturn(aResponse().withBody("invalid value"))); // when client.getPullRequest("id", "user", "repo", "token1"); } @Test public void testGetTokenScopes() throws Exception { stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader(GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER, "repo, user:email") .withBodyFile("github/rest/user/response.json"))); Pair pair = client.getTokenScopes("token1"); String[] scopes = pair.second; String[] expectedScopes = {"repo", "user:email"}; assertNotNull(scopes, "GitHub API should have returned a non-null scope array"); assertEqualsNoOrder( scopes, expectedScopes, "Returned scope array does not match expected values"); assertEquals(pair.first, "github-user"); } @Test public void testGetTokenScopesWithNoScopeHeader() throws Exception { stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("github/rest/user/response.json"))); String[] scopes = client.getTokenScopes("token1").second; assertNotNull(scopes, "GitHub API should have returned a non-null scope array"); assertEquals( scopes.length, 0, "A response with no " + GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER + " header should return an empty array"); } @Test public void testGetTokenScopesWithNoScope() throws Exception { stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader(GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER, "") .withBodyFile("github/rest/user/response.json"))); String[] scopes = client.getTokenScopes("token1").second; assertNotNull(scopes, "GitHub API should have returned a non-null scope array"); assertEquals( scopes.length, 0, "A response with empty " + GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER + " header should return an empty array"); } @Test public void shouldReturnFalseOnConnectedToOtherHost() { assertFalse(client.isConnected("https://other.com")); } @Test public void shouldReturnTrueWhenConnectedToGithub() { assertTrue(client.isConnected(wireMockServer.url("/"))); } @Test() public void shouldReturnNull() throws Exception { // given stubFor(get(urlEqualTo("/api/v3/user")).willReturn(aResponse().withStatus(HTTP_NO_CONTENT))); // when GithubUser user = client.getUser("token"); // then assertNull(user); } @Test( expectedExceptions = ScmBadRequestException.class, expectedExceptionsMessageRegExp = "bad request") public void shouldThrowExceptionOnBadRequestError() throws Exception { // given stubFor( get(urlEqualTo("/api/v3/user")) .willReturn(aResponse().withStatus(HTTP_BAD_REQUEST).withBody("bad request"))); // when client.getUser("token"); } @Test( expectedExceptions = ScmItemNotFoundException.class, expectedExceptionsMessageRegExp = "item not found") public void shouldThrowExceptionOnNotFoundError() throws Exception { // given stubFor( get(urlEqualTo("/api/v3/user")) .willReturn(aResponse().withStatus(HTTP_NOT_FOUND).withBody("item not found"))); // when client.getUser("token"); } @Test( expectedExceptions = ScmBadRequestException.class, expectedExceptionsMessageRegExp = "Unrecognised error") public void shouldThrowExceptionWithUnrecognisedErrorIfResponseBodyIsNull() throws Exception { // given HttpClient httpClient = Mockito.mock(HttpClient.class); HttpResponse response = Mockito.mock(HttpResponse.class); Field declaredField = GithubApiClient.class.getDeclaredField("httpClient"); declaredField.setAccessible(true); declaredField.set(client, httpClient); when(httpClient.send(any(), any())).thenReturn(response); when(response.body()).thenReturn(null); when(response.statusCode()).thenReturn(HTTP_BAD_REQUEST); // when client.getUser("token"); } @Test( expectedExceptions = ScmCommunicationException.class, expectedExceptionsMessageRegExp = "Unexpected status code 502 item not found") public void shouldThrowExceptionOnOtherError() throws Exception { // given stubFor( get(urlEqualTo("/api/v3/user")) .willReturn(aResponse().withStatus(HTTP_BAD_GATEWAY).withBody("item not found"))); // when client.getUser("token"); } @Test public void testGetLatestCommit() throws Exception { stubFor( get(urlEqualTo("/api/v3/repos/eclipse-che/che-server/commits?sha=HEAD&page=1&per_page=1")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBody("[{\"sha\": \"test_sha\", \"url\": \"http://commit.url\" }]"))); GithubCommit commit = client.getLatestCommit("eclipse-che", "che-server", "HEAD", "token1"); assertNotNull(commit, "GitHub API should return the latest commit"); assertEquals(commit.getSha(), "test_sha"); assertEquals(commit.getUrl(), "http://commit.url"); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubAuthorizingFileContentProviderTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.FileNotFoundException; import java.io.IOException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GithubAuthorizingFileContentProviderTest { private PersonalAccessTokenManager personalAccessTokenManager; @BeforeMethod void start() { personalAccessTokenManager = mock(PersonalAccessTokenManager.class); } @Test public void shouldExpandRelativePaths() throws Exception { URLFetcher urlFetcher = mock(URLFetcher.class); GithubUrl githubUrl = new GithubUrl("github") .withUsername("eclipse") .withRepository("che") .withBranch("main") .withServerUrl("https://github.com") .withLatestCommit("d74923ebf968454cf13251f17df69dcd87d3b932"); FileContentProvider fileContentProvider = new GithubAuthorizingFileContentProvider(githubUrl, urlFetcher, personalAccessTokenManager); when(personalAccessTokenManager.getAndStore(anyString())) .thenReturn(new PersonalAccessToken("foo", "provider", "che", "my-token")); fileContentProvider.fetchContent("devfile.yaml"); verify(urlFetcher) .fetch( eq( "https://raw.githubusercontent.com/eclipse/che/d74923ebf968454cf13251f17df69dcd87d3b932/devfile.yaml"), eq("token my-token")); } @Test public void shouldPreserveAbsolutePaths() throws Exception { String raw_url = "https://raw.githubusercontent.com/foo/bar/branch-name/devfile.yaml"; GithubUrl githubUrl = new GithubUrl("github") .withUsername("eclipse") .withRepository("che") .withServerUrl("https://github.com") .withBranch("main") .withLatestCommit("9ac2f42ed62944d164f189afd57f14a2793a7e4b"); URLFetcher urlFetcher = mock(URLFetcher.class); FileContentProvider fileContentProvider = new GithubAuthorizingFileContentProvider(githubUrl, urlFetcher, personalAccessTokenManager); when(personalAccessTokenManager.getAndStore(anyString())) .thenReturn(new PersonalAccessToken(raw_url, "provider", "che", "my-token")); fileContentProvider.fetchContent(raw_url); verify(urlFetcher).fetch(eq(raw_url), eq("token my-token")); } @Test(expectedExceptions = FileNotFoundException.class) public void shouldThrowNotFoundForPublicRepos() throws Exception { String url = "https://raw.githubusercontent.com/foo/bar/branch-name/devfile.yaml"; GithubUrl githubUrl = new GithubUrl("github") .withUsername("eclipse") .withRepository("che") .withServerUrl("https://github.com"); URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); FileContentProvider fileContentProvider = new GithubAuthorizingFileContentProvider(githubUrl, urlFetcher, personalAccessTokenManager); when(personalAccessTokenManager.getAndStore(anyString())) .thenThrow(UnknownScmProviderException.class); when(urlFetcher.fetch(eq(url))).thenThrow(FileNotFoundException.class); fileContentProvider.fetchContent(url); } @Test(expectedExceptions = DevfileException.class) public void shouldThrowDevfileException() throws Exception { String url = "https://raw.githubusercontent.com/foo/bar/branch-name/devfile.yaml"; GithubUrl githubUrl = new GithubUrl("github") .withUsername("eclipse") .withRepository("che") .withServerUrl("https://github.com"); URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); FileContentProvider fileContentProvider = new GithubAuthorizingFileContentProvider(githubUrl, urlFetcher, personalAccessTokenManager); when(personalAccessTokenManager.getAndStore(anyString())) .thenThrow(UnknownScmProviderException.class); when(urlFetcher.fetch(eq(url))).thenThrow(FileNotFoundException.class); when(urlFetcher.fetch(eq("https://github.com/eclipse/che"))).thenThrow(IOException.class); fileContentProvider.fetchContent(url); } @Test public void shouldNotAskGitHubAPIForDifferentDomain() throws Exception { String raw_url = "https://ghserver.com/foo/bar/branch-name/devfile.yaml"; URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); GithubUrl githubUrl = new GithubUrl("github") .withUsername("eclipse") .withRepository("che") .withServerUrl("https://github.com"); FileContentProvider fileContentProvider = new GithubAuthorizingFileContentProvider(githubUrl, urlFetcher, personalAccessTokenManager); var personalAccessToken = new PersonalAccessToken(raw_url, "provider", "che", "my-token"); when(personalAccessTokenManager.getAndStore(anyString())).thenReturn(personalAccessToken); fileContentProvider.fetchContent(raw_url); verify(urlFetcher).fetch(eq(raw_url), eq("token my-token")); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.eclipse.che.security.oauth1.OAuthAuthenticationService.ERROR_QUERY_NAME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.model.factory.ScmInfo; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Validate operations performed by the Github Factory service * * @author Florent Benoit */ @Listeners(MockitoTestNGListener.class) public class GithubFactoryParametersResolverTest { @Mock private URLFetcher urlFetcher; @Mock private DevfileFilenamesProvider devfileFilenamesProvider; /** Parser which will allow to check validity of URLs and create objects. */ private GithubURLParser githubUrlParser; private GithubApiClient githubApiClient; /** Converter allowing to convert github URL to other objects. */ @Spy private GithubSourceStorageBuilder githubSourceStorageBuilder = new GithubSourceStorageBuilder(); /** Parser which will allow to check validity of URLs and create objects. */ private URLFactoryBuilder urlFactoryBuilder; @Mock private PersonalAccessTokenManager personalAccessTokenManager; @Mock private AuthorisationRequestManager authorisationRequestManager; /** * Capturing the location parameter when calling {@link * URLFactoryBuilder#createFactoryFromDevfile(RemoteFactoryUrl, FileContentProvider, Map, * boolean)} */ @Captor private ArgumentCaptor factoryUrlArgumentCaptor; /** Instance of resolver that will be tested. */ private AbstractGithubFactoryParametersResolver abstractGithubFactoryParametersResolver; @BeforeMethod protected void init() throws Exception { this.githubApiClient = mock(GithubApiClient.class); this.urlFactoryBuilder = mock(URLFactoryBuilder.class); githubUrlParser = new GithubURLParser( personalAccessTokenManager, devfileFilenamesProvider, githubApiClient, null, false); abstractGithubFactoryParametersResolver = new GithubFactoryParametersResolver( githubUrlParser, urlFetcher, githubSourceStorageBuilder, authorisationRequestManager, urlFactoryBuilder, personalAccessTokenManager); assertNotNull(this.abstractGithubFactoryParametersResolver); } /** Check missing parameter name can't be accepted by this resolver */ @Test public void checkMissingParameter() { Map parameters = singletonMap("foo", "this is a foo bar"); boolean accept = abstractGithubFactoryParametersResolver.accept(parameters); // shouldn't be accepted assertFalse(accept); } /** Check url which is not a github url can't be accepted by this resolver */ @Test public void checkInvalidAcceptUrl() { Map parameters = singletonMap(URL_PARAMETER_NAME, "http://www.eclipse.org/che"); boolean accept = abstractGithubFactoryParametersResolver.accept(parameters); // shouldn't be accepted assertFalse(accept); } /** Check github url will be be accepted by this resolver */ @Test public void checkValidAcceptUrl() { Map parameters = singletonMap(URL_PARAMETER_NAME, "https://github.com/codenvy/codenvy.git"); boolean accept = abstractGithubFactoryParametersResolver.accept(parameters); // shouldn't be accepted assertTrue(accept); } @Test public void shouldGenerateDevfileForFactoryWithNoDevfile() throws Exception { String githubUrl = "https://github.com/eclipse/che"; when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getLatestCommit(anyString(), anyString(), anyString(), any())) .thenReturn(new GithubCommit().withSha("test-sha")); Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) abstractGithubFactoryParametersResolver.createFactory(params); // then ScmInfoDto scmInfo = factory.getScmInfo(); assertEquals(scmInfo.getRepositoryUrl(), githubUrl + ".git"); assertEquals(scmInfo.getBranch(), null); } @Test public void shouldSkipAuthenticationWhenAccessDenied() throws Exception { // given when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getLatestCommit(anyString(), anyString(), anyString(), any())) .thenReturn(new GithubCommit().withSha("test-sha")); // when Map params = ImmutableMap.of( URL_PARAMETER_NAME, "https://github.com/eclipse/che", ERROR_QUERY_NAME, "access_denied"); abstractGithubFactoryParametersResolver.createFactory(params); // then verify(urlFactoryBuilder) .createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(FileContentProvider.class), anyMap(), eq(true)); } @Test public void shouldNotSkipAuthenticationWhenNoErrorParameterPassed() throws Exception { // given when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getLatestCommit(anyString(), anyString(), anyString(), any())) .thenReturn(new GithubCommit().withSha("test-sha")); // when abstractGithubFactoryParametersResolver.createFactory( ImmutableMap.of(URL_PARAMETER_NAME, "https://github.com/eclipse/che")); // then verify(urlFactoryBuilder) .createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(FileContentProvider.class), anyMap(), eq(false)); } @Test public void shouldSetScmInfoIntoDevfileV2() throws Exception { String githubUrl = "https://github.com/eclipse/che/tree/foobar"; FactoryDevfileV2Dto computedFactory = generateDevfileV2Factory(); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.of(computedFactory)); when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getLatestCommit(anyString(), anyString(), anyString(), any())) .thenReturn(new GithubCommit().withSha("test-sha")); Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) abstractGithubFactoryParametersResolver.createFactory(params); // then ScmInfo scmInfo = factory.getScmInfo(); assertNotNull(scmInfo); assertEquals(scmInfo.getScmProviderName(), "github"); assertEquals(scmInfo.getRepositoryUrl(), "https://github.com/eclipse/che.git"); assertEquals(scmInfo.getBranch(), "foobar"); } @Test public void shouldCreateFactoryWithoutAuthentication() throws ApiException { // given String githubUrl = "https://github.com/user/repo.git"; Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl, ERROR_QUERY_NAME, "access_denied"); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.of(generateDevfileV2Factory())); // when abstractGithubFactoryParametersResolver.createFactory(params); // then verify(urlFactoryBuilder) .createFactoryFromDevfile( any(GithubUrl.class), any(GithubAuthorizingFileContentProvider.class), anyMap(), eq(true)); } @Test public void shouldParseFactoryUrlWithAuthentication() throws Exception { // given githubUrlParser = mock(GithubURLParser.class); abstractGithubFactoryParametersResolver = new GithubFactoryParametersResolver( githubUrlParser, urlFetcher, githubSourceStorageBuilder, authorisationRequestManager, urlFactoryBuilder, personalAccessTokenManager); when(authorisationRequestManager.isStored(eq("github"))).thenReturn(true); // when abstractGithubFactoryParametersResolver.parseFactoryUrl("url"); // then verify(githubUrlParser).parseWithoutAuthentication("url", null); verify(githubUrlParser, never()).parse("url", null); } @Test public void shouldParseFactoryUrlWithOutAuthentication() throws Exception { // given githubUrlParser = mock(GithubURLParser.class); abstractGithubFactoryParametersResolver = new GithubFactoryParametersResolver( githubUrlParser, urlFetcher, githubSourceStorageBuilder, authorisationRequestManager, urlFactoryBuilder, personalAccessTokenManager); when(authorisationRequestManager.isStored(eq("github"))).thenReturn(false); // when abstractGithubFactoryParametersResolver.parseFactoryUrl("url"); // then verify(githubUrlParser).parse("url", null); verify(githubUrlParser, never()).parseWithoutAuthentication("url", null); } private FactoryDevfileV2Dto generateDevfileV2Factory() { return newDto(FactoryDevfileV2Dto.class) .withV(CURRENT_VERSION) .withSource("repo") .withDevfile(Map.of("schemaVersion", "2.0.0")); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubGitUserDataFetcherTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.net.HttpHeaders; import java.util.Optional; import org.eclipse.che.api.factory.server.scm.GitUserData; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GithubGitUserDataFetcherTest { @Mock OAuthAPI oAuthTokenFetcher; @Mock PersonalAccessTokenManager personalAccessTokenManager; GithubUserDataFetcher githubGUDFetcher; final int httpPort = 3301; WireMockServer wireMockServer; WireMock wireMock; final String githubOauthToken = "gho_token1"; @BeforeMethod void start() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).port(httpPort)); wireMockServer.start(); WireMock.configureFor("localhost", httpPort); wireMock = new WireMock("localhost", httpPort); githubGUDFetcher = new GithubUserDataFetcher( "http://che.api", personalAccessTokenManager, new GithubApiClient(wireMockServer.url("/"))); stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + githubOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader(GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER, "repo") .withBodyFile("github/rest/user/response.json"))); } @AfterMethod void stop() { wireMockServer.stop(); } @Test public void shouldFetchGitUserData() throws Exception { PersonalAccessToken token = mock(PersonalAccessToken.class); when(token.getToken()).thenReturn(githubOauthToken); when(token.getScmProviderUrl()).thenReturn(wireMockServer.url("/")); when(personalAccessTokenManager.get(any(Subject.class), eq("github"), eq(null), eq(null))) .thenReturn(Optional.of(token)); GitUserData gitUserData = githubGUDFetcher.fetchGitUserData(null); assertEquals(gitUserData.getScmUsername(), "Github User"); assertEquals(gitUserData.getScmUserEmail(), "github-user@acme.com"); } @Test public void shouldFetchGitUserDataByUrl() throws Exception { // given PersonalAccessToken token = mock(PersonalAccessToken.class); when(token.getToken()).thenReturn(githubOauthToken); when(personalAccessTokenManager.get(any(Subject.class), eq("github"), eq(null), eq(null))) .thenReturn(Optional.empty()); when(personalAccessTokenManager.get( any(Subject.class), eq(null), eq(wireMockServer.baseUrl()), eq(null))) .thenReturn(Optional.of(token)); // when GitUserData gitUserData = githubGUDFetcher.fetchGitUserData(null); // then assertEquals(gitUserData.getScmUsername(), "Github User"); assertEquals(gitUserData.getScmUserEmail(), "github-user@acme.com"); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static org.eclipse.che.api.factory.server.github.GithubPersonalAccessTokenFetcher.DEFAULT_TOKEN_SCOPES; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.testng.Assert.*; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.collect.ImmutableSet; import com.google.common.net.HttpHeaders; import java.util.Collections; import java.util.Optional; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Ignore; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GithubPersonalAccessTokenFetcherTest { @Mock OAuthAPI oAuthAPI; GithubPersonalAccessTokenFetcher githubPATFetcher; final int httpPort = 3301; WireMockServer wireMockServer; WireMock wireMock; final String githubOauthToken = "gho_token1"; @BeforeMethod void start() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).port(httpPort)); wireMockServer.start(); WireMock.configureFor("localhost", httpPort); wireMock = new WireMock("localhost", httpPort); githubPATFetcher = new GithubPersonalAccessTokenFetcher( "http://che.api", oAuthAPI, new GithubApiClient(wireMockServer.url("/"))); } @AfterMethod void stop() { wireMockServer.stop(); } @Test public void shouldNotValidateSCMServerWithTrailingSlash() throws Exception { stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + githubOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader(GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER, "repo") .withBodyFile("github/rest/user/response.json"))); PersonalAccessTokenParams personalAccessTokenParams = new PersonalAccessTokenParams( "https://github.com/", "provider", "scmTokenName", "scmTokenId", githubOauthToken, null); assertTrue( githubPATFetcher.isValid(personalAccessTokenParams).isEmpty(), "Should not validate SCM server with trailing /"); } @Test public void testContainsScope() { String[] tokenScopes = {"repo", "notifications", "write:org", "admin:gpg_key"}; assertTrue( githubPATFetcher.containsScopes(tokenScopes, ImmutableSet.of("repo")), "'repo' scope should have matched directly."); assertTrue( githubPATFetcher.containsScopes(tokenScopes, ImmutableSet.of("public_repo")), "'public_repo' scope should have matched since token has parent scope 'repo'."); assertTrue( githubPATFetcher.containsScopes( tokenScopes, ImmutableSet.of("read:gpg_key", "write:gpg_key")), "'admin:gpg_key' token scope should cover both scope requirement."); assertFalse( githubPATFetcher.containsScopes(tokenScopes, ImmutableSet.of("admin:org")), "'admin:org' scope should not match since token only has scope 'write:org'."); assertFalse( githubPATFetcher.containsScopes(tokenScopes, ImmutableSet.of("gist")), "'gist' shouldn't matche since it is not present in token scope"); assertTrue( githubPATFetcher.containsScopes(tokenScopes, ImmutableSet.of("unknown", "repo")), "'unknown' is not even a valid GitHub scope, so it shouldn't have any impact."); assertTrue( githubPATFetcher.containsScopes(tokenScopes, Collections.emptySet()), "No required scope should always return true"); assertFalse( githubPATFetcher.containsScopes(new String[0], ImmutableSet.of("repo")), "Token has no scope, so it should not match"); assertTrue( githubPATFetcher.containsScopes(new String[0], Collections.emptySet()), "No scope requirement and a token with no scope should match"); } @Ignore @Test( expectedExceptions = ScmCommunicationException.class, expectedExceptionsMessageRegExp = "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: \\[repo, user:email, read:user, read:org, workflow]") public void shouldThrowExceptionOnInsufficientTokenScopes() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(githubOauthToken).withScope(""); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + githubOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader(GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER, "") .withBodyFile("github/rest/user/response.json"))); githubPATFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @Test( expectedExceptions = ScmUnauthorizedException.class, expectedExceptionsMessageRegExp = "Username is not authorized in github OAuth provider.") public void shouldThrowUnauthorizedExceptionWhenUserNotLoggedIn() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); when(oAuthAPI.getOrRefreshToken(anyString())).thenThrow(UnauthorizedException.class); githubPATFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @Test( expectedExceptions = ScmUnauthorizedException.class, expectedExceptionsMessageRegExp = "Username is not authorized in github OAuth provider.") public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(githubOauthToken).withScope(""); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + githubOauthToken)) .willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); githubPATFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @Test public void shouldReturnToken() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(githubOauthToken); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + githubOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader( GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER, DEFAULT_TOKEN_SCOPES.toString().replace("[", "").replace("]", "")) .withBodyFile("github/rest/user/response.json"))); PersonalAccessToken token = githubPATFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); assertNotNull(token); } @Test public void shouldValidatePersonalToken() throws Exception { stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + githubOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader( GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER, DEFAULT_TOKEN_SCOPES.toString().replace("[", "").replace("]", "")) .withBodyFile("github/rest/user/response.json"))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( wireMockServer.url("/"), "provider", "token-name", "tid-23434", githubOauthToken, null); Optional> valid = githubPATFetcher.isValid(params); assertTrue(valid.isPresent()); assertTrue(valid.get().first); } @Test public void shouldValidateOauthToken() throws Exception { stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + githubOauthToken)) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withHeader( GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER, DEFAULT_TOKEN_SCOPES.toString().replace("[", "").replace("]", "")) .withBodyFile("github/rest/user/response.json"))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( wireMockServer.url("/"), "provider", OAUTH_2_PREFIX + "-params-name", "tid-23434", githubOauthToken, null); Optional> valid = githubPATFetcher.isValid(params); assertTrue(valid.isPresent()); assertTrue(valid.get().first); } @Test public void shouldNotValidateExpiredOauthToken() throws Exception { stubFor(get(urlEqualTo("/api/v3/user")).willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( wireMockServer.url("/"), "provider", OAUTH_2_PREFIX + "-token-name", "tid-23434", githubOauthToken, null); assertFalse(githubPATFetcher.isValid(params).isPresent()); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubScmFileResolverTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.*; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GithubScmFileResolverTest { @Mock private DevfileFilenamesProvider devfileFilenamesProvider; GithubURLParser githubURLParser; private URLFetcher urlFetcher; private PersonalAccessTokenManager personalAccessTokenManager; private GithubScmFileResolver githubScmFileResolver; private GithubApiClient githubApiClient; @BeforeMethod void start() { this.githubApiClient = mock(GithubApiClient.class); this.urlFetcher = mock(URLFetcher.class); this.personalAccessTokenManager = mock(PersonalAccessTokenManager.class); githubURLParser = new GithubURLParser( personalAccessTokenManager, devfileFilenamesProvider, githubApiClient, null, false); githubScmFileResolver = new GithubScmFileResolver(githubURLParser, urlFetcher, personalAccessTokenManager); } /** Check url which is not a Gitlab url can't be accepted by this resolver */ @Test public void checkInvalidAcceptUrl() { // shouldn't be accepted assertFalse(githubScmFileResolver.accept("http://foobar.com")); } /** Check url will be be accepted by this resolver */ @Test public void checkValidAcceptUrl() { // should be accepted assertTrue(githubScmFileResolver.accept("https://github.com/test/repo.git")); } @Test public void shouldReturnContentFromUrlFetcher() throws Exception { final String rawContent = "raw_content"; final String filename = "devfile.yaml"; when(urlFetcher.fetch( eq( "https://raw.githubusercontent.com/organization/samples/d74923ebf968454cf13251f17df69dcd87d3b932/devfile.yaml"), anyString())) .thenReturn(rawContent); lenient() .when(personalAccessTokenManager.getAndStore(anyString())) .thenReturn(new PersonalAccessToken("foo", "provider", "che", "my-token")); when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getLatestCommit(anyString(), anyString(), anyString(), any())) .thenReturn( new GithubCommit() .withSha("d74923ebf968454cf13251f17df69dcd87d3b932") .withUrl("http://commit.url")); String content = githubScmFileResolver.fileContent("https://github.com/organization/samples.git", filename); assertEquals(content, rawContent); } @Test public void shouldReturnContentWithoutAuthentication() throws Exception { // given lenient() .when(personalAccessTokenManager.getAndStore(anyString())) .thenThrow(new ScmUnauthorizedException("message", "github", "v1", "url")); // when githubScmFileResolver.fileContent("https://github.com/username/repo.git", "devfile.yaml"); // then verify(urlFetcher).fetch(anyString()); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubURLParserTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.util.Arrays.asList; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import java.lang.reflect.Field; import java.util.Optional; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.subject.Subject; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Validate operations performed by the Github parser * * @author Florent Benoit */ @Listeners(MockitoTestNGListener.class) public class GithubURLParserTest { private GithubApiClient githubApiClient; private PersonalAccessTokenManager personalAccessTokenManager; @Mock private DevfileFilenamesProvider devfileFilenamesProvider; /** Instance of component that will be tested. */ private GithubURLParser githubUrlParser; WireMockServer wireMockServer; WireMock wireMock; /** Setup objects/ */ @BeforeMethod protected void start() throws ApiException { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); this.personalAccessTokenManager = mock(PersonalAccessTokenManager.class); this.githubApiClient = mock(GithubApiClient.class); githubUrlParser = new GithubURLParser( personalAccessTokenManager, devfileFilenamesProvider, githubApiClient, null, false); } /** Check invalid url (not a GitHub one) */ @Test(expectedExceptions = IllegalArgumentException.class) public void invalidUrl() throws ApiException { githubUrlParser.parse("http://www.eclipse.org", null); } @Test public void shouldParseWithBranch() throws ApiException { GithubUrl githubUrl = githubUrlParser.parse("https://github.com/eclipse/che", "branch"); assertEquals(githubUrl.getBranch(), "branch"); } @Test public void shouldParseWithUrlBranch() throws ApiException { GithubUrl githubUrl = githubUrlParser.parse("https://github.com/eclipse/che/tree/master/", "branch"); assertEquals(githubUrl.getBranch(), "master"); } @Test public void shouldParseGithubServerUrlWithIpv6Host() throws ApiException { // given githubUrlParser = new GithubURLParser( personalAccessTokenManager, devfileFilenamesProvider, githubApiClient, "https://[2001:db8::1]", false); when(githubApiClient.isConnected(eq("https://[2001:db8::1]"))).thenReturn(true); when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(asList("devfile.yaml", ".devfile.yaml")); // when GithubUrl githubUrl = githubUrlParser.parse("https://[2001:db8::1]/eclipse/che", null); // then assertEquals(githubUrl.getUsername(), "eclipse"); assertEquals(githubUrl.getRepository(), "che"); } @Test public void shouldParseIpv6UrlWithBranch() throws ApiException { // given githubUrlParser = new GithubURLParser( personalAccessTokenManager, devfileFilenamesProvider, githubApiClient, "https://[2001:db8::1]", false); when(githubApiClient.isConnected(eq("https://[2001:db8::1]"))).thenReturn(true); when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(asList("devfile.yaml", ".devfile.yaml")); // when GithubUrl githubUrl = githubUrlParser.parse("https://[2001:db8::1]/eclipse/che/tree/feature-branch", null); // then assertEquals(githubUrl.getUsername(), "eclipse"); assertEquals(githubUrl.getRepository(), "che"); assertEquals(githubUrl.getBranch(), "feature-branch"); } @Test public void shouldParseIpv6UrlWithRevisionParam() throws ApiException { // given githubUrlParser = new GithubURLParser( personalAccessTokenManager, devfileFilenamesProvider, githubApiClient, "https://[2001:db8::1]", false); when(githubApiClient.isConnected(eq("https://[2001:db8::1]"))).thenReturn(true); when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(asList("devfile.yaml", ".devfile.yaml")); // when GithubUrl githubUrl = githubUrlParser.parse("https://[2001:db8::1]/eclipse/che", "my-branch"); // then assertEquals(githubUrl.getUsername(), "eclipse"); assertEquals(githubUrl.getRepository(), "che"); assertEquals(githubUrl.getBranch(), "my-branch"); } @Test public void shouldParseIpv6UrlWithPullRequestId() throws Exception { // given githubUrlParser = new GithubURLParser( personalAccessTokenManager, devfileFilenamesProvider, githubApiClient, "https://[2001:db8::1]", false); GithubPullRequest pr = new GithubPullRequest() .withState("open") .withHead( new GithubHead() .withRef("pr-branch") .withUser(new GithubUser().withId(0).withName("eclipse").withLogin("eclipse")) .withRepo(new GithubRepo().withName("che"))); when(githubApiClient.isConnected(eq("https://[2001:db8::1]"))).thenReturn(true); when(githubApiClient.getPullRequest(any(), any(), any(), any())).thenReturn(pr); // when GithubUrl githubUrl = githubUrlParser.parse("https://[2001:db8::1]/eclipse/che/pull/123", null); // then assertEquals(githubUrl.getUsername(), "eclipse"); assertEquals(githubUrl.getRepository(), "che"); assertEquals(githubUrl.getBranch(), "pr-branch"); } @Test public void shouldParseIpv6LoopbackAddress() throws ApiException { // given githubUrlParser = new GithubURLParser( personalAccessTokenManager, devfileFilenamesProvider, githubApiClient, "https://[::1]", false); when(githubApiClient.isConnected(eq("https://[::1]"))).thenReturn(true); when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(asList("devfile.yaml", ".devfile.yaml")); // when GithubUrl githubUrl = githubUrlParser.parse("https://[::1]/eclipse/che", null); // then assertEquals(githubUrl.getUsername(), "eclipse"); assertEquals(githubUrl.getRepository(), "che"); } @Test public void shouldParseIpv6FullFormAddress() throws ApiException { // given githubUrlParser = new GithubURLParser( personalAccessTokenManager, devfileFilenamesProvider, githubApiClient, "https://[2001:0db8:0000:0000:0000:0000:0000:0001]", false); when(githubApiClient.isConnected(eq("https://[2001:0db8:0000:0000:0000:0000:0000:0001]"))) .thenReturn(true); when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(asList("devfile.yaml", ".devfile.yaml")); // when GithubUrl githubUrl = githubUrlParser.parse( "https://[2001:0db8:0000:0000:0000:0000:0000:0001]/eclipse/che", null); // then assertEquals(githubUrl.getUsername(), "eclipse"); assertEquals(githubUrl.getRepository(), "che"); } @Test public void shouldParseIpv6DotGitUrl() throws ApiException { // given githubUrlParser = new GithubURLParser( personalAccessTokenManager, devfileFilenamesProvider, githubApiClient, "https://[2001:db8::1]", false); when(githubApiClient.isConnected(eq("https://[2001:db8::1]"))).thenReturn(true); when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(asList("devfile.yaml", ".devfile.yaml")); // when GithubUrl githubUrl = githubUrlParser.parse("https://[2001:db8::1]/eclipse/che.git", null); // then assertEquals(githubUrl.getUsername(), "eclipse"); assertEquals(githubUrl.getRepository(), "che"); } @Test public void shouldParseIpv6UrlViaDynamicPatternMatching() throws ApiException { // The parser is configured for github.com, but getPatternMatcherByUrl() dynamically // creates a pattern from the URL itself, so IPv6 URLs can be parsed even when the // constructor was configured with a different host. when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(asList("devfile.yaml", ".devfile.yaml")); GithubUrl githubUrl = githubUrlParser.parse("https://[2001:db8::1]/eclipse/che", null); assertEquals(githubUrl.getUsername(), "eclipse"); assertEquals(githubUrl.getRepository(), "che"); } /** Check URLs are valid with regexp */ @Test(dataProvider = "UrlsProvider") public void checkRegexp(String url) { assertTrue(githubUrlParser.isValid(url), "url " + url + " is invalid"); } /** Compare parsing */ @Test(dataProvider = "parsing") public void checkParsing(String url, String username, String repository, String path) throws ApiException { when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(asList("devfile.yaml", ".devfile.yaml")); GithubUrl githubUrl = githubUrlParser.parse(url, null); assertEquals(githubUrl.getUsername(), username); assertEquals(githubUrl.getRepository(), repository); assertEquals(githubUrl.getBranch(), path); } /** Compare parsing */ @Test(dataProvider = "parsingBadRepository") public void checkParsingBadRepositoryDoNotModifiesInitialInput(String url, String repository) throws ApiException { // given when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); // when GithubUrl githubUrl = githubUrlParser.parse(url, null); // then assertEquals(githubUrl.getRepository(), repository); } @DataProvider(name = "UrlsProvider") public Object[][] urls() { return new Object[][] { {"https://github.com/eclipse/che"}, {"https://github.com/eclipse/che123"}, {"https://github.com/eclipse/che/"}, {"https://github.com/eclipse/che/tree/4.2.x"}, {"https://github.com/eclipse/che/tree/master/"}, {"https://github.com/eclipse/che/tree/master/dashboard/"}, {"https://github.com/eclipse/che/tree/master/plugins/plugin-git/che-plugin-git-ext-git"}, {"https://github.com/eclipse/che/tree/master/plugins/plugin-git/che-plugin-git-ext-git/"}, {"https://github.com/eclipse/che/pull/11103"}, {"https://github.com/eclipse/che.git"}, {"https://github.com/eclipse/che.with.dots.git"}, {"https://github.com/eclipse/che-with-hyphen"}, {"https://github.com/eclipse/che-with-hyphen.git"}, {"git@github.com:eclipse/che.git)"}, {"git@github.com:eclipse/che)"}, {"git@github.com:eclipse/che123)"}, {"git@github.com:eclipse/che.with.dots.git)"}, {"git@github.com:eclipse/che-with-hyphen)"}, {"git@github.com:eclipse/che-with-hyphen.git)"} }; } @DataProvider(name = "parsing") public Object[][] expectedParsing() { return new Object[][] { {"https://github.com/eclipse/che", "eclipse", "che", null}, {"https://github.com/eclipse/che123", "eclipse", "che123", null}, {"https://github.com/eclipse/che.git", "eclipse", "che", null}, {"https://github.com/eclipse/che.with.dot.git", "eclipse", "che.with.dot", null}, {"https://github.com/eclipse/-.git", "eclipse", "-", null}, {"https://github.com/eclipse/-j.git", "eclipse", "-j", null}, {"https://github.com/eclipse/-", "eclipse", "-", null}, {"https://github.com/eclipse/che-with-hyphen", "eclipse", "che-with-hyphen", null}, {"https://github.com/eclipse/che-with-hyphen.git", "eclipse", "che-with-hyphen", null}, {"https://github.com/eclipse/che/", "eclipse", "che", null}, {"https://github.com/eclipse/repositorygit", "eclipse", "repositorygit", null}, {"https://github.com/eclipse/che/tree/4.2.x", "eclipse", "che", "4.2.x"}, {"https://github.com/eclipse/che/tree/master", "eclipse", "che", "master"}, {"https://github.com/eclipse/che/tree/master/", "eclipse", "che", "master"}, { "https://github.com/eclipse/che/tree/branch/with/slash", "eclipse", "che", "branch/with/slash" }, {"https://github.com/eclipse/che/blob/branch/path/to/", "eclipse", "che", "branch/path/to"}, { "https://github.com/eclipse/che/blob/branch/path/to/devfile.yaml", "eclipse", "che", "branch/path/to" }, { "https://github.com/eclipse/che/blob/branch/path/to/.devfile.yaml", "eclipse", "che", "branch/path/to" }, {"git@github.com:eclipse/che", "eclipse", "che", null}, {"git@github.com:eclipse/che123", "eclipse", "che123", null}, {"git@github.com:eclipse/che.git", "eclipse", "che", null}, {"git@github.com:eclipse/che.with.dot.git", "eclipse", "che.with.dot", null}, {"git@github.com:eclipse/-.git", "eclipse", "-", null}, {"git@github.com:eclipse/-j.git", "eclipse", "-j", null}, {"git@github.com:eclipse/-", "eclipse", "-", null}, {"git@github.com:eclipse/che-with-hyphen", "eclipse", "che-with-hyphen", null}, {"git@github.com:eclipse/che-with-hyphen.git", "eclipse", "che-with-hyphen", null}, {"git@github.com:eclipse/che/", "eclipse", "che", null}, {"git@github.com:eclipse/repositorygit", "eclipse", "repositorygit", null}, }; } @DataProvider(name = "parsingBadRepository") public Object[][] parsingBadRepository() { return new Object[][] { {"https://github.com/eclipse/che .git", "che .git"}, {"https://github.com/eclipse/.git", ".git"}, {"https://github.com/eclipse/myB@dR&pository.git", "myB@dR&pository.git"}, {"https://github.com/eclipse/.", "."}, {"https://github.com/eclipse/івапівап.git", "івапівап.git"}, {"https://github.com/eclipse/ ", " "}, {"https://github.com/eclipse/.", "."}, {"https://github.com/eclipse/ .git", " .git"}, {"git@github.com:eclipse/che .git", "che .git"}, {"git@github.com:eclipse/.git", ".git"}, {"git@github.com:eclipse/myB@dR&pository.git", "myB@dR&pository.git"}, {"git@github.com:eclipse/.", "."}, {"git@github.com:eclipse/івапівап.git", "івапівап.git"}, {"git@github.com:eclipse/ ", " "}, {"git@github.com:eclipse/.", "."}, {"git@github.com:eclipse/ .git", " .git"} }; } /** Check Pull Request with data inside the repository */ @Test public void checkPullRequestFromRepository() throws Exception { String url = "https://github.com/eclipse/che/pull/21276"; GithubPullRequest pr = new GithubPullRequest() .withState("open") .withHead( new GithubHead() .withRef("pr-main-to-7.46.0") .withUser(new GithubUser().withId(0).withName("eclipse").withLogin("eclipse")) .withRepo(new GithubRepo().withName("che"))); when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getPullRequest(any(), any(), any(), any())).thenReturn(pr); GithubUrl githubUrl = githubUrlParser.parse(url, null); assertEquals(githubUrl.getUsername(), "eclipse"); assertEquals(githubUrl.getRepository(), "che"); assertEquals(githubUrl.getBranch(), "pr-main-to-7.46.0"); } /** Check Pull Request with data outside the repository (fork) */ @Test public void checkPullRequestFromForkedRepository() throws Exception { GithubPullRequest pr = new GithubPullRequest() .withState("open") .withHead( new GithubHead() .withRef("main") .withUser(new GithubUser().withLogin("eclipse")) .withRepo(new GithubRepo().withName("che"))); PersonalAccessToken personalAccessToken = mock(PersonalAccessToken.class); when(personalAccessToken.getToken()).thenReturn("token"); when(personalAccessTokenManager.get(any(Subject.class), eq(null), anyString(), eq(null))) .thenReturn(Optional.of(personalAccessToken)); when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getPullRequest(anyString(), anyString(), anyString(), anyString())) .thenReturn(pr); String url = "https://github.com/eclipse/che/pull/20189"; GithubUrl githubUrl = githubUrlParser.parse(url, null); assertEquals(githubUrl.getUsername(), "eclipse"); assertEquals(githubUrl.getRepository(), "che"); assertEquals(githubUrl.getBranch(), "main"); } @Test public void checkPullRequestFromForkedRepositoryWithoutAuthentication() throws Exception { String url = "https://github.com/eclipse/che/pull/21276"; GithubPullRequest pr = new GithubPullRequest() .withState("open") .withHead( new GithubHead() .withRef("pr-main-to-7.46.0-SNAPSHOT") .withUser(new GithubUser().withId(0).withName("eclipse").withLogin("eclipse")) .withRepo(new GithubRepo().withName("che"))); when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getPullRequest(any(), any(), any(), any())).thenReturn(pr); GithubUrl githubUrl = githubUrlParser.parseWithoutAuthentication(url, null); assertEquals(githubUrl.getUsername(), "eclipse"); assertEquals(githubUrl.getRepository(), "che"); assertEquals(githubUrl.getBranch(), "pr-main-to-7.46.0-SNAPSHOT"); verify(personalAccessTokenManager, never()).fetchAndSave(any(Subject.class), anyString()); } /** Check Pull Request is failing with Merged state */ @Test( expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".*found merged.*") public void checkPullRequestMergedState() throws Exception { PersonalAccessToken personalAccessToken = mock(PersonalAccessToken.class); GithubPullRequest githubPullRequest = mock(GithubPullRequest.class); when(githubPullRequest.getState()).thenReturn("merged"); when(personalAccessToken.getToken()).thenReturn("token"); when(personalAccessTokenManager.get(any(Subject.class), eq(null), anyString(), eq(null))) .thenReturn(Optional.of(personalAccessToken)); when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getPullRequest(anyString(), anyString(), anyString(), anyString())) .thenReturn(githubPullRequest); String url = "https://github.com/eclipse/che/pull/11103"; githubUrlParser.parse(url, null); } @Test public void shouldParseServerUr() throws Exception { // given String url = "https://github-server.com/user/repo"; // when GithubUrl githubUrl = githubUrlParser.parse(url, null); // then assertEquals(githubUrl.getUsername(), "user"); assertEquals(githubUrl.getRepository(), "repo"); assertEquals(githubUrl.getHostName(), "https://github-server.com"); } @Test public void shouldParseServerUrWithPullRequestId() throws Exception { // given String url = "https://github-server.com/user/repo/pull/11103"; GithubPullRequest pr = new GithubPullRequest() .withState("open") .withHead( new GithubHead() .withUser(new GithubUser().withId(0).withName("eclipse").withLogin("eclipse")) .withRepo(new GithubRepo().withName("che"))); when(githubApiClient.isConnected(eq("https://github-server.com"))).thenReturn(true); when(githubApiClient.getPullRequest(any(), any(), any(), any())).thenReturn(pr); // when githubUrlParser.parse(url, null); // then verify(personalAccessTokenManager, times(2)) .get(any(Subject.class), eq(null), eq("https://github-server.com"), eq(null)); } @Test public void shouldValidateOldVersionGitHubServerUrl() throws Exception { // given Field endpoint = AbstractGithubURLParser.class.getDeclaredField("endpoint"); endpoint.setAccessible(true); endpoint.set(githubUrlParser, wireMockServer.baseUrl()); String url = wireMockServer.url("/user/repo"); stubFor( get(urlEqualTo("/api/v3/user")) .willReturn( aResponse() .withStatus(HTTP_UNAUTHORIZED) .withBody("{\"message\": \"Must authenticate to access this API.\",\n}"))); // when boolean valid = githubUrlParser.isValid(url); // then assertTrue(valid); } @Test public void shouldValidateGitHubServerUrl() throws Exception { // given Field endpoint = AbstractGithubURLParser.class.getDeclaredField("endpoint"); endpoint.setAccessible(true); endpoint.set(githubUrlParser, wireMockServer.baseUrl()); String url = wireMockServer.url("/user/repo"); stubFor( get(urlEqualTo("/api/v3/user")) .willReturn( aResponse() .withStatus(HTTP_UNAUTHORIZED) .withBody("{\"message\": \"Requires authentication\",\n}"))); // when boolean valid = githubUrlParser.isValid(url); // then assertTrue(valid); } @Test public void shouldNotRequestGitHubSAASUrl() throws Exception { // when githubUrlParser.isValid("https:github.com/repo/user.git"); // then verify(githubApiClient, never()).getUser(anyString()); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubUrlTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import java.util.Arrays; import java.util.Iterator; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Test of {@Link GithubUrl} Note: The parser is also testing the object * * @author Florent Benoit */ @Listeners(MockitoTestNGListener.class) public class GithubUrlTest { @Mock private GithubApiClient githubApiClient; /** Check when there is devfile in the repository */ @Test public void checkDevfileLocation() throws Exception { DevfileFilenamesProvider devfileFilenamesProvider = mock(DevfileFilenamesProvider.class); when(githubApiClient.isConnected("https://github.com")).thenReturn(true); /** Parser used to create the url. */ GithubURLParser githubUrlParser = new GithubURLParser( mock(PersonalAccessTokenManager.class), devfileFilenamesProvider, githubApiClient, null, false); when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); GithubUrl githubUrl = githubUrlParser.parse("https://github.com/eclipse/che", null); assertEquals(githubUrl.devfileFileLocations().size(), 2); Iterator iterator = githubUrl.devfileFileLocations().iterator(); assertEquals( iterator.next().location(), "https://raw.githubusercontent.com/eclipse/che/HEAD/devfile.yaml"); assertEquals( iterator.next().location(), "https://raw.githubusercontent.com/eclipse/che/HEAD/foo.bar"); } @Test public void shouldReturnDevfileLocationFromSSHUrl() throws Exception { DevfileFilenamesProvider devfileFilenamesProvider = mock(DevfileFilenamesProvider.class); when(githubApiClient.isConnected("https://github.com")).thenReturn(true); /** Parser used to create the url. */ GithubURLParser githubUrlParser = new GithubURLParser( mock(PersonalAccessTokenManager.class), devfileFilenamesProvider, githubApiClient, null, false); when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); GithubUrl githubUrl = githubUrlParser.parse("git@github.com:eclipse/che", null); assertEquals(githubUrl.devfileFileLocations().size(), 2); Iterator iterator = githubUrl.devfileFileLocations().iterator(); assertEquals( iterator.next().location(), "https://raw.githubusercontent.com/eclipse/che/HEAD/devfile.yaml"); assertEquals( iterator.next().location(), "https://raw.githubusercontent.com/eclipse/che/HEAD/foo.bar"); } /** Check the original repository */ @Test public void checkRepositoryLocation() throws Exception { DevfileFilenamesProvider devfileFilenamesProvider = mock(DevfileFilenamesProvider.class); /** Parser used to create the url. */ GithubURLParser githubUrlParser = new GithubURLParser( mock(PersonalAccessTokenManager.class), devfileFilenamesProvider, githubApiClient, null, false); when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); GithubUrl githubUrl = githubUrlParser.parse("https://github.com/eclipse/che", null); assertEquals(githubUrl.repositoryLocation(), "https://github.com/eclipse/che.git"); } @Test public void shouldReturnRepositoryLocationFromSSHUrl() throws Exception { DevfileFilenamesProvider devfileFilenamesProvider = mock(DevfileFilenamesProvider.class); /** Parser used to create the url. */ GithubURLParser githubUrlParser = new GithubURLParser( mock(PersonalAccessTokenManager.class), devfileFilenamesProvider, githubApiClient, null, false); when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); GithubUrl githubUrl = githubUrlParser.parse("git@github.com:eclipse/che.git", null); assertEquals(githubUrl.repositoryLocation(), "git@github.com:eclipse/che.git"); } @Test public void testRawFileLocationWithDefaultBranchName() { String file = ".che/che-theia-plugins.yaml"; GithubUrl url = new GithubUrl("github") .withUsername("eclipse") .withRepository("che") .withServerUrl("https://github.com"); assertEquals( url.rawFileLocation(file), "https://raw.githubusercontent.com/eclipse/che/HEAD/.che/che-theia-plugins.yaml"); } @Test public void testRawFileLocationWithCustomBranchName() { String file = ".che/che-theia-plugins.yaml"; GithubUrl url = new GithubUrl("github") .withUsername("eclipse") .withRepository("che") .withBranch("main") .withServerUrl("https://github.com"); assertEquals( url.rawFileLocation(file), "https://raw.githubusercontent.com/eclipse/che/main/.che/che-theia-plugins.yaml"); } @Test public void testRawFileLocationForCommit() { String file = ".che/che-theia-plugins.yaml"; GithubUrl url = new GithubUrl("github") .withUsername("eclipse") .withRepository("che") .withServerUrl("https://github.com") .withBranch("main") .withLatestCommit("c24fd44e0f7296be2e49a380fb8abe2fe4db9100"); assertEquals( url.rawFileLocation(file), "https://raw.githubusercontent.com/eclipse/che/c24fd44e0f7296be2e49a380fb8abe2fe4db9100/.che/che-theia-plugins.yaml"); } } ================================================ FILE: wsmaster/che-core-api-factory-github/src/test/resources/__files/github/rest/pullRequest/response.json ================================================ { "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347", "id": 1, "node_id": "MDExOlB1bGxSZXF1ZXN0MQ==", "html_url": "https://github.com/octocat/Hello-World/pull/1347", "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff", "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch", "issue_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347", "commits_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/commits", "review_comments_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/comments", "review_comment_url": "https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}", "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments", "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", "number": 1347, "state": "open", "locked": true, "title": "Amazing new feature", "user": { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, "body": "Please pull these awesome changes in!", "labels": [ { "id": 208045946, "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=", "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug", "name": "bug", "description": "Something isn't working", "color": "f29513", "default": true } ], "milestone": { "url": "https://api.github.com/repos/octocat/Hello-World/milestones/1", "html_url": "https://github.com/octocat/Hello-World/milestones/v1.0", "labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels", "id": 1002604, "node_id": "MDk6TWlsZXN0b25lMTAwMjYwNA==", "number": 1, "state": "open", "title": "v1.0", "description": "Tracking milestone for version 1.0", "creator": { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, "open_issues": 4, "closed_issues": 8, "created_at": "2011-04-10T20:09:31Z", "updated_at": "2014-03-03T18:58:10Z", "closed_at": "2013-02-12T13:22:01Z", "due_on": "2012-10-09T23:39:01Z" }, "active_lock_reason": "too heated", "created_at": "2011-01-26T19:01:12Z", "updated_at": "2011-01-26T19:01:12Z", "closed_at": "2011-01-26T19:01:12Z", "merged_at": "2011-01-26T19:01:12Z", "merge_commit_sha": "e5bd3914e2e596debea16f433f57875b5b90bcd6", "assignee": { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, "assignees": [ { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, { "login": "hubot", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/hubot_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/hubot", "html_url": "https://github.com/hubot", "followers_url": "https://api.github.com/users/hubot/followers", "following_url": "https://api.github.com/users/hubot/following{/other_user}", "gists_url": "https://api.github.com/users/hubot/gists{/gist_id}", "starred_url": "https://api.github.com/users/hubot/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/hubot/subscriptions", "organizations_url": "https://api.github.com/users/hubot/orgs", "repos_url": "https://api.github.com/users/hubot/repos", "events_url": "https://api.github.com/users/hubot/events{/privacy}", "received_events_url": "https://api.github.com/users/hubot/received_events", "type": "User", "site_admin": true } ], "requested_reviewers": [ { "login": "other_user", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/other_user_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/other_user", "html_url": "https://github.com/other_user", "followers_url": "https://api.github.com/users/other_user/followers", "following_url": "https://api.github.com/users/other_user/following{/other_user}", "gists_url": "https://api.github.com/users/other_user/gists{/gist_id}", "starred_url": "https://api.github.com/users/other_user/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/other_user/subscriptions", "organizations_url": "https://api.github.com/users/other_user/orgs", "repos_url": "https://api.github.com/users/other_user/repos", "events_url": "https://api.github.com/users/other_user/events{/privacy}", "received_events_url": "https://api.github.com/users/other_user/received_events", "type": "User", "site_admin": false } ], "requested_teams": [ { "id": 1, "node_id": "MDQ6VGVhbTE=", "url": "https://api.github.com/teams/1", "html_url": "https://github.com/orgs/github/teams/justice-league", "name": "Justice League", "slug": "justice-league", "description": "A great team.", "privacy": "closed", "permission": "admin", "members_url": "https://api.github.com/teams/1/members{/member}", "repositories_url": "https://api.github.com/teams/1/repos" } ], "head": { "label": "octocat:new-topic", "ref": "new-topic", "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", "user": { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, "repo": { "id": 1296269, "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", "name": "Hello-World", "full_name": "octocat/Hello-World", "owner": { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, "private": false, "html_url": "https://github.com/octocat/Hello-World", "description": "This your first repo!", "fork": false, "url": "https://api.github.com/repos/octocat/Hello-World", "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", "events_url": "https://api.github.com/repos/octocat/Hello-World/events", "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", "git_url": "git:github.com/octocat/Hello-World.git", "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", "ssh_url": "git@github.com:octocat/Hello-World.git", "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", "clone_url": "https://github.com/octocat/Hello-World.git", "mirror_url": "git:git.example.com/octocat/Hello-World", "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", "svn_url": "https://svn.github.com/octocat/Hello-World", "homepage": "https://github.com", "language": null, "forks_count": 9, "stargazers_count": 80, "watchers_count": 80, "size": 108, "default_branch": "master", "open_issues_count": 0, "topics": [ "octocat", "atom", "electron", "api" ], "has_issues": true, "has_projects": true, "has_wiki": true, "has_pages": false, "has_downloads": true, "archived": false, "disabled": false, "pushed_at": "2011-01-26T19:06:43Z", "created_at": "2011-01-26T19:01:12Z", "updated_at": "2011-01-26T19:14:43Z", "permissions": { "admin": false, "push": false, "pull": true }, "allow_rebase_merge": true, "temp_clone_token": "ABTLWHOULUVAXGTRYU7OC2876QJ2O", "allow_squash_merge": true, "allow_merge_commit": true, "allow_forking": true, "forks": 123, "open_issues": 123, "license": { "key": "mit", "name": "MIT License", "url": "https://api.github.com/licenses/mit", "spdx_id": "MIT", "node_id": "MDc6TGljZW5zZW1pdA==" }, "watchers": 123 } }, "base": { "label": "octocat:master", "ref": "master", "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", "user": { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, "repo": { "id": 1296269, "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", "name": "Hello-World", "full_name": "octocat/Hello-World", "owner": { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, "private": false, "html_url": "https://github.com/octocat/Hello-World", "description": "This your first repo!", "fork": false, "url": "https://api.github.com/repos/octocat/Hello-World", "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", "events_url": "https://api.github.com/repos/octocat/Hello-World/events", "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", "git_url": "git:github.com/octocat/Hello-World.git", "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", "ssh_url": "git@github.com:octocat/Hello-World.git", "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", "clone_url": "https://github.com/octocat/Hello-World.git", "mirror_url": "git:git.example.com/octocat/Hello-World", "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", "svn_url": "https://svn.github.com/octocat/Hello-World", "homepage": "https://github.com", "language": null, "forks_count": 9, "stargazers_count": 80, "watchers_count": 80, "size": 108, "default_branch": "master", "open_issues_count": 0, "topics": [ "octocat", "atom", "electron", "api" ], "has_issues": true, "has_projects": true, "has_wiki": true, "has_pages": false, "has_downloads": true, "archived": false, "disabled": false, "pushed_at": "2011-01-26T19:06:43Z", "created_at": "2011-01-26T19:01:12Z", "updated_at": "2011-01-26T19:14:43Z", "permissions": { "admin": false, "push": false, "pull": true }, "allow_rebase_merge": true, "temp_clone_token": "ABTLWHOULUVAXGTRYU7OC2876QJ2O", "allow_squash_merge": true, "allow_merge_commit": true, "forks": 123, "open_issues": 123, "license": { "key": "mit", "name": "MIT License", "url": "https://api.github.com/licenses/mit", "spdx_id": "MIT", "node_id": "MDc6TGljZW5zZW1pdA==" }, "watchers": 123 } }, "_links": { "self": { "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347" }, "html": { "href": "https://github.com/octocat/Hello-World/pull/1347" }, "issue": { "href": "https://api.github.com/repos/octocat/Hello-World/issues/1347" }, "comments": { "href": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments" }, "review_comments": { "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/comments" }, "review_comment": { "href": "https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}" }, "commits": { "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/commits" }, "statuses": { "href": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e" } }, "author_association": "OWNER", "auto_merge": null, "draft": false, "merged": false, "mergeable": true, "rebaseable": true, "mergeable_state": "clean", "merged_by": { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, "comments": 10, "review_comments": 0, "maintainer_can_modify": true, "commits": 3, "additions": 100, "deletions": 3, "changed_files": 5 } ================================================ FILE: wsmaster/che-core-api-factory-github/src/test/resources/__files/github/rest/user/response.json ================================================ { "login": "github-user", "id": 123456789, "node_id": "ddfsSDSDDJKHSDjhd", "avatar_url": "https://avatars.githubusercontent.com/u/123456789?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-user", "html_url": "https://github.com/github-user", "followers_url": "https://api.github.com/users/github-user/followers", "following_url": "https://api.github.com/users/github-user/following{/other_user}", "gists_url": "https://api.github.com/users/github-user/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-user/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-user/subscriptions", "organizations_url": "https://api.github.com/users/github-user/orgs", "repos_url": "https://api.github.com/users/github-user/repos", "events_url": "https://api.github.com/users/github-user/events{/privacy}", "received_events_url": "https://api.github.com/users/github-user/received_events", "type": "User", "site_admin": false, "name": "Github User", "company": "ACME", "blog": "https://acme.com/", "location": "Planet Earth", "email": "github-user@acme.com", "hireable": null, "bio": null, "twitter_username": null, "public_repos": 12, "public_gists": 3, "followers": 1, "following": 0, "created_at": "2019-10-11T15:46:45Z", "updated_at": "2021-06-21T12:38:04Z" } ================================================ FILE: wsmaster/che-core-api-factory-github/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex ================================================ FILE: wsmaster/che-core-api-factory-github-common/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-factory-github-common jar Che Core :: API :: Factory Resolver Github Common true com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.core jackson-databind com.google.guava guava jakarta.inject jakarta.inject-api jakarta.validation jakarta.validation-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-shared org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-factory org.eclipse.che.core che-core-api-factory-shared org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang org.slf4j slf4j-api ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubFactoryParametersResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static java.util.Collections.emptyMap; import static org.eclipse.che.api.factory.shared.Constants.REVISION_PARAMETER_NAME; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.eclipse.che.dto.server.DtoFactory.newDto; import jakarta.validation.constraints.NotNull; import java.util.Map; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.BaseFactoryParameterResolver; import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.*; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** * Provides Factory Parameters resolver for github repositories. * * @author Florent Benoit */ public abstract class AbstractGithubFactoryParametersResolver extends BaseFactoryParameterResolver implements FactoryParametersResolver { /** Parser which will allow to check validity of URLs and create objects. */ private final AbstractGithubURLParser githubUrlParser; private final URLFetcher urlFetcher; /** Builder allowing to build objects from github URL. */ private final GithubSourceStorageBuilder githubSourceStorageBuilder; private final URLFactoryBuilder urlFactoryBuilder; private final PersonalAccessTokenManager personalAccessTokenManager; private final String providerName; public AbstractGithubFactoryParametersResolver( AbstractGithubURLParser githubUrlParser, URLFetcher urlFetcher, GithubSourceStorageBuilder githubSourceStorageBuilder, AuthorisationRequestManager authorisationRequestManager, URLFactoryBuilder urlFactoryBuilder, PersonalAccessTokenManager personalAccessTokenManager, String providerName) { super(authorisationRequestManager, urlFactoryBuilder, providerName); this.providerName = providerName; this.githubUrlParser = githubUrlParser; this.urlFetcher = urlFetcher; this.githubSourceStorageBuilder = githubSourceStorageBuilder; this.urlFactoryBuilder = urlFactoryBuilder; this.personalAccessTokenManager = personalAccessTokenManager; } /** * Check if this resolver can be used with the given parameters. * * @param factoryParameters map of parameters dedicated to factories * @return true if it will be accepted by the resolver implementation or false if it is not * accepted */ @Override public boolean accept(@NotNull final Map factoryParameters) { // Check if url parameter is a github URL return factoryParameters.containsKey(URL_PARAMETER_NAME) && githubUrlParser.isValid(factoryParameters.get(URL_PARAMETER_NAME)); } @Override public String getProviderName() { return providerName; } /** * Create factory object based on provided parameters * * @param factoryParameters map containing factory data parameters provided through URL * @throws BadRequestException when data are invalid */ @Override public FactoryMetaDto createFactory(@NotNull final Map factoryParameters) throws ApiException { // no need to check null value of url parameter as accept() method has performed the check final GithubUrl githubUrl; if (getSkipAuthorisation(factoryParameters)) { githubUrl = githubUrlParser.parseWithoutAuthentication( factoryParameters.get(URL_PARAMETER_NAME), factoryParameters.get(REVISION_PARAMETER_NAME)); } else { githubUrl = githubUrlParser.parse( factoryParameters.get(URL_PARAMETER_NAME), factoryParameters.get(REVISION_PARAMETER_NAME)); } return createFactory( factoryParameters, githubUrl, new GithubFactoryVisitor(githubUrl), new GithubAuthorizingFileContentProvider( githubUrl, urlFetcher, personalAccessTokenManager)); } /** * Visitor that puts the default devfile or updates devfile projects into the Github Factory, if * needed. */ private class GithubFactoryVisitor implements FactoryVisitor { private final GithubUrl githubUrl; private GithubFactoryVisitor(GithubUrl githubUrl) { this.githubUrl = githubUrl; } @Override public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { ScmInfoDto scmInfo = newDto(ScmInfoDto.class) .withScmProviderName(githubUrl.getProviderName()) .withRepositoryUrl(githubUrl.repositoryLocation()); if (githubUrl.getBranch() != null) { scmInfo.withBranch(githubUrl.getBranch()); } return factoryDto.withScmInfo(scmInfo); } } @Override public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException { if (getSkipAuthorisation(emptyMap())) { return githubUrlParser.parseWithoutAuthentication(factoryUrl, null); } else { return githubUrlParser.parse(factoryUrl, null); } } } ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.util.Arrays; import java.util.Map; import java.util.Optional; import java.util.Set; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.*; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.exception.*; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.security.oauth.OAuthAPI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** GitHub OAuth token retriever. */ public abstract class AbstractGithubPersonalAccessTokenFetcher implements PersonalAccessTokenFetcher { private static final Logger LOG = LoggerFactory.getLogger(AbstractGithubPersonalAccessTokenFetcher.class); private static final String OAUTH_PROVIDER_NAME = "github"; private final String apiEndpoint; private final OAuthAPI oAuthAPI; /** GitHub API client. */ private final GithubApiClient githubApiClient; /** Name of this OAuth provider as found in OAuthAPI. */ private final String providerName; /** Collection of OAuth scopes required to make integration with GitHub work. */ public static final Set DEFAULT_TOKEN_SCOPES = ImmutableSet.of("repo", "user:email", "read:user", "read:org", "workflow"); /** * Map of OAuth GitHub scopes where each key is a scope and its value is the parent scope. The * parent scope includes all of its children scopes. This map is used when determining if a token * has the required scopes. See * https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes */ private static final Map SCOPE_MAP = ImmutableMap.builderWithExpectedSize(35) .put("repo", "repo") .put("repo:status", "repo") .put("repo_deployment", "repo") .put("public_repo", "repo") .put("repo:invite", "repo") .put("security_events", "repo") // .put("workflow", "workflow") // .put("write:packages", "write:packages") .put("read:packages", "write:packages") // .put("delete:packages", "delete:packages") // .put("admin:org", "admin:org") .put("write:org", "admin:org") .put("read:org", "admin:org") // .put("admin:public_key", "admin:public_key") .put("write:public_key", "admin:public_key") .put("read:public_key", "admin:public_key") // .put("admin:repo_hook", "admin:repo_hook") .put("write:repo_hook", "admin:repo_hook") .put("read:repo_hook", "admin:repo_hook") // .put("admin:org_hook", "admin:org_hook") // .put("gist", "gist") // .put("notifications", "notifications") // .put("user", "user") .put("read:user", "user") .put("user:email", "user") .put("user:follow", "user") // .put("delete_repo", "delete_repo") // .put("write:discussion", "write:discussion") .put("read:discussion", "write:discussion") // .put("admin:enterprise", "admin:enterprise") .put("manage_billing:enterprise", "admin:enterprise") .put("read:enterprise", "admin:enterprise") // .put("admin:gpg_key", "admin:gpg_key") .put("write:gpg_key", "admin:gpg_key") .put("read:gpg_key", "admin:gpg_key") .build(); /** * Constructor used for testing only. * * @param apiEndpoint * @param oAuthAPI * @param githubApiClient */ AbstractGithubPersonalAccessTokenFetcher( String apiEndpoint, OAuthAPI oAuthAPI, GithubApiClient githubApiClient, String providerName) { this.apiEndpoint = apiEndpoint; this.oAuthAPI = oAuthAPI; this.githubApiClient = githubApiClient; this.providerName = providerName; } public PersonalAccessToken refreshPersonalAccessToken(Subject cheSubject, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { // Tokens generated via GitHub OAuth app do not have an expiration date, so we don't need to // refresh them. return fetchPersonalAccessToken(cheSubject, scmServerUrl); } @Override public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { OAuthToken oAuthToken; if (githubApiClient == null || !githubApiClient.isConnected(scmServerUrl)) { LOG.debug("not a valid url {} for current fetcher ", scmServerUrl); return null; } try { oAuthToken = oAuthAPI.getOrRefreshToken(providerName); String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5); String tokenId = NameGenerator.generate("id-", 5); Optional> valid = isValid( new PersonalAccessTokenParams( scmServerUrl, // Despite the fact that we may have two GitHub oauth providers, we always set // "github" to the token provider name. The specific GitHub oauth provider // references to the specific token by the url parameter. OAUTH_PROVIDER_NAME, tokenName, tokenId, oAuthToken.getToken(), null)); if (valid.isEmpty()) { throw buildScmUnauthorizedException(cheSubject); } else if (!valid.get().first) { throw new ScmCommunicationException( "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: " + DEFAULT_TOKEN_SCOPES.toString()); } return new PersonalAccessToken( scmServerUrl, OAUTH_PROVIDER_NAME, cheSubject.getUserId(), valid.get().second, tokenName, tokenId, oAuthToken.getToken()); } catch (UnauthorizedException e) { throw buildScmUnauthorizedException(cheSubject); } catch (NotFoundException nfe) { throw new UnknownScmProviderException(nfe.getMessage(), scmServerUrl); } catch (ServerException | ForbiddenException | BadRequestException | ConflictException e) { LOG.error(e.getMessage()); throw new ScmCommunicationException(e.getMessage(), e); } } private ScmUnauthorizedException buildScmUnauthorizedException(Subject cheSubject) { return new ScmUnauthorizedException( cheSubject.getUserName() + " is not authorized in " + this.providerName + " OAuth provider.", this.providerName, "2.0", getLocalAuthenticateUrl()); } @Override @Deprecated public Optional isValid(PersonalAccessToken personalAccessToken) { if (!githubApiClient.isConnected(personalAccessToken.getScmProviderUrl())) { LOG.debug("not a valid url {} for current fetcher ", personalAccessToken.getScmProviderUrl()); return Optional.empty(); } try { if (personalAccessToken.getScmTokenName() != null && personalAccessToken.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { String[] scopes = githubApiClient.getTokenScopes(personalAccessToken.getToken()).second; return Optional.of(containsScopes(scopes, DEFAULT_TOKEN_SCOPES)); } else { // No REST API for PAT-s in Github found yet. Just try to do some action. GithubUser user = githubApiClient.getUser(personalAccessToken.getToken()); if (personalAccessToken.getScmUserName().equals(user.getLogin())) { return Optional.of(Boolean.TRUE); } else { return Optional.of(Boolean.FALSE); } } } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } @Override public Optional> isValid(PersonalAccessTokenParams params) throws ScmCommunicationException { GithubApiClient apiClient; if (githubApiClient.isConnected(params.getScmProviderUrl())) { // The url from the token has the same url as the api client, no need to create a new one. apiClient = githubApiClient; } else { if (OAUTH_PROVIDER_NAME.equals(params.getScmTokenName())) { apiClient = new GithubApiClient(params.getScmProviderUrl()); } else { LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); return Optional.empty(); } } try { // TODO: add PAT scope validation // No way to get scopes from PAT-s or GitHub app tokens found yet. Just try to do some action. GithubUser user = apiClient.getUser(params.getToken()); return Optional.of(Pair.of(Boolean.TRUE, user.getLogin())); } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.empty(); } } /** * Checks if the tokenScopes array contains the requiredScopes. * * @param tokenScopes Scopes from token * @param requiredScopes Mandatory scopes * @return If all mandatory scopes are contained in the token's scopes */ boolean containsScopes(String[] tokenScopes, Set requiredScopes) { Arrays.sort(tokenScopes); // We need check that the token has the required minimal scopes. The scopes can be normalized // by GitHub, so we need to be careful for sub-scopes being included in parent scopes. for (String requiredScope : requiredScopes) { String parentScope = SCOPE_MAP.get(requiredScope); if (parentScope == null) { // requiredScope is not recognized as a GitHub scope, so just skip it. continue; } if (Arrays.binarySearch(tokenScopes, parentScope) < 0 && Arrays.binarySearch(tokenScopes, requiredScope) < 0) { return false; } } return true; } private String getLocalAuthenticateUrl() { return apiEndpoint + "/oauth/authenticate?oauth_provider=" + providerName + "&scope=" + Joiner.on(',').join(DEFAULT_TOKEN_SCOPES) + "&request_method=POST&signature_method=rsa"; } } ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubScmFileResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; import jakarta.validation.constraints.NotNull; import java.io.IOException; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.factory.server.ScmFileResolver; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** Github specific SCM file resolver. */ public abstract class AbstractGithubScmFileResolver implements ScmFileResolver { private final AbstractGithubURLParser githubUrlParser; private final URLFetcher urlFetcher; private final PersonalAccessTokenManager personalAccessTokenManager; public AbstractGithubScmFileResolver( AbstractGithubURLParser githubUrlParser, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { this.githubUrlParser = githubUrlParser; this.urlFetcher = urlFetcher; this.personalAccessTokenManager = personalAccessTokenManager; } @Override public boolean accept(@NotNull String repository) { // Check if repository parameter is a github URL return githubUrlParser.isValid(repository); } @Override public String fileContent(@NotNull String repository, @NotNull String filePath) throws ApiException { final GithubUrl githubUrl = githubUrlParser.parse(repository, null); try { return fetchContent(githubUrl, filePath, false); } catch (DevfileException exception) { // This catch might mean that the authentication was rejected by user, try to repeat the fetch // without authentication flow. try { return fetchContent(githubUrl, filePath, true); } catch (DevfileException devfileException) { throw toApiException(devfileException); } } } private String fetchContent(GithubUrl githubUrl, String filePath, boolean skipAuthentication) throws DevfileException, NotFoundException { try { GithubAuthorizingFileContentProvider contentProvider = new GithubAuthorizingFileContentProvider( githubUrl, urlFetcher, personalAccessTokenManager); return skipAuthentication ? contentProvider.fetchContentWithoutAuthentication(filePath) : contentProvider.fetchContent(filePath); } catch (IOException e) { throw new NotFoundException(e.getMessage()); } } } ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.regex.Pattern.compile; import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; import static org.eclipse.che.api.factory.server.github.GithubApiClient.GITHUB_SAAS_ENDPOINT; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import jakarta.validation.constraints.NotNull; import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.*; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Parser of String Github URLs and provide {@link GithubUrl} objects. * * @author Florent Benoit */ public abstract class AbstractGithubURLParser { private static final Logger LOG = LoggerFactory.getLogger(AbstractGithubURLParser.class); private final PersonalAccessTokenManager tokenManager; private final DevfileFilenamesProvider devfileFilenamesProvider; private final GithubApiClient apiClient; /** * Regexp to find repository details (repository name, project name and branch and subfolder) * Examples of valid URLs are in the test class. */ private final Pattern githubPattern; private final String githubPatternTemplate = "%s/(?[^/]+)/(?[^/]++)((/)|(?:/((tree)|(blob))/(?.++))|(/pull/(?\\d++)))?$"; private final Pattern githubSSHPattern; private final String githubSSHPatternTemplate = "^git@%s:(?.*)/(?.*)$"; private final boolean disableSubdomainIsolation; private final String providerName; private final String endpoint; private final boolean isGitHubServer; /** Constructor used for testing only. */ AbstractGithubURLParser( PersonalAccessTokenManager tokenManager, DevfileFilenamesProvider devfileFilenamesProvider, GithubApiClient githubApiClient, String oauthEndpoint, boolean disableSubdomainIsolation, String providerName) { this.tokenManager = tokenManager; this.devfileFilenamesProvider = devfileFilenamesProvider; this.apiClient = githubApiClient; this.disableSubdomainIsolation = disableSubdomainIsolation; this.providerName = providerName; // Check if the given OAuth endpoint is a GitHub server URL. If the OAuth endpoint is not // defined, or it equals the GitHub SaaS endpoint, it means that the given URL is a GitHub SaaS // URL (https://github.com). Otherwise, the given URL is a GitHub server URL. this.isGitHubServer = !isNullOrEmpty(oauthEndpoint) && !GITHUB_SAAS_ENDPOINT.equals(oauthEndpoint); endpoint = isNullOrEmpty(oauthEndpoint) ? GITHUB_SAAS_ENDPOINT : trimEnd(oauthEndpoint, '/'); this.githubPattern = compile(format(githubPatternTemplate, endpoint)); this.githubSSHPattern = compile(format(githubSSHPatternTemplate, URI.create(endpoint).getHost())); } // Check if the given URL is a valid GitHub URL. public boolean isValid(@NotNull String url) { String trimmedUrl = trimEnd(url, '/'); return // Check if the given URL matches the GitHub URL patterns. It works if OAuth is configured and // GitHub server URL is known or the repository URL points to GitHub SaaS (https://github.com). githubPattern.matcher(trimmedUrl).matches() || githubSSHPattern.matcher(trimmedUrl).matches() // Check whether PAT is configured for the GitHub server URL. It is sufficient to confirm // that the URL is a valid GitHub URL. || isUserTokenPresent(trimmedUrl) // Check if the given URL is a valid GitHub URL by reaching the endpoint of the GitHub // server and analysing the response. This query basically only needs to be performed if the // specified repository URL does not point to GitHub SaaS. || (!isGitHubServer && isApiRequestRelevant(trimmedUrl)); } // Try to find the given url in a manually added user namespace token secret. private boolean isUserTokenPresent(String repositoryUrl) { Optional serverUrlOptional = getServerUrl(repositoryUrl); if (serverUrlOptional.isPresent()) { String serverUrl = serverUrlOptional.get(); try { Optional token = tokenManager.get(EnvironmentContext.getCurrent().getSubject(), null, serverUrl, null); if (token.isPresent()) { PersonalAccessToken accessToken = token.get(); return accessToken.getScmTokenName().equals(providerName); } } catch (ScmConfigurationPersistenceException | ScmCommunicationException exception) { return false; } } return false; } // Try to call an API request to see if the given url matches self-hosted GitHub Enterprise. private boolean isApiRequestRelevant(String repositoryUrl) { Optional serverUrlOptional = getServerUrl(repositoryUrl); if (serverUrlOptional.isPresent()) { String serverUrl = serverUrlOptional.get(); GithubApiClient githubApiClient = new GithubApiClient(serverUrl); try { // If the user request catches the unauthorised error, it means that the provided url // belongs to GitHub. githubApiClient.getUser(""); } catch (ScmUnauthorizedException e) { // Check the error message as well, because other providers might also return 401 // for such requests. return e.getMessage().contains("Requires authentication") || // for older GitHub Enterprise versions e.getMessage().contains("Must authenticate to access this API."); } catch (ScmItemNotFoundException e) { // GitHub API v3 (/api/v3/user) returned 404 — this server does not expose the GitHub // v3 API path. Fall back to checking the Gitea-compatible API (/api/v1/user): if that // endpoint returns 401 the server is a GitHub-compatible instance (e.g. Gitea). return isGiteaCompatibleServer(serverUrl); } catch (ScmBadRequestException | IllegalArgumentException | ScmCommunicationException e) { return false; } } return false; } /** * Returns {@code true} when the server at {@code serverUrl} exposes a Gitea-style GitHub * compatible API ({@code /api/v1/user}) and requires authentication (HTTP 401). */ private boolean isGiteaCompatibleServer(String serverUrl) { try { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(serverUrl + "/api/v1/user")) .timeout(Duration.ofSeconds(10)) .GET() .build(); HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.discarding()); return response.statusCode() == 401; } catch (Exception e) { LOG.debug("Failed to reach Gitea API at {}: {}", serverUrl, e.getMessage()); return false; } } private Optional getServerUrl(String repositoryUrl) { // If the given repository url is an SSH url, generate the base url from the pattern: // https://. if (repositoryUrl.startsWith("git@")) { String substring = repositoryUrl.substring(4); // Handle IPv6 addresses in SSH URLs (e.g., git@[2001:db8::1]:repo) if (substring.startsWith("[")) { int closingBracket = substring.indexOf(']'); if (closingBracket > 0) { return Optional.of("https://" + substring.substring(0, closingBracket + 1)); } } return Optional.of("https://" + substring.substring(0, substring.indexOf(":"))); } // Use URI parsing to properly handle IPv6 addresses try { URI uri = URI.create(repositoryUrl); if (uri.getScheme() != null && uri.getHost() != null) { String authority = uri.getRawAuthority(); if (authority == null) { String host = uri.getHost(); boolean ipv6 = host != null && host.contains(":"); String hostForUrl = ipv6 ? "[" + host + "]" : host; int port = uri.getPort(); authority = port == -1 ? hostForUrl : hostForUrl + ":" + port; } if (authority != null) { String serverUrl = uri.getScheme() + "://" + authority; // Remove path and query from the server URL int authorityIdx = repositoryUrl.indexOf(authority); if (authorityIdx >= 0) { int pathIndex = authorityIdx + authority.length(); if (pathIndex < repositoryUrl.length() && repositoryUrl.charAt(pathIndex) == '/') { return Optional.of(serverUrl); } } } } } catch (IllegalArgumentException e) { // Fall through to old logic if URI parsing fails } // Otherwise, extract the base url from the given repository url by cutting the url after the // first slash. Matcher serverUrlMatcher = compile("[^/|:]/").matcher(repositoryUrl); if (serverUrlMatcher.find()) { return Optional.of( repositoryUrl.substring(0, repositoryUrl.indexOf(serverUrlMatcher.group()) + 1)); } return Optional.empty(); } public GithubUrl parseWithoutAuthentication(String url, @Nullable String revision) throws ApiException { return parse(trimEnd(url, '/'), false, revision); } public GithubUrl parse(String url, @Nullable String revision) throws ApiException { return parse(trimEnd(url, '/'), true, revision); } private IllegalArgumentException buildIllegalArgumentException(String url) { return new IllegalArgumentException( format("The given url %s is not a valid github URL. ", url)); } private GithubUrl parse(String url, boolean authenticationRequired, @Nullable String revision) throws ApiException { Matcher matcher; boolean isHTTPSUrl = githubPattern.matcher(url).matches(); if (isHTTPSUrl) { matcher = githubPattern.matcher(url); } else if (githubSSHPattern.matcher(url).matches()) { matcher = githubSSHPattern.matcher(url); } else { matcher = getPatternMatcherByUrl(url).orElseThrow(() -> buildIllegalArgumentException(url)); isHTTPSUrl = url.startsWith("http"); } if (!matcher.matches()) { throw buildIllegalArgumentException(url); } String serverUrl = getServerUrl(url).orElseThrow(() -> buildIllegalArgumentException(url)); String repoUser = matcher.group("repoUser"); String repoName = matcher.group("repoName"); if (repoName.matches("^[\\w-][\\w.-]*?\\.git$")) { repoName = repoName.substring(0, repoName.length() - 4); } String branchFromUrl = null; String pullRequestId = null; if (isHTTPSUrl) { branchFromUrl = getBranch(matcher.group("path")); pullRequestId = matcher.group("pullRequestId"); } if (pullRequestId != null) { GithubPullRequest pullRequest = this.getPullRequest(serverUrl, pullRequestId, repoUser, repoName, authenticationRequired); if (pullRequest != null) { String state = pullRequest.getState(); if (!"open".equalsIgnoreCase(state)) { throw new IllegalArgumentException( format( "The given Pull Request url %s is not Opened, (found %s), thus it can't be opened as branch may have been removed.", url, state)); } GithubHead pullRequestHead = pullRequest.getHead(); repoUser = pullRequestHead.getUser().getLogin(); repoName = pullRequestHead.getRepo().getName(); branchFromUrl = pullRequestHead.getRef(); } } String latestCommit = null; GithubCommit commit = getLatestCommit( serverUrl, repoUser, repoName, isNullOrEmpty(revision) ? firstNonNull(branchFromUrl, "HEAD") : revision, authenticationRequired); if (commit != null) { latestCommit = commit.getSha(); } return new GithubUrl(providerName) .withUsername(repoUser) .withRepository(repoName) .setIsHTTPSUrl(isHTTPSUrl) .withServerUrl(serverUrl) .withDisableSubdomainIsolation(disableSubdomainIsolation) .withBranch(isNullOrEmpty(branchFromUrl) ? revision : branchFromUrl) .withLatestCommit(latestCommit) .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .withUrl(url); } /* * Get branch from the file path by extracting the segment before the devfile name. * Example of a file path: "branch/devfile.yaml" => "branch" */ private String getBranch(String path) { if (path != null && path.contains("/")) { Optional devfileNameOptional = devfileFilenamesProvider.getConfiguredDevfileFilenames().stream() .filter(devfileName -> path.endsWith("/" + devfileName)) .findAny(); return devfileNameOptional.map(s -> path.substring(0, path.indexOf(s) - 1)).orElse(path); } return path; } private GithubPullRequest getPullRequest( String githubEndpoint, String pullRequestId, String repoUser, String repoName, boolean authenticationRequired) throws ApiException { try { // prepare token Subject subject = EnvironmentContext.getCurrent().getSubject(); PersonalAccessToken personalAccessToken = null; Optional token = tokenManager.get(subject, null, githubEndpoint, null); if (token.isPresent()) { personalAccessToken = token.get(); } else if (authenticationRequired) { personalAccessToken = tokenManager.fetchAndSave(subject, githubEndpoint); } GithubApiClient apiClient = this.apiClient.isConnected(githubEndpoint) ? this.apiClient : new GithubApiClient(githubEndpoint); // get pull request return apiClient.getPullRequest( pullRequestId, repoUser, repoName, personalAccessToken != null ? personalAccessToken.getToken() : null); } catch (UnknownScmProviderException e) { // get pull request without authentication try { return apiClient.getPullRequest(pullRequestId, repoUser, repoName, null); } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException | ScmUnauthorizedException exception) { LOG.error("Failed to authenticate to GitHub", e); } } catch (ScmUnauthorizedException e) { throw toApiException(e); } catch (ScmCommunicationException | UnsatisfiedScmPreconditionException | ScmConfigurationPersistenceException e) { LOG.error("Failed to authenticate to GitHub", e); } catch (ScmItemNotFoundException | ScmBadRequestException e) { LOG.error("Failed retrieve GitHub Pull Request", e); } return null; } private GithubCommit getLatestCommit( String githubEndpoint, String repoUser, String repoName, String branchName, boolean authenticationRequired) { GithubApiClient apiClient = this.apiClient.isConnected(githubEndpoint) ? this.apiClient : new GithubApiClient(githubEndpoint); try { // prepare token Subject subject = EnvironmentContext.getCurrent().getSubject(); PersonalAccessToken personalAccessToken = null; Optional token = tokenManager.get(subject, null, githubEndpoint, null); if (token.isPresent()) { personalAccessToken = token.get(); } else if (authenticationRequired) { personalAccessToken = tokenManager.fetchAndSave(subject, githubEndpoint); } // get latest commit return apiClient.getLatestCommit( repoUser, repoName, branchName, personalAccessToken != null ? personalAccessToken.getToken() : null); } catch (UnknownScmProviderException | ScmUnauthorizedException e) { // get latest commit without authentication try { return apiClient.getLatestCommit(repoUser, repoName, branchName, null); } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException | URISyntaxException | ScmUnauthorizedException exception) { LOG.error("Failed to authenticate to GitHub", e); } } catch (ScmCommunicationException | UnsatisfiedScmPreconditionException | ScmConfigurationPersistenceException e) { LOG.error("Failed to authenticate to GitHub", e); } catch (ScmItemNotFoundException | ScmBadRequestException | URISyntaxException e) { LOG.error("Failed to retrieve the latest commit", e); e.printStackTrace(); } return null; } /** * Converts an SSH git URL (git@host:path) to a parseable URI (ssh://git@host/path), handling IPv6 * addresses where colons in the address must not be replaced. */ private static String sshToUri(String url) { // Strip "git@" prefix String afterAt = url.substring(4); if (afterAt.startsWith("[")) { // IPv6: git@[2001:db8::1]:path -> ssh://git@[2001:db8::1]/path int closingBracket = afterAt.indexOf(']'); if (closingBracket >= 0 && closingBracket + 1 < afterAt.length()) { String host = afterAt.substring(0, closingBracket + 1); // Skip the colon separator after the closing bracket String path = afterAt.substring(closingBracket + 1); if (path.startsWith(":")) { path = path.substring(1); } return "ssh://git@" + host + "/" + path; } } // Regular hostname: git@host:path -> ssh://git@host/path return "ssh://" + url.replace(":", "/"); } private Optional getPatternMatcherByUrl(String url) { URI uri = URI.create(url.matches(format(githubSSHPatternTemplate, ".*")) ? sshToUri(url) : url); String scheme = uri.getScheme(); String host = uri.getHost(); // IPv6 addresses are returned WITH brackets, e.g. "[fd00::1]" // uri.getRawAuthority() already contains the correct authority string (including brackets // for IPv6 and the port number), so we can quote it directly to build a literal-match regex. String rawAuthority = uri.getRawAuthority(); final String hostForRegex; if (rawAuthority != null) { hostForRegex = Pattern.quote(scheme + "://" + rawAuthority); } else { // Fallback: build from host + port String authority = host != null ? host : ""; if (uri.getPort() > 0) { authority += ":" + uri.getPort(); } hostForRegex = Pattern.quote(scheme + "://" + authority); } Matcher matcher = compile(format(githubPatternTemplate, hostForRegex)).matcher(url); if (matcher.matches()) { return Optional.of(matcher); } else { // For SSH git@ URLs the host is used without brackets even for IPv6. final String hostOnlyForRegex; if (host != null && host.startsWith("[") && host.endsWith("]")) { // Strip brackets that uri.getHost() includes for IPv6. hostOnlyForRegex = Pattern.quote(host.substring(1, host.length() - 1)); } else { hostOnlyForRegex = host != null ? Pattern.quote(host) : ""; } matcher = compile(format(githubSSHPatternTemplate, hostOnlyForRegex)).matcher(url); return matcher.matches() ? Optional.of(matcher) : Optional.empty(); } } } ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import java.util.Set; import org.eclipse.che.api.factory.server.scm.AbstractGitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.GitUserData; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; /** GitHub user data retriever. */ public abstract class AbstractGithubUserDataFetcher extends AbstractGitUserDataFetcher { private final String apiEndpoint; /** GitHub API client. */ private final GithubApiClient githubApiClient; /** Name of this OAuth provider as found in OAuthAPI. */ private final String providerName; /** Collection of OAuth scopes required to make integration with GitHub work. */ public static final Set DEFAULT_TOKEN_SCOPES = ImmutableSet.of("repo", "user:email", "read:user"); private static final String NO_USERNAME_AND_EMAIL_ERROR_MESSAGE = "User name and/or email is not found in the GitHub profile."; /** Constructor used for testing only. */ public AbstractGithubUserDataFetcher( String apiEndpoint, PersonalAccessTokenManager personalAccessTokenManager, GithubApiClient githubApiClient, String providerName) { super(providerName, githubApiClient.getServerUrl(), personalAccessTokenManager); this.providerName = providerName; this.githubApiClient = githubApiClient; this.apiEndpoint = apiEndpoint; } @Override protected GitUserData fetchGitUserDataWithOAuthToken(String token) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { GithubUser user = githubApiClient.getUser(token); if (isNullOrEmpty(user.getName()) || isNullOrEmpty(user.getEmail())) { throw new ScmItemNotFoundException(NO_USERNAME_AND_EMAIL_ERROR_MESSAGE); } else { return new GitUserData(user.getName(), user.getEmail()); } } @Override protected GitUserData fetchGitUserDataWithPersonalAccessToken( PersonalAccessToken personalAccessToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { GithubApiClient apiClient = githubApiClient.isConnected(personalAccessToken.getScmProviderUrl()) ? githubApiClient : new GithubApiClient(personalAccessToken.getScmProviderUrl()); GithubUser user = apiClient.getUser(personalAccessToken.getToken()); if (isNullOrEmpty(user.getName()) || isNullOrEmpty(user.getEmail())) { throw new ScmItemNotFoundException(NO_USERNAME_AND_EMAIL_ERROR_MESSAGE); } else { return new GitUserData(user.getName(), user.getEmail()); } } protected String getLocalAuthenticateUrl() { return apiEndpoint + "/oauth/authenticate?oauth_provider=" + providerName + "&scope=" + Joiner.on(',').join(DEFAULT_TOKEN_SCOPES) + "&request_method=POST&signature_method=rsa"; } } ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static com.google.common.base.Strings.isNullOrEmpty; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.time.Duration.ofSeconds; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Charsets; import com.google.common.base.Splitter; import com.google.common.io.CharStreams; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.Optional; import java.util.concurrent.Executors; import java.util.function.Function; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** GitHub API operations helper. */ public class GithubApiClient { private static final Logger LOG = LoggerFactory.getLogger(GithubApiClient.class); /** GitHub endpoint URL. */ public static final String GITHUB_SAAS_ENDPOINT = "https://github.com"; public static final String GITHUB_SAAS_ENDPOINT_API = "https://api.github.com"; public static final String GITHUB_SAAS_ENDPOINT_RAW = "https://raw.githubusercontent.com"; /** GitHub HTTP header containing OAuth scopes. */ public static final String GITHUB_OAUTH_SCOPES_HEADER = "X-OAuth-Scopes"; private final HttpClient httpClient; private final URI apiServerUrl; private final URI scmServerUrl; private static final Duration DEFAULT_HTTP_TIMEOUT = ofSeconds(10); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); /** Default constructor, binds http client to GitHub API url */ public GithubApiClient(@Nullable String serverUrl) { String trimmedServerUrl = !isNullOrEmpty(serverUrl) ? trimEnd(serverUrl, '/') : null; this.apiServerUrl = URI.create( isNullOrEmpty(trimmedServerUrl) || trimmedServerUrl.equals(GITHUB_SAAS_ENDPOINT) ? GITHUB_SAAS_ENDPOINT_API + "/" : trimmedServerUrl + "/api/v3/"); this.scmServerUrl = URI.create(isNullOrEmpty(trimmedServerUrl) ? GITHUB_SAAS_ENDPOINT : trimmedServerUrl); this.httpClient = HttpClient.newBuilder() .executor( Executors.newCachedThreadPool( new ThreadFactoryBuilder() .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setNameFormat(GithubApiClient.class.getName() + "-%d") .setDaemon(true) .build())) .connectTimeout(DEFAULT_HTTP_TIMEOUT) .version(HttpClient.Version.HTTP_1_1) .build(); } /** * Returns the user associated with the provided OAuth access token. * * @see https://docs.github.com/en/rest/reference/users#get-the-authenticated-user * @param authenticationToken OAuth access token used by the user. * @return Information about the user associated with the token * @throws ScmItemNotFoundException * @throws ScmCommunicationException * @throws ScmBadRequestException */ public GithubUser getUser(String authenticationToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("./user"); HttpRequest request = buildGithubApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); return executeRequest( httpClient, request, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); return OBJECT_MAPPER.readValue(result, GithubUser.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } /** * Fetches and returns a pull request. * * @param id pull request ID * @param username user name * @param repoName repository name * @param authenticationToken oauth access token, can be NULL as the GitHub can handle some * requests without authentication * @return pull request */ public GithubPullRequest getPullRequest( String id, String username, String repoName, String authenticationToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { final URI uri = apiServerUrl.resolve(String.format("./repos/%s/%s/pulls/%s", username, repoName, id)); HttpRequest request = buildGithubApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); return executeRequest( httpClient, request, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); return OBJECT_MAPPER.readValue(result, GithubPullRequest.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } /** * Returns the latest commit of the branch. * *

    GitHub REST API documentation: https://docs.github.com/en/rest/commits/commits * * @param user user or organization name * @param repository repository name * @param branch required branch * @param authenticationToken OAuth access token, can be NULL as the GitHub can handle some * requests without authentication * @return the latest commit of the branch */ public GithubCommit getLatestCommit( String user, String repository, String branch, @Nullable String authenticationToken) throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, URISyntaxException, ScmUnauthorizedException { final URI uri = apiServerUrl.resolve(String.format("./repos/%s/%s/commits", user, repository)); final URI requestURI = new URI( uri.getScheme(), uri.getAuthority(), uri.getPath(), String.format("sha=%s&page=1&per_page=1", branch), null); HttpRequest request = buildGithubApiRequest(requestURI, authenticationToken); LOG.trace("executeRequest={}", request); return executeRequest( httpClient, request, response -> { try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); return OBJECT_MAPPER.readValue(result, GithubCommit[].class)[0]; } catch (IOException e) { throw new UncheckedIOException(e); } }); } /** * Returns the scopes of the OAuth token. * *

    See GitHub documentation at * https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps * * @param authenticationToken The OAuth token to inspect. * @return Array of scopes from the supplied token, empty array if no scope. * @throws ScmItemNotFoundException * @throws ScmCommunicationException * @throws ScmBadRequestException */ public Pair getTokenScopes(String authenticationToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("./user"); HttpRequest request = buildGithubApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); return executeRequest( httpClient, request, response -> { Optional responseScopes = response.headers().firstValue(GITHUB_OAUTH_SCOPES_HEADER); String[] scopes = Splitter.on(',') .trimResults() .omitEmptyStrings() .splitToList(responseScopes.orElse("")) .toArray(String[]::new); try { String result = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); GithubUser user = OBJECT_MAPPER.readValue(result, GithubUser.class); return Pair.of(user.getLogin(), scopes); } catch (IOException e) { throw new UncheckedIOException(e); } }); } /** Returns the GitHub endpoint URL. */ public String getServerUrl() { return this.scmServerUrl.toString(); } /** * Builds and returns HttpRequest to acces the GitHub API. * * @param uri request uri * @param authenticationToken authentication token, can be NULL as the GitHub can handle some * requests without authentication * @return HttpRequest object */ private HttpRequest buildGithubApiRequest(URI uri, @Nullable String authenticationToken) { if (isNullOrEmpty(authenticationToken)) { return HttpRequest.newBuilder(uri) .headers("Accept", "application/vnd.github.v3+json") .timeout(DEFAULT_HTTP_TIMEOUT) .build(); } else { return HttpRequest.newBuilder(uri) .headers( "Authorization", "token " + authenticationToken, "Accept", "application/vnd.github.v3+json") .timeout(DEFAULT_HTTP_TIMEOUT) .build(); } } private T executeRequest( HttpClient httpClient, HttpRequest request, Function, T> responseConverter) throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { String provider = GITHUB_SAAS_ENDPOINT.equals(getServerUrl()) ? "github" : "github-server"; try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); int statusCode = response.statusCode(); LOG.trace("executeRequest={} response {}", request, statusCode); if (statusCode == HTTP_OK) { return responseConverter.apply(response); } else if (statusCode == HTTP_NO_CONTENT) { return null; } else { String body = response.body() == null ? "Unrecognised error" : CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); switch (statusCode) { case HTTP_BAD_REQUEST: throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); case HTTP_UNAUTHORIZED: throw new ScmUnauthorizedException(body, "github", "v2", ""); default: throw new ScmCommunicationException( "Unexpected status code " + statusCode + " " + body, statusCode, provider); } } } catch (IOException | InterruptedException | UncheckedIOException e) { throw new ScmCommunicationException(e.getMessage(), e, provider); } } /** * Checks if the provided url belongs to this client (GitHub) * * @param scmServerUrl the SCM url to verify * @return If the provided url is recognized by the current client */ public boolean isConnected(String scmServerUrl) { return this.scmServerUrl.equals(URI.create(trimEnd(scmServerUrl, '/'))); } } ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubAuthorizingFileContentProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import java.io.IOException; import org.eclipse.che.api.factory.server.scm.AuthorizingFileContentProvider; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** Github specific authorizing file content provider. */ class GithubAuthorizingFileContentProvider extends AuthorizingFileContentProvider { GithubAuthorizingFileContentProvider( GithubUrl githubUrl, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { super(githubUrl, urlFetcher, personalAccessTokenManager); } /** * Formatting OAuth token as HTTP Authorization header. * *

    GitHub Authorization HTTP header format is described here: * https://docs.github.com/en/rest/overview/resources-in-the-rest-api#oauth2-token-sent-in-a-header */ @Override protected String formatAuthorization(String token, boolean isPAT) { return "token " + token; } @Override protected boolean isPublicRepository(GithubUrl remoteFactoryUrl) { try { urlFetcher.fetch( remoteFactoryUrl.getHostName() + '/' + remoteFactoryUrl.getUsername() + '/' + remoteFactoryUrl.getRepository()); return true; } catch (IOException e) { return false; } } } ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubCommit.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) public class GithubCommit { private String sha; private String url; public String getSha() { return sha; } public void setSha(String sha) { this.sha = sha; } public GithubCommit withSha(String sha) { this.sha = sha; return this; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public GithubCommit withUrl(String url) { this.url = url; return this; } } ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubPullRequest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) class GithubRepo { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public GithubRepo withName(String name) { this.name = name; return this; } } @JsonIgnoreProperties(ignoreUnknown = true) class GithubHead { private String ref; private GithubUser user; private GithubRepo repo; public String getRef() { return ref; } public void setRef(String ref) { this.ref = ref; } public GithubHead withRef(String ref) { this.ref = ref; return this; } public GithubUser getUser() { return user; } public void setUser(GithubUser user) { this.user = user; } public GithubHead withUser(GithubUser user) { this.user = user; return this; } public GithubRepo getRepo() { return repo; } public void setRepo(GithubRepo repo) { this.repo = repo; } public GithubHead withRepo(GithubRepo repo) { this.repo = repo; return this; } } @JsonIgnoreProperties(ignoreUnknown = true) public class GithubPullRequest { private String state; private GithubHead head; public String getState() { return state; } public void setState(String state) { this.state = state; } public GithubPullRequest withState(String state) { this.state = state; return this; } public GithubHead getHead() { return head; } public void setHead(GithubHead head) { this.head = head; } public GithubPullRequest withHead(GithubHead head) { this.head = head; return this; } } ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubSourceStorageBuilder.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static org.eclipse.che.dto.server.DtoFactory.newDto; import java.util.HashMap; import java.util.Map; import javax.inject.Singleton; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; /** * Create {@link ProjectConfigDto} object from objects * * @author Florent Benoit */ @Singleton public class GithubSourceStorageBuilder { /** * Create SourceStorageDto DTO by using data of a github url * * @param githubUrl an instance of {@link GithubUrl} * @return newly created source storage DTO object */ public SourceStorageDto buildWorkspaceConfigSource(GithubUrl githubUrl) { // Create map for source storage dto Map parameters = new HashMap<>(2); parameters.put("branch", githubUrl.getBranch()); return newDto(SourceStorageDto.class) .withLocation(githubUrl.repositoryLocation()) .withType("github") .withParameters(parameters); } /** * Create SourceStorageDto DTO by using data of a github url * * @param githubUrl an instance of {@link GithubUrl} * @return newly created source DTO object */ public SourceDto buildDevfileSource(GithubUrl githubUrl) { return newDto(SourceDto.class) .withLocation(githubUrl.repositoryLocation()) .withType("github") .withBranch(githubUrl.getBranch()); } } ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import static com.google.common.base.Strings.isNullOrEmpty; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.StringJoiner; import java.util.stream.Collectors; import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; /** * Representation of a github URL, allowing to get details from it. * *

    like https://github.com// * https://github.com///tree/ * * @author Florent Benoit */ public class GithubUrl extends DefaultFactoryUrl { private final String providerName; private static final String HOSTNAME = "https://github.com"; /** Username part of github URL */ private String username; private boolean isHTTPSUrl = true; /** Repository part of the URL. */ private String repository; /** Branch name */ private String branch; /** SHA of the latest commit in the current branch */ private String latestCommit; private String serverUrl; private boolean disableSubdomainIsolation; /** Devfile filenames list */ private final List devfileFilenames = new ArrayList<>(); /** * Creation of this instance is made by the parser so user may not need to create a new instance * directly */ protected GithubUrl(String providerName) { this.providerName = providerName; } @Override public String getProviderName() { return providerName; } /** * Gets username of this github url * * @return the username part */ public String getUsername() { return this.username; } public GithubUrl withUsername(String userName) { this.username = userName; return this; } public GithubUrl setIsHTTPSUrl(boolean isHTTPSUrl) { this.isHTTPSUrl = isHTTPSUrl; return this; } /** * Gets repository of this github url * * @return the repository part */ public String getRepository() { return this.repository; } protected GithubUrl withRepository(String repository) { this.repository = repository; return this; } protected GithubUrl withDevfileFilenames(List devfileFilenames) { this.devfileFilenames.addAll(devfileFilenames); return this; } @Override public void setDevfileFilename(String devfileName) { this.devfileFilenames.clear(); this.devfileFilenames.add(devfileName); } /** * Gets branch of this github url * * @return the branch part */ public String getBranch() { return this.branch; } protected GithubUrl withBranch(String branch) { if (!isNullOrEmpty(branch)) { this.branch = branch; } return this; } /** * Retuna SHA of the latest commimt * * @return latest commit SHA */ public String getLatestCommit() { return this.latestCommit; } /** * Sets SHA of the latest commit * * @param latestCommit latest commit SHA */ protected GithubUrl withLatestCommit(String latestCommit) { if (!isNullOrEmpty(latestCommit)) { this.latestCommit = latestCommit; } return this; } public GithubUrl withServerUrl(String serverUrl) { this.serverUrl = serverUrl; return this; } public GithubUrl withDisableSubdomainIsolation(boolean disableSubdomainIsolation) { this.disableSubdomainIsolation = disableSubdomainIsolation; return this; } /** * Provides list of configured devfile filenames with locations * * @return list of devfile filenames and locations */ @Override public List devfileFileLocations() { return devfileFilenames.stream().map(this::createDevfileLocation).collect(Collectors.toList()); } private DevfileLocation createDevfileLocation(String devfileFilename) { return new DevfileLocation() { @Override public Optional filename() { return Optional.of(devfileFilename); } @Override public String location() { return rawFileLocation(devfileFilename); } }; } /** * Provides location to raw content of specified file * * @return location of specified file in a repository */ public String rawFileLocation(String fileName) { String branchName = latestCommit != null ? latestCommit : branch != null ? branch : "HEAD"; final String rawBaseUrl; if (HOSTNAME.equals(serverUrl)) { rawBaseUrl = "https://raw.githubusercontent.com"; } else { // IPv6 addresses (recognised by "://[") cannot carry a "raw." subdomain prefix. // Treat them the same as servers with subdomain isolation disabled. boolean isIPv6 = serverUrl.contains("://["); if (disableSubdomainIsolation || isIPv6) { rawBaseUrl = serverUrl + "/raw"; } else { rawBaseUrl = serverUrl.substring(0, serverUrl.indexOf("://") + 3) + "raw." + serverUrl.substring(serverUrl.indexOf("://") + 3); } } return new StringJoiner("/") .add(rawBaseUrl) .add(username) .add(repository) .add(branchName) .add(fileName) .toString(); } @Override public String getHostName() { // TODO: rework this method to return hostname in format https:///user/repo return serverUrl; } /** * Provides location to the repository part of the full github URL. * * @return location of the repository. */ protected String repositoryLocation() { if (isHTTPSUrl) { return serverUrl + "/" + this.username + "/" + this.repository + ".git"; } return "git@" + getHostName().substring(getHostName().indexOf("://") + 3) + ":" + this.username + "/" + this.repository + ".git"; } } ================================================ FILE: wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubUser.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.github; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Objects; @JsonIgnoreProperties(ignoreUnknown = true) public class GithubUser { private long id; private String login; private String email; private String name; public long getId() { return id; } public void setId(long id) { this.id = id; } public GithubUser withId(long id) { this.id = id; return this; } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public GithubUser withLogin(String login) { this.login = login; return this; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public GithubUser withEmail(String email) { this.email = email; return this; } public String getName() { return name; } public void setName(String name) { this.name = name; } public GithubUser withName(String name) { this.name = name; return this; } @Override public String toString() { return "GithubUser{" + "id=" + id + ", login='" + login + '\'' + ", email='" + email + '\'' + ", name='" + name + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } GithubUser that = (GithubUser) o; return id == that.id && Objects.equals(login, that.login) && Objects.equals(email, that.email) && Objects.equals(name, that.name); } @Override public int hashCode() { return Objects.hash(id, login, email, name); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-factory-gitlab jar Che Core :: API :: Factory Resolver Gitlab com.google.inject guice jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-factory org.eclipse.che.core che-core-api-factory-gitlab-common org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-commons-annotations jakarta.servlet jakarta.servlet-api provided org.eclipse.che.core che-core-api-core provided org.eclipse.che.core che-core-commons-lang provided ch.qos.logback logback-classic test com.github.tomakehurst wiremock-jre8-standalone test com.google.guava guava test jakarta.ws.rs jakarta.ws.rs-api test org.eclipse.che.core che-core-api-auth-shared test org.eclipse.che.core che-core-api-dto test org.eclipse.che.core che-core-api-factory-shared test org.eclipse.che.core che-core-api-model test org.eclipse.che.core che-core-commons-json test org.mockito mockito-core test org.mockito mockito-testng test org.slf4j jcl-over-slf4j test org.testng testng test org.wiremock wiremock-standalone test ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** * Provides Factory Parameters resolver for Gitlab repositories. * * @author Max Shaposhnyk */ @Singleton public class GitlabFactoryParametersResolver extends AbstractGitlabFactoryParametersResolver implements FactoryParametersResolver { private static final String PROVIDER_NAME = "gitlab"; @Inject public GitlabFactoryParametersResolver( URLFactoryBuilder urlFactoryBuilder, URLFetcher urlFetcher, GitlabUrlParser gitlabURLParser, PersonalAccessTokenManager personalAccessTokenManager, AuthorisationRequestManager authorisationRequestManager) { super( urlFactoryBuilder, urlFetcher, gitlabURLParser, personalAccessTokenManager, authorisationRequestManager, PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverSecond.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** * Provides Factory Parameters resolver for Gitlab repositories. * * @author Max Shaposhnyk */ @Singleton public class GitlabFactoryParametersResolverSecond extends AbstractGitlabFactoryParametersResolver implements FactoryParametersResolver { private static final String PROVIDER_NAME = "gitlab_2"; @Inject public GitlabFactoryParametersResolverSecond( URLFactoryBuilder urlFactoryBuilder, URLFetcher urlFetcher, GitlabUrlParserSecond gitlabURLParser, PersonalAccessTokenManager personalAccessTokenManager, AuthorisationRequestManager authorisationRequestManager) { super( urlFactoryBuilder, urlFetcher, gitlabURLParser, personalAccessTokenManager, authorisationRequestManager, PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabModule.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; public class GitlabModule extends AbstractModule { @Override protected void configure() { Multibinder tokenFetcherMultibinder = Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(GitlabOAuthTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(GitlabOAuthTokenFetcherSecond.class); Multibinder gitUserDataMultibinder = Multibinder.newSetBinder(binder(), GitUserDataFetcher.class); gitUserDataMultibinder.addBinding().to(GitlabUserDataFetcher.class); gitUserDataMultibinder.addBinding().to(GitlabUserDataFetcherSecond.class); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.security.oauth.OAuthAPI; /** GitLab OAuth token retriever. */ public class GitlabOAuthTokenFetcher extends AbstractGitlabOAuthTokenFetcher { private static final String OAUTH_PROVIDER_NAME = "gitlab"; @Inject public GitlabOAuthTokenFetcher( @Nullable @Named("che.integration.gitlab.oauth_endpoint") String serverUrl, @Named("che.api") String apiEndpoint, OAuthAPI oAuthAPI) { super(serverUrl, apiEndpoint, oAuthAPI, OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherSecond.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.security.oauth.OAuthAPI; /** GitLab OAuth token retriever. */ public class GitlabOAuthTokenFetcherSecond extends AbstractGitlabOAuthTokenFetcher { private static final String OAUTH_PROVIDER_NAME = "gitlab_2"; @Inject public GitlabOAuthTokenFetcherSecond( @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String serverUrl, @Named("che.api") String apiEndpoint, OAuthAPI oAuthAPI) { super(serverUrl, apiEndpoint, oAuthAPI, OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolver.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import javax.inject.Inject; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** GitLab specific SCM file resolver. */ public class GitlabScmFileResolver extends AbstractGitlabScmFileResolver { @Inject public GitlabScmFileResolver( GitlabUrlParser gitlabUrlParser, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { super(gitlabUrlParser, urlFetcher, personalAccessTokenManager); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolverSecond.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import javax.inject.Inject; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** GitLab specific SCM file resolver. */ public class GitlabScmFileResolverSecond extends AbstractGitlabScmFileResolver { @Inject public GitlabScmFileResolverSecond( GitlabUrlParserSecond gitlabUrlParser, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { super(gitlabUrlParser, urlFetcher, personalAccessTokenManager); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; /** * Parser of String Gitlab URLs and provide {@link GitlabUrl} objects. * * @author Max Shaposhnyk */ public class GitlabUrlParser extends AbstractGitlabUrlParser { private static final String OAUTH_PROVIDER_NAME = "gitlab"; @Inject public GitlabUrlParser( @Nullable @Named("che.integration.gitlab.oauth_endpoint") String serverUrl, DevfileFilenamesProvider devfileFilenamesProvider, PersonalAccessTokenManager personalAccessTokenManager) { super(serverUrl, devfileFilenamesProvider, personalAccessTokenManager, OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserSecond.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; /** * Parser of String Gitlab URLs and provide {@link GitlabUrl} objects. * * @author Max Shaposhnyk */ public class GitlabUrlParserSecond extends AbstractGitlabUrlParser { private static final String OAUTH_PROVIDER_NAME = "gitlab_2"; @Inject public GitlabUrlParserSecond( @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String serverUrl, DevfileFilenamesProvider devfileFilenamesProvider, PersonalAccessTokenManager personalAccessTokenManager) { super(serverUrl, devfileFilenamesProvider, personalAccessTokenManager, OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.factory.server.scm.*; import org.eclipse.che.commons.annotation.Nullable; /** Gitlab OAuth token retriever. */ public class GitlabUserDataFetcher extends AbstractGitlabUserDataFetcher { /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "gitlab"; @Inject public GitlabUserDataFetcher( @Nullable @Named("che.integration.gitlab.oauth_endpoint") String serverUrl, @Named("che.api") String apiEndpoint, PersonalAccessTokenManager personalAccessTokenManager) { super(serverUrl, apiEndpoint, personalAccessTokenManager, OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherSecond.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.factory.server.scm.*; import org.eclipse.che.commons.annotation.Nullable; /** Gitlab OAuth token retriever. */ public class GitlabUserDataFetcherSecond extends AbstractGitlabUserDataFetcher { /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "gitlab_2"; @Inject public GitlabUserDataFetcherSecond( @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String serverUrl, @Named("che.api") String apiEndpoint, PersonalAccessTokenManager personalAccessTokenManager) { super(serverUrl, apiEndpoint, personalAccessTokenManager, OAUTH_PROVIDER_NAME); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClientTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.eclipse.che.api.factory.server.gitlab.GitlabOAuthTokenFetcher.DEFAULT_TOKEN_SCOPES; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.collect.Sets; import com.google.common.net.HttpHeaders; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitlabApiClientTest { private GitlabApiClient client; WireMockServer wireMockServer; WireMock wireMock; @BeforeMethod void start() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); client = new GitlabApiClient(wireMockServer.url("/")); } @AfterMethod void stop() { wireMockServer.stop(); } @Test public void testGetUser() throws Exception { stubFor( get(urlEqualTo("/api/v4/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("gitlab/rest/api/v4/user/response.json"))); GitlabUser user = client.getUser("token1"); assertNotNull(user); } @Test public void shouldGetPersonalAccessTokenInfo() throws Exception { // given stubFor( get(urlEqualTo("/api/v4/personal_access_tokens/self")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token1")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("gitlab/rest/api/v4/user/PAT_info.json"))); // when GitlabPersonalAccessTokenInfo tokenInfo = client.getPersonalAccessTokenInfo("token1"); // then assertNotNull(tokenInfo); assertEquals(tokenInfo.getId(), 1); assertTrue(Sets.newHashSet(tokenInfo.getScopes()).containsAll(DEFAULT_TOKEN_SCOPES)); } @Test public void shouldReturnFalseOnConnectedToOtherHost() { assertFalse(client.isConnected("https://other.com")); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static java.lang.String.format; import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; import static java.net.HttpURLConnection.HTTP_OK; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import java.io.FileNotFoundException; import java.net.URI; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitlabAuthorizingFileContentProviderTest { @Mock private PersonalAccessTokenManager personalAccessTokenManager; @Mock private URLFetcher urlFetcher; private WireMockServer wireMockServer; private WireMock wireMock; @BeforeMethod public void start() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); } @AfterMethod void stop() { wireMockServer.stop(); } @Test public void shouldExpandRelativePaths() throws Exception { GitlabUrl gitlabUrl = new GitlabUrl().withHostName("gitlab.net").withSubGroups("eclipse/che"); FileContentProvider fileContentProvider = new GitlabAuthorizingFileContentProvider(gitlabUrl, urlFetcher, personalAccessTokenManager); var personalAccessToken = new PersonalAccessToken("foo", "provider", "che", "my-token"); when(personalAccessTokenManager.getAndStore(anyString())).thenReturn(personalAccessToken); fileContentProvider.fetchContent("devfile.yaml"); verify(urlFetcher) .fetch( eq( "https://gitlab.net/api/v4/projects/eclipse%2Fche/repository/files/devfile.yaml/raw?ref=HEAD"), eq("Bearer my-token")); } @Test public void shouldPreserveAbsolutePaths() throws Exception { GitlabUrl gitlabUrl = new GitlabUrl().withHostName("gitlab.net").withSubGroups("eclipse/che"); FileContentProvider fileContentProvider = new GitlabAuthorizingFileContentProvider(gitlabUrl, urlFetcher, personalAccessTokenManager); String url = "https://gitlab.net/api/v4/projects/eclipse%2Fche/repository/files/devfile.yaml/raw"; var personalAccessToken = new PersonalAccessToken(url, "provider", "che", "my-token"); when(personalAccessTokenManager.getAndStore(anyString())).thenReturn(personalAccessToken); fileContentProvider.fetchContent(url); verify(urlFetcher).fetch(eq(url), eq("Bearer my-token")); } @Test(expectedExceptions = FileNotFoundException.class) public void shouldThrowFileNotFoundException() throws Exception { // given when(urlFetcher.fetch( eq( wireMockServer.url( "/api/v4/projects/eclipse%2Fche/repository/files/devfile.yaml/raw?ref=HEAD")))) .thenThrow(new FileNotFoundException()); when(personalAccessTokenManager.getAndStore(anyString())) .thenThrow(new UnknownScmProviderException("", "")); URI uri = URI.create(wireMockServer.url("/")); GitlabUrl gitlabUrl = new GitlabUrl() .withScheme("http") .withHostName(format("%s:%s", uri.getHost(), uri.getPort())) .withSubGroups("eclipse/che"); FileContentProvider fileContentProvider = new GitlabAuthorizingFileContentProvider(gitlabUrl, urlFetcher, personalAccessTokenManager); stubFor(get(urlEqualTo("/eclipse/che")).willReturn(aResponse().withStatus(HTTP_OK))); // when fileContentProvider.fetchContent("devfile.yaml"); } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Could not reach devfile at test path") public void shouldThrowDevfileException() throws Exception { // given when(urlFetcher.fetch( eq( wireMockServer.url( "/api/v4/projects/eclipse%2Fche/repository/files/devfile.yaml/raw?ref=HEAD")))) .thenThrow(new FileNotFoundException("test path")); when(personalAccessTokenManager.getAndStore(anyString())) .thenThrow(new UnknownScmProviderException("", "")); URI uri = URI.create(wireMockServer.url("/")); GitlabUrl gitlabUrl = new GitlabUrl() .withScheme("http") .withHostName(format("%s:%s", uri.getHost(), uri.getPort())) .withSubGroups("eclipse/che"); FileContentProvider fileContentProvider = new GitlabAuthorizingFileContentProvider(gitlabUrl, urlFetcher, personalAccessTokenManager); stubFor(get(urlEqualTo("/eclipse/che")).willReturn(aResponse().withStatus(HTTP_MOVED_TEMP))); // when fileContentProvider.fetchContent("devfile.yaml"); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabCustomPortUrlParserTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitlabCustomPortUrlParserTest { @Mock private DevfileFilenamesProvider devfileFilenamesProvider; /** Instance of component that will be tested. */ private GitlabUrlParser gitlabUrlParser; private WireMockServer wireMockServer; private WireMock wireMock; @BeforeClass public void prepare() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); } @BeforeMethod public void setUp() { gitlabUrlParser = new GitlabUrlParser( "https://gitlab.custom.com:31280", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); } /** Check URLs are valid with regexp */ @Test(dataProvider = "UrlsProvider") public void checkRegexp(String url) { assertTrue(gitlabUrlParser.isValid(url), "url " + url + " is invalid"); } /** Compare parsing */ @Test(dataProvider = "parsing") public void checkParsing(String url, String project, String subGroups, String branch) { GitlabUrl gitlabUrl = gitlabUrlParser.parse(url, null); assertEquals(gitlabUrl.getProject(), project); assertEquals(gitlabUrl.getSubGroups(), subGroups); assertEquals(gitlabUrl.getBranch(), branch); } /** Compare parsing */ @Test(dataProvider = "parsing") public void shouldParseWithoutPredefinedEndpoint( String url, String project, String subGroups, String branch) { // given gitlabUrlParser = new GitlabUrlParser(null, devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when GitlabUrl gitlabUrl = gitlabUrlParser.parse(url, null); // then assertEquals(gitlabUrl.getProject(), project); assertEquals(gitlabUrl.getSubGroups(), subGroups); assertEquals(gitlabUrl.getBranch(), branch); } @Test public void shouldValidateUrlByApiRequest() { // given String url = wireMockServer.url("/user/repo"); stubFor( get(urlEqualTo("/oauth/token/info")) .willReturn( aResponse() .withStatus(401) .withBody( "{\"error\":\"invalid_token\",\"error_description\":\"The access token is invalid\",\"state\":\"unauthorized\"}"))); // when boolean result = gitlabUrlParser.isValid(url); // then assertTrue(result); } @Test public void shouldNotValidateUrlByApiRequestWithPlainStringResponse() { // given String url = wireMockServer.url("/user/repo"); stubFor( get(urlEqualTo("/oauth/token/info")) .willReturn(aResponse().withStatus(401).withBody("plain string error"))); // when boolean result = gitlabUrlParser.isValid(url); // then assertFalse(result); } @Test public void shouldNotValidateUrlByApiRequest() { // given String url = wireMockServer.url("/user/repo"); stubFor(get(urlEqualTo("/oauth/token/info")).willReturn(aResponse().withStatus(500))); // when boolean result = gitlabUrlParser.isValid(url); // then assertFalse(result); } @DataProvider(name = "UrlsProvider") public Object[][] urls() { return new Object[][] { {"https://gitlab.custom.com:31280/user/project/test1.git"}, {"https://gitlab.custom.com:31280/user/project1.git"}, {"https://gitlab.custom.com:31280/scm/project/test1.git"}, {"https://gitlab.custom.com:31280/user/project/"}, {"https://gitlab.custom.com:31280/user/project/repo/"}, {"https://gitlab.custom.com:31280/user/project/-/tree/master/"}, {"https://gitlab.custom.com:31280/user/project/repo/-/tree/master/subfolder"}, {"git@gitlab.custom.com:user/project/test1.git"}, {"git@gitlab.custom.com:user/project1.git"}, {"git@gitlab.custom.com:scm/project/test1.git"}, {"git@gitlab.custom.com:user/project/"}, {"git@gitlab.custom.com:user/project/repo/"}, }; } @DataProvider(name = "parsing") public Object[][] expectedParsing() { return new Object[][] { {"https://gitlab.custom.com:31280/user/project1.git", "project1", "user/project1", null}, { "https://gitlab.custom.com:31280/user/project/test1.git", "test1", "user/project/test1", null }, { "https://gitlab.custom.com:31280/user/project/group1/group2/test1.git", "test1", "user/project/group1/group2/test1", null }, {"https://gitlab.custom.com:31280/user/project/", "project", "user/project", null}, {"https://gitlab.custom.com:31280/user/project/repo/", "repo", "user/project/repo", null}, {"git@gitlab.custom.com:user/project1.git", "project1", "user/project1", null}, {"git@gitlab.custom.com:user/project/test1.git", "test1", "user/project/test1", null}, { "git@gitlab.custom.com:user/project/group1/group2/test1.git", "test1", "user/project/group1/group2/test1", null }, {"git@gitlab.custom.com:user/project/", "project", "user/project", null}, {"git@gitlab.custom.com:user/project/repo/", "repo", "user/project/repo", null}, { "https://gitlab.custom.com:31280/user/project/-/tree/master/", "project", "user/project", "master" }, { "https://gitlab.custom.com:31280/user/project/repo/-/tree/foo", "repo", "user/project/repo", "foo" }, { "https://gitlab.custom.com:31280/user/project/repo/-/tree/branch/with/slash", "repo", "user/project/repo", "branch/with/slash" }, { "https://gitlab.custom.com:31280/user/project/group1/group2/repo/-/tree/foo/", "repo", "user/project/group1/group2/repo", "foo" } }; } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.eclipse.che.security.oauth1.OAuthAuthenticationService.ERROR_QUERY_NAME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.model.factory.ScmInfo; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Validate operations performed by the Github Factory service * * @author Florent Benoit */ @Listeners(MockitoTestNGListener.class) public class GitlabFactoryParametersResolverTest { @Mock private URLFactoryBuilder urlFactoryBuilder; @Mock private URLFetcher urlFetcher; @Mock private DevfileFilenamesProvider devfileFilenamesProvider; GitlabUrlParser gitlabUrlParser; @Mock private PersonalAccessTokenManager personalAccessTokenManager; @Mock private AuthorisationRequestManager authorisationRequestManager; private GitlabFactoryParametersResolver gitlabFactoryParametersResolver; @BeforeMethod protected void init() { gitlabUrlParser = new GitlabUrlParser( "http://gitlab.2mcl.com", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); assertNotNull(this.gitlabUrlParser); gitlabFactoryParametersResolver = new GitlabFactoryParametersResolver( urlFactoryBuilder, urlFetcher, gitlabUrlParser, personalAccessTokenManager, authorisationRequestManager); assertNotNull(this.gitlabFactoryParametersResolver); } /** Check url which is not a Gitlab url can't be accepted by this resolver */ @Test public void checkInvalidAcceptUrl() { Map parameters = singletonMap(URL_PARAMETER_NAME, "http://github.com/user/repo"); // shouldn't be accepted assertFalse(gitlabFactoryParametersResolver.accept(parameters)); } /** Check Gitlab url will be be accepted by this resolver */ @Test public void checkValidAcceptUrl() { Map parameters = singletonMap(URL_PARAMETER_NAME, "http://gitlab.2mcl.com/test/proj/repo.git"); // should be accepted assertTrue(gitlabFactoryParametersResolver.accept(parameters)); } @Test public void shouldGenerateDevfileForFactoryWithNoDevfileOrJson() throws Exception { String gitlabUrl = "http://gitlab.2mcl.com/test/proj/repo.git"; when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.empty()); Map params = ImmutableMap.of(URL_PARAMETER_NAME, gitlabUrl); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) gitlabFactoryParametersResolver.createFactory(params); // then ScmInfoDto scmInfo = factory.getScmInfo(); assertEquals(scmInfo.getRepositoryUrl(), gitlabUrl); assertEquals(scmInfo.getBranch(), null); } @Test public void shouldSetDefaultProjectIntoDevfileIfNotSpecified() throws Exception { String gitlabUrl = "http://gitlab.2mcl.com/test/proj/repo/-/tree/foobar"; FactoryDevfileV2Dto computedFactory = generateDevfileV2Factory(); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.of(computedFactory)); Map params = ImmutableMap.of(URL_PARAMETER_NAME, gitlabUrl); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) gitlabFactoryParametersResolver.createFactory(params); // then assertNotNull(factory.getDevfile()); ScmInfoDto source = factory.getScmInfo(); assertEquals(source.getRepositoryUrl(), "http://gitlab.2mcl.com/test/proj/repo.git"); assertEquals(source.getBranch(), "foobar"); } @Test public void shouldCreateFactoryWithoutAuthentication() throws ApiException { // given String gitlabUrl = "https://gitlab.com/user/repo.git"; Map params = ImmutableMap.of(URL_PARAMETER_NAME, gitlabUrl, ERROR_QUERY_NAME, "access_denied"); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.of(generateDevfileV2Factory())); // when gitlabFactoryParametersResolver.createFactory(params); // then verify(urlFactoryBuilder) .createFactoryFromDevfile( any(GitlabUrl.class), any(GitlabAuthorizingFileContentProvider.class), anyMap(), eq(true)); } @Test public void shouldSetScmInfoIntoDevfileV2() throws Exception { String gitlabUrl = "http://gitlab.2mcl.com/eclipse/che/-/tree/foobar"; FactoryDevfileV2Dto computedFactory = generateDevfileV2Factory(); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.of(computedFactory)); Map params = ImmutableMap.of(URL_PARAMETER_NAME, gitlabUrl); // when FactoryDevfileV2Dto factory = (FactoryDevfileV2Dto) gitlabFactoryParametersResolver.createFactory(params); // then ScmInfo scmInfo = factory.getScmInfo(); assertNotNull(scmInfo); assertEquals(scmInfo.getScmProviderName(), "gitlab"); assertEquals(scmInfo.getRepositoryUrl(), "http://gitlab.2mcl.com/eclipse/che.git"); assertEquals(scmInfo.getBranch(), "foobar"); } private FactoryDevfileV2Dto generateDevfileV2Factory() { return newDto(FactoryDevfileV2Dto.class) .withV(CURRENT_VERSION) .withSource("repo") .withDevfile(Map.of("schemaVersion", "2.0.0")); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.net.HttpHeaders; import java.util.Collections; import java.util.Optional; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitlabOAuthTokenFetcherTest { @Mock OAuthAPI oAuthAPI; GitlabOAuthTokenFetcher oAuthTokenFetcher; WireMockServer wireMockServer; WireMock wireMock; @BeforeMethod void start() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); oAuthTokenFetcher = new GitlabOAuthTokenFetcher(wireMockServer.url("/"), "http://che.api", oAuthAPI); } @AfterMethod void stop() { wireMockServer.stop(); } @Test( expectedExceptions = ScmCommunicationException.class, expectedExceptionsMessageRegExp = "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: \\[api, write_repository]") public void shouldThrowExceptionOnInsufficientTokenScopes() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken("oauthtoken").withScope("api repo"); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/api/v4/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer oauthtoken")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("gitlab/rest/api/v4/user/response.json"))); stubFor( get(urlEqualTo("/oauth/token/info")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer oauthtoken")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("gitlab/rest/api/v4/user/token_info_lack_scopes.json"))); oAuthTokenFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @Test( expectedExceptions = ScmUnauthorizedException.class, expectedExceptionsMessageRegExp = "Username is not authorized in gitlab OAuth provider.") public void shouldThrowUnauthorizedExceptionWhenUserNotLoggedIn() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); when(oAuthAPI.getOrRefreshToken(anyString())).thenThrow(UnauthorizedException.class); oAuthTokenFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @Test public void shouldReturnToken() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken("oauthtoken").withScope("api write_repository openid"); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/oauth/token/info")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer oauthtoken")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("gitlab/rest/api/v4/user/token_info.json"))); stubFor( get(urlEqualTo("/api/v4/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer oauthtoken")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("gitlab/rest/api/v4/user/response.json"))); PersonalAccessToken token = oAuthTokenFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); assertNotNull(token); } @Test( expectedExceptions = ScmCommunicationException.class, expectedExceptionsMessageRegExp = "OAuth 2 is not configured for SCM provider \\[gitlab\\]. For details, refer " + "the documentation in section of SCM providers configuration.") public void shouldThrowScmCommunicationExceptionWhenNoOauthIsConfigured() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); GitlabOAuthTokenFetcher localFetcher = new GitlabOAuthTokenFetcher(wireMockServer.url("/"), "http://che.api", null); localFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @Test public void shouldValidateOAuthToken() throws Exception { stubFor( get(urlEqualTo("/api/v4/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token123")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("gitlab/rest/api/v4/user/response.json"))); stubFor( get(urlEqualTo("/oauth/token/info")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token123")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("gitlab/rest/api/v4/user/token_info.json"))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( wireMockServer.baseUrl(), "provider", "oauth2-token-name", "tid-23434", "token123", null); Optional> valid = oAuthTokenFetcher.isValid(params); assertTrue(valid.isPresent()); assertTrue(valid.get().first); } @Test( expectedExceptions = ScmUnauthorizedException.class, expectedExceptionsMessageRegExp = "Username is not authorized in gitlab OAuth provider.") public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception { Subject subject = new SubjectImpl("Username", Collections.emptyList(), "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken("token").withScope(""); when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/api/v4/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token token")) .willReturn(aResponse().withStatus(HTTP_FORBIDDEN))); oAuthTokenFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolverTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.*; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitlabScmFileResolverTest { public static final String SCM_URL = "http://gitlab.2mcl.com"; GitlabUrlParser gitlabUrlParser; @Mock private URLFetcher urlFetcher; @Mock private DevfileFilenamesProvider devfileFilenamesProvider; @Mock private PersonalAccessTokenManager personalAccessTokenManager; private GitlabScmFileResolver gitlabScmFileResolver; @BeforeMethod protected void init() { gitlabUrlParser = new GitlabUrlParser( SCM_URL, devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); assertNotNull(this.gitlabUrlParser); gitlabScmFileResolver = new GitlabScmFileResolver(gitlabUrlParser, urlFetcher, personalAccessTokenManager); assertNotNull(this.gitlabScmFileResolver); } /** Check url which is not a Gitlab url can't be accepted by this resolver */ @Test public void checkInvalidAcceptUrl() { // shouldn't be accepted assertFalse(gitlabScmFileResolver.accept("http://github.com/user/repo")); } /** Check Gitlab url will be be accepted by this resolver */ @Test public void checkValidAcceptUrl() { // should be accepted assertTrue(gitlabScmFileResolver.accept("http://gitlab.2mcl.com/test/proj/repo.git")); } @Test public void shouldReturnContentFromUrlFetcher() throws Exception { final String rawContent = "raw_content"; final String filename = "devfile.yaml"; when(personalAccessTokenManager.getAndStore(any(String.class))) .thenReturn(new PersonalAccessToken(SCM_URL, "provider", "root", "token123")); when(urlFetcher.fetch(anyString(), eq("Bearer token123"))).thenReturn(rawContent); String content = gitlabScmFileResolver.fileContent("http://gitlab.2mcl.com/test/proj/repo.git", filename); assertEquals(content, rawContent); } @Test public void shouldFetchContentWithoutAuthentication() throws Exception { // given when(personalAccessTokenManager.getAndStore(anyString())) .thenThrow(new ScmUnauthorizedException("message", "gitlab", "v1", "url")); // when gitlabScmFileResolver.fileContent("https://gitlab.com/username/repo.git", "devfile.yaml"); // then verify(urlFetcher).fetch(anyString()); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlCustomPortTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static java.lang.String.format; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import java.util.Arrays; import java.util.Iterator; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Test of {@Link GitlabUrl} Note: The parser is also testing the {@code GitlabURLParser} object * * @author Florent Benoit */ @Listeners(MockitoTestNGListener.class) public class GitlabUrlCustomPortTest { @Mock private DevfileFilenamesProvider devfileFilenamesProvider; /** Parser used to create the url. */ private GitlabUrlParser gitlabUrlParser; /** Setup objects/ */ @BeforeMethod protected void init() { when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); gitlabUrlParser = new GitlabUrlParser( "https://gitlab.net:3120", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); } /** Check when there is devfile in the repository */ @Test(dataProvider = "urlsProvider") public void checkDevfileLocation(String repoUrl, String fileUrl) { lenient() .when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); GitlabUrl gitlabUrl = gitlabUrlParser.parse(repoUrl, null); assertEquals(gitlabUrl.devfileFileLocations().size(), 2); Iterator iterator = gitlabUrl.devfileFileLocations().iterator(); assertEquals(iterator.next().location(), format(fileUrl, "devfile.yaml")); assertEquals(iterator.next().location(), format(fileUrl, "foo.bar")); } @Test(dataProvider = "urlsProvider") public void shouldReturnProviderUrl(String repoUrl, String ignored) { // when GitlabUrl gitlabUrl = gitlabUrlParser.parse(repoUrl, null); // then assertEquals(gitlabUrl.getProviderUrl(), "https://gitlab.net:3120"); } @DataProvider public static Object[][] urlsProvider() { return new Object[][] { { "https://gitlab.net:3120/eclipse/che.git", "https://gitlab.net:3120/api/v4/projects/eclipse%%2Fche/repository/files/%s/raw?ref=HEAD" }, { "https://gitlab.net:3120/eclipse/fooproj/che.git", "https://gitlab.net:3120/api/v4/projects/eclipse%%2Ffooproj%%2Fche/repository/files/%s/raw?ref=HEAD" }, // { // "git@gitlab.net:eclipse/che.git", // "https://gitlab.net:3120/api/v4/projects/eclipse%%2Fche/repository/files/%s/raw?ref=HEAD" // }, // { // "git@gitlab.net:eclipse/fooproj/che.git", // // "https://gitlab.net:3120/api/v4/projects/eclipse%%2Ffooproj%%2Fche/repository/files/%s/raw?ref=HEAD" // }, { "https://gitlab.net:3120/eclipse/fooproj/-/tree/master/", "https://gitlab.net:3120/api/v4/projects/eclipse%%2Ffooproj/repository/files/%s/raw?ref=master" }, { "https://gitlab.net:3120/eclipse/fooproj/che/-/tree/foobranch/", "https://gitlab.net:3120/api/v4/projects/eclipse%%2Ffooproj%%2Fche/repository/files/%s/raw?ref=foobranch" }, }; } /** Check the original repository */ @Test(dataProvider = "repoProvider2") public void checkRepositoryLocation(String rawUrl, String repoUrl) { GitlabUrl gitlabUrl = gitlabUrlParser.parse(rawUrl, null); assertEquals(gitlabUrl.repositoryLocation(), repoUrl); } @DataProvider public static Object[][] repoProvider2() { return new Object[][] { {"https://gitlab.net:3120/eclipse/che.git", "https://gitlab.net:3120/eclipse/che.git"}, { "https://gitlab.net:3120/eclipse/foo/che.git", "https://gitlab.net:3120/eclipse/foo/che.git" }, {"git@gitlab.net:eclipse/che.git", "git@gitlab.net:eclipse/che.git"}, {"git@gitlab.net:eclipse/foo/che.git", "git@gitlab.net:eclipse/foo/che.git"}, { "https://gitlab.net:3120/eclipse/fooproj/che/-/tree/master/", "https://gitlab.net:3120/eclipse/fooproj/che.git" } }; } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitlabUrlParserTest { @Mock private DevfileFilenamesProvider devfileFilenamesProvider; /** Instance of component that will be tested. */ private GitlabUrlParser gitlabUrlParser; private WireMockServer wireMockServer; private WireMock wireMock; @BeforeClass public void prepare() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); } @BeforeMethod public void setUp() { gitlabUrlParser = new GitlabUrlParser( "https://gitlab1.com", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); } /** Check URLs are valid with regexp */ @Test(dataProvider = "UrlsProvider") public void checkRegexp(String url) { assertTrue(gitlabUrlParser.isValid(url), "url " + url + " is invalid"); } @Test public void shouldParseWithBranch() throws ApiException { GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://gitlab.com/user/project/test.git", "branch"); assertEquals(gitlabUrl.getBranch(), "branch"); } @Test public void shouldGetProviderUrlWithExtraSegment() throws ApiException { gitlabUrlParser = new GitlabUrlParser( "https://gitlab-server.com/scm", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://gitlab-server.com/scm/user/project/test.git", null); assertEquals(gitlabUrl.getProviderUrl(), "https://gitlab-server.com/scm"); } @Test public void shouldGetProviderUrlWithExtraSegmentOnIpv6Endpoint() throws ApiException { gitlabUrlParser = new GitlabUrlParser( "https://[2001:db8::1]/scm", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://[2001:db8::1]/scm/user/project/test.git", null); assertEquals(gitlabUrl.getProviderUrl(), "https://[2001:db8::1]/scm"); } @Test public void shouldGetProviderUrlWithExtraSegmentOnIpv6EndpointWithPort() throws ApiException { gitlabUrlParser = new GitlabUrlParser( "https://[2001:db8::1]:8443/scm", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://[2001:db8::1]:8443/scm/user/project/test.git", null); assertEquals(gitlabUrl.getProviderUrl(), "https://[2001:db8::1]:8443/scm"); } @Test public void shouldParseIpv6UrlWithoutExtraSegment() throws ApiException { // given gitlabUrlParser = new GitlabUrlParser( "https://[2001:db8::1]", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://[2001:db8::1]/user/project.git", null); // then assertEquals(gitlabUrl.getProject(), "project"); assertEquals(gitlabUrl.getSubGroups(), "user/project"); assertEquals(gitlabUrl.getProviderUrl(), "https://[2001:db8::1]"); } @Test public void shouldParseIpv6UrlWithBranch() throws ApiException { // given gitlabUrlParser = new GitlabUrlParser( "https://[2001:db8::1]", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://[2001:db8::1]/user/project/-/tree/feature-branch", null); // then assertEquals(gitlabUrl.getProject(), "project"); assertEquals(gitlabUrl.getSubGroups(), "user/project"); assertEquals(gitlabUrl.getBranch(), "feature-branch"); } @Test public void shouldParseIpv6UrlWithBranchContainingSlash() throws ApiException { // given gitlabUrlParser = new GitlabUrlParser( "https://[2001:db8::1]", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://[2001:db8::1]/user/project/-/tree/feature/my-branch", null); // then assertEquals(gitlabUrl.getProject(), "project"); assertEquals(gitlabUrl.getSubGroups(), "user/project"); assertEquals(gitlabUrl.getBranch(), "feature/my-branch"); } @Test public void shouldParseIpv6UrlWithRevisionParam() throws ApiException { // given gitlabUrlParser = new GitlabUrlParser( "https://[2001:db8::1]", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://[2001:db8::1]/user/project.git", "my-branch"); // then assertEquals(gitlabUrl.getProject(), "project"); assertEquals(gitlabUrl.getSubGroups(), "user/project"); assertEquals(gitlabUrl.getBranch(), "my-branch"); } @Test public void shouldParseIpv6UrlWithSubgroups() throws ApiException { // given gitlabUrlParser = new GitlabUrlParser( "https://[2001:db8::1]", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://[2001:db8::1]/group/subgroup/project.git", null); // then assertEquals(gitlabUrl.getProject(), "project"); assertEquals(gitlabUrl.getSubGroups(), "group/subgroup/project"); } @Test public void shouldParseIpv6LoopbackAddress() throws ApiException { // given gitlabUrlParser = new GitlabUrlParser( "https://[::1]", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://[::1]/user/project.git", null); // then assertEquals(gitlabUrl.getProject(), "project"); assertEquals(gitlabUrl.getSubGroups(), "user/project"); assertEquals(gitlabUrl.getProviderUrl(), "https://[::1]"); } @Test public void shouldParseIpv6FullFormAddress() throws ApiException { // given gitlabUrlParser = new GitlabUrlParser( "https://[2001:0db8:0000:0000:0000:0000:0000:0001]", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when GitlabUrl gitlabUrl = gitlabUrlParser.parse( "https://[2001:0db8:0000:0000:0000:0000:0000:0001]/user/project.git", null); // then assertEquals(gitlabUrl.getProject(), "project"); assertEquals(gitlabUrl.getSubGroups(), "user/project"); } @Test public void shouldValidateIpv6Url() { // given gitlabUrlParser = new GitlabUrlParser( "https://[2001:db8::1]", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when/then assertTrue(gitlabUrlParser.isValid("https://[2001:db8::1]/user/project.git")); } @Test public void shouldValidateIpv6UrlWithPort() { // given gitlabUrlParser = new GitlabUrlParser( "https://[2001:db8::1]:8443", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when/then assertTrue(gitlabUrlParser.isValid("https://[2001:db8::1]:8443/user/project.git")); } @Test public void shouldValidateIpv6UrlWithBranch() { // given gitlabUrlParser = new GitlabUrlParser( "https://[2001:db8::1]", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when/then assertTrue(gitlabUrlParser.isValid("https://[2001:db8::1]/user/project/-/tree/master")); } @Test public void shouldParseIpv6UrlViaDynamicPatternMatching() throws ApiException { // The parser is configured for gitlab1.com (via setUp), but getPatternMatcherByUrl() // dynamically creates a pattern from the URL itself. This exercises the fixed // IPv6 bracket handling in getPatternMatcherByUrl() -- the code path where the // double-bracketing bug (Issue #4 in the verdict) was fixed. GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://[2001:db8::1]/user/project.git", null); assertEquals(gitlabUrl.getProject(), "project"); assertEquals(gitlabUrl.getSubGroups(), "user/project"); } @Test public void shouldParseIpv6UrlViaDynamicPatternMatchingWithBranch() throws ApiException { // Exercises getPatternMatcherByUrl() dynamic path with IPv6 and branch GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://[2001:db8::1]/user/project/-/tree/feature-branch", null); assertEquals(gitlabUrl.getProject(), "project"); assertEquals(gitlabUrl.getSubGroups(), "user/project"); assertEquals(gitlabUrl.getBranch(), "feature-branch"); } @Test public void shouldParseWithUrlBranch() throws ApiException { GitlabUrl gitlabUrl = gitlabUrlParser.parse("https://gitlab.com/user/project/-/tree/master/", "branch"); assertEquals(gitlabUrl.getBranch(), "master"); } /** Compare parsing */ @Test(dataProvider = "parsing") public void checkParsing(String url, String project, String subGroups, String branch) { GitlabUrl gitlabUrl = gitlabUrlParser.parse(url, null); assertEquals(gitlabUrl.getProject(), project); assertEquals(gitlabUrl.getSubGroups(), subGroups); assertEquals(gitlabUrl.getBranch(), branch); } /** Compare parsing */ @Test(dataProvider = "parsing") public void shouldParseWithoutPredefinedEndpoint( String url, String project, String subGroups, String branch) { // given gitlabUrlParser = new GitlabUrlParser(null, devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); // when GitlabUrl gitlabUrl = gitlabUrlParser.parse(url, null); // then assertEquals(gitlabUrl.getProject(), project); assertEquals(gitlabUrl.getSubGroups(), subGroups); assertEquals(gitlabUrl.getBranch(), branch); } @Test public void shouldValidateUrlByApiRequest() { // given String url = wireMockServer.url("/user/repo"); stubFor( get(urlEqualTo("/oauth/token/info")) .willReturn( aResponse() .withStatus(401) .withBody( "{\"error\":\"invalid_token\",\"error_description\":\"The access token is invalid\",\"state\":\"unauthorized\"}"))); // when boolean result = gitlabUrlParser.isValid(url); // then assertTrue(result); } @Test public void shouldNotValidateUrlByApiRequestWithPlainStringResponse() { // given String url = wireMockServer.url("/user/repo"); stubFor( get(urlEqualTo("/oauth/token/info")) .willReturn(aResponse().withStatus(401).withBody("plain string error"))); // when boolean result = gitlabUrlParser.isValid(url); // then assertFalse(result); } @Test public void shouldNotValidateUrlByApiRequest() { // given String url = wireMockServer.url("/user/repo"); stubFor(get(urlEqualTo("/oauth/token/info")).willReturn(aResponse().withStatus(500))); // when boolean result = gitlabUrlParser.isValid(url); // then assertFalse(result); } @DataProvider(name = "UrlsProvider") public Object[][] urls() { return new Object[][] { {"https://gitlab1.com/user/project/test1.git"}, {"https://gitlab1.com/user/project1.git"}, {"https://gitlab1.com/scm/project/test1.git"}, {"https://gitlab1.com/user/project/"}, {"https://gitlab1.com/user/project/repo/"}, {"https://gitlab1.com/user/project/-/tree/master/"}, {"https://gitlab1.com/user/project/repo/-/tree/master/subfolder"}, {"git@gitlab1.com:user/project/test1.git"}, {"git@gitlab1.com:user/project1.git"}, {"git@gitlab1.com:scm/project/test1.git"}, {"git@gitlab1.com:user/project/"}, {"git@gitlab1.com:user/project/repo/"}, }; } @DataProvider(name = "parsing") public Object[][] expectedParsing() { return new Object[][] { {"https://gitlab1.com/user/project1.git", "project1", "user/project1", null}, {"https://gitlab1.com/user/project/test1.git", "test1", "user/project/test1", null}, { "https://gitlab1.com/user/project/group1/group2/test1.git", "test1", "user/project/group1/group2/test1", null }, {"https://gitlab1.com/user/project/", "project", "user/project", null}, {"https://gitlab1.com/user/project/repo/", "repo", "user/project/repo", null}, {"git@gitlab1.com:user/project1.git", "project1", "user/project1", null}, {"git@gitlab1.com:user/project/test1.git", "test1", "user/project/test1", null}, { "git@gitlab1.com:user/project/group1/group2/test1.git", "test1", "user/project/group1/group2/test1", null }, {"git@gitlab1.com:user/project/", "project", "user/project", null}, {"git@gitlab1.com:user/project/repo/", "repo", "user/project/repo", null}, {"https://gitlab1.com/user/project/-/tree/master/", "project", "user/project", "master"}, {"https://gitlab1.com/user/project/repo/-/tree/foo", "repo", "user/project/repo", "foo"}, { "https://gitlab1.com/user/project/repo/-/tree/branch/with/slash", "repo", "user/project/repo", "branch/with/slash" }, { "https://gitlab1.com/user/project/group1/group2/repo/-/tree/foo/", "repo", "user/project/group1/group2/repo", "foo" } }; } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static java.lang.String.format; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import java.util.Arrays; import java.util.Iterator; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Test of {@Link GitlabUrl} Note: The parser is also testing the {@code GitlabURLParser} object * * @author Florent Benoit */ @Listeners(MockitoTestNGListener.class) public class GitlabUrlTest { @Mock private DevfileFilenamesProvider devfileFilenamesProvider; /** Parser used to create the url. */ private GitlabUrlParser gitlabUrlParser; /** Setup objects/ */ @BeforeMethod protected void init() { when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); gitlabUrlParser = new GitlabUrlParser( "https://gitlab.net", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); } /** Check when there is devfile in the repository */ @Test(dataProvider = "urlsProvider") public void checkDevfileLocation(String repoUrl, String fileUrl) { lenient() .when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) .thenReturn(Arrays.asList("devfile.yaml", "foo.bar")); GitlabUrl gitlabUrl = gitlabUrlParser.parse(repoUrl, null); assertEquals(gitlabUrl.devfileFileLocations().size(), 2); Iterator iterator = gitlabUrl.devfileFileLocations().iterator(); assertEquals(iterator.next().location(), format(fileUrl, "devfile.yaml")); assertEquals(iterator.next().location(), format(fileUrl, "foo.bar")); } @Test(dataProvider = "urlsProvider") public void shouldReturnProviderUrl(String repoUrl, String ignored) { // when GitlabUrl gitlabUrl = gitlabUrlParser.parse(repoUrl, null); // then assertEquals(gitlabUrl.getProviderUrl(), "https://gitlab.net"); } @DataProvider public static Object[][] urlsProvider() { return new Object[][] { { "https://gitlab.net/eclipse/che.git", "https://gitlab.net/api/v4/projects/eclipse%%2Fche/repository/files/%s/raw?ref=HEAD" }, { "https://gitlab.net/eclipse/fooproj/che.git", "https://gitlab.net/api/v4/projects/eclipse%%2Ffooproj%%2Fche/repository/files/%s/raw?ref=HEAD" }, { "git@gitlab.net:eclipse/che.git", "https://gitlab.net/api/v4/projects/eclipse%%2Fche/repository/files/%s/raw?ref=HEAD" }, { "git@gitlab.net:eclipse/fooproj/che.git", "https://gitlab.net/api/v4/projects/eclipse%%2Ffooproj%%2Fche/repository/files/%s/raw?ref=HEAD" }, { "https://gitlab.net/eclipse/fooproj/-/tree/master/", "https://gitlab.net/api/v4/projects/eclipse%%2Ffooproj/repository/files/%s/raw?ref=master" }, { "https://gitlab.net/eclipse/fooproj/che/-/tree/foobranch/", "https://gitlab.net/api/v4/projects/eclipse%%2Ffooproj%%2Fche/repository/files/%s/raw?ref=foobranch" }, }; } /** Check the original repository */ @Test(dataProvider = "repoProvider") public void checkRepositoryLocation(String rawUrl, String repoUrl) { GitlabUrl gitlabUrl = gitlabUrlParser.parse(rawUrl, null); assertEquals(gitlabUrl.repositoryLocation(), repoUrl); } @DataProvider public static Object[][] repoProvider() { return new Object[][] { {"https://gitlab.net/eclipse/che.git", "https://gitlab.net/eclipse/che.git"}, {"https://gitlab.net/eclipse/foo/che.git", "https://gitlab.net/eclipse/foo/che.git"}, {"git@gitlab.net:eclipse/che.git", "git@gitlab.net:eclipse/che.git"}, {"git@gitlab.net:eclipse/foo/che.git", "git@gitlab.net:eclipse/foo/che.git"}, { "https://gitlab.net/eclipse/fooproj/che/-/tree/master/", "https://gitlab.net/eclipse/fooproj/che.git" } }; } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.net.HttpHeaders; import java.lang.reflect.Field; import java.util.Optional; import org.eclipse.che.api.factory.server.scm.GitUserData; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.commons.subject.Subject; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitlabUserDataFetcherTest { @Mock PersonalAccessTokenManager personalAccessTokenManager; GitlabUserDataFetcher gitlabUserDataFetcher; WireMockServer wireMockServer; WireMock wireMock; @BeforeMethod void start() { wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); gitlabUserDataFetcher = new GitlabUserDataFetcher( wireMockServer.url("/"), "http://che.api", personalAccessTokenManager); stubFor( get(urlEqualTo("/api/v4/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer oauthtoken")) .willReturn( aResponse() .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("gitlab/rest/api/v4/user/response.json"))); } @AfterMethod void stop() { wireMockServer.stop(); } @Test public void shouldFetchGitUserData() throws Exception { PersonalAccessToken token = mock(PersonalAccessToken.class); when(token.getToken()).thenReturn("oauthtoken"); when(token.getScmProviderUrl()).thenReturn(wireMockServer.url("/")); when(personalAccessTokenManager.get(any(Subject.class), eq("gitlab"), eq(null), eq(null))) .thenReturn(Optional.of(token)); GitUserData gitUserData = gitlabUserDataFetcher.fetchGitUserData(null); assertEquals(gitUserData.getScmUsername(), "John Smith"); assertEquals(gitUserData.getScmUserEmail(), "john@example.com"); } @Test public void shouldFetchGitUserDataByUrl() throws Exception { // given PersonalAccessToken token = mock(PersonalAccessToken.class); when(token.getToken()).thenReturn("oauthtoken"); when(personalAccessTokenManager.get(any(Subject.class), eq("gitlab"), eq(null), eq(null))) .thenReturn(Optional.empty()); when(personalAccessTokenManager.get( any(Subject.class), eq(null), eq(wireMockServer.url("/")), eq(null))) .thenReturn(Optional.of(token)); // when GitUserData gitUserData = gitlabUserDataFetcher.fetchGitUserData(null); // then assertEquals(gitUserData.getScmUsername(), "John Smith"); assertEquals(gitUserData.getScmUserEmail(), "john@example.com"); } @Test public void shouldSetSAASUrlAsDefault() throws Exception { gitlabUserDataFetcher = new GitlabUserDataFetcher(null, "http://che.api", personalAccessTokenManager); Field serverUrlField = gitlabUserDataFetcher.getClass().getSuperclass().getDeclaredField("serverUrl"); serverUrlField.setAccessible(true); String serverUrl = (String) serverUrlField.get(gitlabUserDataFetcher); assertEquals(serverUrl, "https://gitlab.com"); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/resources/__files/gitlab/rest/api/v4/user/PAT_info.json ================================================ { "id": 1, "name": "gitlab", "scopes": ["api", "write_repository"], "expires_at": "2024-07-18", "created_at": "2023-07-19T07:41:19.492Z" } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/resources/__files/gitlab/rest/api/v4/user/response.json ================================================ { "id": 1, "username": "john_smith", "email": "john@example.com", "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg", "web_url": "http://localhost:3000/john_smith", "created_at": "2012-05-23T08:00:58Z", "bio": "", "bio_html": "", "location": null, "public_email": "john@example.com", "skype": "", "linkedin": "", "twitter": "", "website_url": "", "organization": "", "last_sign_in_at": "2012-06-01T11:41:01Z", "confirmed_at": "2012-05-23T09:05:22Z", "theme_id": 1, "last_activity_on": "2012-05-23", "color_scheme_id": 2, "projects_limit": 100, "current_sign_in_at": "2012-06-02T06:36:55Z", "identities": [ {"provider": "github", "extern_uid": "2435223452345"}, {"provider": "bitbucket", "extern_uid": "john_smith"}, {"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"} ], "can_create_group": true, "can_create_project": true, "two_factor_enabled": true, "external": false, "private_profile": false } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/resources/__files/gitlab/rest/api/v4/user/token_info.json ================================================ { "resource_owner_id": 1, "scope": ["api", "write_repository", "openid"], "expires_in": null, "application": {"uid": "1cb242f495280beb4291e64bee2a17f330902e499882fe8e1e2aa875519cab33"}, "created_at": 1575890427 } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/resources/__files/gitlab/rest/api/v4/user/token_info_lack_scopes.json ================================================ { "resource_owner_id": 1, "scope": ["api"], "expires_in": null, "application": {"uid": "1cb242f495280beb4291e64bee2a17f330902e499882fe8e1e2aa875519cab33"}, "created_at": 1575890427 } ================================================ FILE: wsmaster/che-core-api-factory-gitlab/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-factory-gitlab-common jar Che Core :: API :: Factory Resolver Gitlab Common com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.core jackson-databind com.google.code.gson gson com.google.guava guava jakarta.validation jakarta.validation-api org.eclipse.che.core che-core-api-auth org.eclipse.che.core che-core-api-auth-shared org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-factory org.eclipse.che.core che-core-api-factory-shared org.eclipse.che.core che-core-api-workspace org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang org.slf4j slf4j-api jakarta.servlet jakarta.servlet-api provided ch.qos.logback logback-classic test com.github.tomakehurst wiremock-jre8-standalone test jakarta.ws.rs jakarta.ws.rs-api test org.eclipse.che.core che-core-commons-json test org.mockito mockito-core test org.mockito mockito-testng test org.slf4j jcl-over-slf4j test org.testng testng test org.wiremock wiremock-standalone test org.apache.maven.plugins maven-compiler-plugin 8 8 ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabFactoryParametersResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static org.eclipse.che.api.factory.shared.Constants.REVISION_PARAMETER_NAME; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; import static org.eclipse.che.dto.server.DtoFactory.newDto; import jakarta.validation.constraints.NotNull; import java.util.Map; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.BaseFactoryParameterResolver; import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; /** * Provides Factory Parameters resolver for Gitlab repositories. * * @author Max Shaposhnyk */ public class AbstractGitlabFactoryParametersResolver extends BaseFactoryParameterResolver implements FactoryParametersResolver { private final URLFetcher urlFetcher; private final AbstractGitlabUrlParser gitlabURLParser; private final PersonalAccessTokenManager personalAccessTokenManager; private final String providerName; public AbstractGitlabFactoryParametersResolver( URLFactoryBuilder urlFactoryBuilder, URLFetcher urlFetcher, AbstractGitlabUrlParser gitlabURLParser, PersonalAccessTokenManager personalAccessTokenManager, AuthorisationRequestManager authorisationRequestManager, String providerName) { super(authorisationRequestManager, urlFactoryBuilder, providerName); this.urlFetcher = urlFetcher; this.gitlabURLParser = gitlabURLParser; this.personalAccessTokenManager = personalAccessTokenManager; this.providerName = providerName; } /** * Check if this resolver can be used with the given parameters. * * @param factoryParameters map of parameters dedicated to factories * @return true if it will be accepted by the resolver implementation or false if it is not * accepted */ @Override public boolean accept(@NotNull final Map factoryParameters) { return factoryParameters.containsKey(URL_PARAMETER_NAME) && gitlabURLParser.isValid(factoryParameters.get(URL_PARAMETER_NAME)); } @Override public String getProviderName() { return providerName; } /** * Create factory object based on provided parameters * * @param factoryParameters map containing factory data parameters provided through URL * @throws BadRequestException when data are invalid */ @Override public FactoryMetaDto createFactory(@NotNull final Map factoryParameters) throws ApiException { // no need to check null value of url parameter as accept() method has performed the check final GitlabUrl gitlabUrl = gitlabURLParser.parse( factoryParameters.get(URL_PARAMETER_NAME), factoryParameters.get(REVISION_PARAMETER_NAME)); // create factory from the following location if location exists, else create default factory return createFactory( factoryParameters, gitlabUrl, new GitlabFactoryVisitor(gitlabUrl), new GitlabAuthorizingFileContentProvider( gitlabUrl, urlFetcher, personalAccessTokenManager)); } /** * Visitor that puts the default devfile or updates devfile projects into the Gitlab Factory, if * needed. */ private class GitlabFactoryVisitor implements FactoryVisitor { private final GitlabUrl gitlabUrl; private GitlabFactoryVisitor(GitlabUrl gitlabUrl) { this.gitlabUrl = gitlabUrl; } @Override public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { ScmInfoDto scmInfo = newDto(ScmInfoDto.class) .withScmProviderName(gitlabUrl.getProviderName()) .withRepositoryUrl(gitlabUrl.repositoryLocation()); if (gitlabUrl.getBranch() != null) { scmInfo.withBranch(gitlabUrl.getBranch()); } return factoryDto.withScmInfo(scmInfo); } } @Override public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException { return gitlabURLParser.parse(factoryUrl, null); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabOAuthTokenFetcher.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static java.lang.String.format; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.util.Optional; import java.util.Set; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.security.oauth.OAuthAPI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** GitLab OAuth token retriever. */ public class AbstractGitlabOAuthTokenFetcher implements PersonalAccessTokenFetcher { private static final Logger LOG = LoggerFactory.getLogger(AbstractGitlabOAuthTokenFetcher.class); public static final Set DEFAULT_TOKEN_SCOPES = ImmutableSet.of("api", "write_repository"); private final OAuthAPI oAuthAPI; private final String serverUrl; private final String apiEndpoint; private final String providerName; public AbstractGitlabOAuthTokenFetcher( String serverUrl, String apiEndpoint, OAuthAPI oAuthAPI, String providerName) { this.serverUrl = trimEnd(serverUrl, '/'); this.apiEndpoint = apiEndpoint; this.providerName = providerName; this.oAuthAPI = oAuthAPI; } @Override public PersonalAccessToken refreshPersonalAccessToken(Subject cheSubject, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, true); } @Override public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, false); } private PersonalAccessToken fetchOrRefreshPersonalAccessToken( Subject cheSubject, String scmServerUrl, boolean forceRefreshToken) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { scmServerUrl = trimEnd(scmServerUrl, '/'); GitlabApiClient gitlabApiClient = getApiClient(scmServerUrl); if (gitlabApiClient == null || !gitlabApiClient.isConnected(scmServerUrl)) { LOG.debug("not a valid url {} for current fetcher ", scmServerUrl); return null; } if (oAuthAPI == null) { throw new ScmCommunicationException( format( "OAuth 2 is not configured for SCM provider [%s]. For details, refer " + "the documentation in section of SCM providers configuration.", providerName)); } OAuthToken oAuthToken; try { oAuthToken = forceRefreshToken ? oAuthAPI.refreshToken(providerName) : oAuthAPI.getOrRefreshToken(providerName); String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5); String tokenId = NameGenerator.generate("id-", 5); Optional> valid = isValid( new PersonalAccessTokenParams( scmServerUrl, providerName, tokenName, tokenId, oAuthToken.getToken(), null)); if (valid.isEmpty()) { throw buildScmUnauthorizedException(cheSubject); } else if (!valid.get().first) { throw new ScmCommunicationException( "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: " + DEFAULT_TOKEN_SCOPES); } return new PersonalAccessToken( scmServerUrl, providerName, cheSubject.getUserId(), valid.get().second, tokenName, tokenId, oAuthToken.getToken()); } catch (UnauthorizedException e) { throw buildScmUnauthorizedException(cheSubject); } catch (NotFoundException nfe) { throw new UnknownScmProviderException(nfe.getMessage(), scmServerUrl); } catch (ServerException | ForbiddenException | BadRequestException | ConflictException e) { LOG.warn(e.getMessage()); throw new ScmCommunicationException(e.getMessage(), e); } } private ScmUnauthorizedException buildScmUnauthorizedException(Subject cheSubject) { return new ScmUnauthorizedException( cheSubject.getUserName() + " is not authorized in " + providerName + " OAuth provider.", providerName, "2.0", getLocalAuthenticateUrl()); } @Override public Optional isValid(PersonalAccessToken personalAccessToken) { GitlabApiClient gitlabApiClient = getApiClient(personalAccessToken.getScmProviderUrl()); if (gitlabApiClient == null || !gitlabApiClient.isConnected(personalAccessToken.getScmProviderUrl())) { if (personalAccessToken.getScmTokenName().equals(providerName)) { gitlabApiClient = new GitlabApiClient(personalAccessToken.getScmProviderUrl()); } else { LOG.debug( "not a valid url {} for current fetcher ", personalAccessToken.getScmProviderUrl()); return Optional.empty(); } } if (personalAccessToken.getScmTokenName() != null && personalAccessToken.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { // validation OAuth token by special API call try { GitlabOauthTokenInfo info = gitlabApiClient.getOAuthTokenInfo(personalAccessToken.getToken()); return Optional.of(Sets.newHashSet(info.getScope()).containsAll(DEFAULT_TOKEN_SCOPES)); } catch (ScmItemNotFoundException | ScmCommunicationException | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } else { // validating personal access token from secret. Since PAT API is accessible only in // latest GitLab version, we just perform check by accessing something from API. try { GitlabUser user = gitlabApiClient.getUser(personalAccessToken.getToken()); if (personalAccessToken.getScmUserName().equals(user.getUsername())) { return Optional.of(Boolean.TRUE); } else { return Optional.of(Boolean.FALSE); } } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } } @Override public Optional> isValid(PersonalAccessTokenParams params) throws ScmCommunicationException { GitlabApiClient gitlabApiClient = getApiClient(params.getScmProviderUrl()); if (gitlabApiClient == null || !gitlabApiClient.isConnected(params.getScmProviderUrl())) { if (providerName.equals(params.getScmTokenName())) { gitlabApiClient = new GitlabApiClient(params.getScmProviderUrl()); } else { LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); return Optional.empty(); } } try { GitlabUser user = gitlabApiClient.getUser(params.getToken()); if (params.getScmTokenName() != null && params.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { // validation OAuth token by special API call GitlabOauthTokenInfo info = gitlabApiClient.getOAuthTokenInfo(params.getToken()); return Optional.of( Pair.of( Sets.newHashSet(info.getScope()).containsAll(DEFAULT_TOKEN_SCOPES) ? Boolean.TRUE : Boolean.FALSE, user.getUsername())); } // validating personal access token from secret. Since PAT API is accessible only in // latest GitLab version, we just perform check by accessing something from API. // TODO: add PAT scope validation return Optional.of(Pair.of(Boolean.TRUE, user.getUsername())); } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.empty(); } } private String getLocalAuthenticateUrl() { return apiEndpoint + "/oauth/authenticate?oauth_provider=" + providerName + "&scope=" + Joiner.on('+').join(DEFAULT_TOKEN_SCOPES) + "&request_method=POST&signature_method=rsa"; } private GitlabApiClient getApiClient(String serverUrl) { return serverUrl.equals(this.serverUrl) ? new GitlabApiClient(serverUrl) : null; } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabScmFileResolver.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; import java.io.IOException; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.factory.server.ScmFileResolver; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** GitLab specific SCM file resolver. */ public class AbstractGitlabScmFileResolver implements ScmFileResolver { private final AbstractGitlabUrlParser gitlabUrlParser; private final URLFetcher urlFetcher; private final PersonalAccessTokenManager personalAccessTokenManager; public AbstractGitlabScmFileResolver( AbstractGitlabUrlParser gitlabUrlParser, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { this.gitlabUrlParser = gitlabUrlParser; this.urlFetcher = urlFetcher; this.personalAccessTokenManager = personalAccessTokenManager; } @Override public boolean accept(String repository) { return gitlabUrlParser.isValid(repository); } @Override public String fileContent(String repository, String filePath) throws ApiException { GitlabUrl gitlabUrl = gitlabUrlParser.parse(repository, null); try { return fetchContent(gitlabUrl, filePath, false); } catch (DevfileException exception) { // This catch might mean that the authentication was rejected by user, try to repeat the fetch // without authentication flow. try { return fetchContent(gitlabUrl, filePath, true); } catch (DevfileException devfileException) { throw toApiException(devfileException); } } } private String fetchContent(GitlabUrl gitlabUrl, String filePath, boolean skipAuthentication) throws DevfileException, NotFoundException { try { GitlabAuthorizingFileContentProvider contentProvider = new GitlabAuthorizingFileContentProvider( gitlabUrl, urlFetcher, personalAccessTokenManager); return skipAuthentication ? contentProvider.fetchContentWithoutAuthentication(filePath) : contentProvider.fetchContent(filePath); } catch (IOException e) { throw new NotFoundException(e.getMessage()); } } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUrlParser.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.regex.Pattern.compile; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import jakarta.validation.constraints.NotNull; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; /** * Parser of String Gitlab URLs and provide {@link GitlabUrl} objects. * * @author Max Shaposhnyk */ public class AbstractGitlabUrlParser { private final DevfileFilenamesProvider devfileFilenamesProvider; private final PersonalAccessTokenManager personalAccessTokenManager; private final String providerName; private static final List gitlabUrlPatternTemplates = List.of( "^(?%s)://(?%s)(:(?%s))?/(?([^/]++/?)+)/-/tree/(?.++)(/)?", "^(?%s)://(?%s)(:(?%s))?/(?.*)"); // a wider one, should be // the last in // the list private final String gitlabSSHPatternTemplate = "^git@(?%s):(?.*)$"; // list private final List gitlabUrlPatterns = new ArrayList<>(); public AbstractGitlabUrlParser( String serverUrl, DevfileFilenamesProvider devfileFilenamesProvider, PersonalAccessTokenManager personalAccessTokenManager, String providerName) { this.devfileFilenamesProvider = devfileFilenamesProvider; this.personalAccessTokenManager = personalAccessTokenManager; this.providerName = providerName; if (isNullOrEmpty(serverUrl)) { gitlabUrlPatternTemplates.forEach( t -> gitlabUrlPatterns.add(compile(format(t, "https", "gitlab.com", 443)))); gitlabUrlPatterns.add(compile(format(gitlabSSHPatternTemplate, "gitlab.com"))); } else { String trimmedEndpoint = trimEnd(serverUrl, '/'); URI uri = URI.create(trimmedEndpoint); String schema = uri.getScheme(); // We support GitLab endpoints that include an extra path segment, e.g.: // https://gitlab-server.com/scm // and want to match repo URLs like: // https://gitlab-server.com/scm/group/project.git // // In that case, we treat "/scm" as part of the fixed endpoint prefix, not as part of the // repository path. // Review feedback: use "hostname" naming instead of "endpointWithoutScheme". final String hostname = uri.getAuthority(); final String endpointPath = Objects.toString(uri.getPath(), ""); // What if URI#getAuthority() is null? Fallback to string parsing. final String authority; if (hostname != null) { authority = hostname; } else { final int schemeSeparatorIdx = trimmedEndpoint.indexOf("://"); final String withoutScheme = schemeSeparatorIdx >= 0 ? trimmedEndpoint.substring(schemeSeparatorIdx + 3) : trimmedEndpoint; final int firstSlashIdx = withoutScheme.indexOf('/'); authority = firstSlashIdx >= 0 ? withoutScheme.substring(0, firstSlashIdx) : withoutScheme; } // authority may be: // - gitlab-server.com // - gitlab-server.com:8443 // - [2001:db8::1] // - [2001:db8::1]:8443 final String hostOnly; if (authority.startsWith("[")) { int closingBracket = authority.indexOf(']'); hostOnly = closingBracket > 0 ? authority.substring(0, closingBracket + 1) : authority; } else { int colonIdx = authority.indexOf(':'); hostOnly = colonIdx > 0 ? authority.substring(0, colonIdx) : authority; } // HTTP(S) patterns should match the configured endpoint prefix (including any extra path // segment). SSH pattern should match host only. // For HTTP(S) patterns we include any configured endpointPath and keep the port (if any) as a // part of the "host" group. This allows GitlabUrl#getProviderUrl() to return values like // "https://host:8443/scm" even though GitlabUrl models "host" and "port" separately. final String httpHostForRegex = Pattern.quote(authority + endpointPath); final String sshHostForRegex = Pattern.quote(hostOnly); for (String gitlabUrlPatternTemplate : gitlabUrlPatternTemplates) { gitlabUrlPatterns.add( compile(format(gitlabUrlPatternTemplate, schema, httpHostForRegex, uri.getPort()))); } gitlabUrlPatterns.add(compile(format(gitlabSSHPatternTemplate, sshHostForRegex))); } } private boolean isUserTokenPresent(String repositoryUrl) { Optional serverUrlOptional = getServerUrl(repositoryUrl); if (serverUrlOptional.isPresent()) { String serverUrl = serverUrlOptional.get(); try { Optional token = personalAccessTokenManager.get( EnvironmentContext.getCurrent().getSubject(), null, serverUrl, null); if (token.isPresent()) { PersonalAccessToken accessToken = token.get(); return accessToken.getScmTokenName().equals(providerName); } } catch (ScmConfigurationPersistenceException | ScmCommunicationException exception) { return false; } } return false; } public boolean isValid(@NotNull String url) { return gitlabUrlPatterns.stream() .anyMatch(pattern -> pattern.matcher(trimEnd(url, '/')).matches()) // If the Gitlab URL is not configured, try to find it in a manually added user namespace // token. || isUserTokenPresent(url) // Try to call an API request to see if the URL matches Gitlab. || isApiRequestRelevant(url); } private boolean isApiRequestRelevant(String repositoryUrl) { Optional serverUrlOptional = getServerUrl(repositoryUrl); if (serverUrlOptional.isPresent()) { GitlabApiClient gitlabApiClient = new GitlabApiClient(serverUrlOptional.get()); try { // If the token request catches the unauthorised error, it means that the provided url // belongs to Gitlab. gitlabApiClient.getOAuthTokenInfo(""); } catch (ScmUnauthorizedException e) { // Some Git providers e.g. Azure Devops Server, may return unauthorized exception on invalid // API request, but Gitlab API returns unauthorized error message in JSON format, so to be // sure that the URL belongs to Gitlab, we need to check if the error message is a valid // JSON. return isJsonValid(e.getMessage()); } catch (ScmItemNotFoundException | IllegalArgumentException | ScmCommunicationException e) { return false; } } return false; } private boolean isJsonValid(String message) { try { JsonObject unused = new JsonParser().parse(message).getAsJsonObject(); return true; } catch (Exception exception) { return false; } } /** * Converts an SSH git URL (git@host:path) to a parseable URI (ssh://git@host/path), handling IPv6 * addresses where colons in the address must not be replaced. */ private static String sshToUri(String url) { // Strip "git@" prefix String afterAt = url.substring(4); if (afterAt.startsWith("[")) { // IPv6: git@[2001:db8::1]:path -> ssh://git@[2001:db8::1]/path int closingBracket = afterAt.indexOf(']'); if (closingBracket >= 0 && closingBracket + 1 < afterAt.length()) { String host = afterAt.substring(0, closingBracket + 1); // Skip the colon separator after the closing bracket String path = afterAt.substring(closingBracket + 1); if (path.startsWith(":")) { path = path.substring(1); } return "ssh://git@" + host + "/" + path; } } // Regular hostname: git@host:path -> ssh://git@host/path return "ssh://" + url.replace(":", "/"); } private Optional getPatternMatcherByUrl(String url) { URI uri = URI.create(url.matches(format(gitlabSSHPatternTemplate, ".*")) ? sshToUri(url) : url); String scheme = uri.getScheme(); String host = uri.getHost(); // Handle IPv6 addresses: escape brackets for regex final String hostForRegex; if (host != null && host.startsWith("[") && host.endsWith("]")) { // IPv6 address - strip brackets, then re-add with regex escaping String ipv6 = host.substring(1, host.length() - 1); hostForRegex = "\\[" + Pattern.quote(ipv6) + "\\]"; } else if (host != null) { // Regular hostname - escape special regex characters hostForRegex = Pattern.quote(host); } else { hostForRegex = ""; } return gitlabUrlPatternTemplates.stream() .map(t -> compile(format(t, scheme, hostForRegex, uri.getPort())).matcher(url)) .filter(Matcher::matches) .findAny() .or( () -> { Matcher matcher = compile(format(gitlabSSHPatternTemplate, hostForRegex)).matcher(url); if (matcher.matches()) { return Optional.of(matcher); } return Optional.empty(); }); } private Optional getServerUrl(String repositoryUrl) { if (repositoryUrl.startsWith("git@")) { String substring = repositoryUrl.substring(4); // Handle IPv6 addresses in SSH URLs (e.g., git@[2001:db8::1]:repo) if (substring.startsWith("[")) { int closingBracket = substring.indexOf(']'); if (closingBracket > 0) { return Optional.of("https://" + substring.substring(0, closingBracket + 1)); } } return Optional.of("https://" + substring.substring(0, substring.indexOf(":"))); } // Use URI parsing to properly handle IPv6 addresses try { URI uri = URI.create(repositoryUrl); if (uri.getScheme() != null && uri.getHost() != null) { String authority = uri.getRawAuthority(); if (authority == null) { String host = uri.getHost(); boolean ipv6 = host != null && host.contains(":"); String hostForUrl = ipv6 ? "[" + host + "]" : host; int port = uri.getPort(); authority = port == -1 ? hostForUrl : hostForUrl + ":" + port; } if (authority != null) { String serverUrl = uri.getScheme() + "://" + authority; // Remove path and query from the server URL int authorityIdx = repositoryUrl.indexOf(authority); if (authorityIdx >= 0) { int pathIndex = authorityIdx + authority.length(); if (pathIndex < repositoryUrl.length() && repositoryUrl.charAt(pathIndex) == '/') { return Optional.of(serverUrl); } } } } } catch (IllegalArgumentException e) { // Fall through to old logic if URI parsing fails } // Fallback for non-standard URLs Matcher serverUrlMatcher = compile("[^/|:]/").matcher(repositoryUrl); if (serverUrlMatcher.find()) { return Optional.of( repositoryUrl.substring(0, repositoryUrl.indexOf(serverUrlMatcher.group()) + 1)); } return Optional.empty(); } /** * Parses url-s like https://gitlab.apps.cluster-327a.327a.example.opentlc.com/root/proj1.git into * {@link GitlabUrl} objects. */ public GitlabUrl parse(String url, @Nullable String revision) { String trimmedUrl = trimEnd(url, '/'); Optional matcherOptional = gitlabUrlPatterns.stream() .map(pattern -> pattern.matcher(trimmedUrl)) .filter(Matcher::matches) .findFirst() .or(() -> getPatternMatcherByUrl(trimmedUrl)); if (matcherOptional.isPresent()) { return parse(matcherOptional.get(), revision).withUrl(trimmedUrl); } else { throw new UnsupportedOperationException( "The gitlab integration is not configured properly and cannot be used at this moment." + "Please refer to docs to check the Gitlab integration instructions"); } } private GitlabUrl parse(Matcher matcher, @Nullable String revision) { String scheme = null; String port = null; try { scheme = matcher.group("scheme"); } catch (IllegalArgumentException e) { // ok no such group } String host = matcher.group("host"); try { port = matcher.group("port"); } catch (IllegalArgumentException e) { // ok no such group } String subGroups = trimEnd(matcher.group("subgroups"), '/'); if (subGroups.endsWith(".git")) { subGroups = subGroups.substring(0, subGroups.length() - 4); } String branchFromUrl = null; try { branchFromUrl = matcher.group("branch"); } catch (IllegalArgumentException e) { // ok no such group } return new GitlabUrl() .withHostName(host) .withPort(port) .withScheme(scheme) .withSubGroups(subGroups) .withBranch(isNullOrEmpty(branchFromUrl) ? revision : branchFromUrl) .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUserDataFetcher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import java.util.Set; import org.eclipse.che.api.factory.server.scm.*; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.annotation.Nullable; /** Gitlab OAuth token retriever. */ public class AbstractGitlabUserDataFetcher extends AbstractGitUserDataFetcher { private final String serverUrl; private final String apiEndpoint; private final String providerName; public static final Set DEFAULT_TOKEN_SCOPES = ImmutableSet.of("api", "write_repository", "openid"); private static final String GITLAB_SAAS_ENDPOINT = "https://gitlab.com"; public AbstractGitlabUserDataFetcher( @Nullable String serverUrl, String apiEndpoint, PersonalAccessTokenManager personalAccessTokenManager, String providerName) { super( providerName, isNullOrEmpty(serverUrl) ? GITLAB_SAAS_ENDPOINT : serverUrl, personalAccessTokenManager); this.serverUrl = super.oAuthProviderUrl; this.apiEndpoint = apiEndpoint; this.providerName = providerName; } @Override protected GitUserData fetchGitUserDataWithOAuthToken(String token) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { GitlabUser user = new GitlabApiClient(serverUrl).getUser(token); return new GitUserData(user.getName(), user.getEmail()); } @Override protected GitUserData fetchGitUserDataWithPersonalAccessToken( PersonalAccessToken personalAccessToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { GitlabUser user = new GitlabApiClient(personalAccessToken.getScmProviderUrl()) .getUser(personalAccessToken.getToken()); return new GitUserData(user.getName(), user.getEmail()); } protected String getLocalAuthenticateUrl() { return apiEndpoint + "/oauth/authenticate?oauth_provider=" + providerName + "&scope=" + Joiner.on('+').join(DEFAULT_TOKEN_SCOPES) + "&request_method=POST&signature_method=rsa"; } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.time.Duration.ofSeconds; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Charsets; import com.google.common.io.CharStreams; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.concurrent.Executors; import java.util.function.Function; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** GitLab API operations helper. */ public class GitlabApiClient { private static final Logger LOG = LoggerFactory.getLogger(GitlabApiClient.class); private final HttpClient httpClient; private final String serverUrl; private static final Duration DEFAULT_HTTP_TIMEOUT = ofSeconds(10); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public GitlabApiClient(String serverUrl) { this.serverUrl = trimEnd(serverUrl, '/'); this.httpClient = HttpClient.newBuilder() .executor( Executors.newCachedThreadPool( new ThreadFactoryBuilder() .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setNameFormat(GitlabApiClient.class.getName() + "-%d") .setDaemon(true) .build())) .connectTimeout(DEFAULT_HTTP_TIMEOUT) .version(HttpClient.Version.HTTP_1_1) .build(); } public GitlabUser getUser(String authenticationToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { final URI uri = URI.create(serverUrl + "/api/v4/user"); HttpRequest request = HttpRequest.newBuilder(uri) .headers("Authorization", "Bearer " + authenticationToken) .timeout(DEFAULT_HTTP_TIMEOUT) .build(); LOG.trace("executeRequest={}", request); return executeRequest( httpClient, request, inputStream -> { try { String result = CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8)); return OBJECT_MAPPER.readValue(result, GitlabUser.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } public GitlabPersonalAccessTokenInfo getPersonalAccessTokenInfo(String authenticationToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { final URI uri = URI.create(serverUrl + "/api/v4/personal_access_tokens/self"); HttpRequest request = HttpRequest.newBuilder(uri) .headers("Authorization", "Bearer " + authenticationToken) .timeout(DEFAULT_HTTP_TIMEOUT) .build(); LOG.trace("executeRequest={}", request); try { return executeRequest( httpClient, request, inputStream -> { try { String result = CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8)); return OBJECT_MAPPER.readValue(result, GitlabPersonalAccessTokenInfo.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } catch (ScmBadRequestException e) { throw new ScmCommunicationException(e.getMessage(), e); } } public GitlabOauthTokenInfo getOAuthTokenInfo(String authenticationToken) throws ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { final URI uri = URI.create(serverUrl + "/oauth/token/info"); HttpRequest request = HttpRequest.newBuilder(uri) .headers("Authorization", "Bearer " + authenticationToken) .timeout(DEFAULT_HTTP_TIMEOUT) .build(); LOG.trace("executeRequest={}", request); try { return executeRequest( httpClient, request, inputStream -> { try { String result = CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8)); return OBJECT_MAPPER.readValue(result, GitlabOauthTokenInfo.class); } catch (IOException e) { throw new UncheckedIOException(e); } }); } catch (ScmBadRequestException e) { throw new ScmCommunicationException(e.getMessage(), e); } } private T executeRequest( HttpClient httpClient, HttpRequest request, Function bodyConverter) throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { String provider = "http://gitlab.com".equals(serverUrl.toString()) ? "gitlab" : "gitlab-server"; try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); LOG.trace("executeRequest={} response {}", request, response.statusCode()); if (response.statusCode() == 200) { return bodyConverter.apply(response.body()); } else if (response.statusCode() == 204) { return null; } else { String body = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); switch (response.statusCode()) { case HTTP_BAD_REQUEST: throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); case HTTP_UNAUTHORIZED: throw new ScmUnauthorizedException(body, "gitlab", "v2", ""); default: throw new ScmCommunicationException( "Unexpected status code " + response.statusCode() + " " + response, response.statusCode(), provider); } } } catch (IOException | InterruptedException | UncheckedIOException e) { throw new ScmCommunicationException(e.getMessage(), e, provider); } } public boolean isConnected(String scmServerUrl) { return serverUrl.equals(scmServerUrl); } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static java.net.HttpURLConnection.HTTP_OK; import static java.time.Duration.ofSeconds; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.concurrent.Executors; import org.eclipse.che.api.factory.server.scm.AuthorizingFileContentProvider; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; /** Gitlab specific authorizing file content provider. */ class GitlabAuthorizingFileContentProvider extends AuthorizingFileContentProvider { private final HttpClient httpClient; private static final Duration DEFAULT_HTTP_TIMEOUT = ofSeconds(10); GitlabAuthorizingFileContentProvider( GitlabUrl gitlabUrl, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { super(gitlabUrl, urlFetcher, personalAccessTokenManager); this.httpClient = HttpClient.newBuilder() .executor( Executors.newCachedThreadPool( new ThreadFactoryBuilder() .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setNameFormat(GitlabAuthorizingFileContentProvider.class.getName() + "-%d") .setDaemon(true) .build())) .connectTimeout(DEFAULT_HTTP_TIMEOUT) .version(HttpClient.Version.HTTP_1_1) .build(); } @Override protected boolean isPublicRepository(GitlabUrl remoteFactoryUrl) { HttpRequest request = HttpRequest.newBuilder( URI.create( remoteFactoryUrl.getProviderUrl() + '/' + remoteFactoryUrl.getSubGroups())) .timeout(DEFAULT_HTTP_TIMEOUT) .build(); try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); return response.statusCode() == HTTP_OK; } catch (IOException | InterruptedException e) { return false; } } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Arrays; import java.util.Objects; @JsonIgnoreProperties(ignoreUnknown = true) public class GitlabOauthTokenInfo { private long resource_owner_id; private String[] scope; private long expires_in; private long created_at; public long getResource_owner_id() { return resource_owner_id; } public void setResource_owner_id(long resource_owner_id) { this.resource_owner_id = resource_owner_id; } public String[] getScope() { return scope; } public void setScope(String[] scope) { this.scope = scope; } public long getExpires_in() { return expires_in; } public void setExpires_in(long expires_in) { this.expires_in = expires_in; } public long getCreated_at() { return created_at; } public void setCreated_at(long created_at) { this.created_at = created_at; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } GitlabOauthTokenInfo info = (GitlabOauthTokenInfo) o; return resource_owner_id == info.resource_owner_id && expires_in == info.expires_in && created_at == info.created_at && Arrays.equals(scope, info.scope); } @Override public int hashCode() { int result = Objects.hash(resource_owner_id, expires_in, created_at); result = 31 * result + Arrays.hashCode(scope); return result; } @Override public String toString() { return "GitlabOauthTokenInfo{" + "resource_owner_id=" + resource_owner_id + ", scope=" + Arrays.toString(scope) + ", expires_in=" + expires_in + ", created_at=" + created_at + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Arrays; import java.util.Objects; @JsonIgnoreProperties(ignoreUnknown = true) public class GitlabPersonalAccessTokenInfo { private int id; private String[] scopes; private String expires_at; private String created_at; public int getId() { return id; } public void setId(int id) { this.id = id; } public String[] getScopes() { return scopes; } public void setScopes(String[] scopes) { this.scopes = scopes; } public String getExpires_at() { return expires_at; } public void setExpires_at(String expires_at) { this.expires_at = expires_at; } public String getCreated_at() { return created_at; } public void setCreated_at(String created_at) { this.created_at = created_at; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } GitlabPersonalAccessTokenInfo info = (GitlabPersonalAccessTokenInfo) o; return id == info.id && Objects.equals(expires_at, info.expires_at) && created_at == info.created_at && Arrays.equals(scopes, info.scopes); } @Override public int hashCode() { int result = Objects.hash(id, expires_at, created_at); result = 31 * result + Arrays.hashCode(scopes); return result; } @Override public String toString() { return "GitlabOauthTokenInfo{" + "resource_owner_id=" + id + ", scope=" + Arrays.toString(scopes) + ", expires_at=" + expires_at + ", created_at=" + created_at + '}'; } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import static com.google.common.base.Strings.isNullOrEmpty; import static java.net.URLEncoder.encode; import com.google.common.base.Charsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.StringJoiner; import java.util.stream.Collectors; import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; /** * Representation of a gitlab URL, allowing to get details from it. * *

    like https://gitlab.com/path/to/repository or * https://gitlab.com/path/to/repository/-/tree/ * * @author Max Shaposhnyk */ public class GitlabUrl extends DefaultFactoryUrl { private final String NAME = "gitlab"; /** Hostname of the gitlab URL */ private String hostName; /** Port of the gitlab URL */ private String port; /** Scheme of the gitlab URL */ private String scheme; /** Project part of the gitlab URL */ private String project; /** * Incorporates subgroups in the project path of the gitlab URL. See details at: * https://docs.gitlab.com/ee/user/group/subgroups/ */ private String subGroups; /** Branch name */ private String branch; /** Devfile filenames list */ private final List devfileFilenames = new ArrayList<>(); /** * Creation of this instance is made by the parser so user may not need to create a new instance * directly */ protected GitlabUrl() {} @Override public String getProviderName() { return NAME; } @Override public String getProviderUrl() { return (isNullOrEmpty(scheme) ? "https" : scheme) + "://" + hostName + (isNullOrEmpty(port) ? "" : ":" + port); } /** * Gets hostname of this gitlab url * * @return the project part */ public String getHostName() { return this.hostName; } public GitlabUrl withHostName(String hostName) { this.hostName = hostName; return this; } public GitlabUrl withPort(String port) { this.port = port; return this; } public GitlabUrl withScheme(String scheme) { this.scheme = scheme; return this; } /** * Gets project of this bitbucket url * * @return the project part */ public String getProject() { return this.project; } public String getSubGroups() { return subGroups; } protected GitlabUrl withSubGroups(String subGroups) { this.subGroups = subGroups; String[] subGroupsItems = subGroups.split("/"); this.project = subGroupsItems[subGroupsItems.length - 1]; // project (repository) name is the last item return this; } protected GitlabUrl withDevfileFilenames(List devfileFilenames) { this.devfileFilenames.addAll(devfileFilenames); return this; } @Override public void setDevfileFilename(String devfileName) { this.devfileFilenames.clear(); this.devfileFilenames.add(devfileName); } /** * Gets branch of this gitlab url * * @return the branch part */ public String getBranch() { return this.branch; } protected GitlabUrl withBranch(String branch) { if (!isNullOrEmpty(branch)) { this.branch = branch; } return this; } /** * Provides list of configured devfile filenames with locations * * @return list of devfile filenames and locations */ @Override public List devfileFileLocations() { return devfileFilenames.stream().map(this::createDevfileLocation).collect(Collectors.toList()); } private DevfileLocation createDevfileLocation(String devfileFilename) { return new DevfileLocation() { @Override public Optional filename() { return Optional.of(devfileFilename); } @Override public String location() { return rawFileLocation(devfileFilename); } }; } /** * Provides location to raw content of specified file * * @return location of specified file in a repository */ public String rawFileLocation(String fileName) { String resultUrl = new StringJoiner("/") .add( (isNullOrEmpty(scheme) ? "https" : scheme) + "://" + hostName + (isNullOrEmpty(port) ? "" : ":" + port)) .add("api/v4/projects") // use URL-encoded path to the project as a selector instead of id .add(encode(subGroups, Charsets.UTF_8)) .add("repository") .add("files") .add(encode(fileName, Charsets.UTF_8)) .add("raw?ref=" + (isNullOrEmpty(branch) ? "HEAD" : branch)) .toString(); return resultUrl; } /** * Provides location to the repository part of the full gitlab URL. * * @return location of the repository. */ protected String repositoryLocation() { if (isNullOrEmpty(scheme)) { return "git@" + hostName + ":" + subGroups + ".git"; } return scheme + "://" + hostName + (isNullOrEmpty(port) ? "" : ":" + port) + "/" + subGroups + ".git"; } } ================================================ FILE: wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.server.gitlab; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Objects; @JsonIgnoreProperties(ignoreUnknown = true) public class GitlabUser { private long id; private String username; private String email; private String name; private String state; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getState() { return state; } public void setState(String state) { this.state = state; } @Override public String toString() { return "GitlabUser{" + "id=" + id + ", userame='" + username + '\'' + ", email='" + email + '\'' + ", name='" + name + '\'' + ", state='" + state + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } GitlabUser that = (GitlabUser) o; return id == that.id && Objects.equals(username, that.username) && Objects.equals(email, that.email) && Objects.equals(name, that.name) && Objects.equals(state, that.state); } @Override public int hashCode() { return Objects.hash(id, username, email, name, state); } } ================================================ FILE: wsmaster/che-core-api-factory-shared/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-factory-shared jar Che Core :: API :: Factory :: Shared ${project.build.directory}/generated-sources/dto/ false com.google.code.gson gson org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-model provided org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} maven-compiler-plugin pre-compile generate-sources compile org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} server process-sources generate org.eclipse.che.api.factory.shared.dto ${dto-generator-out-directory} org.eclipse.che.api.factory.shared.dto.server.DtoServerImpls server org.eclipse.che.core che-core-api-factory-shared ${project.version} org.eclipse.che.core che-core-api-model ${project.version} ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/Constants.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared; import java.util.Map; /** * Constants for Factory API. * * @author Anton Korneta */ public final class Constants { public static final String CURRENT_VERSION = "4.0"; // factory links rel attributes public static final String IMAGE_REL_ATT = "image"; public static final String RETRIEVE_FACTORY_REL_ATT = "self"; public static final String FACTORY_ACCEPTANCE_REL_ATT = "accept"; public static final String NAMED_FACTORY_ACCEPTANCE_REL_ATT = "accept-named"; // url factory parameter names public static final String URL_PARAMETER_NAME = "url"; public static final String REVISION_PARAMETER_NAME = "revision"; public static final Map DEFAULT_DEVFILE = Map.of("schemaVersion", "2.3.0"); private Constants() {} } ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/AuthorDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.factory.Author; import org.eclipse.che.dto.shared.DTO; /** * Describes author of the factory * * @author Alexander Garagatyi */ @DTO public interface AuthorDto extends Author { /** Id of user that create factory, set by the server */ @Override @FactoryParameter(obligation = OPTIONAL, setByServer = true) String getUserId(); void setUserId(String userId); AuthorDto withUserId(String userId); /** * @return Creation time of factory, set by the server (in milliseconds, from Unix epoch, no * timezone) */ @Override @FactoryParameter(obligation = OPTIONAL, setByServer = true) Long getCreated(); void setCreated(Long created); AuthorDto withCreated(Long created); /** Name of the author */ @FactoryParameter(obligation = OPTIONAL) String getName(); void setName(String name); AuthorDto withName(String name); /** Email of the author */ @FactoryParameter(obligation = OPTIONAL) String getEmail(); void setEmail(String email); AuthorDto withEmail(String email); } ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDevfileV2Dto.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.dto.shared.JsonFieldName; /** * Factory DTO for Devfile v2. As che-server don't know the structure of Devfile v2, we're using * just generic {@code Map} here. */ @DTO public interface FactoryDevfileV2Dto extends FactoryMetaDto, Hyperlinks { @Override default FactoryMetaDto acceptVisitor(FactoryVisitor visitor) { return visitor.visit(this); } @Override FactoryDevfileV2Dto withV(String v); @FactoryParameter(obligation = MANDATORY) Map getDevfile(); void setDevfile(Map devfile); FactoryDevfileV2Dto withDevfile(Map devfile); @FactoryParameter(obligation = OPTIONAL) @JsonFieldName("scm_info") ScmInfoDto getScmInfo(); FactoryDevfileV2Dto withScmInfo(ScmInfoDto scmInfo); @Override FactoryDevfileV2Dto withSource(String source); @Override FactoryDevfileV2Dto withLinks(List links); } ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryMetaDto.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import java.util.List; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; import org.eclipse.che.api.core.rest.shared.dto.Link; /** Ancestor for Factory DTOs that does not know about devfile version it will hold. */ public interface FactoryMetaDto extends Hyperlinks { /** * Gives an option to update the factory based on devfile version. See {@link FactoryVisitor}. * * @param visitor visitor that should update the factory * @return updated factory */ FactoryMetaDto acceptVisitor(FactoryVisitor visitor); @FactoryParameter(obligation = MANDATORY) String getV(); void setV(String v); FactoryMetaDto withV(String v); /** * Indicates filename in repository from which the factory was created (for example, .devfile) or * just contains 'repo' value if factory was created from bare GitHub repository. For custom raw * URL's (pastebin, gist etc) value is {@code null} */ @FactoryParameter(obligation = OPTIONAL, setByServer = true) String getSource(); void setSource(String source); FactoryMetaDto withSource(String source); @FactoryParameter(obligation = OPTIONAL) String getName(); void setName(String name); FactoryMetaDto withName(String name); @FactoryParameter(obligation = OPTIONAL, setByServer = true) String getId(); void setId(String id); FactoryMetaDto withId(String id); @FactoryParameter(obligation = OPTIONAL) AuthorDto getCreator(); void setCreator(AuthorDto creator); FactoryMetaDto withCreator(AuthorDto creator); @Override FactoryMetaDto withLinks(List links); @FactoryParameter(obligation = OPTIONAL, trackedOnly = true) PoliciesDto getPolicies(); void setPolicies(PoliciesDto policies); FactoryMetaDto withPolicies(PoliciesDto policies); @FactoryParameter(obligation = OPTIONAL) IdeDto getIde(); void setIde(IdeDto ide); FactoryMetaDto withIde(IdeDto ide); } ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryVisitor.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared.dto; /** * Visitor that allows us to do necessary updates to the factory, like include default devfile, set * project url, set branch of the project etc. */ public interface FactoryVisitor { /** * Visit factory with devfile v2. * *

    Implementation should update given factory and give it back. * *

    Che-server does not know devfile v2 structure so most likely we don't want to do anything * with it. The default implementation is here for that reason. * * @param factoryDto factory to visit * @return update factory */ default FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { // most likely nothing to do with Devfile v2 factory as we don't know or touch the structure return factoryDto; } } ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/IdeActionDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import java.util.Map; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.factory.Action; import org.eclipse.che.dto.shared.DTO; /** * Describe ide action. * * @author Sergii Kabashniuk */ @DTO public interface IdeActionDto extends Action { /** * Action Id * * @return id of action. */ @Override @FactoryParameter(obligation = OPTIONAL) String getId(); void setId(String id); IdeActionDto withId(String id); /** * * * * @return Action properties */ @Override @FactoryParameter(obligation = OPTIONAL) Map getProperties(); void setProperties(Map properties); IdeActionDto withProperties(Map properties); } ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/IdeDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.factory.Ide; import org.eclipse.che.dto.shared.DTO; /** * Describe IDE interface Look and Feel * * @author Sergii Kabashniuk */ @DTO public interface IdeDto extends Ide { /** * @return configuration of IDE on application loaded event. */ @Override @FactoryParameter(obligation = OPTIONAL) OnAppLoadedDto getOnAppLoaded(); void setOnAppLoaded(OnAppLoadedDto onAppLoaded); IdeDto withOnAppLoaded(OnAppLoadedDto onAppLoaded); /** * @return configuration of IDE on application closed event. */ @Override @FactoryParameter(obligation = OPTIONAL) OnAppClosedDto getOnAppClosed(); void setOnAppClosed(OnAppClosedDto onAppClosed); IdeDto withOnAppClosed(OnAppClosedDto onAppClosed); /** * @return configuration of IDE on projects loaded event. */ @Override @FactoryParameter(obligation = OPTIONAL) OnProjectsLoadedDto getOnProjectsLoaded(); void setOnProjectsLoaded(OnProjectsLoadedDto onProjectsLoaded); IdeDto withOnProjectsLoaded(OnProjectsLoadedDto onProjectsLoaded); } ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppClosedDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import java.util.List; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.factory.OnAppClosed; import org.eclipse.che.dto.shared.DTO; /** * Describe IDE look and feel on application closed event. * * @author Sergii Kabashniuk */ @DTO public interface OnAppClosedDto extends OnAppClosed { /** * @return actions for current event. */ @Override @FactoryParameter(obligation = OPTIONAL) List getActions(); void setActions(List actions); OnAppClosedDto withActions(List actions); } ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppLoadedDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import java.util.List; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.factory.OnAppLoaded; import org.eclipse.che.dto.shared.DTO; /** * Describe IDE look and feel on application loaded event. * * @author Sergii Kabashniuk */ @DTO public interface OnAppLoadedDto extends OnAppLoaded { /** * @return actions for current event. */ @Override @FactoryParameter(obligation = OPTIONAL) List getActions(); void setActions(List actions); OnAppLoadedDto withActions(List actions); } ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnProjectsLoadedDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import java.util.List; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.factory.OnProjectsLoaded; import org.eclipse.che.dto.shared.DTO; /** * Describe IDE look and feel on project opened event. * * @author Sergii Kabashniuk */ @DTO public interface OnProjectsLoadedDto extends OnProjectsLoaded { /** * @return actions for current event. */ @Override @FactoryParameter(obligation = OPTIONAL) List getActions(); void setActions(List actions); OnProjectsLoadedDto withActions(List actions); } ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/PoliciesDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.factory.Policies; import org.eclipse.che.dto.shared.DTO; /** * Describe restrictions of the factory * * @author andrew00x * @author Alexander Garagatyi */ @DTO public interface PoliciesDto extends Policies { /** Restrict access if referer header doesn't match this field */ // Do not change referer to referrer @Override @FactoryParameter(obligation = OPTIONAL) String getReferer(); void setReferer(String referer); PoliciesDto withReferer(String referer); /** Restrict access for factories used earlier then author supposes */ @Override @FactoryParameter(obligation = OPTIONAL) Long getSince(); void setSince(Long since); PoliciesDto withSince(Long since); /** Restrict access for factories used later then author supposes */ @Override @FactoryParameter(obligation = OPTIONAL) Long getUntil(); void setUntil(Long until); PoliciesDto withUntil(Long until); /** Workspace creation strategy */ @Override @FactoryParameter(obligation = OPTIONAL) String getCreate(); void setCreate(String create); PoliciesDto withCreate(String create); } ================================================ FILE: wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/ScmInfoDto.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.factory.shared.dto; import org.eclipse.che.api.core.model.factory.ScmInfo; import org.eclipse.che.dto.shared.DTO; import org.eclipse.che.dto.shared.JsonFieldName; @DTO public interface ScmInfoDto extends ScmInfo { @Override @JsonFieldName("scm_provider") String getScmProviderName(); void setScmProviderName(String scmProviderName); ScmInfoDto withScmProviderName(String scmProviderName); @Override @JsonFieldName("clone_url") String getRepositoryUrl(); void setRepositoryUrl(String repositoryUrl); ScmInfoDto withRepositoryUrl(String repositoryUrl); @Override String getBranch(); void setBranch(String branch); ScmInfoDto withBranch(String branch); } ================================================ FILE: wsmaster/che-core-api-logger/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-logger jar Che Core :: API :: Logger false ch.qos.logback logback-classic com.google.guava guava com.google.inject guice io.swagger.core.v3 swagger-annotations-jakarta jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-logger-shared org.eclipse.che.core che-core-api-workspace-shared org.slf4j slf4j-api jakarta.ws.rs jakarta.ws.rs-api provided commons-fileupload commons-fileupload test io.rest-assured rest-assured test org.eclipse.che.core che-core-commons-test test org.everrest everrest-assured test org.everrest everrest-core test org.everrest everrest-test test org.hamcrest hamcrest test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test ================================================ FILE: wsmaster/che-core-api-logger/src/main/java/org/eclipse/che/api/logger/ErrorRuntimeLogEventLogger.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.logger; import static com.google.common.base.Strings.isNullOrEmpty; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.api.workspace.shared.dto.event.RuntimeLogEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The goal of this class is to catch all RuntimeLogEvent events from error stream and dump them to * slf4j log. */ @Singleton public class ErrorRuntimeLogEventLogger implements EventSubscriber { private static final Logger LOG = LoggerFactory.getLogger(ErrorRuntimeLogEventLogger.class); @Inject public void subscribe(EventService eventService) { eventService.subscribe(this, RuntimeLogEvent.class); } @Override public void onEvent(RuntimeLogEvent event) { if ("stderr".equalsIgnoreCase(event.getStream()) && !isNullOrEmpty(event.getText())) { RuntimeIdentityDto identity = event.getRuntimeId(); LOG.error( "{} error from owner=`{}` env=`{}` workspace=`{}` text=`{}` time=`{}`", event.getMachineName() != null ? "Machine `" + event.getMachineName() + "`" : "Runtime", identity.getOwnerId(), identity.getEnvName(), identity.getWorkspaceId(), event.getText(), event.getTime()); } } } ================================================ FILE: wsmaster/che-core-api-logger/src/main/java/org/eclipse/che/api/logger/LoggerService.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.logger; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static org.eclipse.che.dto.server.DtoFactory.newDto; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import java.util.List; import java.util.stream.Collectors; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.logger.shared.dto.LoggerDto; import org.slf4j.LoggerFactory; /** * Defines Logger REST API. It allows to manage the loggers (with log level) dynamically. * * @author Florent Benoit */ @Tag(name = "logger", description = "Logger REST API") @Path("/logger") public class LoggerService extends Service { @GET @Path("/{name}") @Produces(APPLICATION_JSON) @Operation( summary = "Get the logger level for the given logger", responses = { @ApiResponse( responseCode = "200", description = "The response contains requested logger entity"), @ApiResponse( responseCode = "404", description = "The logger with specified name does not exist") }) public LoggerDto getLoggerByName( @Parameter(description = "logger name") @PathParam("name") String name) throws NotFoundException { return asDto(getLogger(name)); } @GET @Produces(APPLICATION_JSON) @Operation( summary = "Get loggers which are configured. This operation can be performed only by authorized user", responses = { @ApiResponse( responseCode = "200", description = "The loggers successfully fetched", content = @Content(array = @ArraySchema(schema = @Schema(implementation = LoggerDto.class)))), }) public List getLoggers( @Parameter(description = "The number of the items to skip") @DefaultValue("0") @QueryParam("skipCount") Integer skipCount, @Parameter(description = "The limit of the items in the response, default is 30") @DefaultValue("30") @QueryParam("maxItems") Integer maxItems) { LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); return loggerContext.getLoggerList().stream() .filter(log -> log.getLevel() != null || log.iteratorForAppenders().hasNext()) .skip(skipCount) .limit(maxItems) .map(this::asDto) .collect(Collectors.toList()); } protected LoggerDto asDto(final Logger log) { return newDto(LoggerDto.class).withName(log.getName()).withLevel(log.getLevel().levelStr); } @PUT @Path("/{name}") @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) @Operation( summary = "Update the logger level", responses = { @ApiResponse(responseCode = "200", description = "The logger successfully updated"), }) public LoggerDto updateLogger( @Parameter(description = "logger name") @PathParam("name") String name, LoggerDto update) throws NotFoundException { Logger logger = getLogger(name); logger.setLevel(Level.toLevel(update.getLevel())); return asDto(logger); } @POST @Path("/{name}") @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) @Operation( summary = "Create a new logger level", responses = { @ApiResponse(responseCode = "200", description = "The logger successfully created"), }) public LoggerDto createLogger( @Parameter(description = "logger name") @PathParam("name") String name, LoggerDto createdLogger) throws NotFoundException { Logger logger = getLogger(name, false); logger.setLevel(Level.toLevel(createdLogger.getLevel())); return asDto(logger); } /** Check if given logger exists */ protected Logger getLogger(String name) throws NotFoundException { return getLogger(name, true); } /** * Gets a logger, if checkLevel is true and if logger has no level defined it will return a * NameNotFound exception */ protected Logger getLogger(String name, boolean checkLevel) throws NotFoundException { LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); Logger log = loggerContext.getLogger(name); if (checkLevel && log.getLevel() == null) { throw new NotFoundException("The logger with name " + name + " is not existing."); } return log; } } ================================================ FILE: wsmaster/che-core-api-logger/src/main/java/org/eclipse/che/api/logger/deploy/LoggerModule.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.logger.deploy; import com.google.inject.AbstractModule; import org.eclipse.che.api.logger.ErrorRuntimeLogEventLogger; public class LoggerModule extends AbstractModule { @Override protected void configure() { bind(org.eclipse.che.api.logger.LoggerService.class); bind(ErrorRuntimeLogEventLogger.class).asEagerSingleton(); } } ================================================ FILE: wsmaster/che-core-api-logger/src/test/java/org/eclipse/che/api/logger/LoggerServiceTest.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.logger; import static io.restassured.RestAssured.given; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; import static org.everrest.assured.JettyHttpServer.SECURE_PATH; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import io.restassured.response.Response; import java.util.Collections; import java.util.List; import org.eclipse.che.api.core.rest.ApiExceptionMapper; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; import org.eclipse.che.api.logger.shared.dto.LoggerDto; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.dto.server.DtoFactory; import org.everrest.assured.EverrestJetty; import org.everrest.core.Filter; import org.everrest.core.GenericContainerRequest; import org.everrest.core.RequestFilter; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests for {@link org.eclipse.che.api.logger.LoggerService}. * * @author Florent Benoit */ @Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class}) public class LoggerServiceTest { @SuppressWarnings("unused") private static final ApiExceptionMapper MAPPER = new ApiExceptionMapper(); private static final String NAMESPACE = "user"; private static final String USER_ID = "user123"; private static final String LOGGER_NAME = "org.eclipse.che.api-sample-logger"; @SuppressWarnings("unused") private static final EnvironmentFilter FILTER = new EnvironmentFilter(); private LoggerService loggerService; @BeforeMethod public void setup() { loggerService = new LoggerService(); } @Test public void shouldGetLogger() { final Response response = given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/logger/ROOT"); assertEquals(response.getStatusCode(), 200); LoggerDto remoteLoggerDto = DtoFactory.getInstance().createDtoFromJson(response.body().print(), LoggerDto.class); assertNotNull(remoteLoggerDto); assertEquals(remoteLoggerDto.getName(), "ROOT"); assertEquals(remoteLoggerDto.getLevel(), "INFO"); } @Test public void shouldGetNotFoundExceptionOnUnknownLogger() { final Response response = given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/logger/unknown"); assertEquals(response.getStatusCode(), 404); assertEquals( DtoFactory.getInstance() .createDtoFromJson(response.body().print(), ServiceError.class) .getMessage(), "The logger with name unknown is not existing."); } @Test public void shouldGetLoggers() throws Exception { final Response response = given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/logger"); assertEquals(response.getStatusCode(), 200); List loggers = unwrapDtoList(response, LoggerDto.class); LoggerDto rootLoggerDto = newDto(LoggerDto.class).withName("ROOT").withLevel("INFO"); assertTrue(loggers.contains(rootLoggerDto)); } @Test public void shouldCreateLogger() throws Exception { LoggerDto toCreateLoggerDto = newDto(LoggerDto.class).withName(LOGGER_NAME).withLevel("INFO"); final Response response = given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(toCreateLoggerDto) .when() .post(SECURE_PATH + "/logger/" + LOGGER_NAME); assertEquals(response.getStatusCode(), 200); List loggers = this.loggerService.getLoggers(0, 30); assertTrue(loggers.contains(toCreateLoggerDto)); } @Test(dependsOnMethods = "shouldCreateLogger") public void shouldUpdateLogger() throws Exception { LoggerDto toUpdateLoggerDto = newDto(LoggerDto.class).withName(LOGGER_NAME).withLevel("DEBUG"); final Response response = given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType("application/json") .body(toUpdateLoggerDto) .when() .put(SECURE_PATH + "/logger/" + LOGGER_NAME); assertEquals(response.getStatusCode(), 200); List loggers = this.loggerService.getLoggers(0, 30); assertTrue(loggers.contains(toUpdateLoggerDto)); } @Test(dependsOnMethods = "shouldCreateLogger") public void shouldGetLoggerPaginateSkip() { final Response response = given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/logger/?skipCount=100"); assertEquals(response.getStatusCode(), 200); List loggers = unwrapDtoList(response, LoggerDto.class); assertEquals(loggers.size(), 0); } @Test(dependsOnMethods = "shouldCreateLogger") public void shouldGetLoggerPaginateLimit() { final Response response = given() .auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get(SECURE_PATH + "/logger/?maxItems=1"); assertEquals(response.getStatusCode(), 200); List loggers = unwrapDtoList(response, LoggerDto.class); assertEquals(loggers.size(), 1); } private static List unwrapDtoList(Response response, Class dtoClass) { return DtoFactory.getInstance().createListDtoFromJson(response.body().print(), dtoClass); } @Filter public static class EnvironmentFilter implements RequestFilter { @Override public void doFilter(GenericContainerRequest request) { EnvironmentContext.getCurrent() .setSubject(new SubjectImpl(NAMESPACE, Collections.emptyList(), USER_ID, "token", false)); } } } ================================================ FILE: wsmaster/che-core-api-logger/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: wsmaster/che-core-api-logger-shared/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-logger-shared jar Che Core :: API :: Logger :: Shared ${project.build.directory}/generated-sources/dto/ com.google.code.gson gson org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} generate-server-dto process-sources generate org.eclipse.che.api.logger.shared.dto ${dto-generator-out-directory} org.eclipse.che.api.logger.server.dto.DtoServerImpls server org.eclipse.che.core che-core-api-logger-shared ${project.version} maven-compiler-plugin pre-compile generate-sources compile org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} ================================================ FILE: wsmaster/che-core-api-logger-shared/src/main/java/org/eclipse/che/api/logger/shared/dto/LoggerDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.logger.shared.dto; import org.eclipse.che.dto.shared.DTO; @DTO public interface LoggerDto { public String getName(); public void setName(String name); public LoggerDto withName(String name); public String getLevel(); public void setLevel(String level); public LoggerDto withLevel(String level); } ================================================ FILE: wsmaster/che-core-api-metrics/extending-che-monitoring-metrics.adoc ================================================ // monitoring-che [id="extending-che-monitoring-metrics_che"] = Extending Che monitoring metrics This section describes how to create a metric or a group of metrics to extend the monitoring metrics that Che is exposing. Che has two major modules metrics: * `che-core-metrics-core` -- contains core metrics module * `che-core-api-metrics` -- contains metrics that are dependent on core Che components, such as workspace or user managers .Procedure * Create a class that extends the `MeterBinder` class. This allows to register the created metric in the overridden `bindTo(MeterRegistry registry)` method. + The following is an example of a metric that has a function that supplies the value for it: + .Example metric [source,java] ---- public class UserMeterBinder implements MeterBinder { private final UserManager userManager; @Inject public UserMeterBinder(UserManager userManager) { this.userManager = userManager; } @Override public void bindTo(MeterRegistry registry) { Gauge.builder("che.user.total", this::count) .description("Total amount of users") .register(registry); } private double count() { try { return userManager.getTotalCount(); } catch (ServerException e) { return Double.NaN; } } ---- + Alternatively, the metric can be stored with a reference and updated manually in other place in the code. .Additional resources * link:https://prometheus.io/docs/practices/naming/[Metric and label naming for Prometheus] * link:https://prometheus.io/docs/concepts/metric_types/[Metric types for Prometheus] ================================================ FILE: wsmaster/che-core-api-metrics/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-metrics jar Che Core :: API :: Metrics com.google.guava guava com.google.inject guice io.micrometer micrometer-core jakarta.inject jakarta.inject-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-api-user org.eclipse.che.core che-core-api-workspace-shared org.slf4j slf4j-api ch.qos.logback logback-classic test org.eclipse.che.core che-core-api-dto test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test ================================================ FILE: wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/RuntimeLogMeterBinder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import java.nio.charset.StandardCharsets; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.RuntimeLogEvent; /** Counts sent messages and bytes to runtime log by listening to {@link RuntimeLogEvent}s. */ @Singleton public class RuntimeLogMeterBinder implements MeterBinder { private final EventService eventService; private Counter messages; private Counter bytes; @Inject RuntimeLogMeterBinder(EventService eventService) { this.eventService = eventService; } @Override public void bindTo(MeterRegistry registry) { messages = Counter.builder("runtime_log_messages") .baseUnit("message") .description("number of sent messages to runtime log") .register(registry); bytes = Counter.builder("runtime_log_bytes") .baseUnit("byte") .description("number of sent bytes to runtime log") .register(registry); eventService.subscribe( (e) -> { messages.increment(); bytes.increment(e.getText().getBytes(StandardCharsets.UTF_8).length); }, RuntimeLogEvent.class); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/UserMeterBinder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.UserManager; /** Provide metric for number of users in Che database */ @Singleton public class UserMeterBinder implements MeterBinder { private final UserManager userManager; @Inject public UserMeterBinder(UserManager userManager) { this.userManager = userManager; } @Override public void bindTo(MeterRegistry registry) { Gauge.builder("che.user.total", this::count) .description("Total amount of users") .register(registry); } private double count() { try { return userManager.getTotalCount(); } catch (ServerException e) { return Double.NaN; } } } ================================================ FILE: wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WorkspaceBinders.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import java.util.Arrays; /** Utility methods for workspace meter binders. */ final class WorkspaceBinders { private static final String METRIC_NAME_PREFIX = "che.workspace."; private static final String[] STANDARD_TAGS = new String[] {"area", "workspace"}; private WorkspaceBinders() {} /** Produces a name for the workspace metric with the standard prefix. */ static String workspaceMetric(String name) { return METRIC_NAME_PREFIX + name; } /** * Produces a list of tags to add to the metric. The returned array always contains standard tags * common to all workspace metrics. * * @param tags the additional tags to add in addition to the standard tags * @return an array representing the tags */ static String[] withStandardTags(String... tags) { if (tags.length == 0) { return STANDARD_TAGS; } String[] ret = Arrays.copyOf(STANDARD_TAGS, STANDARD_TAGS.length + tags.length); System.arraycopy(tags, 0, ret, STANDARD_TAGS.length, tags.length); return ret; } } ================================================ FILE: wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WorkspaceFailureMeterBinder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static org.eclipse.che.api.metrics.WorkspaceBinders.withStandardTags; import static org.eclipse.che.api.metrics.WorkspaceBinders.workspaceMetric; import com.google.inject.Inject; import com.google.inject.Singleton; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; /** * Counts errors in workspaces while in different statuses. I.e. the errors while starting, running * or stopping are counted separately. The counter IDs only differ in the "while" tag which * specifies the workspace status in which the failure occurred. */ @Singleton public class WorkspaceFailureMeterBinder implements MeterBinder { private final EventService eventService; private Counter startingStoppedFailureCounter; private Counter stoppingStoppedFailureCounter; private Counter runningStoppedFailureCounter; @Inject public WorkspaceFailureMeterBinder(EventService eventService) { this.eventService = eventService; } @Override public void bindTo(MeterRegistry registry) { startingStoppedFailureCounter = bindFailureFrom(WorkspaceStatus.STARTING, registry); runningStoppedFailureCounter = bindFailureFrom(WorkspaceStatus.RUNNING, registry); stoppingStoppedFailureCounter = bindFailureFrom(WorkspaceStatus.STOPPING, registry); // only subscribe to the event once we have the counters ready eventService.subscribe( event -> { if (event.getError() == null || event.getStatus() != WorkspaceStatus.STOPPED || event.isInitiatedByUser()) { return; } Counter counter; switch (event.getPrevStatus()) { case STARTING: counter = startingStoppedFailureCounter; break; case RUNNING: counter = runningStoppedFailureCounter; break; case STOPPING: counter = stoppingStoppedFailureCounter; break; default: return; } counter.increment(); }, WorkspaceStatusEvent.class); } private Counter bindFailureFrom(WorkspaceStatus previousState, MeterRegistry registry) { // there's apparently a convention to suffix the counters with "_total" (which is what the name // will end up looking like). return Counter.builder(workspaceMetric("failure.total")) .tags(withStandardTags("while", previousState.name())) .description("The count of failed workspaces") .register(registry); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WorkspaceInterruptedStartAttemptsMeterBinder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static org.eclipse.che.api.metrics.WorkspaceBinders.workspaceMetric; import com.google.inject.Inject; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; /** Counts number of workspace startup interruption. */ @Singleton public class WorkspaceInterruptedStartAttemptsMeterBinder implements MeterBinder { private final EventService eventService; private Counter interruptionCounter; @Inject public WorkspaceInterruptedStartAttemptsMeterBinder(EventService eventService) { this.eventService = eventService; } @Override public void bindTo(MeterRegistry registry) { interruptionCounter = Counter.builder(workspaceMetric("start.interrupt.total")) .description("The count of workspace startup interruption") .register(registry); // only subscribe to the event once we have the counters ready eventService.subscribe( event -> { if (event.getPrevStatus() == WorkspaceStatus.STARTING && event.getStatus() == WorkspaceStatus.STOPPED && event.isInitiatedByUser()) { interruptionCounter.increment(); } }, WorkspaceStatusEvent.class); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WorkspaceStartAttemptsMeterBinder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static java.lang.Boolean.FALSE; import static org.eclipse.che.api.metrics.WorkspaceBinders.withStandardTags; import static org.eclipse.che.api.metrics.WorkspaceBinders.workspaceMetric; import static org.eclipse.che.api.workspace.shared.Constants.DEBUG_WORKSPACE_START; import com.google.inject.Inject; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; /** Counts number of attempts to start workspace. */ @Singleton public class WorkspaceStartAttemptsMeterBinder implements MeterBinder { private final EventService eventService; private Counter startingCounter; private Counter startingDebugCounter; @Inject public WorkspaceStartAttemptsMeterBinder(EventService eventService) { this.eventService = eventService; } @Override public void bindTo(MeterRegistry registry) { startingCounter = Counter.builder(workspaceMetric("starting_attempts.total")) .tags(withStandardTags("debug", "false")) .description("The count of workspaces start attempts") .register(registry); startingDebugCounter = Counter.builder(workspaceMetric("starting_attempts.total")) .tags(withStandardTags("debug", "true")) .description("The count of workspaces start attempts in debug mode") .register(registry); // only subscribe to the event once we have the counters ready eventService.subscribe( event -> { if (event.getPrevStatus() == WorkspaceStatus.STOPPED && event.getStatus() == WorkspaceStatus.STARTING) { if (event.getOptions() != null && Boolean.parseBoolean( event.getOptions().getOrDefault(DEBUG_WORKSPACE_START, FALSE.toString()))) { startingDebugCounter.increment(); } else { startingCounter.increment(); } } }, WorkspaceStatusEvent.class); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WorkspaceStartTrackerMeterBinder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static org.eclipse.che.api.metrics.WorkspaceBinders.withStandardTags; import static org.eclipse.che.api.metrics.WorkspaceBinders.workspaceMetric; import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.binder.MeterBinder; import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * {@link MeterBinder} that is providing metrics about workspace successful or failed start time. */ @Singleton public class WorkspaceStartTrackerMeterBinder implements MeterBinder { private static final Logger LOG = LoggerFactory.getLogger(WorkspaceStartTrackerMeterBinder.class); private final EventService eventService; private final Map workspaceStartTime; private Timer successTimer; private Timer failTimer; @Inject public WorkspaceStartTrackerMeterBinder(EventService eventService) { this(eventService, new ConcurrentHashMap<>()); } @VisibleForTesting WorkspaceStartTrackerMeterBinder( EventService eventService, Map workspaceStartTime) { this.eventService = eventService; this.workspaceStartTime = workspaceStartTime; } @Override public void bindTo(MeterRegistry registry) { successTimer = Timer.builder(workspaceMetric("start.time")) .description("The time of workspace start") .tags(withStandardTags("result", "success")) .publishPercentileHistogram() .sla(Duration.ofSeconds(10)) .minimumExpectedValue(Duration.ofSeconds(10)) .maximumExpectedValue(Duration.ofMinutes(15)) .register(registry); failTimer = Timer.builder(workspaceMetric("start.time")) .description("The time of workspace start") .tags(withStandardTags("result", "fail")) .publishPercentileHistogram() .sla(Duration.ofSeconds(10)) .minimumExpectedValue(Duration.ofSeconds(10)) .maximumExpectedValue(Duration.ofMinutes(15)) .register(registry); // only subscribe to the event once we have the counters ready eventService.subscribe(this::handleWorkspaceStatusChange, WorkspaceStatusEvent.class); } private void handleWorkspaceStatusChange(WorkspaceStatusEvent event) { if (event.getPrevStatus() == WorkspaceStatus.STOPPED && event.getStatus() == WorkspaceStatus.STARTING) { workspaceStartTime.put(event.getWorkspaceId(), System.currentTimeMillis()); } else if (event.getPrevStatus() == WorkspaceStatus.STARTING) { Long startTime = workspaceStartTime.remove(event.getWorkspaceId()); if (startTime == null) { // this situation is possible when starting workspace is recovered or some bug happened LOG.warn("No workspace start time recorded for workspace {}", event.getWorkspaceId()); return; } if (event.getStatus() == WorkspaceStatus.RUNNING) { successTimer.record(Duration.ofMillis(System.currentTimeMillis() - startTime)); } else if (event.getStatus() == WorkspaceStatus.STOPPED) { failTimer.record(Duration.ofMillis(System.currentTimeMillis() - startTime)); } } } } ================================================ FILE: wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WorkspaceStopTrackerMeterBinder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.*; import static org.eclipse.che.api.metrics.WorkspaceBinders.withStandardTags; import static org.eclipse.che.api.metrics.WorkspaceBinders.workspaceMetric; import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.binder.MeterBinder; import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Singleton; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * {@link MeterBinder} that is providing metrics about workspace stop time. * *

    We're measuring these state transitions: * *

     *   RUNNING -> STOPPING -> STOPPED
     *   STARTING -> STOPPING -> STOPPED
     * 
    */ @Singleton public class WorkspaceStopTrackerMeterBinder implements MeterBinder { private static final Logger LOG = LoggerFactory.getLogger(WorkspaceStopTrackerMeterBinder.class); private final EventService eventService; private final Map workspaceStopTime; private Timer stopTimer; @Inject public WorkspaceStopTrackerMeterBinder(EventService eventService) { this(eventService, new ConcurrentHashMap<>()); } @VisibleForTesting WorkspaceStopTrackerMeterBinder(EventService eventService, Map workspaceStopTime) { this.eventService = eventService; this.workspaceStopTime = workspaceStopTime; } @Override public void bindTo(MeterRegistry registry) { stopTimer = Timer.builder(workspaceMetric("stop.time")) .description("The time of workspace stop") .tags(withStandardTags("result", "success")) .register(registry); // only subscribe to the event once we have the counters ready eventService.subscribe(this::handleWorkspaceStatusChange, WorkspaceStatusEvent.class); } private void handleWorkspaceStatusChange(WorkspaceStatusEvent event) { if ((event.getPrevStatus() == RUNNING || event.getPrevStatus() == STARTING) && event.getStatus() == STOPPING) { workspaceStopTime.put(event.getWorkspaceId(), System.currentTimeMillis()); } else if (event.getPrevStatus() == STOPPING) { Long stopTime = workspaceStopTime.remove(event.getWorkspaceId()); if (stopTime == null) { LOG.warn("No workspace stop time recorded for workspace {}", event.getWorkspaceId()); return; } if (event.getStatus() == STOPPED) { stopTimer.record(Duration.ofMillis(System.currentTimeMillis() - stopTime)); } else { LOG.error("Unexpected change of status from STOPPING to {}", event.getStatus()); return; } } } } ================================================ FILE: wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WorkspaceSuccessfulStartAttemptsMeterBinder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static org.eclipse.che.api.metrics.WorkspaceBinders.workspaceMetric; import com.google.inject.Inject; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; /** Counts number of successfully started workspaces. */ @Singleton public class WorkspaceSuccessfulStartAttemptsMeterBinder implements MeterBinder { private final EventService eventService; private Counter startedCounter; @Inject public WorkspaceSuccessfulStartAttemptsMeterBinder(EventService eventService) { this.eventService = eventService; } @Override public void bindTo(MeterRegistry registry) { startedCounter = Counter.builder(workspaceMetric("started.total")) .description("The count of started workspaces") .register(registry); // only subscribe to the event once we have the counters ready eventService.subscribe( event -> { if (event.getPrevStatus() == WorkspaceStatus.STARTING && event.getStatus() == WorkspaceStatus.RUNNING) { startedCounter.increment(); } }, WorkspaceStatusEvent.class); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WorkspaceSuccessfulStopAttemptsMeterBinder.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static org.eclipse.che.api.metrics.WorkspaceBinders.workspaceMetric; import com.google.inject.Inject; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; /** Counts number of successfully stopped workspaces. */ @Singleton public class WorkspaceSuccessfulStopAttemptsMeterBinder implements MeterBinder { private final EventService eventService; private Counter stoppedCounter; @Inject public WorkspaceSuccessfulStopAttemptsMeterBinder(EventService eventService) { this.eventService = eventService; } @Override public void bindTo(MeterRegistry registry) { stoppedCounter = Counter.builder(workspaceMetric("stopped.total")) .description("The count of stopped workspaces") .register(registry); // only subscribe to the event once we have the counters ready eventService.subscribe( event -> { if ((event.getError() == null) && (event.getPrevStatus() == WorkspaceStatus.STOPPING && event.getStatus() == WorkspaceStatus.STOPPED)) { stoppedCounter.increment(); } }, WorkspaceStatusEvent.class); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WsMasterMetricsModule.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import io.micrometer.core.instrument.binder.MeterBinder; /** * A Guice module to bind all our metric binders to a single multi-binder. The set of all metric * binders is used to produce the Prometheus metrics on request. */ public class WsMasterMetricsModule extends AbstractModule { @Override protected void configure() { Multibinder meterMultibinder = Multibinder.newSetBinder(binder(), MeterBinder.class); meterMultibinder.addBinding().to(WorkspaceFailureMeterBinder.class); meterMultibinder.addBinding().to(WorkspaceStartTrackerMeterBinder.class); meterMultibinder.addBinding().to(WorkspaceStopTrackerMeterBinder.class); meterMultibinder.addBinding().to(WorkspaceSuccessfulStartAttemptsMeterBinder.class); meterMultibinder.addBinding().to(WorkspaceSuccessfulStopAttemptsMeterBinder.class); meterMultibinder.addBinding().to(WorkspaceStartAttemptsMeterBinder.class); meterMultibinder.addBinding().to(WorkspaceInterruptedStartAttemptsMeterBinder.class); meterMultibinder.addBinding().to(UserMeterBinder.class); meterMultibinder.addBinding().to(RuntimeLogMeterBinder.class); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/UserMeterBinderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static java.lang.Double.NaN; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.UserManager; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class UserMeterBinderTest { @Mock private UserManager userManager; private MeterRegistry registry; @BeforeMethod public void setUp() { registry = new SimpleMeterRegistry(); UserMeterBinder meterBinder = new UserMeterBinder(userManager); meterBinder.bindTo(registry); } @Test public void shouldCollectUserCount() throws Exception { when(userManager.getTotalCount()).thenReturn(5L); assertEquals(registry.find("che.user.total").gauge().value(), 5.0); } @Test public void shouldCollectNaNUserCountIfExceptionOccursInManager() throws Exception { doThrow(ServerException.class).when(userManager).getTotalCount(); assertEquals(registry.find("che.user.total").gauge().value(), NaN); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceFailureMeterBinderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static java.util.Arrays.asList; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.eclipse.che.dto.server.DtoFactory; import org.mockito.ArgumentCaptor; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class WorkspaceFailureMeterBinderTest { private Collection failureCounters; private EventSubscriber events; @BeforeMethod public void setup() { MeterRegistry registry = new SimpleMeterRegistry(); EventService eventService = mock(EventService.class); WorkspaceFailureMeterBinder meterBinder = new WorkspaceFailureMeterBinder(eventService); meterBinder.bindTo(registry); @SuppressWarnings("unchecked") ArgumentCaptor> statusChangeEventCaptor = ArgumentCaptor.forClass(EventSubscriber.class); failureCounters = registry.find("che.workspace.failure.total").counters(); verify(eventService) .subscribe(statusChangeEventCaptor.capture(), eq(WorkspaceStatusEvent.class)); events = statusChangeEventCaptor.getValue(); } @Test(dataProvider = "failureWhileInStatus") public void shouldCollectFailureCountsPerStatus(WorkspaceStatus failureStatus) { events.onEvent( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(failureStatus) .withStatus(WorkspaceStatus.STOPPED) .withError("D'oh!") .withWorkspaceId("1")); List restOfCounters = new ArrayList<>(failureCounters); Counter counter = failureCounters.stream() .filter(c -> failureStatus.name().equals(c.getId().getTag("while"))) .findAny() .orElseThrow( () -> new AssertionError( "Could not find a counter for failure status " + failureStatus)); restOfCounters.remove(counter); assertEquals(counter.count(), 1d); restOfCounters.forEach(c -> assertEquals(c.count(), 0d)); } @Test(dataProvider = "failureWhileInStatus") public void shouldNotCollectFailureWhenNoErrorInEvent(WorkspaceStatus prevStatus) { events.onEvent( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(prevStatus) .withStatus(WorkspaceStatus.STOPPED) .withWorkspaceId("1")); failureCounters.forEach(c -> assertEquals(c.count(), 0d)); } @Test public void shouldNotCollectInterruptedEvent() { // given WorkspaceStatusEvent event = DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STARTING) .withStatus(WorkspaceStatus.STOPPED) .withInitiatedByUser(true) .withError("interrupted") .withWorkspaceId("1"); // when events.onEvent(event); // then failureCounters.forEach(c -> assertEquals(c.count(), 0d)); } @Test(dataProvider = "allStatusTransitionsWithoutToStopped") public void shouldNotCollectFailureWhenNotTransitioningToStopped( WorkspaceStatus from, WorkspaceStatus to) { // This really doesn't make much sense because the codebase always transitions the workspace // to STOPPED on any kind of failure. This is just a precaution that a potential bug in the // rest of the codebase doesn't affect the metric collection ;) events.onEvent( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(from) .withStatus(to) .withError("D'oh!") .withWorkspaceId("1")); failureCounters.forEach(c -> assertEquals(c.count(), 0d)); } @DataProvider public Object[][] failureWhileInStatus() { return new Object[][] { new Object[] {WorkspaceStatus.STARTING}, new Object[] {WorkspaceStatus.RUNNING}, new Object[] {WorkspaceStatus.STOPPING}, }; } @DataProvider public Object[][] allStatusTransitionsWithoutToStopped() { List> transitions = new ArrayList<>(9); for (WorkspaceStatus from : WorkspaceStatus.values()) { for (WorkspaceStatus to : WorkspaceStatus.values()) { if (from == to || to == WorkspaceStatus.STOPPED) { continue; } transitions.add(asList(from, to)); } } return transitions.stream().map(List::toArray).toArray(Object[][]::new); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceInterruptedStartAttemptsMeterBinderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static java.util.Arrays.asList; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.ArrayList; import java.util.List; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.eclipse.che.dto.server.DtoFactory; import org.mockito.ArgumentCaptor; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class WorkspaceInterruptedStartAttemptsMeterBinderTest { private EventSubscriber events; private Counter interruptedCounter; @BeforeMethod public void setup() { MeterRegistry registry = new SimpleMeterRegistry(); EventService eventService = mock(EventService.class); WorkspaceInterruptedStartAttemptsMeterBinder meterBinder = new WorkspaceInterruptedStartAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); @SuppressWarnings("unchecked") ArgumentCaptor> statusChangeEventCaptor = ArgumentCaptor.forClass(EventSubscriber.class); interruptedCounter = registry.find("che.workspace.start.interrupt.total").counter(); verify(eventService) .subscribe(statusChangeEventCaptor.capture(), eq(WorkspaceStatusEvent.class)); events = statusChangeEventCaptor.getValue(); } @Test public void shouldCountWorkspaceInterruptedEvent() { // given WorkspaceStatusEvent event = DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STARTING) .withStatus(WorkspaceStatus.STOPPED) .withInitiatedByUser(true) .withError("interrupted") .withWorkspaceId("1"); // when events.onEvent(event); // then Assert.assertEquals(interruptedCounter.count(), 1.0); } @Test public void shouldNotCountWorkspaceNonInterruptedEvent() { // given WorkspaceStatusEvent event = DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STARTING) .withStatus(WorkspaceStatus.STOPPED) .withInitiatedByUser(false) .withError("interrupted") .withWorkspaceId("1"); // when events.onEvent(event); // then Assert.assertEquals(interruptedCounter.count(), 0.0); } @Test(dataProvider = "allStatusTransitionsWithoutToStopped") public void shouldNotCollectInterruptionWhenNotTransitioningToStopped( WorkspaceStatus from, WorkspaceStatus to) { // This really doesn't make much sense because the codebase always transitions the workspace // to STOPPED on interruption. This is just a precaution that a potential bug in the // rest of the codebase doesn't affect the metric collection ;) events.onEvent( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(from) .withStatus(to) .withInitiatedByUser(true) .withError("D'oh!") .withWorkspaceId("1")); Assert.assertEquals(interruptedCounter.count(), 0.0); } @DataProvider public Object[][] allStatusTransitionsWithoutToStopped() { List> transitions = new ArrayList<>(9); for (WorkspaceStatus from : WorkspaceStatus.values()) { for (WorkspaceStatus to : WorkspaceStatus.values()) { if (from == to || to == WorkspaceStatus.STOPPED) { continue; } transitions.add(asList(from, to)); } } return transitions.stream().map(List::toArray).toArray(Object[][]::new); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceStartAttemptsMeterBinderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static java.util.Arrays.asList; import static java.util.Collections.singletonMap; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.ArrayList; import java.util.List; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.eclipse.che.dto.server.DtoFactory; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class WorkspaceStartAttemptsMeterBinderTest { private EventService eventService; private MeterRegistry registry; @BeforeMethod public void setUp() { eventService = new EventService(); registry = new SimpleMeterRegistry(); } @Test(dataProvider = "allStatusTransitionsWithoutStarting") public void shouldNotCollectEvents(WorkspaceStatus from, WorkspaceStatus to) { // given WorkspaceStartAttemptsMeterBinder meterBinder = new WorkspaceStartAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(from) .withStatus(to) .withWorkspaceId("id1")); // then Counter successful = registry.find("che.workspace.starting_attempts.total").counter(); Assert.assertEquals(successful.count(), 0.0); } @Test public void shouldCollectOnlyStart() { // given WorkspaceStartAttemptsMeterBinder meterBinder = new WorkspaceStartAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STOPPED) .withStatus(WorkspaceStatus.STARTING) .withWorkspaceId("id1")); // then Counter successful = registry.find("che.workspace.starting_attempts.total").tag("debug", "false").counter(); Assert.assertEquals(successful.count(), 1.0); Counter debug = registry.find("che.workspace.starting_attempts.total").tag("debug", "true").counter(); Assert.assertEquals(debug.count(), 0.0); } @Test public void shouldCollectOnlyDebugStart() { // given WorkspaceStartAttemptsMeterBinder meterBinder = new WorkspaceStartAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STOPPED) .withStatus(WorkspaceStatus.STARTING) .withWorkspaceId("id1") .withOptions(singletonMap(Constants.DEBUG_WORKSPACE_START, Boolean.TRUE.toString()))); // then Counter successful = registry.find("che.workspace.starting_attempts.total").tag("debug", "false").counter(); Assert.assertEquals(successful.count(), 0.0); Counter debug = registry.find("che.workspace.starting_attempts.total").tag("debug", "true").counter(); Assert.assertEquals(debug.count(), 1.0); } @Test public void shouldNotCollectDebugStartWhenSetToFalse() { // given WorkspaceStartAttemptsMeterBinder meterBinder = new WorkspaceStartAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STOPPED) .withStatus(WorkspaceStatus.STARTING) .withWorkspaceId("id1") .withOptions(singletonMap(Constants.DEBUG_WORKSPACE_START, Boolean.FALSE.toString()))); // then Counter successful = registry.find("che.workspace.starting_attempts.total").tag("debug", "false").counter(); Assert.assertEquals(successful.count(), 1.0); Counter debug = registry.find("che.workspace.starting_attempts.total").tag("debug", "true").counter(); Assert.assertEquals(debug.count(), 0.0); } @Test public void shouldNotCollectDebugStartWhenSetToStrangeString() { // given WorkspaceStartAttemptsMeterBinder meterBinder = new WorkspaceStartAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STOPPED) .withStatus(WorkspaceStatus.STARTING) .withWorkspaceId("id1") .withOptions(singletonMap(Constants.DEBUG_WORKSPACE_START, "strange"))); // then Counter successful = registry.find("che.workspace.starting_attempts.total").tag("debug", "false").counter(); Assert.assertEquals(successful.count(), 1.0); Counter debug = registry.find("che.workspace.starting_attempts.total").tag("debug", "true").counter(); Assert.assertEquals(debug.count(), 0.0); } @Test public void shouldNotCollectDebugStartWhenNotSetInOptions() { // given WorkspaceStartAttemptsMeterBinder meterBinder = new WorkspaceStartAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STOPPED) .withStatus(WorkspaceStatus.STARTING) .withWorkspaceId("id1") .withOptions(singletonMap("bla", "bol"))); // then Counter successful = registry.find("che.workspace.starting_attempts.total").tag("debug", "false").counter(); Assert.assertEquals(successful.count(), 1.0); Counter debug = registry.find("che.workspace.starting_attempts.total").tag("debug", "true").counter(); Assert.assertEquals(debug.count(), 0.0); } @DataProvider public Object[][] allStatusTransitionsWithoutStarting() { List> transitions = new ArrayList<>(9); for (WorkspaceStatus from : WorkspaceStatus.values()) { for (WorkspaceStatus to : WorkspaceStatus.values()) { if (from == WorkspaceStatus.STOPPED && to == WorkspaceStatus.STARTING) { continue; } transitions.add(asList(from, to)); } } return transitions.stream().map(List::toArray).toArray(Object[][]::new); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceStartTrackerMeterBinderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static java.util.Arrays.asList; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.eclipse.che.dto.server.DtoFactory; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class WorkspaceStartTrackerMeterBinderTest { private EventService eventService; private MeterRegistry registry; private WorkspaceStartTrackerMeterBinder meterBinder; private Map workspaceStartTime; @BeforeMethod public void setUp() { eventService = new EventService(); registry = new SimpleMeterRegistry(); workspaceStartTime = new ConcurrentHashMap<>(); meterBinder = new WorkspaceStartTrackerMeterBinder(eventService, workspaceStartTime); meterBinder.bindTo(registry); } @Test public void shouldRecordWorkspaceStartTime() { // given // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STOPPED) .withStatus(WorkspaceStatus.STARTING) .withWorkspaceId("id1")); // then Assert.assertTrue(workspaceStartTime.containsKey("id1")); } @Test(dataProvider = "allStatusTransitionsWithoutStarting") public void shouldNotRecordWorkspaceStartTimeForNonStartingStatuses( WorkspaceStatus from, WorkspaceStatus to) { // given // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(from) .withStatus(to) .withWorkspaceId("id1")); // then Assert.assertTrue(workspaceStartTime.isEmpty()); } @Test public void shouldCountSuccessfulStart() { // given workspaceStartTime.put("id1", System.currentTimeMillis() - 60 * 1000); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STARTING) .withStatus(WorkspaceStatus.RUNNING) .withWorkspaceId("id1")); // then Timer t = registry.find("che.workspace.start.time").tag("result", "success").timer(); Assert.assertEquals(t.count(), 1); Assert.assertTrue(t.totalTime(TimeUnit.MILLISECONDS) >= 60 * 1000); } @Test public void shouldCountFailedStart() { // given workspaceStartTime.put("id1", System.currentTimeMillis() - 60 * 1000); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STARTING) .withStatus(WorkspaceStatus.STOPPED) .withWorkspaceId("id1")); // then Timer t = registry.find("che.workspace.start.time").tag("result", "fail").timer(); Assert.assertEquals(t.count(), 1); Assert.assertTrue(t.totalTime(TimeUnit.MILLISECONDS) >= 60 * 1000); } @Test public void shouldIgnoreNotRecordedStartOnFailedStart() { // given // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STARTING) .withStatus(WorkspaceStatus.STOPPED) .withWorkspaceId("id1")); // then Timer t = registry.find("che.workspace.start.time").tag("result", "fail").timer(); Assert.assertEquals(t.count(), 0); } @Test public void shouldIgnoreNotRecordedStartOnSuccessStart() { // given // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STARTING) .withStatus(WorkspaceStatus.STOPPED) .withWorkspaceId("id1")); // then Timer t = registry.find("che.workspace.start.time").tag("result", "fail").timer(); Assert.assertEquals(t.count(), 0); } @DataProvider public Object[][] allStatusTransitionsWithoutStarting() { List> transitions = new ArrayList<>(9); for (WorkspaceStatus from : WorkspaceStatus.values()) { for (WorkspaceStatus to : WorkspaceStatus.values()) { if (from == WorkspaceStatus.STOPPED && to == WorkspaceStatus.STARTING) { continue; } transitions.add(asList(from, to)); } } return transitions.stream().map(List::toArray).toArray(Object[][]::new); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceStopTrackerMeterBinderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static java.util.Arrays.asList; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.eclipse.che.dto.server.DtoFactory; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class WorkspaceStopTrackerMeterBinderTest { private EventService eventService; private MeterRegistry registry; private WorkspaceStopTrackerMeterBinder meterBinder; private Map workspaceStopTime; @BeforeMethod public void setUp() { eventService = new EventService(); registry = new SimpleMeterRegistry(); workspaceStopTime = new ConcurrentHashMap<>(); meterBinder = new WorkspaceStopTrackerMeterBinder(eventService, workspaceStopTime); meterBinder.bindTo(registry); } @Test public void shouldRecordWorkspaceStopTime() { // given // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.RUNNING) .withStatus(WorkspaceStatus.STOPPING) .withWorkspaceId("id1")); // then Assert.assertTrue(workspaceStopTime.containsKey("id1")); } @Test(dataProvider = "allStatusTransitionsWithoutStarting") public void shouldNotRecordWorkspaceStopTimeForNonStoppingStatuses( WorkspaceStatus from, WorkspaceStatus to) { // given // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(from) .withStatus(to) .withWorkspaceId("id1")); // then Assert.assertTrue(workspaceStopTime.isEmpty()); } @Test public void shouldCountSuccessfulStart() { // given workspaceStopTime.put("id1", System.currentTimeMillis() - 60 * 1000); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STOPPING) .withStatus(WorkspaceStatus.STOPPED) .withWorkspaceId("id1")); // then Timer t = registry.find("che.workspace.stop.time").tag("result", "success").timer(); Assert.assertEquals(t.count(), 1); Assert.assertTrue(t.totalTime(TimeUnit.MILLISECONDS) >= 60 * 1000); } @DataProvider public Object[][] allStatusTransitionsWithoutStarting() { List> transitions = new ArrayList<>(9); for (WorkspaceStatus from : WorkspaceStatus.values()) { for (WorkspaceStatus to : WorkspaceStatus.values()) { if ((from == WorkspaceStatus.RUNNING || from == WorkspaceStatus.STARTING) && to == WorkspaceStatus.STOPPING) { continue; } transitions.add(asList(from, to)); } } return transitions.stream().map(List::toArray).toArray(Object[][]::new); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceSuccessfulStartAttemptsMeterBinderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static java.util.Arrays.asList; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.ArrayList; import java.util.List; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.eclipse.che.dto.server.DtoFactory; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class WorkspaceSuccessfulStartAttemptsMeterBinderTest { private EventService eventService; private MeterRegistry registry; @BeforeMethod public void setUp() { eventService = new EventService(); registry = new SimpleMeterRegistry(); } @Test(dataProvider = "allStatusTransitionsWithoutRunning") public void shouldNotCollectEvents(WorkspaceStatus from, WorkspaceStatus to) { // given WorkspaceSuccessfulStartAttemptsMeterBinder meterBinder = new WorkspaceSuccessfulStartAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(from) .withStatus(to) .withWorkspaceId("id1")); // then Counter successful = registry.find("che.workspace.started.total").counter(); Assert.assertEquals(successful.count(), 0.0); } @Test public void shouldCollectOnlyStarted() { // given WorkspaceSuccessfulStartAttemptsMeterBinder meterBinder = new WorkspaceSuccessfulStartAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STARTING) .withStatus(WorkspaceStatus.RUNNING) .withWorkspaceId("id1")); // then Counter successful = registry.find("che.workspace.started.total").counter(); Assert.assertEquals(successful.count(), 1.0); } @DataProvider public Object[][] allStatusTransitionsWithoutRunning() { List> transitions = new ArrayList<>(9); for (WorkspaceStatus from : WorkspaceStatus.values()) { for (WorkspaceStatus to : WorkspaceStatus.values()) { if (from == WorkspaceStatus.STARTING && to == WorkspaceStatus.RUNNING) { continue; } transitions.add(asList(from, to)); } } return transitions.stream().map(List::toArray).toArray(Object[][]::new); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceSuccessfulStopAttemptsMeterBinderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.metrics; import static java.util.Arrays.asList; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.ArrayList; import java.util.List; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; import org.eclipse.che.dto.server.DtoFactory; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class WorkspaceSuccessfulStopAttemptsMeterBinderTest { private EventService eventService; private MeterRegistry registry; @BeforeMethod public void setUp() { eventService = new EventService(); registry = new SimpleMeterRegistry(); } @Test(dataProvider = "allStatusTransitionsWithoutStopping") public void shouldNotCollectEvents(WorkspaceStatus from, WorkspaceStatus to) { // given WorkspaceSuccessfulStopAttemptsMeterBinder meterBinder = new WorkspaceSuccessfulStopAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(from) .withStatus(to) .withWorkspaceId("id1")); // then Counter successful = registry.find("che.workspace.stopped.total").counter(); Assert.assertEquals(successful.count(), 0.0); } @Test public void shouldCollectOnlyStoppedWithoutError() { // given WorkspaceSuccessfulStopAttemptsMeterBinder meterBinder = new WorkspaceSuccessfulStopAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STOPPING) .withStatus(WorkspaceStatus.STOPPED) .withWorkspaceId("id1")); // then Counter successful = registry.find("che.workspace.stopped.total").counter(); Assert.assertEquals(successful.count(), 1.0); } @Test public void shouldNotCollectStoppedWithError() { // given WorkspaceSuccessfulStopAttemptsMeterBinder meterBinder = new WorkspaceSuccessfulStopAttemptsMeterBinder(eventService); meterBinder.bindTo(registry); // when eventService.publish( DtoFactory.newDto(WorkspaceStatusEvent.class) .withPrevStatus(WorkspaceStatus.STOPPING) .withStatus(WorkspaceStatus.STOPPED) .withError("Error during workspace stop") .withWorkspaceId("id1")); // then Counter successful = registry.find("che.workspace.stopped.total").counter(); Assert.assertEquals(successful.count(), 0.0); } @DataProvider public Object[][] allStatusTransitionsWithoutStopping() { List> transitions = new ArrayList<>(9); for (WorkspaceStatus from : WorkspaceStatus.values()) { for (WorkspaceStatus to : WorkspaceStatus.values()) { if (from == WorkspaceStatus.STOPPING && to == WorkspaceStatus.STOPPED) { continue; } transitions.add(asList(from, to)); } } return transitions.stream().map(List::toArray).toArray(Object[][]::new); } } ================================================ FILE: wsmaster/che-core-api-metrics/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: wsmaster/che-core-api-ssh/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-ssh jar Che Core :: API :: SSH false com.google.inject guice com.google.inject.extensions guice-persist com.jcraft jsch commons-fileupload commons-fileupload jakarta.inject jakarta.inject-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-ssh-shared org.eclipse.che.core che-core-api-user org.eclipse.che.core che-core-commons-annotations org.eclipse.persistence jakarta.persistence provided ================================================ FILE: wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/SshManager.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.ssh.server; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.KeyPair; import java.io.ByteArrayOutputStream; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; import org.eclipse.che.api.ssh.server.spi.SshDao; /** * Facade for Ssh related operations. * * @author Sergii Leschenko */ @Singleton public class SshManager { private final JSch genJSch; private final SshDao sshDao; @Inject public SshManager(SshDao sshDao) { this.sshDao = sshDao; this.genJSch = new JSch(); } /** * Generates and stores ssh pair for specified user. * * @param owner the id of the user who will be the owner of the ssh pair * @param service service name pf ssh pair * @param name name of pair * @return instance of generated ssh pair * @throws ConflictException when given ssh pair cannot be generated or created * @throws ServerException when any other error occurs during ssh pair generating or creating */ public SshPairImpl generatePair(String owner, String service, String name) throws ServerException, ConflictException { KeyPair keyPair; try { keyPair = KeyPair.genKeyPair(genJSch, 2, 2048); } catch (JSchException e) { throw new ServerException("Failed to generate ssh pair.", e); } ByteArrayOutputStream privateBuff = new ByteArrayOutputStream(); keyPair.writePrivateKey(privateBuff); ByteArrayOutputStream publicBuff = new ByteArrayOutputStream(); keyPair.writePublicKey(publicBuff, null); final SshPairImpl generatedSshPair = new SshPairImpl(owner, service, name, publicBuff.toString(), privateBuff.toString()); sshDao.create(generatedSshPair); return generatedSshPair; } /** * Creates new ssh pair for specified user. * * @param sshPair ssh pair to create * @throws ConflictException when given ssh pair cannot be created * @throws ServerException when any other error occurs during ssh pair creating */ public void createPair(SshPairImpl sshPair) throws ServerException, ConflictException { sshDao.create(sshPair); } /** * Returns ssh pair by owner, service and name. * * @param owner the id of the user who is the owner of the ssh pair * @param service service name of ssh pair * @param name name of ssh pair * @return ssh pair instance * @throws NotFoundException when ssh pair is not found * @throws ServerException when any other error occurs during ssh pair fetching */ public SshPairImpl getPair(String owner, String service, String name) throws NotFoundException, ServerException { return sshDao.get(owner, service, name); } /** * Returns ssh pairs by owner and service. * * @param owner the id of the user who is the owner of the ssh pairs * @param service service name of ssh pair * @return list of ssh pair with given service and owned by given service. * @throws ServerException when any other error occurs during ssh pair fetching */ public List getPairs(String owner, String service) throws ServerException { return sshDao.get(owner, service); } /** * Removes ssh pair by owner, service and name. * * @param owner the id of the user who is the owner of the ssh pair * @param service service name of ssh pair * @param name of ssh pair * @throws NotFoundException when ssh pair is not found * @throws ServerException when any other error occurs during ssh pair removing */ public void removePair(String owner, String service, String name) throws ServerException, NotFoundException { sshDao.remove(owner, service, name); } } ================================================ FILE: wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/SshService.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.ssh.server; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static org.eclipse.che.api.ssh.shared.Constants.LINK_REL_GET_PAIR; import static org.eclipse.che.api.ssh.shared.Constants.LINK_REL_REMOVE_PAIR; import static org.eclipse.che.dto.server.DtoFactory.newDto; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; import javax.inject.Inject; import org.apache.commons.fileupload.FileItem; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.util.LinksHelper; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; import org.eclipse.che.api.ssh.shared.dto.GenerateSshPairRequest; import org.eclipse.che.api.ssh.shared.dto.SshPairDto; import org.eclipse.che.api.ssh.shared.model.SshPair; import org.eclipse.che.commons.env.EnvironmentContext; /** * Defines Ssh Rest API. * * @author Sergii Leschenko */ @Deprecated public class SshService extends Service { private final SshManager sshManager; @Inject public SshService(SshManager sshManager) { this.sshManager = sshManager; } public Response generatePair(GenerateSshPairRequest request) throws BadRequestException, ServerException, ConflictException { requiredNotNull(request, "Generate ssh pair request required"); requiredNotNull(request.getService(), "Service name required"); requiredNotNull(request.getName(), "Name required"); final SshPairImpl generatedPair = sshManager.generatePair(getCurrentUserId(), request.getService(), request.getName()); return Response.status(Response.Status.CREATED) .entity(asDto(injectLinks(asDto(generatedPair)))) .build(); } public Response createPair(Iterator formData) throws BadRequestException, ServerException, ConflictException { String service = null; String name = null; String privateKey = null; String publicKey = null; while (formData.hasNext()) { FileItem item = formData.next(); String fieldName = item.getFieldName(); switch (fieldName) { case "service": service = item.getString(); break; case "name": name = item.getString(); break; case "privateKey": privateKey = item.getString(); break; case "publicKey": publicKey = item.getString(); break; default: // do nothing } } requiredNotNull(service, "Service name required"); requiredNotNull(name, "Name required"); if (privateKey == null && publicKey == null) { throw new BadRequestException("Key content was not provided."); } sshManager.createPair( new SshPairImpl(getCurrentUserId(), service, name, publicKey, privateKey)); // We should send 200 response code and body with empty line // through specific of html form that doesn't invoke complete submit handler return Response.ok("", MediaType.TEXT_HTML).build(); } public void createPair(SshPairDto sshPair) throws BadRequestException, ServerException, ConflictException { requiredNotNull(sshPair, "Ssh pair required"); requiredNotNull(sshPair.getService(), "Service name required"); requiredNotNull(sshPair.getName(), "Name required"); if (sshPair.getPublicKey() == null && sshPair.getPrivateKey() == null) { throw new BadRequestException("Key content was not provided."); } sshManager.createPair(new SshPairImpl(getCurrentUserId(), sshPair)); } public SshPairDto getPair(String service, String name) throws NotFoundException, ServerException, BadRequestException { requiredNotNull(name, "Name of ssh pair"); return injectLinks(asDto(sshManager.getPair(getCurrentUserId(), service, name))); } public void removePair(String service, String name) throws ServerException, NotFoundException, BadRequestException { requiredNotNull(name, "Name of ssh pair"); sshManager.removePair(getCurrentUserId(), service, name); } public List getPairs(String service) throws ServerException { return sshManager.getPairs(getCurrentUserId(), service).stream() .map(sshPair -> injectLinks(asDto(sshPair))) .collect(Collectors.toList()); } private static String getCurrentUserId() { return EnvironmentContext.getCurrent().getSubject().getUserId(); } private static SshPairDto asDto(SshPair pair) { return newDto(SshPairDto.class) .withService(pair.getService()) .withName(pair.getName()) .withPublicKey(pair.getPublicKey()) .withPrivateKey(pair.getPrivateKey()); } private SshPairDto injectLinks(SshPairDto sshPairDto) { final UriBuilder uriBuilder = getServiceContext().getServiceUriBuilder(); final Link getPairsLink = LinksHelper.createLink( "GET", uriBuilder .clone() .path(getClass(), "getPairs") .build(sshPairDto.getService()) .toString(), APPLICATION_JSON, LINK_REL_GET_PAIR); final Link removePairLink = LinksHelper.createLink( "DELETE", uriBuilder .clone() .path(getClass(), "removePair") .build(sshPairDto.getService(), sshPairDto.getName()) .toString(), APPLICATION_JSON, LINK_REL_REMOVE_PAIR); final Link getPairLink = LinksHelper.createLink( "GET", uriBuilder .clone() .path(getClass(), "getPair") .build(sshPairDto.getService(), sshPairDto.getName()) .toString(), APPLICATION_JSON, LINK_REL_GET_PAIR); return sshPairDto.withLinks(Arrays.asList(getPairsLink, removePairLink, getPairLink)); } /** * Checks object reference is not {@code null} * * @param object object reference to check * @param subject used as subject of exception message "{subject} required" * @throws BadRequestException when object reference is {@code null} */ private void requiredNotNull(Object object, String subject) throws BadRequestException { if (object == null) { throw new BadRequestException(subject + " required"); } } } ================================================ FILE: wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/JpaSshDao.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.ssh.server.jpa; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import com.google.inject.persist.Transactional; import java.util.List; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import javax.persistence.EntityManager; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; import org.eclipse.che.api.ssh.server.spi.SshDao; /** * JPA based implementation of {@link SshDao}. * * @author Mihail Kuznyetsov * @author Yevhenii Voevodin */ @Singleton public class JpaSshDao implements SshDao { @Inject private Provider managerProvider; @Override public void create(SshPairImpl sshPair) throws ServerException, ConflictException { requireNonNull(sshPair); try { doCreate(sshPair); } catch (RuntimeException e) { throw new ServerException(e); } } @Override @Transactional public List get(String owner, String service) throws ServerException { requireNonNull(owner); requireNonNull(service); try { return managerProvider .get() .createNamedQuery("SshKeyPair.getByOwnerAndService", SshPairImpl.class) .setParameter("owner", owner) .setParameter("service", service) .getResultList(); } catch (RuntimeException e) { throw new ServerException(e.getLocalizedMessage(), e); } } @Override @Transactional public SshPairImpl get(String owner, String service, String name) throws ServerException, NotFoundException { requireNonNull(owner); requireNonNull(service); requireNonNull(name); try { SshPairImpl result = managerProvider .get() .find(SshPairImpl.class, new SshPairPrimaryKey(owner, service, name)); if (result == null) { throw new NotFoundException( format("Ssh pair with service '%s' and name '%s' was not found.", service, name)); } return result; } catch (RuntimeException e) { throw new ServerException(e.getLocalizedMessage(), e); } } @Override public void remove(String owner, String service, String name) throws ServerException, NotFoundException { requireNonNull(owner); requireNonNull(service); requireNonNull(name); try { doRemove(owner, service, name); } catch (RuntimeException e) { throw new ServerException(e); } } @Override @Transactional public List get(String owner) throws ServerException { requireNonNull(owner, "Required non-null owner"); try { return managerProvider .get() .createNamedQuery("SshKeyPair.getByOwner", SshPairImpl.class) .setParameter("owner", owner) .getResultList(); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Transactional protected void doCreate(SshPairImpl entity) { EntityManager manager = managerProvider.get(); manager.persist(entity); manager.flush(); } @Transactional protected void doRemove(String owner, String service, String name) throws NotFoundException { EntityManager manager = managerProvider.get(); SshPairImpl entity = manager.find(SshPairImpl.class, new SshPairPrimaryKey(owner, service, name)); if (entity == null) { throw new NotFoundException( format("Ssh pair with service '%s' and name '%s' was not found.", service, name)); } manager.remove(entity); manager.flush(); } } ================================================ FILE: wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/SshJpaModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.ssh.server.jpa; import com.google.inject.AbstractModule; import org.eclipse.che.api.ssh.server.spi.SshDao; /** * @author Yevhenii Voevodin */ public class SshJpaModule extends AbstractModule { @Override protected void configure() { bind(SshDao.class).to(JpaSshDao.class); } } ================================================ FILE: wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/SshPairPrimaryKey.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.ssh.server.jpa; import java.io.Serializable; import java.util.Objects; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; /** * Primary key for {@link SshPairImpl} entity * * @author Mihail Kuznyetsov */ public class SshPairPrimaryKey implements Serializable { private String owner; private String service; private String name; public SshPairPrimaryKey() {} public SshPairPrimaryKey(String owner, String service, String name) { this.owner = owner; this.service = service; this.name = name; } public String getOwner() { return owner; } public String getService() { return service; } public String getName() { return name; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof SshPairPrimaryKey)) return false; final SshPairPrimaryKey other = (SshPairPrimaryKey) obj; return Objects.equals(owner, other.owner) && Objects.equals(service, other.service) && Objects.equals(name, other.name); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(owner); hash = 31 * hash + Objects.hashCode(service); hash = 31 * hash + Objects.hashCode(name); return hash; } } ================================================ FILE: wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/model/impl/SshPairImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.ssh.server.model.impl; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; import org.eclipse.che.api.ssh.server.jpa.SshPairPrimaryKey; import org.eclipse.che.api.ssh.shared.model.SshPair; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.commons.annotation.Nullable; /** * @author Sergii Leschenko */ @Entity(name = "SshKeyPair") @NamedQueries({ @NamedQuery( name = "SshKeyPair.getByOwnerAndService", query = "SELECT pair " + "FROM SshKeyPair pair " + "WHERE pair.owner = :owner " + " AND pair.service = :service"), @NamedQuery( name = "SshKeyPair.getByOwner", query = "SELECT pair " + "FROM SshKeyPair pair " + "WHERE pair.owner = :owner") }) @IdClass(SshPairPrimaryKey.class) @Table(name = "sshkeypair") public class SshPairImpl implements SshPair { @Id @Column(name = "owner") private String owner; @Id @Column(name = "service") private String service; @Id @Column(name = "name") private String name; @Column(name = "publickey", columnDefinition = "TEXT") private String publicKey; @Column(name = "privatekey", columnDefinition = "TEXT") private String privateKey; @ManyToOne @JoinColumn(name = "owner", insertable = false, updatable = false) private UserImpl user; public SshPairImpl() {} public SshPairImpl( String owner, String service, String name, String publicKey, String privateKey) { this.owner = owner; this.service = service; this.name = name; this.publicKey = publicKey; this.privateKey = privateKey; } public SshPairImpl(String owner, SshPair sshPair) { this.owner = owner; this.service = sshPair.getService(); this.name = sshPair.getName(); this.publicKey = sshPair.getPublicKey(); this.privateKey = sshPair.getPrivateKey(); } public SshPairImpl(SshPairImpl sshPair) { this(sshPair.owner, sshPair.service, sshPair.name, sshPair.publicKey, sshPair.privateKey); } public String getOwner() { return owner; } @Override public String getService() { return service; } @Override public String getName() { return name; } @Override @Nullable public String getPublicKey() { return publicKey; } @Override @Nullable public String getPrivateKey() { return privateKey; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof SshPairImpl)) return false; final SshPairImpl other = (SshPairImpl) obj; return Objects.equals(owner, other.owner) && Objects.equals(service, other.service) && Objects.equals(name, other.name) && Objects.equals(publicKey, other.publicKey) && Objects.equals(privateKey, other.privateKey); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(owner); hash = 31 * hash + Objects.hashCode(service); hash = 31 * hash + Objects.hashCode(name); hash = 31 * hash + Objects.hashCode(publicKey); hash = 31 * hash + Objects.hashCode(privateKey); return hash; } @Override public String toString() { return "SshPairImpl{" + "owner='" + owner + '\'' + ", service='" + service + '\'' + ", name='" + name + '\'' + ", publicKey='" + publicKey + '\'' + ", privateKey='" + privateKey + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/spi/SshDao.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.ssh.server.spi; import java.util.List; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; /** * Defines data access object contract for {@link SshPairImpl}. * * @author Sergii Leschenko */ public interface SshDao { /** * Creates new ssh pair for specified user. * * @param sshPair ssh pair to create * @throws ConflictException when specified user already has ssh pair with given service and name * @throws NullPointerException when {@code sshPair} is null * @throws ServerException when any other error occurs during ssh pair creating */ void create(SshPairImpl sshPair) throws ServerException, ConflictException; /** * Returns ssh pairs by owner and service. * * @param owner the id of the user who is the owner of the ssh pairs * @param service service name of ssh pair * @return list of ssh pair with given service and owned by given service. * @throws NullPointerException when {@code owner} or {@code service} is null * @throws ServerException when any other error occurs during ssh pair fetching */ List get(String owner, String service) throws ServerException; /** * Returns ssh pair by owner, service and name. * * @param owner the id of the user who is the owner of the ssh pair * @param service service name of ssh pair * @param name name of ssh pair * @return ssh pair instance * @throws NullPointerException when {@code owner} or {@code service} or {@code name} is null * @throws NotFoundException when ssh pair is not found * @throws ServerException when any other error occurs during ssh pair fetching */ SshPairImpl get(String owner, String service, String name) throws ServerException, NotFoundException; /** * Removes ssh pair by owner, service and name. * * @param owner the id of the user who is the owner of the ssh pair * @param service service name of ssh pair * @param name of ssh pair * @throws NullPointerException when {@code owner} or {@code service} or {@code name} is null * @throws NotFoundException when ssh pair is not found * @throws ServerException when any other error occurs during ssh pair removing */ void remove(String owner, String service, String name) throws ServerException, NotFoundException; /** * Gets ssh pairs by owner. * * @param owner the owner of the ssh key * @return the list of the ssh key pairs owned by the {@code owner}, or empty list if there are no * ssh key pairs by the given {@code owner} * @throws NullPointerException when {@code owner} is null * @throws ServerException when any error occurs(e.g. database connection error) */ List get(String owner) throws ServerException; } ================================================ FILE: wsmaster/che-core-api-ssh-shared/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-ssh-shared jar Che Core :: API :: SSH :: Shared ${project.build.directory}/generated-sources/dto/ com.google.code.gson gson org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} generate-server-dto process-sources generate org.eclipse.che.api.ssh.shared.dto ${dto-generator-out-directory} org.eclipse.che.api.ssh.server.dto.DtoServerImpls server org.eclipse.che.core che-core-api-ssh-shared ${project.version} maven-compiler-plugin pre-compile generate-sources compile org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} ================================================ FILE: wsmaster/che-core-api-ssh-shared/src/main/java/org/eclipse/che/api/ssh/shared/Constants.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.ssh.shared; /** * Constants for ssh API * * @author Sergii Leschenko */ public final class Constants { public static final String LINK_REL_GENERATE_PAIR = "create pair"; public static final String LINK_REL_CREATE_PAIR = "create pair"; public static final String LINK_REL_GET_PAIRS = "get pairs"; public static final String LINK_REL_GET_PAIR = "get pair"; public static final String LINK_REL_REMOVE_PAIR = "remove pair"; private Constants() {} } ================================================ FILE: wsmaster/che-core-api-ssh-shared/src/main/java/org/eclipse/che/api/ssh/shared/dto/GenerateSshPairRequest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.ssh.shared.dto; import org.eclipse.che.dto.shared.DTO; /** * Interface describe a request for generating a SSH key pair. * * @author Sergii Leschenko */ @DTO public interface GenerateSshPairRequest { /** Returns name service that will use generated ssh pair. */ String getService(); void setService(String service); GenerateSshPairRequest withService(String service); /** Returns name for generated ssh pair */ String getName(); void setName(String name); GenerateSshPairRequest withName(String name); } ================================================ FILE: wsmaster/che-core-api-ssh-shared/src/main/java/org/eclipse/che/api/ssh/shared/dto/SshPairDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.ssh.shared.dto; import java.util.List; import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.ssh.shared.model.SshPair; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leschenko */ @DTO public interface SshPairDto extends SshPair, Hyperlinks { @Override String getService(); void setService(String service); SshPairDto withService(String service); @Override String getName(); void setName(String name); SshPairDto withName(String name); @Override String getPublicKey(); void setPublicKey(String publicKey); SshPairDto withPublicKey(String publicKey); @Override String getPrivateKey(); void setPrivateKey(String privateKey); SshPairDto withPrivateKey(String privateKey); @Override SshPairDto withLinks(List links); } ================================================ FILE: wsmaster/che-core-api-ssh-shared/src/main/java/org/eclipse/che/api/ssh/shared/model/SshPair.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.ssh.shared.model; import org.eclipse.che.commons.annotation.Nullable; /** * Defines ssh pair * * @author Sergii Leschenko */ public interface SshPair { /** Returns name service that use current ssh pair. It is mandatory. */ String getService(); /** Returns name of ssh pair. It is mandatory. */ String getName(); /** Returns content of public key. It is optional */ @Nullable String getPublicKey(); /** Returns content of private key. It is optional */ @Nullable String getPrivateKey(); } ================================================ FILE: wsmaster/che-core-api-system/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-system jar Che Core :: API :: System ${project.build.directory}/generated-sources/dto/ false com.google.code.gson gson com.google.guava guava com.google.inject guice io.swagger.core.v3 swagger-annotations-jakarta jakarta.annotation jakarta.annotation-api jakarta.inject jakarta.inject-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-system-shared org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-inject org.eclipse.che.core che-core-commons-lang org.eclipse.che.core che-core-commons-schedule org.slf4j slf4j-api jakarta.websocket jakarta.websocket-api provided ch.qos.logback logback-classic test commons-fileupload commons-fileupload test io.rest-assured rest-assured test org.eclipse.persistence jakarta.persistence test org.everrest everrest-assured test org.hamcrest hamcrest test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} maven-compiler-plugin pre-compile generate-sources compile org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} generate-server-dto process-sources generate org.eclipse.che.api.system.shared.dto ${dto-generator-out-directory} org.eclipse.che.api.system.shared.dto.DtoServerImpls server org.eclipse.che.core che-core-api-system ${project.version} ================================================ FILE: wsmaster/che-core-api-system/src/main/java/org/eclipse/che/api/system/server/CronThreadPullTermination.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.Collections; import java.util.Set; import org.eclipse.che.commons.schedule.executor.ThreadPullLauncher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Terminates {@link ThreadPullLauncher}. * * @author Anton Korneta */ @Singleton public class CronThreadPullTermination implements ServiceTermination { private static final Logger LOG = LoggerFactory.getLogger(CronThreadPullTermination.class); public static final String SERVICE_NAME = "CronJobService"; private final ThreadPullLauncher launcher; @Inject public CronThreadPullTermination(ThreadPullLauncher launcher) { this.launcher = launcher; } @Override public void terminate() throws InterruptedException { suspend(); } @Override public void suspend() throws InterruptedException { try { launcher.shutdown(); } catch (RuntimeException ex) { LOG.error("Failed to stop cron job thread pool. Cause: " + ex.getMessage()); } } @Override public String getServiceName() { return SERVICE_NAME; } @Override public Set getDependencies() { return Collections.emptySet(); } } ================================================ FILE: wsmaster/che-core-api-system/src/main/java/org/eclipse/che/api/system/server/DtoConverter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import org.eclipse.che.api.system.shared.dto.SystemEventDto; import org.eclipse.che.api.system.shared.dto.SystemServiceEventDto; import org.eclipse.che.api.system.shared.dto.SystemServiceItemStoppedEventDto; import org.eclipse.che.api.system.shared.dto.SystemStatusChangedEventDto; import org.eclipse.che.api.system.shared.event.SystemEvent; import org.eclipse.che.api.system.shared.event.SystemStatusChangedEvent; import org.eclipse.che.api.system.shared.event.service.SystemServiceEvent; import org.eclipse.che.api.system.shared.event.service.SystemServiceItemStoppedEvent; import org.eclipse.che.dto.server.DtoFactory; /** * Converts events to corresponding DTOs. * * @author Yevhenii Voevodin */ public final class DtoConverter { /** Creates {@link SystemStatusChangedEventDto} from event. */ public static SystemStatusChangedEventDto asDto(SystemStatusChangedEvent event) { SystemStatusChangedEventDto dto = DtoFactory.newDto(SystemStatusChangedEventDto.class); dto.setType(event.getType()); dto.setStatus(event.getStatus()); dto.setPrevStatus(event.getPrevStatus()); return dto; } /** Creates {@link SystemServiceEventDto} from event. */ public static SystemServiceEventDto asDto(SystemServiceEvent event) { SystemServiceEventDto dto = DtoFactory.newDto(SystemServiceEventDto.class); dto.setService(event.getServiceName()); dto.setType(event.getType()); return dto; } /** Creates {@link SystemServiceItemStoppedEventDto} from event. */ public static SystemServiceItemStoppedEventDto asDto(SystemServiceItemStoppedEvent event) { SystemServiceItemStoppedEventDto dto = DtoFactory.newDto(SystemServiceItemStoppedEventDto.class); dto.setService(event.getServiceName()); dto.setType(event.getType()); dto.setCurrent(event.getCurrent()); dto.setTotal(event.getTotal()); dto.setItem(event.getItem()); return dto; } /** * Converts given event to the corresponding DTO, if event type is unknown throws {@link * IllegalArgumentException}. */ public static SystemEventDto asDto(SystemEvent event) { switch (event.getType()) { case STATUS_CHANGED: return asDto((SystemStatusChangedEvent) event); case SERVICE_ITEM_STOPPED: return asDto((SystemServiceItemStoppedEvent) event); case SERVICE_STOPPED: case STOPPING_SERVICE: return asDto((SystemServiceEvent) event); default: throw new IllegalArgumentException( "Can't convert event to dto, event type '" + event.getType() + "' is unknown"); } } private DtoConverter() {} } ================================================ FILE: wsmaster/che-core-api-system/src/main/java/org/eclipse/che/api/system/server/JvmManager.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import com.sun.management.HotSpotDiagnosticMXBean; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.nio.file.Files; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.zip.ZipOutputStream; import javax.inject.Singleton; import org.eclipse.che.commons.lang.IoUtil; import org.eclipse.che.commons.lang.ZipUtils; /** The class that allow getting different diagnostic information from the current JVM. */ @Singleton public class JvmManager { private static final DateFormat MILLIS_FORMAT = new SimpleDateFormat("mm:ss:SSS"); private final ThreadMXBean threadMxBean; private final HotSpotDiagnosticMXBean hotSpotMxBean; public JvmManager() { threadMxBean = ManagementFactory.getThreadMXBean(); hotSpotMxBean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class); } /** * Write thread dump that contains stack traces of existed threads, plus it contains some other * diagnostic information about threads such as: daemon, priority, etc. */ public void writeThreadDump(OutputStream outputStream) throws IOException { ThreadInfo[] threadInfos = threadMxBean.getThreadInfo(threadMxBean.getAllThreadIds(), Integer.MAX_VALUE); Map threadInfoMap = new HashMap<>(); for (ThreadInfo threadInfo : threadInfos) { threadInfoMap.put(threadInfo.getThreadId(), threadInfo); } try (OutputStreamWriter writer = new OutputStreamWriter(outputStream)) { Map stacks = Thread.getAllStackTraces(); writer.write(String.format("Dump of %d threads at %Tc\n", stacks.size(), new Date())); for (Map.Entry entry : stacks.entrySet()) { Thread thread = entry.getKey(); writer.write( String.format( "\"%s\" prio=%d tid=%d state=%s daemon=%s\n", thread.getName(), thread.getPriority(), thread.getId(), thread.getState(), thread.isDaemon())); ThreadInfo threadInfo = threadInfoMap.get(thread.getId()); if (threadInfo != null) { writer.write( String.format( " native=%s, suspended=%s, block=%d, wait=%s\n", threadInfo.isInNative(), threadInfo.isSuspended(), threadInfo.getBlockedCount(), threadInfo.getWaitedCount())); writer.write( String.format( " lock=%s owned by %s (%s), cpu=%s, user=%s\n", threadInfo.getLockName(), threadInfo.getLockOwnerName(), threadInfo.getLockOwnerId(), MILLIS_FORMAT.format( new Date(threadMxBean.getThreadCpuTime(threadInfo.getThreadId()) / 1000000L)), MILLIS_FORMAT.format( new Date( threadMxBean.getThreadUserTime(threadInfo.getThreadId()) / 1000000L)))); } for (StackTraceElement element : entry.getValue()) { writer.append(" ").append(element.toString()).append(System.lineSeparator()); } writer.write(System.lineSeparator()); } writer.write("------------------------------------------------------"); writer.write(System.lineSeparator()); writer.write("Non-daemon threads: "); writer.write(System.lineSeparator()); for (Thread thread : stacks.keySet()) { if (!thread.isDaemon()) { writer .append("\"") .append(thread.getName()) .append("\", ") .append(System.lineSeparator()); } } writer.write("------------------------------------------------------"); writer.write(System.lineSeparator()); writer.write("Blocked threads: "); writer.write(System.lineSeparator()); for (Thread thread : stacks.keySet()) { if (thread.getState() == Thread.State.BLOCKED) { writer .append("\"") .append(thread.getName()) .append("\", ") .append(System.lineSeparator()); } } writer.write("------------------------------------------------------"); } } /** Create a file with a zipped hprof heap dump. */ public File createZippedHeapDump() throws IOException { File tmpFolder = Files.createTempDirectory("heapdump").toFile(); File heapFile = new File(tmpFolder, "heapdump.hprof"); hotSpotMxBean.dumpHeap(heapFile.getAbsolutePath(), false); File zip = File.createTempFile("heapdump", ".zip"); try (ZipOutputStream result = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zip)))) { ZipUtils.add(result, heapFile.toPath()); } IoUtil.deleteRecursive(tmpFolder); return zip; } } ================================================ FILE: wsmaster/che-core-api-system/src/main/java/org/eclipse/che/api/system/server/JvmService.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.StreamingOutput; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; import javax.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * REST API for JVM manipulations. * * @author Sergii Kabashniuk */ @Tag(name = "jvm", description = "API for JVM manipulations") @Path("/jvm") public class JvmService { private static final Logger LOG = LoggerFactory.getLogger(JvmService.class); private final JvmManager manager; @Inject public JvmService(JvmManager manager) { this.manager = manager; } @GET @Path("/dump/thread") @Produces(MediaType.TEXT_PLAIN) @Operation( summary = "Get thread dump of jvm", responses = { @ApiResponse(responseCode = "200", description = "The response contains thread dump"), @ApiResponse(responseCode = "500", description = "Internal server error occurred") }) public StreamingOutput threadDump() { return manager::writeThreadDump; } @GET @Path("/dump/heap") @Produces("application/zip") @Operation( summary = "Get heap dump of jvm", responses = { @ApiResponse(responseCode = "200", description = "The response contains jvm heap dump"), @ApiResponse(responseCode = "500", description = "Internal server error occurred") }) public Response heapDump() throws IOException { File heapDump = manager.createZippedHeapDump(); heapDump.deleteOnExit(); return Response.ok( new FileInputStream(heapDump) { @Override public void close() throws IOException { super.close(); if (!heapDump.delete()) { LOG.warn("Not able to delete temporary file {}", heapDump); } } }, "application/zip") .header("Content-Length", String.valueOf(Files.size(heapDump.toPath()))) .header("Content-Disposition", "attachment; filename=heapdump.hprof.zip") .build(); } } ================================================ FILE: wsmaster/che-core-api-system/src/main/java/org/eclipse/che/api/system/server/ServiceTermination.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import java.util.Set; import org.eclipse.che.api.system.shared.event.service.StoppingSystemServiceEvent; import org.eclipse.che.api.system.shared.event.service.SystemServiceItemStoppedEvent; import org.eclipse.che.api.system.shared.event.service.SystemServiceStoppedEvent; /** * Defines an interface for implementing termination or suspend process for a certain service. * * @author Yevhenii Voevodin */ public interface ServiceTermination { /** * Terminates a certain service. It's expected that termination is synchronous. * * @throws InterruptedException as termination is synchronous some of the implementations may need * to wait for asynchronous jobs to finish their execution, so if termination is interrupted * and implementation supports termination it should throw an interrupted exception */ void terminate() throws InterruptedException; /** * Suspends a certain service. Means that no more new service entities should be created and/or * executed etc. * * @throws UnsupportedOperationException if this operation is not supported * @throws InterruptedException as suspend is synchronous some of the implementations may need to * wait for asynchronous jobs to finish their execution, so if suspend is interrupted and * implementation supports suspending it should throw an interrupted exception */ default void suspend() throws InterruptedException, UnsupportedOperationException { throw new UnsupportedOperationException("This operation is not supported."); } /** * Returns the name of the service which is terminated by this termination. The name is used for * logging/sending events like {@link StoppingSystemServiceEvent}, {@link * SystemServiceItemStoppedEvent} or {@link SystemServiceStoppedEvent}. */ String getServiceName(); /** * Returns set of terminations service names on which the given termination depends, i.e. it MUST * be executed after them. * * @return list of dependencies is any, or empty list otherwise. */ Set getDependencies(); } ================================================ FILE: wsmaster/che-core-api-system/src/main/java/org/eclipse/che/api/system/server/ServiceTerminator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSortedSet; import java.util.Comparator; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.system.shared.event.service.StoppingSystemServiceEvent; import org.eclipse.che.api.system.shared.event.service.SuspendingSystemServiceEvent; import org.eclipse.che.api.system.shared.event.service.SystemServiceStoppedEvent; import org.eclipse.che.api.system.shared.event.service.SystemServiceSuspendedEvent; import org.eclipse.che.inject.ConfigurationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Terminates or suspends system services. * * @author Yevhenii Voevodin */ class ServiceTerminator { private static final Logger LOG = LoggerFactory.getLogger(ServiceTerminator.class); private final EventService eventService; private final Set terminations; @Inject ServiceTerminator(EventService eventService, Set terminations) { this.eventService = eventService; checkNamesAndDependencies(terminations); this.terminations = ImmutableSortedSet.copyOf(new ServiceTerminationComparator(terminations), terminations); } /** * Terminates system services in a order satisfying termination dependencies. * * @throws InterruptedException when termination is interrupted */ void terminateAll() throws InterruptedException { for (ServiceTermination termination : terminations) { LOG.info("Shutting down '{}' service", termination.getServiceName()); doTerminate(termination); } } /** * Suspends system services in a order satisfying termination dependencies. * * @throws InterruptedException when suspending is interrupted */ void suspendAll() throws InterruptedException { for (ServiceTermination termination : terminations) { LOG.info("Suspending down '{}' service", termination.getServiceName()); eventService.publish(new SuspendingSystemServiceEvent(termination.getServiceName())); try { termination.suspend(); eventService.publish(new SystemServiceSuspendedEvent(termination.getServiceName())); LOG.info("Service '{}' is suspended", termination.getServiceName()); } catch (UnsupportedOperationException e) { LOG.info( "Suspending down '{}' service isn't supported, terminating it", termination.getServiceName()); doTerminate(termination); } catch (InterruptedException x) { LOG.error( "Interrupted while waiting for '{}' service to suspend", termination.getServiceName()); throw x; } } } @VisibleForTesting void doTerminate(ServiceTermination termination) throws InterruptedException { eventService.publish(new StoppingSystemServiceEvent(termination.getServiceName())); try { termination.terminate(); } catch (InterruptedException x) { LOG.error( "Interrupted while waiting for '{}' service to shutdown", termination.getServiceName()); throw x; } LOG.info("Service '{}' is shut down", termination.getServiceName()); eventService.publish(new SystemServiceStoppedEvent(termination.getServiceName())); } private void checkNamesAndDependencies(Set terminationSet) { Set uniqueNamesSet = new HashSet<>(); terminationSet.forEach( t -> { if (!uniqueNamesSet.add(t.getServiceName())) { throw new ConfigurationException( String.format( "Duplicate termination found with service name %s", t.getServiceName())); } }); terminationSet.forEach( t -> { if (!uniqueNamesSet.containsAll(t.getDependencies())) { throw new RuntimeException( String.format("Unknown dependency found in termination %s", t.getServiceName())); } }); } public static class ServiceTerminationComparator implements Comparator { private final Map> dependencies; public ServiceTerminationComparator(Set terminations) { this.dependencies = terminations.stream() .collect( Collectors.toMap( ServiceTermination::getServiceName, ServiceTermination::getDependencies)); } @Override public int compare(ServiceTermination o1, ServiceTermination o2) { return checkTransitiveDependency(o1.getServiceName(), o2.getServiceName(), new HashSet<>()); } // Recursively dig into dependencies and sort them out private int checkTransitiveDependency(String o1, String o2, Set loopList) { if (loopList.contains(o1)) { throw new RuntimeException("Circular dependency found between terminations " + loopList); } Set directDependencies = dependencies.get(o1); if (directDependencies.isEmpty()) { return -1; } else { if (directDependencies.contains(o2)) { return 1; } else { loopList.add(o1); for (String dependency : directDependencies) { if (checkTransitiveDependency(dependency, o2, loopList) > 0) { return 1; } } return -1; } } } } } ================================================ FILE: wsmaster/che-core-api-system/src/main/java/org/eclipse/che/api/system/server/SystemEventsWebsocketBroadcaster.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import com.google.common.annotations.VisibleForTesting; import jakarta.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.notification.RemoteSubscriptionManager; import org.eclipse.che.api.system.shared.event.SystemEvent; /** * Broadcasts system status events to the websocket channel. * * @author Yevhenii Voevodin */ @Singleton public class SystemEventsWebsocketBroadcaster { public static final String SYSTEM_STATE_METHOD_NAME = "system/state"; private final RemoteSubscriptionManager remoteSubscriptionManager; @Inject public SystemEventsWebsocketBroadcaster(RemoteSubscriptionManager remoteSubscriptionManager) { this.remoteSubscriptionManager = remoteSubscriptionManager; } @PostConstruct @VisibleForTesting void subscribe() { remoteSubscriptionManager.register( SYSTEM_STATE_METHOD_NAME, SystemEvent.class, (systemEvent, stringStringMap) -> true); } } ================================================ FILE: wsmaster/che-core-api-system/src/main/java/org/eclipse/che/api/system/server/SystemManager.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import static org.eclipse.che.api.system.server.DtoConverter.asDto; import static org.eclipse.che.api.system.shared.SystemStatus.PREPARING_TO_SHUTDOWN; import static org.eclipse.che.api.system.shared.SystemStatus.READY_TO_SHUTDOWN; import static org.eclipse.che.api.system.shared.SystemStatus.RUNNING; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.ThreadFactoryBuilder; import jakarta.annotation.PreDestroy; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.system.shared.SystemStatus; import org.eclipse.che.api.system.shared.event.SystemStatusChangedEvent; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Facade for system operations. * * @author Yevhenii Voevodin */ @Singleton public class SystemManager { private static final Logger LOG = LoggerFactory.getLogger(SystemManager.class); private final AtomicReference statusRef; private final EventService eventService; private final ServiceTerminator terminator; private final CountDownLatch shutdownLatch = new CountDownLatch(1); @Inject public SystemManager(ServiceTerminator terminator, EventService eventService) { this.terminator = terminator; this.eventService = eventService; this.statusRef = new AtomicReference<>(RUNNING); } /** * Stops some of the system services preparing system to full shutdown. System status is changed * from {@link SystemStatus#RUNNING} to {@link SystemStatus#PREPARING_TO_SHUTDOWN}. * * @throws ConflictException when system status is different from running */ public void stopServices() throws ConflictException { if (!statusRef.compareAndSet(RUNNING, PREPARING_TO_SHUTDOWN)) { throw new ConflictException( "System shutdown has been already called, system status: " + statusRef.get()); } ExecutorService exec = Executors.newSingleThreadExecutor( new ThreadFactoryBuilder() .setDaemon(false) .setNameFormat("ShutdownSystemServicesPool") .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .build()); exec.execute(ThreadLocalPropagateContext.wrap(this::doStopServices)); exec.shutdown(); } /** * Suspends some of the system services preparing system to lighter shutdown. System status is * changed from {@link SystemStatus#RUNNING} to {@link SystemStatus#PREPARING_TO_SHUTDOWN}. * * @throws ConflictException when system status is different from running */ public void suspendServices() throws ConflictException { if (!statusRef.compareAndSet(RUNNING, PREPARING_TO_SHUTDOWN)) { throw new ConflictException( "System shutdown has been already called, system status: " + statusRef.get()); } ExecutorService exec = Executors.newSingleThreadExecutor( new ThreadFactoryBuilder() .setDaemon(false) .setNameFormat("SuspendSystemServicesPool") .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .build()); exec.execute(ThreadLocalPropagateContext.wrap(this::doSuspendServices)); exec.shutdown(); } /** * Gets current system status. * * @see SystemStatus */ public SystemStatus getSystemStatus() { return statusRef.get(); } /** Synchronously stops corresponding services. */ private void doStopServices() { LOG.info("Preparing system to shutdown"); eventService.publish(asDto(new SystemStatusChangedEvent(RUNNING, PREPARING_TO_SHUTDOWN))); try { terminator.terminateAll(); statusRef.set(READY_TO_SHUTDOWN); eventService.publish( asDto(new SystemStatusChangedEvent(PREPARING_TO_SHUTDOWN, READY_TO_SHUTDOWN))); LOG.info("System is ready to shutdown"); } catch (InterruptedException x) { LOG.error("Interrupted while waiting for system service to shutdown components"); Thread.currentThread().interrupt(); } finally { shutdownLatch.countDown(); } } /** Synchronously stops corresponding services. */ private void doSuspendServices() { LOG.info("Preparing system to shutdown"); eventService.publish(asDto(new SystemStatusChangedEvent(RUNNING, PREPARING_TO_SHUTDOWN))); try { terminator.suspendAll(); statusRef.set(READY_TO_SHUTDOWN); eventService.publish( asDto(new SystemStatusChangedEvent(PREPARING_TO_SHUTDOWN, READY_TO_SHUTDOWN))); LOG.info("System is ready to shutdown"); } catch (InterruptedException x) { LOG.error("Interrupted while waiting for system service to shutdown components"); Thread.currentThread().interrupt(); } finally { shutdownLatch.countDown(); } } @PreDestroy @VisibleForTesting void shutdown() throws InterruptedException { if (!statusRef.compareAndSet(RUNNING, PREPARING_TO_SHUTDOWN)) { shutdownLatch.await(); } else { doSuspendServices(); } } } ================================================ FILE: wsmaster/che-core-api-system/src/main/java/org/eclipse/che/api/system/server/SystemModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; /** * @author Anton Korneta */ public class SystemModule extends AbstractModule { @Override protected void configure() { bind(org.eclipse.che.api.system.server.SystemService.class); bind(org.eclipse.che.api.system.server.JvmService.class); bind(org.eclipse.che.api.system.server.JvmManager.class).asEagerSingleton(); bind(org.eclipse.che.api.system.server.SystemEventsWebsocketBroadcaster.class) .asEagerSingleton(); Multibinder.newSetBinder(binder(), ServiceTermination.class); } } ================================================ FILE: wsmaster/che-core-api-system/src/main/java/org/eclipse/che/api/system/server/SystemService.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import static java.util.Collections.singletonList; import static org.eclipse.che.api.core.util.LinksHelper.createLink; import static org.eclipse.che.api.system.server.SystemEventsWebsocketBroadcaster.SYSTEM_STATE_METHOD_NAME; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import javax.inject.Inject; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.LinkParameter; import org.eclipse.che.api.system.shared.dto.SystemStateDto; import org.eclipse.che.dto.server.DtoFactory; /** * REST API for system state management. * * @author Yevhenii Voevodin */ @Tag(name = "system", description = "API for system state management") @Path("/system") public class SystemService extends Service { private final SystemManager manager; @Inject public SystemService(SystemManager manager) { this.manager = manager; } @POST @Path("/stop") @Operation( summary = "Stops system services. Prepares system to shutdown", responses = { @ApiResponse(responseCode = "204", description = "The system is preparing to stop"), @ApiResponse(responseCode = "409", description = "Stop has been already called") }) public void stop(@QueryParam("shutdown") @DefaultValue("false") boolean shutdown) throws ConflictException { if (shutdown) { manager.stopServices(); } else { manager.suspendServices(); } } @GET @Path("/state") @Produces("application/json") @Operation( summary = "Gets current system state", responses = { @ApiResponse(responseCode = "200", description = "The response contains system status"), @ApiResponse(responseCode = "409", description = "Stop has been already called") }) public SystemStateDto getState() { Link wsLink = createLink( "GET", getServiceContext() .getBaseUriBuilder() .scheme("https".equals(uriInfo.getBaseUri().getScheme()) ? "wss" : "ws") .path("websocket") .build() .toString(), "system.state.channel", singletonList( DtoFactory.newDto(LinkParameter.class) .withName("channel") .withDefaultValue(SYSTEM_STATE_METHOD_NAME) .withRequired(true))); return DtoFactory.newDto(SystemStateDto.class) .withStatus(manager.getSystemStatus()) .withLinks(singletonList(wsLink)); } } ================================================ FILE: wsmaster/che-core-api-system/src/test/java/org/eclipse/che/api/system/server/DtoConverterTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import static org.eclipse.che.api.system.shared.SystemStatus.PREPARING_TO_SHUTDOWN; import static org.eclipse.che.api.system.shared.SystemStatus.RUNNING; import static org.testng.Assert.assertEquals; import java.util.EnumSet; import org.eclipse.che.api.system.shared.dto.SystemServiceEventDto; import org.eclipse.che.api.system.shared.dto.SystemServiceItemStoppedEventDto; import org.eclipse.che.api.system.shared.dto.SystemStatusChangedEventDto; import org.eclipse.che.api.system.shared.event.EventType; import org.eclipse.che.api.system.shared.event.SystemStatusChangedEvent; import org.eclipse.che.api.system.shared.event.service.StoppingSystemServiceEvent; import org.eclipse.che.api.system.shared.event.service.SystemServiceItemStoppedEvent; import org.eclipse.che.api.system.shared.event.service.SystemServiceStoppedEvent; import org.testng.annotations.Test; /** * Tests {@link DtoConverter}. * * @author Yevhenii Voevodin */ public class DtoConverterTest { @Test public void convertsSystemStatusChangedEvent() { SystemStatusChangedEvent event = new SystemStatusChangedEvent(RUNNING, PREPARING_TO_SHUTDOWN); SystemStatusChangedEventDto dto = DtoConverter.asDto(event); assertEquals(dto.getType(), EventType.STATUS_CHANGED); assertEquals(dto.getPrevStatus(), event.getPrevStatus()); assertEquals(dto.getStatus(), event.getStatus()); } @Test public void convertsSystemServiceStoppingEvent() { StoppingSystemServiceEvent event = new StoppingSystemServiceEvent("service1"); SystemServiceEventDto dto = DtoConverter.asDto(event); assertEquals(dto.getType(), EventType.STOPPING_SERVICE); assertEquals(dto.getService(), event.getServiceName()); } @Test public void convertsSystemServiceStoppedEvent() { SystemServiceStoppedEvent event = new SystemServiceStoppedEvent("service1"); SystemServiceEventDto dto = DtoConverter.asDto(event); assertEquals(dto.getType(), EventType.SERVICE_STOPPED); assertEquals(dto.getService(), event.getServiceName()); } @Test public void convertsSystemServiceItemStoppedEvent() { SystemServiceItemStoppedEvent event = new SystemServiceItemStoppedEvent("service1", "workspace1", 3, 5); SystemServiceItemStoppedEventDto dto = DtoConverter.asDto(event); assertEquals(dto.getType(), EventType.SERVICE_ITEM_STOPPED); assertEquals(dto.getService(), event.getServiceName()); assertEquals(dto.getItem(), event.getItem()); assertEquals(dto.getCurrent(), event.getCurrent()); assertEquals(dto.getTotal(), event.getTotal()); } @Test public void allEventTypesAreHandled() { EnumSet handled = EnumSet.of( EventType.STATUS_CHANGED, EventType.STOPPING_SERVICE, EventType.SUSPENDING_SERVICE, EventType.SERVICE_ITEM_STOPPED, EventType.SERVICE_ITEM_SUSPENDED, EventType.SERVICE_SUSPENDED, EventType.SERVICE_STOPPED); assertEquals(handled, EnumSet.allOf(EventType.class)); } } ================================================ FILE: wsmaster/che-core-api-system/src/test/java/org/eclipse/che/api/system/server/JvmServiceTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import static io.restassured.RestAssured.expect; import static org.testng.Assert.*; import io.restassured.response.Response; import jakarta.ws.rs.core.MediaType; import java.io.File; import java.io.IOException; import java.nio.file.Files; import org.eclipse.che.commons.lang.ZipUtils; import org.everrest.assured.EverrestJetty; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(EverrestJetty.class) public class JvmServiceTest { JvmService service = new JvmService(new JvmManager()); @Test public void testThreadDump() { final Response response = expect().statusCode(200).contentType(MediaType.TEXT_PLAIN).when().get("/jvm/dump/thread"); assertTrue(response.body().asString().contains("Dump of")); assertTrue(response.body().asString().contains("\"main\" prio=")); assertTrue(response.body().asString().contains("\"main\" prio=")); assertTrue(response.body().asString().contains("Non-daemon threads")); assertTrue(response.body().asString().contains("Blocked threads")); } @Test public void testHeapDump() throws IOException { final Response response = expect().statusCode(200).contentType("application/zip").when().get("/jvm/dump/heap"); byte[] array = response.asByteArray(); File tmp = File.createTempFile("test", "zip"); tmp.deleteOnExit(); Files.write(tmp.toPath(), array); assertTrue(ZipUtils.isZipFile(tmp)); assertTrue(array.length > 1000); } } ================================================ FILE: wsmaster/che-core-api-system/src/test/java/org/eclipse/che/api/system/server/SystemManagerTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import static org.eclipse.che.api.system.shared.SystemStatus.PREPARING_TO_SHUTDOWN; import static org.eclipse.che.api.system.shared.SystemStatus.READY_TO_SHUTDOWN; import static org.eclipse.che.api.system.shared.SystemStatus.RUNNING; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; import java.util.Iterator; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.system.shared.dto.SystemStatusChangedEventDto; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link SystemManager}. * * @author Yevhenii Voevodin */ @Listeners(MockitoTestNGListener.class) public class SystemManagerTest { @Mock private ServiceTerminator terminator; @Mock private EventService eventService; @Captor private ArgumentCaptor eventsCaptor; private SystemManager systemManager; @BeforeMethod public void init() { MockitoAnnotations.initMocks(this); systemManager = new SystemManager(terminator, eventService); } @Test public void isRunningByDefault() { assertEquals(systemManager.getSystemStatus(), RUNNING); } @Test public void servicesAreSuspended() throws Exception { systemManager.suspendServices(); verifySuspendCompleted(); } @Test public void servicesAreStopped() throws Exception { systemManager.stopServices(); verifyShutdownCompleted(); } @Test(expectedExceptions = ConflictException.class) public void exceptionIsThrownWhenStoppingServicesTwice() throws Exception { systemManager.stopServices(); systemManager.stopServices(); } @Test public void shutdownDoesNotFailIfServicesAreAlreadyStopped() throws Exception { systemManager.stopServices(); systemManager.shutdown(); verifyShutdownCompleted(); } @Test public void shutdownStopsServicesIfNotStopped() throws Exception { systemManager.shutdown(); verifySuspendCompleted(); } private void verifyShutdownCompleted() throws InterruptedException { verify(terminator, timeout(2000)).terminateAll(); verifyEvents(); } private void verifySuspendCompleted() throws InterruptedException { verify(terminator, timeout(2000)).suspendAll(); verifyEvents(); } private void verifyEvents() { verify(eventService, times(2)).publish(eventsCaptor.capture()); Iterator eventsIt = eventsCaptor.getAllValues().iterator(); assertEquals( eventsIt.next(), newDto(SystemStatusChangedEventDto.class) .withPrevStatus(RUNNING) .withStatus(PREPARING_TO_SHUTDOWN)); assertEquals( eventsIt.next(), newDto(SystemStatusChangedEventDto.class) .withPrevStatus(PREPARING_TO_SHUTDOWN) .withStatus(READY_TO_SHUTDOWN)); } } ================================================ FILE: wsmaster/che-core-api-system/src/test/java/org/eclipse/che/api/system/server/SystemTerminatorTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.server; import static java.util.stream.Collectors.toSet; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableSet; import java.util.Collections; import java.util.Set; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.system.shared.event.service.StoppingSystemServiceEvent; import org.eclipse.che.api.system.shared.event.service.SuspendingSystemServiceEvent; import org.eclipse.che.api.system.shared.event.service.SystemServiceStoppedEvent; import org.eclipse.che.api.system.shared.event.service.SystemServiceSuspendedEvent; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link ServiceTerminator}. * * @author Yevhenii Voevodin */ @Listeners(MockitoTestNGListener.class) public class SystemTerminatorTest { @Mock private EventService eventService; @Mock private ServiceTermination termination1; @Mock private ServiceTermination termination2; private ServiceTerminator terminator; @BeforeMethod public void setUp() { when(termination1.getServiceName()).thenReturn("service1"); when(termination2.getServiceName()).thenReturn("service2"); terminator = new ServiceTerminator(eventService, ImmutableSet.of(termination1, termination2)); } @Test public void executesTerminations() throws Exception { terminator.terminateAll(); verify(termination1).terminate(); verify(termination2).terminate(); verify(eventService).publish(new StoppingSystemServiceEvent("service1")); verify(eventService).publish(new SystemServiceStoppedEvent("service1")); verify(eventService).publish(new StoppingSystemServiceEvent("service2")); verify(eventService).publish(new SystemServiceStoppedEvent("service2")); } @Test public void executesSuspendals() throws Exception { terminator.suspendAll(); verify(termination1).suspend(); verify(termination2).suspend(); verify(eventService).publish(new SuspendingSystemServiceEvent("service1")); verify(eventService).publish(new SystemServiceSuspendedEvent("service1")); verify(eventService).publish(new SuspendingSystemServiceEvent("service2")); verify(eventService).publish(new SystemServiceSuspendedEvent("service2")); } @Test public void executesTermitationsWhenSuspendalsNotSupported() throws Exception { doThrow(UnsupportedOperationException.class).when(termination1).suspend(); terminator.suspendAll(); verify(termination1).terminate(); verify(termination2).suspend(); verify(eventService).publish(new SuspendingSystemServiceEvent("service1")); verify(eventService).publish(new StoppingSystemServiceEvent("service1")); verify(eventService).publish(new SystemServiceStoppedEvent("service1")); verify(eventService).publish(new SuspendingSystemServiceEvent("service2")); verify(eventService).publish(new SystemServiceSuspendedEvent("service2")); } @Test( expectedExceptions = InterruptedException.class, expectedExceptionsMessageRegExp = "interrupt!") public void stopsExecutingTerminationIfOneIsInterrupted() throws Exception { doThrow(new InterruptedException("interrupt!")).when(termination1).terminate(); terminator.terminateAll(); } @Test(dataProvider = "dependableTerminations") public void shouldOrderTerminationsByDependency( Set terminations, Set expectedOrder) throws Exception { ServiceTerminator localTerminator = spy(new ServiceTerminator(eventService, terminations)); localTerminator.suspendAll(); ArgumentCaptor captor = ArgumentCaptor.forClass(ServiceTermination.class); verify(localTerminator, times(terminations.size())).doTerminate(captor.capture()); assertEquals( captor.getAllValues().stream().map(ServiceTermination::getServiceName).collect(toSet()), expectedOrder); } @Test( dataProvider = "loopableTerminations", expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Circular dependency found between terminations \\[B, D\\]") public void shouldFailOnCyclicDependency(Set terminations) throws Exception { new ServiceTerminator(eventService, terminations); } @Test( dataProvider = "sameNameTerminations", expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Duplicate termination found with service name .+") public void shouldFailOnTerminationsWithSameServiceName(Set terminations) throws Exception { new ServiceTerminator(eventService, terminations); } @Test( dataProvider = "wrongDependencyTerminations", expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Unknown dependency found in termination .+") public void shouldFailOnTerminationsWithUnexistingDeps(Set terminations) throws Exception { new ServiceTerminator(eventService, terminations); } @DataProvider(name = "dependableTerminations") public Object[][] dependableTerminations() { return new Object[][] { { ImmutableSet.of( getServiceTerminationWithDependency("A", Collections.emptySet()), getServiceTerminationWithDependency("B", ImmutableSet.of("C", "D", "G")), getServiceTerminationWithDependency("C", Collections.emptySet()), getServiceTerminationWithDependency("D", ImmutableSet.of("C")), getServiceTerminationWithDependency("E", ImmutableSet.of("B")), getServiceTerminationWithDependency("F", ImmutableSet.of("C")), getServiceTerminationWithDependency("G", ImmutableSet.of("C"))), ImmutableSet.of("A", "C", "D", "F", "G", "B", "E") } }; } @DataProvider(name = "loopableTerminations") public Object[][] loopableTerminations() { return new Object[][] { { ImmutableSet.of( getServiceTerminationWithDependency("A", Collections.emptySet()), getServiceTerminationWithDependency("B", ImmutableSet.of("C", "D")), getServiceTerminationWithDependency("C", Collections.emptySet()), getServiceTerminationWithDependency("D", ImmutableSet.of("B")) // loop here ) } }; } @DataProvider(name = "sameNameTerminations") public Object[][] sameNameTerminations() { return new Object[][] { { ImmutableSet.of( getServiceTerminationWithDependency("A", Collections.emptySet()), getServiceTerminationWithDependency("C", Collections.emptySet()), getServiceTerminationWithDependency("C", Collections.emptySet())) } }; } @DataProvider(name = "wrongDependencyTerminations") public Object[][] wrongDependencyTerminations() { return new Object[][] { { ImmutableSet.of( getServiceTerminationWithDependency("A", Collections.emptySet()), // no such termination getServiceTerminationWithDependency("C", ImmutableSet.of("B"))) } }; } private ServiceTermination getServiceTerminationWithDependency( String name, Set depencencies) { return new ServiceTermination() { @Override public void terminate() throws InterruptedException {} @Override public String getServiceName() { return name; } @Override public Set getDependencies() { return depencencies; } }; } } ================================================ FILE: wsmaster/che-core-api-system/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: wsmaster/che-core-api-system-shared/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-system-shared jar Che Core :: API :: System Shared ${project.build.directory}/generated-sources/dto/ false com.google.code.gson gson org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-commons-annotations org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} maven-compiler-plugin pre-compile generate-sources compile org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} generate-server-dto process-sources generate org.eclipse.che.api.system.shared.dto ${dto-generator-out-directory} org.eclipse.che.api.system.shared.dto.DtoServerImpls server org.eclipse.che.core che-core-api-system-shared ${project.version} ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/SystemStatus.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared; /** * Defines system status. * * @author Yevhenii Voevodin */ public enum SystemStatus { /** The system is running, which means that it wasn't stopped via system API. */ RUNNING, /** The system stops corresponding services and will be eventually {@link #READY_TO_SHUTDOWN}. */ PREPARING_TO_SHUTDOWN, /** All the necessary services are stopped, system is ready to be shut down. */ READY_TO_SHUTDOWN } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/dto/SystemEventDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.dto; import org.eclipse.che.api.core.notification.EventOrigin; import org.eclipse.che.api.system.shared.event.EventType; import org.eclipse.che.api.system.shared.event.SystemEvent; import org.eclipse.che.dto.shared.DTO; /** * DTO for {@link SystemEvent}. * * @author Yevhenii Voevodin */ @DTO @EventOrigin("system") public interface SystemEventDto extends SystemEvent { void setType(EventType type); SystemEventDto withType(EventType type); } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/dto/SystemServiceEventDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.dto; import org.eclipse.che.dto.shared.DTO; /** * DTO for system service events. * * @author Yevhenii Voevodin */ @DTO public interface SystemServiceEventDto extends SystemEventDto { /** Returns the name of the service described by this event. */ String getService(); void setService(String service); SystemServiceEventDto withService(String service); } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/dto/SystemServiceItemStoppedEventDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.dto; import org.eclipse.che.api.system.shared.event.EventType; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.dto.shared.DTO; /** * See {@link EventType#SERVICE_ITEM_STOPPED} for details. * * @author Yevhenii Voevodin */ @DTO public interface SystemServiceItemStoppedEventDto extends SystemServiceEventDto { /** Returns an item for which this event is published(like workspace id). */ String getItem(); void setItem(String item); SystemServiceItemStoppedEventDto withItem(String item); /** * Returns an amount of items currently stopped, it's either present with {@link #getTotal()} or * missing at all(null is returned). */ @Nullable Integer getCurrent(); void setCurrent(Integer current); SystemServiceItemStoppedEventDto withCurrent(Integer current); /** * Returns total count of items which had not been stopped before service shutdown was called. * It's either present with {@link #getCurrent()} or missing at all(null is returned). */ @Nullable Integer getTotal(); void setTotal(Integer total); SystemServiceItemStoppedEventDto withTotal(Integer total); SystemServiceItemStoppedEventDto withService(String service); } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/dto/SystemStateDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.dto; import java.util.List; import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.system.shared.SystemStatus; import org.eclipse.che.dto.shared.DTO; /** * Describes current system state. * * @author Yevhenii Voevodin */ @DTO public interface SystemStateDto extends Hyperlinks { /** Returns current system status. */ SystemStatus getStatus(); void setStatus(SystemStatus status); SystemStateDto withStatus(SystemStatus status); SystemStateDto withLinks(List links); } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/dto/SystemStatusChangedEventDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.dto; import org.eclipse.che.api.core.notification.EventOrigin; import org.eclipse.che.api.system.shared.SystemStatus; import org.eclipse.che.api.system.shared.event.SystemStatusChangedEvent; import org.eclipse.che.dto.shared.DTO; /** * DTO for {@link SystemStatusChangedEvent}. * * @author Yevhenii Voevodin */ @DTO @EventOrigin("system") public interface SystemStatusChangedEventDto extends SystemEventDto { /** Returns new status of the system. */ SystemStatus getStatus(); void setStatus(SystemStatus status); SystemStatusChangedEventDto withStatus(SystemStatus status); /** Returns the previous status of the system. */ SystemStatus getPrevStatus(); void setPrevStatus(SystemStatus prevStatus); SystemStatusChangedEventDto withPrevStatus(SystemStatus prevStatus); } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/event/EventType.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.event; /** * Defines set of system event types. * * @author Yevhenii Voevodin */ public enum EventType { /** Published when system status is changed. */ STATUS_CHANGED, /** * Published when system is starting shutting down a service. This is the first event published * for a certain service. * *
       *     STOPPING_SERVICE -> (0..N)SERVICE_ITEM_STOPPED -> SERVICE_STOPPED
       * 
    */ STOPPING_SERVICE, /** * Published when system is starting to suspend a service. This is the first event published for a * certain service. * *
       *     SUSPENDING_SERVICE -> (0..N)SERVICE_ITEM_SUSPENDED -> SERVICE_SUSPENDED
       * 
    */ SUSPENDING_SERVICE, /** * Published after service item is stopped. Events of such type are published between {@link * #STOPPING_SERVICE} and {@link #SERVICE_STOPPED} events. */ SERVICE_ITEM_STOPPED, /** * Published after service item is suspended. Events of such type are published between {@link * #SUSPENDING_SERVICE} and {@link #SERVICE_SUSPENDED} events. */ SERVICE_ITEM_SUSPENDED, /** * Published when shutting down of a service is finished. The last event in the chain for a * certain service. */ SERVICE_STOPPED, /** * Published when suspending a service is finished. The last event in the chain for a certain * service. */ SERVICE_SUSPENDED } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/event/SystemEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.event; /** * The base interface for system events. * * @author Yevhenii Voevodin */ public interface SystemEvent { /** Returns type of this event. */ EventType getType(); } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/event/SystemStatusChangedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.event; import java.util.Objects; import org.eclipse.che.api.system.shared.SystemStatus; /** * Describes system status changes. * * @author Yevhenii Voevodin */ public class SystemStatusChangedEvent implements SystemEvent { private final SystemStatus status; private final SystemStatus prevStatus; public SystemStatusChangedEvent(SystemStatus prevStatus, SystemStatus status) { this.status = status; this.prevStatus = prevStatus; } @Override public EventType getType() { return EventType.STATUS_CHANGED; } public SystemStatus getStatus() { return status; } public SystemStatus getPrevStatus() { return prevStatus; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof SystemStatusChangedEvent)) { return false; } final SystemStatusChangedEvent that = (SystemStatusChangedEvent) obj; return Objects.equals(status, that.status) && Objects.equals(prevStatus, that.prevStatus); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(status); hash = 31 * hash + Objects.hashCode(prevStatus); return hash; } @Override public String toString() { return "SystemStatusChangedEvent{" + "status=" + status + ", prevStatus=" + prevStatus + '}'; } } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/event/service/StoppingSystemServiceEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.event.service; import org.eclipse.che.api.system.shared.event.EventType; /** * See {@link EventType#STOPPING_SERVICE} description. * * @author Yevhenii Voevodin */ public class StoppingSystemServiceEvent extends SystemServiceEvent { public StoppingSystemServiceEvent(String serviceName) { super(serviceName); } @Override public EventType getType() { return EventType.STOPPING_SERVICE; } } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/event/service/SuspendingSystemServiceEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.event.service; import org.eclipse.che.api.system.shared.event.EventType; /** * See {@link EventType#SUSPENDING_SERVICE} description. * * @author Max Shaposhnyk */ public class SuspendingSystemServiceEvent extends SystemServiceEvent { public SuspendingSystemServiceEvent(String serviceName) { super(serviceName); } @Override public EventType getType() { return EventType.SUSPENDING_SERVICE; } } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/event/service/SystemServiceEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.event.service; import java.util.Objects; import org.eclipse.che.api.system.shared.event.SystemEvent; /** * The base class for system service events. * * @author Yevhenii Voevodin */ public abstract class SystemServiceEvent implements SystemEvent { protected final String serviceName; protected SystemServiceEvent(String serviceName) { this.serviceName = Objects.requireNonNull(serviceName, "Service name required"); } public String getServiceName() { return serviceName; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof SystemServiceEvent)) { return false; } final SystemServiceEvent that = (SystemServiceEvent) obj; return Objects.equals(getType(), that.getType()) && Objects.equals(serviceName, that.serviceName); } @Override public int hashCode() { int hash = 17; hash = 31 * hash + Objects.hashCode(getType()); hash = 31 * hash + Objects.hashCode(serviceName); return hash; } @Override public String toString() { return getClass().getSimpleName() + "{eventType='" + getType() + "', serviceName=" + serviceName + "'}"; } } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/event/service/SystemServiceItemStoppedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.event.service; import java.util.Objects; import org.eclipse.che.api.system.shared.event.EventType; import org.eclipse.che.commons.annotation.Nullable; /** * See {@link EventType#SERVICE_ITEM_STOPPED} description. * * @author Yevhenii Voevodin */ public class SystemServiceItemStoppedEvent extends SystemServiceEvent { private final String item; private Integer total; private Integer current; public SystemServiceItemStoppedEvent(String serviceName, String item) { super(serviceName); this.item = Objects.requireNonNull(item, "Item required"); } public SystemServiceItemStoppedEvent( String serviceName, String item, @Nullable Integer current, @Nullable Integer total) { this(serviceName, item); this.current = current; this.total = total; } @Override public EventType getType() { return EventType.SERVICE_ITEM_STOPPED; } public String getItem() { return item; } @Nullable public Integer getTotal() { return total; } @Nullable public Integer getCurrent() { return current; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof SystemServiceItemStoppedEvent)) { return false; } final SystemServiceItemStoppedEvent that = (SystemServiceItemStoppedEvent) obj; return super.equals(that) && item.equals(that.item) && Objects.equals(total, that.total) && Objects.equals(current, that.current); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + super.hashCode(); hash = 31 * hash + item.hashCode(); hash = 31 * hash + Objects.hashCode(total); hash = 31 * hash + Objects.hashCode(current); return hash; } @Override public String toString() { return "SystemServiceItemStoppedEvent{" + "item='" + item + '\'' + ", total=" + total + ", current=" + current + ", eventType='" + getType() + '\'' + ", service='" + getServiceName() + "\'}"; } } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/event/service/SystemServiceItemSuspendedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.event.service; import java.util.Objects; import org.eclipse.che.api.system.shared.event.EventType; import org.eclipse.che.commons.annotation.Nullable; /** * See {@link EventType#SERVICE_ITEM_SUSPENDED} description. * * @author Max Shaposhnyk */ public class SystemServiceItemSuspendedEvent extends SystemServiceEvent { private final String item; private Integer total; private Integer current; public SystemServiceItemSuspendedEvent(String serviceName, String item) { super(serviceName); this.item = Objects.requireNonNull(item, "Item required"); } public SystemServiceItemSuspendedEvent( String serviceName, String item, @Nullable Integer current, @Nullable Integer total) { this(serviceName, item); this.current = current; this.total = total; } @Override public EventType getType() { return EventType.SERVICE_ITEM_SUSPENDED; } public String getItem() { return item; } @Nullable public Integer getTotal() { return total; } @Nullable public Integer getCurrent() { return current; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof SystemServiceItemSuspendedEvent)) { return false; } final SystemServiceItemSuspendedEvent that = (SystemServiceItemSuspendedEvent) obj; return super.equals(that) && item.equals(that.item) && Objects.equals(total, that.total) && Objects.equals(current, that.current); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + super.hashCode(); hash = 31 * hash + item.hashCode(); hash = 31 * hash + Objects.hashCode(total); hash = 31 * hash + Objects.hashCode(current); return hash; } @Override public String toString() { return "SystemServiceItemSuspendedEvent{" + "item='" + item + '\'' + ", total=" + total + ", current=" + current + ", eventType='" + getType() + '\'' + ", service='" + getServiceName() + "\'}"; } } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/event/service/SystemServiceStoppedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.event.service; import org.eclipse.che.api.system.shared.event.EventType; /** * See {@link EventType#SERVICE_STOPPED} description. * * @author Yevhenii Voevodin */ public class SystemServiceStoppedEvent extends SystemServiceEvent { public SystemServiceStoppedEvent(String serviceName) { super(serviceName); } @Override public EventType getType() { return EventType.SERVICE_STOPPED; } } ================================================ FILE: wsmaster/che-core-api-system-shared/src/main/java/org/eclipse/che/api/system/shared/event/service/SystemServiceSuspendedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.system.shared.event.service; import org.eclipse.che.api.system.shared.event.EventType; /** * See {@link EventType#SERVICE_SUSPENDED} description. * * @author Max Shaposhnyk */ public class SystemServiceSuspendedEvent extends SystemServiceEvent { public SystemServiceSuspendedEvent(String serviceName) { super(serviceName); } @Override public EventType getType() { return EventType.SERVICE_SUSPENDED; } } ================================================ FILE: wsmaster/che-core-api-user/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-user Che Core :: API :: User false com.google.code.gson gson com.google.guava guava com.google.inject guice io.swagger.core.v3 swagger-annotations-jakarta jakarta.annotation jakarta.annotation-api jakarta.inject jakarta.inject-api jakarta.ws.rs jakarta.ws.rs-api org.eclipse.che.core che-core-api-account org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-api-user-shared org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.core che-core-commons-lang org.eclipse.che.core che-core-commons-test org.slf4j slf4j-api com.google.inject.extensions guice-persist provided org.eclipse.persistence jakarta.persistence provided commons-fileupload commons-fileupload test io.rest-assured rest-assured test org.eclipse.che.core che-core-commons-inject test org.eclipse.che.core che-core-commons-json test org.eclipse.che.core che-core-sql-schema test org.eclipse.persistence org.eclipse.persistence.core test org.eclipse.persistence org.eclipse.persistence.jpa test org.everrest everrest-assured test org.everrest everrest-core test org.flywaydb flyway-core test org.hamcrest hamcrest test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test org.apache.maven.plugins maven-jar-plugin test-jar **/spi/tck/*.* ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/AppStatesPreferenceCleaner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.inject.Inject; import com.google.inject.Singleton; import jakarta.annotation.PostConstruct; import java.util.Map; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.workspace.shared.event.WorkspaceRemovedEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Handler for clean up app state preference when workspace is removed. */ @Singleton public class AppStatesPreferenceCleaner implements EventSubscriber { private static final Logger LOG = LoggerFactory.getLogger(AppStatesPreferenceCleaner.class); /** The name of the property for the mappings in user preferences. */ public static final String APP_STATES_PREFERENCE_PROPERTY = "IdeAppStates"; private JsonParser jsonParser; private EventService eventService; private UserManager userManager; private PreferenceManager preferenceManager; @Inject public AppStatesPreferenceCleaner( JsonParser jsonParser, EventService eventService, UserManager userManager, PreferenceManager preferenceManager) { this.jsonParser = jsonParser; this.eventService = eventService; this.userManager = userManager; this.preferenceManager = preferenceManager; } @PostConstruct public void subscribe() { eventService.subscribe(this); } @Override public void onEvent(WorkspaceRemovedEvent workspaceRemovedEvent) { try { Workspace workspace = workspaceRemovedEvent.getWorkspace(); User user = userManager.getByName(workspace.getNamespace()); if (user == null) { return; } String userId = user.getId(); Map preferences = preferenceManager.find(userId); String appStates = preferences.get(APP_STATES_PREFERENCE_PROPERTY); if (appStates == null) { return; } JsonObject workspaces = jsonParser.parse(appStates).getAsJsonObject(); JsonElement removedWorkspacePreferences = workspaces.remove(workspace.getId()); if (removedWorkspacePreferences != null) { preferences.put(APP_STATES_PREFERENCE_PROPERTY, workspaces.toString()); preferenceManager.save(userId, preferences); } } catch (NotFoundException | ServerException e) { Workspace workspace = workspaceRemovedEvent.getWorkspace(); LOG.error( "Unable to clean up preferences for owner of the workspace {} with namespace {} because of '{}'", workspace.getId(), workspace.getNamespace(), e.getMessage()); } } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/CheUserCreator.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import static java.util.Collections.emptyList; import jakarta.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.account.api.AccountManager; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.model.impl.UserImpl; /** * Creates 'che' default user. * * @author Anton Korneta */ @Deprecated @Singleton public class CheUserCreator { @Inject private UserManager userManager; @Inject private AccountManager accountManager; @PostConstruct public void createCheUser() throws ServerException { try { userManager.getById("che"); } catch (NotFoundException ex) { try { final UserImpl cheUser = new UserImpl("che", "che@eclipse.org", "che", "secret", emptyList()); userManager.create(cheUser, false); } catch (ConflictException ignore) { } } } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/Constants.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; /** * Constants for User/Profile/Preferences API. * * @author Yevhenii Voevodin * @author Max Shaposhnik */ public final class Constants { /** Profile link relationships. */ public static final String LINK_REL_CURRENT_PROFILE = "current_profile"; public static final String LINK_REL_CURRENT_PROFILE_ATTRIBUTES = "current_profile.attributes"; public static final String LINK_REL_PROFILE = "profile"; public static final String LINK_REL_PROFILE_ATTRIBUTES = "profile.attributes"; /** User links relationships. */ public static final String LINK_REL_USER = "user"; public static final String LINK_REL_CURRENT_USER = "current_user"; public static final String LINK_REL_CURRENT_USER_PASSWORD = "current_user.password"; public static final String LINK_REL_CURRENT_USER_SETTINGS = "current_user.settings"; /** Preferences links relationships. */ public static final String LINK_REL_PREFERENCES = "preferences"; public static final String LINK_REL_SELF = "self"; public static final int ID_LENGTH = 16; public static final int PASSWORD_LENGTH = 10; private Constants() {} } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/DtoConverter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.user.shared.dto.UserDto; import org.eclipse.che.dto.server.DtoFactory; /** * Helps to convert to/from DTOs related to user. * * @author Anatoliy Bazko */ public final class DtoConverter { /** Converts {@link User} to {@link UserDto}. */ public static UserDto asDto(User user) { return DtoFactory.getInstance() .createDto(UserDto.class) .withId(user.getId()) .withEmail(user.getEmail()) .withName(user.getName()) .withAliases(user.getAliases()); } private DtoConverter() {} } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/NotImplementedTokenValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import javax.inject.Singleton; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.model.user.User; @Singleton public class NotImplementedTokenValidator implements TokenValidator { @Override public User validateToken(String token) throws ConflictException { throw new ConflictException("Token validation do not implemented"); } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/PreferenceManager.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import static java.util.Objects.requireNonNull; import com.google.common.util.concurrent.Striped; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.spi.PreferenceDao; /** * Preferences manager layer, simplifies preferences service by taking all the business logic out * from the service and making that logic easily reusable throughout the system. * *

    The manager doesn't perform any bean validations and it is expected that all the incoming * objects are valid, nevertheless this exactly the right place for performing business validations. * * @author Yevhenii Voevodin */ @Deprecated @Singleton public class PreferenceManager { private static final Striped UPDATE_REENTRANT_LOCKS = Striped.lazyWeakLock(32); @Inject private PreferenceDao preferenceDao; /** * Associates the given {@code preferences} with the given {@code userId}. * *

    Note that this method will override all the existing properties for the user with id {@code * userId}. * * @param userId the user id whom the {@code preferences} belong to * @param preferences the preferences to associate with the {@code userId} * @throws NullPointerException when either {@code userId} or {@code preferences} is null * @throws ServerException when any error occurs */ public void save(String userId, Map preferences) throws ServerException { requireNonNull(userId, "Required non-null user id"); requireNonNull(preferences, "Required non-null preferences"); preferenceDao.setPreferences(userId, preferences); } /** * Updates the preferences of the user by merging given {@code preferences} with the existing * preferences. If user doesn't have any preferences then the given {@code preferences} will be * associated with the user. * * @param userId the user whose preferences should be updated * @param preferences preferences update * @return all the user's preferences including the update * @throws NullPointerException when either {@code userId} or {@code preferences} is null * @throws ServerException when any error occurs */ public Map update(String userId, Map preferences) throws ServerException { requireNonNull(userId, "Required non-null user id"); requireNonNull(preferences, "Required non-null preferences"); // Holding reference to prevent garbage collection // this reentrantLock helps to avoid race-conditions when parallel updates are applied final Lock reentrantLock = UPDATE_REENTRANT_LOCKS.get(userId); reentrantLock.lock(); try { final Map found = preferenceDao.getPreferences(userId); found.putAll(preferences); preferenceDao.setPreferences(userId, found); return found; } finally { reentrantLock.unlock(); } } /** * Finds user's preferences. * * @param userId user id to find preferences * @return found preferences or empty map, if there are no preferences related to user * @throws NullPointerException when {@code userId} is null * @throws ServerException when any error occurs */ public Map find(String userId) throws ServerException { requireNonNull(userId, "Required non-null user id"); return Collections.emptyMap(); } /** * Finds user's preferences. * * @param userId user id to find preferences * @param keyFilter regex which is used to filter preferences by keys, so result contains only the * user's preferences that match {@code keyFilter} regex * @return found preferences filtered by {@code keyFilter} or an empty map if there are no * preferences related to user * @throws NullPointerException when {@code userId} is null * @throws ServerException when any error occurs */ public Map find(String userId, String keyFilter) throws ServerException { requireNonNull(userId, "Required non-null user id"); return Collections.emptyMap(); } /** * Removes(clears) user's preferences. * * @param userId the id of the user to remove preferences * @throws NullPointerException when {@code userId} is null * @throws ServerException when any error occurs */ public void remove(String userId) throws ServerException { requireNonNull(userId, "Required non-null user id"); preferenceDao.remove(userId); } /** * Removes the preferences with the given {@code names}. * * @param userId the id of the user to remove preferences * @param names the names to remove * @throws NullPointerException when either {@code userId} or {@code names} is null * @throws ServerException when any error occurs */ public void remove(String userId, List names) throws ServerException { requireNonNull(userId, "Required non-null user id"); requireNonNull(names, "Required non-null preference names"); // Holding reference to prevent garbage collection // this reentrantLock helps to avoid race-conditions when parallel updates are applied final Lock reentrantLock = UPDATE_REENTRANT_LOCKS.get(userId); reentrantLock.lock(); try { final Map preferences = preferenceDao.getPreferences(userId); names.forEach(preferences::remove); preferenceDao.setPreferences(userId, preferences); } finally { reentrantLock.unlock(); } } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/ProfileManager.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import static java.util.Objects.requireNonNull; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.Profile; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.user.server.model.impl.ProfileImpl; import org.eclipse.che.api.user.server.spi.ProfileDao; /** * Preferences manager layer, simplifies prefernces service by taking all the business logic out * from the service and making that logic easily reusable throughout the system. * *

    The manager doesn't perform any bean validations and it is expected that all the incoming * objects are valid, nevertheless this exactly the right place for performing business validations. * * @author Yevhenii Voevodin */ @Singleton public class ProfileManager { @Inject private ProfileDao profileDao; /** * Finds the profile related to the user with given {@code userId}. * * @param userId the id to search the user's profile * @return found profile * @throws NullPointerException when {@code userId} is null * @throws NotFoundException when there is no profile for the user with the id {@code userId} * @throws ServerException when any other error occurs */ public Profile getById(String userId) throws NotFoundException, ServerException { requireNonNull(userId, "Required non-null user id"); return profileDao.getById(userId); } /** * Creates a new user's profile . * * @param profile new profile * @throws NullPointerException when profile is null * @throws ConflictException when profile for the user {@code profile.getUserId()} already exists * @throws ServerException when any other error occurs */ public void create(Profile profile) throws ServerException, ConflictException { requireNonNull(profile, "Required non-null profile"); profileDao.create(new ProfileImpl(profile)); } /** * Updates current profile using replace strategy. * *

    Note that {@link Profile#getEmail()} can't be updated using this method as it is mirrored * from the {@link User#getEmail()}. * * @param profile profile update * @throws NullPointerException when {@code profile} is null * @throws NotFoundException when there is no profile for the user with the id {@code * profile.getUserId()} * @throws ServerException when any other error occurs */ public void update(Profile profile) throws NotFoundException, ServerException { requireNonNull(profile, "Required non-null profile"); profileDao.update(new ProfileImpl(profile)); } /** * Removes the user's profile. * *

    Note that this method won't throw any exception when user doesn't have the corresponding * profile. * * @param userId the id of the user, whose profile should be removed * @throws NullPointerException when {@code id} is null * @throws ServerException when any other error occurs */ public void remove(String userId) throws ServerException { requireNonNull(userId, "Required non-null user id"); profileDao.remove(userId); } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/TokenValidator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.model.user.User; /** * Validates token. * * @author Eugene Voevodin * @see UserService */ public interface TokenValidator { /** * Validates {@code token}. * * @return user email * @throws ConflictException when token is not valid */ User validateToken(String token) throws ConflictException; } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserManager.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static org.eclipse.che.api.user.server.Constants.ID_LENGTH; import static org.eclipse.che.api.user.server.Constants.PASSWORD_LENGTH; import static org.eclipse.che.commons.lang.NameGenerator.generate; import com.google.common.collect.Sets; import com.google.inject.persist.Transactional; import java.util.Optional; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.Profile; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.user.server.event.UserCreatedEvent; import org.eclipse.che.api.user.server.event.UserRemovedEvent; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.user.server.spi.PreferenceDao; import org.eclipse.che.api.user.server.spi.ProfileDao; import org.eclipse.che.api.user.server.spi.UserDao; /** * Facade for {@link User} and {@link Profile} related operations. * * @author Max Shaposhnik (mshaposhnik@codenvy.com) * @author Yevhenii Voevodin * @author Anton Korneta */ @Deprecated @Singleton public class UserManager { public static final String PERSONAL_ACCOUNT = "personal"; protected final UserDao userDao; protected final ProfileDao profileDao; protected final PreferenceDao preferencesDao; protected final Set reservedNames; protected final EventService eventService; @Inject public UserManager( UserDao userDao, ProfileDao profileDao, PreferenceDao preferencesDao, EventService eventService, @Named("che.auth.reserved_user_names") String[] reservedNames) { this.userDao = userDao; this.profileDao = profileDao; this.preferencesDao = preferencesDao; this.eventService = eventService; this.reservedNames = Sets.newHashSet(reservedNames); } /** * Creates new user and his profile. NOTE: The user data is no longer persisted in the database. * * @param newUser created user * @throws NullPointerException when {@code newUser} is null * @throws ConflictException when user with such name/email/alias already exists * @throws ServerException when any other error occurs */ public User create(User newUser, boolean isTemporary) throws ConflictException, ServerException { requireNonNull(newUser, "Required non-null user"); if (reservedNames.contains(newUser.getName().toLowerCase())) { throw new ConflictException(String.format("Username '%s' is reserved", newUser.getName())); } final String userId = newUser.getId() != null ? newUser.getId() : generate("user", ID_LENGTH); final UserImpl user = new UserImpl( userId, newUser.getEmail(), newUser.getName(), firstNonNull(newUser.getPassword(), generate("", PASSWORD_LENGTH)), newUser.getAliases()); eventService.publish(new UserCreatedEvent(user)); return user; } @Transactional(rollbackOn = {RuntimeException.class, ApiException.class}) protected void doCreate(UserImpl user, boolean isTemporary) throws ConflictException, ServerException { throw new UnsupportedOperationException( "'doCreate' method should never be called. User information is no longer persisted in the database."); } /** * Updates user by replacing an existing user entity with a new one. * * @param user user update * @throws NullPointerException when {@code user} is null * @throws NotFoundException when user with id {@code user.getId()} is not found * @throws ConflictException when user's new alias/email/name is not unique * @throws ServerException when any other error occurs */ public void update(User user) throws NotFoundException, ServerException, ConflictException { requireNonNull(user, "Required non-null user"); userDao.update(new UserImpl(user)); } /** * Finds user by given {@code id}. * * @param id user identifier * @return user instance * @throws NullPointerException when {@code id} is null * @throws NotFoundException when user doesn't exist * @throws ServerException when any other error occurs */ public User getById(String id) throws NotFoundException, ServerException { requireNonNull(id, "Required non-null id"); return userDao.getById(id); } /** * Finds user by given {@code alias}. * * @param alias user alias * @return user instance * @throws NullPointerException when {@code alias} is null * @throws NotFoundException when user doesn't exist * @throws ServerException when any other error occurs */ public User getByAlias(String alias) throws NotFoundException, ServerException { requireNonNull(alias, "Required non-null alias"); return userDao.getByAlias(alias); } /** * Finds user by given {@code name}. * * @param name user name * @return user instance * @throws NullPointerException when {@code name} is null * @throws NotFoundException when user doesn't exist * @throws ServerException when any other error occurs */ public User getByName(String name) throws NotFoundException, ServerException { requireNonNull(name, "Required non-null name"); return userDao.getByName(name); } /** * Finds user by given {@code email}. * * @param email user email * @return user instance * @throws NullPointerException when {@code email} is null * @throws NotFoundException when user doesn't exist * @throws ServerException when any other error occurs */ public User getByEmail(String email) throws NotFoundException, ServerException { requireNonNull(email, "Required non-null email"); return userDao.getByEmail(email); } /** * Finds all users {@code email}. * * @param maxItems the maximum number of users to return * @param skipCount the number of users to skip * @return user instance * @throws IllegalArgumentException when {@code maxItems} or {@code skipCount} is negative * @throws ServerException when any other error occurs */ public Page getAll(int maxItems, long skipCount) throws ServerException { checkArgument(maxItems >= 0, "The number of items to return can't be negative."); checkArgument(skipCount >= 0, "The number of items to skip can't be negative."); return userDao.getAll(maxItems, skipCount); } /** * Returns all users whose email address contains specified {@code emailPart}. * * @param emailPart fragment of user's email * @param maxItems the maximum number of users to return * @param skipCount the number of users to skip * @return list of matched users * @throws NullPointerException when {@code emailPart} is null * @throws IllegalArgumentException when {@code maxItems} or {@code skipCount} is negative or when * {@code skipCount} more than {@value Integer#MAX_VALUE} * @throws ServerException when any other error occurs */ public Page getByEmailPart(String emailPart, int maxItems, long skipCount) throws ServerException { requireNonNull(emailPart, "Required non-null email part"); checkArgument(maxItems >= 0, "The number of items to return can't be negative"); checkArgument( skipCount >= 0 && skipCount <= Integer.MAX_VALUE, "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE); return userDao.getByEmailPart(emailPart, maxItems, skipCount); } /** * Returns all users whose name contains specified {@code namePart}. * * @param namePart fragment of user's name * @param maxItems the maximum number of users to return * @param skipCount the number of users to skip * @return list of matched users * @throws NullPointerException when {@code namePart} is null * @throws IllegalArgumentException when {@code maxItems} or {@code skipCount} is negative or when * {@code skipCount} more than {@value Integer#MAX_VALUE} * @throws ServerException when any other error occurs */ public Page getByNamePart(String namePart, int maxItems, long skipCount) throws ServerException { requireNonNull(namePart, "Required non-null name part"); checkArgument(maxItems >= 0, "The number of items to return can't be negative"); checkArgument( skipCount >= 0 && skipCount <= Integer.MAX_VALUE, "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE); return userDao.getByNamePart(namePart, maxItems, skipCount); } /** * Gets total count of all users * * @return user count * @throws ServerException when any error occurs */ public long getTotalCount() throws ServerException { return userDao.getTotalCount(); } /** * Removes user by given {@code id}. * * @param id user identifier * @throws NullPointerException when {@code id} is null * @throws ConflictException when given user cannot be deleted * @throws ServerException when any other error occurs */ public void remove(String id) throws ServerException, ConflictException { requireNonNull(id, "Required non-null id"); doRemove(id); eventService.publish(new UserRemovedEvent(id)); } /** * Method is used to retrieve user object from Che DB for given user {@code id}, {@code email}, * and {@code username}. Various actualization operations may be performed: * *

    - if user is found in Che DB by the given {@code id}, then it will check, if it's email, * matches the {@code email} , and update it in DB if necessary. * *

    - if user is not found in Che DB by the given {@code id} , then attempt to create one. But * also, it will attempt to get one by {@code email}. If such is found, he will be removed. That * way, there will be no conflict with existing user id or email upon recreation. In case of * conflict with user name, it may be prepended randomized symbols * * @param id - user id from * @param email - user email * @param username - user name * @return user object from Che Database, with all needed actualization operations performed on * him * @throws ServerException if this exception during user creation, removal, or retrieval * @throws ConflictException if this exception occurs during user creation or removal */ public User getOrCreateUser(String id, String email, String username) throws ServerException, ConflictException { return new UserImpl(id, email, username, generate("", 12), emptyList()); } /** * Method is used to retrieve user object from Che DB for given user {@code id} and {@code * username} * *

    - if user is found in Che DB by the given {@code id}, then it will update it in DB if * necessary. * *

    - if user is not found in Che DB by the given {@code id} , then attempt to create one. In * case of conflict with user name, it may be prepended randomized symbols * * @param id - user id from * @param username - user name * @return user object from Che Database, with all needed actualization operations performed on * him * @throws ServerException if this exception during user creation, removal, or retrieval * @throws ConflictException if this exception occurs during user creation or removal */ public User getOrCreateUser(String id, String username) throws ServerException, ConflictException { return new UserImpl(id, username + "@che", username, generate("", 12), emptyList()); } @Transactional( rollbackOn = {RuntimeException.class, ServerException.class, ConflictException.class}) protected void doRemove(String id) throws ConflictException, ServerException { UserImpl user; try { user = userDao.getById(id); } catch (NotFoundException ignored) { return; } preferencesDao.remove(id); profileDao.remove(id); userDao.remove(id); } /** * Performs check that {@code email} matches with the one in local DB, and synchronize them * otherwise */ private User actualizeUserEmail(User actualUser, String email) throws ServerException { if (isNullOrEmpty(email) || actualUser.getEmail().equals(email)) { return actualUser; } UserImpl update = new UserImpl(actualUser); update.setEmail(email); try { update(update); } catch (NotFoundException e) { throw new ServerException("Unable to actualize user email. User not found.", e); } catch (ConflictException e) { throw new ServerException( "Unable to actualize user email. Another user with such email exists", e); } return update; } private Optional getUserById(String id) throws ServerException { try { return Optional.of(getById(id)); } catch (NotFoundException e) { return Optional.empty(); } } private Optional getUserByEmail(String email) throws ServerException { try { return Optional.of(getByEmail(email)); } catch (NotFoundException e) { return Optional.empty(); } } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserService.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import javax.inject.Inject; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.commons.env.EnvironmentContext; /** * User REST API. * * @author Yevhenii Voevodin * @author Anton Korneta */ @Path("/user") @Tag(name = "user", description = "User REST API") public class UserService extends Service { @Inject public UserService() {} @GET @Path("/id") @Produces(TEXT_PLAIN) @Operation( summary = "Get current user's id", responses = { @ApiResponse( responseCode = "200", description = "The response contains current user's id ('0000-00-0000' is returned for the anonymous user)"), }) public String getId() { return userId(); } private static String userId() { return EnvironmentContext.getCurrent().getSubject().getUserId(); } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserValidator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.common.annotations.VisibleForTesting; import javax.inject.Inject; import org.eclipse.che.account.spi.AccountValidator; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.User; /** * Utils for username validation and normalization. * * @author Mihail Kuznyetsov * @author Yevhenii Voevodin * @author Sergii Leschenko */ public class UserValidator { @VisibleForTesting static final String GENERATED_NAME_PREFIX = "username"; private final AccountValidator accountValidator; @Inject public UserValidator(AccountValidator accountValidator) { this.accountValidator = accountValidator; } /** * Checks whether given user is valid. * * @param user user to check * @throws BadRequestException when user is not valid */ public void checkUser(User user) throws BadRequestException { if (user == null) { throw new BadRequestException("User required"); } if (isNullOrEmpty(user.getName())) { throw new BadRequestException("User name required"); } if (!isValidName(user.getName())) { throw new BadRequestException( "Username may only contain alphanumeric characters or single hyphens inside"); } if (isNullOrEmpty(user.getEmail())) { throw new BadRequestException("User email required"); } if (user.getPassword() != null) { checkPassword(user.getPassword()); } } /** * Checks whether password is ok. * * @param password password to check * @throws BadRequestException when password is not valid */ public void checkPassword(String password) throws BadRequestException { if (password == null) { throw new BadRequestException("Password required"); } if (password.length() < 8) { throw new BadRequestException("Password should contain at least 8 characters"); } int numOfLetters = 0; int numOfDigits = 0; for (char passwordChar : password.toCharArray()) { if (Character.isDigit(passwordChar)) { numOfDigits++; } else if (Character.isLetter(passwordChar)) { numOfLetters++; } } if (numOfDigits == 0 || numOfLetters == 0) { throw new BadRequestException("Password should contain letters and digits"); } } /** * Validate name, if it doesn't contain illegal characters * * @param name username * @return true if valid name, false otherwise */ public boolean isValidName(String name) { return accountValidator.isValidName(name); } /** * Remove illegal characters from username, to make it URL-friendly. If all characters are * illegal, return automatically generated username. Also ensures username is unique, if not, adds * digits to it's end. * * @param name username * @return username without illegal characters */ public String normalizeUserName(String name) throws ServerException { return accountValidator.normalizeAccountName(name, GENERATED_NAME_PREFIX); } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/event/UserCreatedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.event; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.notification.EventOrigin; import org.eclipse.che.api.user.server.model.impl.UserImpl; /** * Will be published when {@link UserImpl user} is created. * * @author Anatolii Bazko */ @EventOrigin("user") public class UserCreatedEvent { private final User user; public UserCreatedEvent(User user) { this.user = user; } public User getUser() { return user; } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/event/UserRemovedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.event; import org.eclipse.che.api.core.notification.EventOrigin; import org.eclipse.che.api.user.server.model.impl.UserImpl; /** * Published after {@link UserImpl user} removed. * * @author Sergii Kabashniuk */ @EventOrigin("user") public class UserRemovedEvent { private final String userId; public UserRemovedEvent(String userId) { this.userId = userId; } /** Returns id of removed user */ public String getUserId() { return userId; } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaPreferenceDao.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.jpa; import static java.util.Objects.requireNonNull; import com.google.inject.persist.Transactional; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import javax.persistence.EntityManager; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.spi.PreferenceDao; /** * Implementation of {@link PreferenceDao}. * * @author Anton Korneta */ @Singleton public class JpaPreferenceDao implements PreferenceDao { @Inject private Provider managerProvider; @Override public void setPreferences(String userId, Map preferences) throws ServerException { requireNonNull(userId); requireNonNull(preferences); final PreferenceEntity prefs = new PreferenceEntity(userId, preferences); if (preferences.isEmpty()) { remove(userId); } else { try { doSetPreference(prefs); } catch (RuntimeException ex) { throw new ServerException(ex.getLocalizedMessage(), ex); } } } @Override @Transactional public Map getPreferences(String userId) throws ServerException { requireNonNull(userId); try { final EntityManager manager = managerProvider.get(); final PreferenceEntity prefs = manager.find(PreferenceEntity.class, userId); return prefs == null ? new HashMap<>() : prefs.getPreferences(); } catch (RuntimeException ex) { throw new ServerException(ex.getLocalizedMessage(), ex); } } @Override @Transactional public Map getPreferences(String userId, String filter) throws ServerException { requireNonNull(userId); requireNonNull(filter); try { final EntityManager manager = managerProvider.get(); final PreferenceEntity prefs = manager.find(PreferenceEntity.class, userId); if (prefs == null) { return new HashMap<>(); } final Map preferences = prefs.getPreferences(); if (!filter.isEmpty()) { final Pattern pattern = Pattern.compile(filter); return preferences.entrySet().stream() .filter(preference -> pattern.matcher(preference.getKey()).matches()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } else { return preferences; } } catch (RuntimeException ex) { throw new ServerException(ex.getLocalizedMessage(), ex); } } @Override public void remove(String userId) throws ServerException { requireNonNull(userId); try { doRemove(userId); } catch (RuntimeException ex) { throw new ServerException(ex); } } @Transactional protected void doSetPreference(PreferenceEntity prefs) { final EntityManager manager = managerProvider.get(); final PreferenceEntity existing = manager.find(PreferenceEntity.class, prefs.getUserId()); if (existing != null) { manager.merge(prefs); } else { manager.persist(prefs); } manager.flush(); } @Transactional protected void doRemove(String userId) { final EntityManager manager = managerProvider.get(); final PreferenceEntity prefs = manager.find(PreferenceEntity.class, userId); if (prefs != null) { manager.remove(prefs); manager.flush(); } } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaProfileDao.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.jpa; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import com.google.inject.persist.Transactional; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import javax.persistence.EntityManager; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.model.impl.ProfileImpl; import org.eclipse.che.api.user.server.spi.ProfileDao; @Singleton public class JpaProfileDao implements ProfileDao { @Inject private Provider managerProvider; @Override public void create(ProfileImpl profile) throws ServerException, ConflictException { requireNonNull(profile, "Required non-null profile"); try { doCreate(profile); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override public void update(ProfileImpl profile) throws NotFoundException, ServerException { requireNonNull(profile, "Required non-null profile"); try { doUpdate(profile); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override public void remove(String id) throws ServerException { requireNonNull(id, "Required non-null id"); try { doRemove(id); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public ProfileImpl getById(String userId) throws NotFoundException, ServerException { requireNonNull(userId, "Required non-null id"); try { final EntityManager manager = managerProvider.get(); final ProfileImpl profile = manager.find(ProfileImpl.class, userId); if (profile == null) { throw new NotFoundException(format("Couldn't find profile for user with id '%s'", userId)); } manager.refresh(profile); return profile; } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Transactional protected void doCreate(ProfileImpl profile) { EntityManager manager = managerProvider.get(); manager.persist(profile); manager.flush(); } @Transactional protected void doUpdate(ProfileImpl profile) throws NotFoundException { final EntityManager manager = managerProvider.get(); if (manager.find(ProfileImpl.class, profile.getUserId()) == null) { throw new NotFoundException( format( "Couldn't update profile, because profile for user with id '%s' doesn't exist", profile.getUserId())); } manager.merge(profile); manager.flush(); } @Transactional protected void doRemove(String userId) { final EntityManager manager = managerProvider.get(); final ProfileImpl profile = manager.find(ProfileImpl.class, userId); if (profile != null) { manager.remove(profile); manager.flush(); } } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaUserDao.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.jpa; import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import com.google.inject.persist.Transactional; import java.util.List; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.user.server.spi.UserDao; import org.eclipse.che.security.PasswordEncryptor; /** * JPA based implementation of {@link UserDao}. * * @author Yevhenii Voevodin * @author Anton Korneta * @author Igor Vinokur */ @Singleton public class JpaUserDao implements UserDao { @Inject protected Provider managerProvider; @Inject private PasswordEncryptor encryptor; @Override @Transactional public UserImpl getByAliasAndPassword(String emailOrName, String password) throws NotFoundException, ServerException { requireNonNull(emailOrName, "Required non-null email or name"); requireNonNull(password, "Required non-null password"); try { final UserImpl user = managerProvider .get() .createNamedQuery("User.getByAliasAndPassword", UserImpl.class) .setParameter("alias", emailOrName) .getSingleResult(); if (!encryptor.test(password, user.getPassword())) { throw new NotFoundException( format("User with email or name '%s' and given password doesn't exist", emailOrName)); } return erasePassword(user); } catch (NoResultException x) { throw new NotFoundException( format("User with email or name '%s' and given password doesn't exist", emailOrName)); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override public void create(UserImpl user) throws ConflictException, ServerException { requireNonNull(user, "Required non-null user"); try { if (user.getPassword() != null) { user.setPassword(encryptor.encrypt(user.getPassword())); } doCreate(user); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override public void update(UserImpl update) throws NotFoundException, ServerException, ConflictException { requireNonNull(update, "Required non-null update"); try { doUpdate(update); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override public void remove(String id) throws ServerException { requireNonNull(id, "Required non-null id"); try { doRemove(id); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override public UserImpl getByAlias(String alias) throws NotFoundException, ServerException { requireNonNull(alias, "Required non-null alias"); try { return erasePassword( managerProvider .get() .createNamedQuery("User.getByAlias", UserImpl.class) .setParameter("alias", alias) .getSingleResult()); } catch (NoResultException x) { throw new NotFoundException(format("User with alias '%s' doesn't exist", alias)); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public UserImpl getById(String id) throws NotFoundException, ServerException { requireNonNull(id, "Required non-null id"); try { final UserImpl user = managerProvider.get().find(UserImpl.class, id); if (user == null) { throw new NotFoundException(format("User with id '%s' doesn't exist", id)); } return erasePassword(user); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public UserImpl getByName(String name) throws NotFoundException, ServerException { requireNonNull(name, "Required non-null name"); try { return erasePassword( managerProvider .get() .createNamedQuery("User.getByName", UserImpl.class) .setParameter("name", name) .getSingleResult()); } catch (NoResultException x) { throw new NotFoundException(format("User with name '%s' doesn't exist", name)); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public UserImpl getByEmail(String email) throws NotFoundException, ServerException { requireNonNull(email, "Required non-null email"); try { return erasePassword( managerProvider .get() .createNamedQuery("User.getByEmail", UserImpl.class) .setParameter("email", email) .getSingleResult()); } catch (NoResultException x) { throw new NotFoundException(format("User with email '%s' doesn't exist", email)); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public Page getAll(int maxItems, long skipCount) throws ServerException { // TODO need to ensure that 'getAll' query works with same data as 'getTotalCount' checkArgument(maxItems >= 0, "The number of items to return can't be negative."); checkArgument( skipCount >= 0 && skipCount <= Integer.MAX_VALUE, "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE); try { final List list = managerProvider .get() .createNamedQuery("User.getAll", UserImpl.class) .setMaxResults(maxItems) .setFirstResult((int) skipCount) .getResultList() .stream() .map(JpaUserDao::erasePassword) .collect(toList()); return new Page<>(list, skipCount, maxItems, getTotalCount()); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public Page getByNamePart(String namePart, int maxItems, long skipCount) throws ServerException { requireNonNull(namePart, "Required non-null name part"); checkArgument(maxItems >= 0, "The number of items to return can't be negative"); checkArgument( skipCount >= 0 && skipCount <= Integer.MAX_VALUE, "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE); try { final List list = managerProvider .get() .createNamedQuery("User.getByNamePart", UserImpl.class) .setParameter("name", namePart.toLowerCase()) .setMaxResults(maxItems) .setFirstResult((int) skipCount) .getResultList() .stream() .map(JpaUserDao::erasePassword) .collect(toList()); final long count = managerProvider .get() .createNamedQuery("User.getByNamePartCount", Long.class) .setParameter("name", namePart.toLowerCase()) .getSingleResult(); return new Page<>(list, skipCount, maxItems, count); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public Page getByEmailPart(String emailPart, int maxItems, long skipCount) throws ServerException { requireNonNull(emailPart, "Required non-null email part"); checkArgument(maxItems >= 0, "The number of items to return can't be negative"); checkArgument( skipCount >= 0 && skipCount <= Integer.MAX_VALUE, "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE); try { final List list = managerProvider .get() .createNamedQuery("User.getByEmailPart", UserImpl.class) .setParameter("email", emailPart.toLowerCase()) .setMaxResults(maxItems) .setFirstResult((int) skipCount) .getResultList() .stream() .map(JpaUserDao::erasePassword) .collect(toList()); final long count = managerProvider .get() .createNamedQuery("User.getByEmailPartCount", Long.class) .setParameter("email", emailPart.toLowerCase()) .getSingleResult(); return new Page<>(list, skipCount, maxItems, count); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public long getTotalCount() throws ServerException { try { return managerProvider .get() .createNamedQuery("User.getTotalCount", Long.class) .getSingleResult(); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Transactional(rollbackOn = {RuntimeException.class, ApiException.class}) protected void doCreate(UserImpl user) throws ConflictException, ServerException { EntityManager manage = managerProvider.get(); manage.persist(user); manage.flush(); } @Transactional protected void doUpdate(UserImpl update) throws NotFoundException { final EntityManager manager = managerProvider.get(); final UserImpl user = manager.find(UserImpl.class, update.getId()); if (user == null) { throw new NotFoundException( format("Couldn't update user with id '%s' because it doesn't exist", update.getId())); } final String password = update.getPassword(); if (password != null) { update.setPassword(encryptor.encrypt(password)); } else { update.setPassword(user.getPassword()); } manager.merge(update); manager.flush(); } @Transactional(rollbackOn = {RuntimeException.class, ServerException.class}) protected void doRemove(String id) { final EntityManager manager = managerProvider.get(); final UserImpl user = manager.find(UserImpl.class, id); if (user != null) { manager.remove(user); manager.flush(); } } // Returns user instance copy without password private static UserImpl erasePassword(UserImpl source) { return new UserImpl( source.getId(), source.getEmail(), source.getName(), null, source.getAliases()); } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/PreferenceEntity.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.jpa; import java.util.HashMap; import java.util.Map; import java.util.Objects; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.Table; /** * Describes JPA implementation of user's preferences. * * @author Anton Korneta * @author Yevhenii Voevodin */ @Entity(name = "Preference") @Table(name = "preference") public class PreferenceEntity { @Id @Column(name = "userid") private String userId; @ElementCollection @CollectionTable( name = "preference_preferences", joinColumns = @JoinColumn(name = "preference_userid")) @MapKeyColumn(name = "name") @Column(name = "value_param", columnDefinition = "TEXT") private Map preferences; public PreferenceEntity() {} public PreferenceEntity(String userId, Map preferences) { this.userId = userId; this.preferences = preferences; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public Map getPreferences() { if (preferences == null) { preferences = new HashMap<>(); } return preferences; } public void setPreferences(Map preferences) { this.preferences = preferences; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof PreferenceEntity)) return false; final PreferenceEntity other = (PreferenceEntity) obj; return Objects.equals(userId, other.userId) && getPreferences().equals(other.getPreferences()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(userId); hash = 31 * hash + getPreferences().hashCode(); return hash; } @Override public String toString() { return "PreferenceEntity{" + "userId='" + userId + '\'' + ", preferences=" + preferences + '}'; } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/UserJpaModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.jpa; import com.google.inject.AbstractModule; import org.eclipse.che.api.user.server.spi.PreferenceDao; import org.eclipse.che.api.user.server.spi.ProfileDao; import org.eclipse.che.api.user.server.spi.UserDao; import org.eclipse.che.security.PBKDF2PasswordEncryptor; import org.eclipse.che.security.PasswordEncryptor; /** * @author Yevhenii Voevodin */ public class UserJpaModule extends AbstractModule { @Override protected void configure() { bind(PasswordEncryptor.class).to(PBKDF2PasswordEncryptor.class); bind(UserDao.class).to(JpaUserDao.class); bind(ProfileDao.class).to(JpaProfileDao.class); bind(PreferenceDao.class).to(JpaPreferenceDao.class); } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/model/impl/ProfileImpl.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.model.impl; import java.util.HashMap; import java.util.Map; import java.util.Objects; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; import org.eclipse.che.api.core.model.user.Profile; /** * Data object for the {@link Profile}. * * @author Yevhenii Voevodin */ @Entity(name = "Profile") @Table(name = "profile") public class ProfileImpl implements Profile { @Id @Column(name = "userid") private String userId; @PrimaryKeyJoinColumn private UserImpl user; @ElementCollection @MapKeyColumn(name = "name") @Column(name = "value_param", nullable = false) @CollectionTable(name = "profile_attributes", joinColumns = @JoinColumn(name = "user_id")) private Map attributes; public ProfileImpl() {} public ProfileImpl(String userId) { this.userId = userId; } public ProfileImpl(String userId, Map attributes) { this.userId = userId; if (attributes != null) { this.attributes = new HashMap<>(attributes); } } public ProfileImpl(Profile profile) { this(profile.getUserId(), profile.getAttributes()); } public ProfileImpl(ProfileImpl profile) { this(profile.getUserId(), profile.getAttributes()); this.user = profile.user; } @Override public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } @Override public Map getAttributes() { if (attributes == null) { this.attributes = new HashMap<>(); } return attributes; } public UserImpl getUser() { return user; } public void setAttributes(Map attributes) { this.attributes = attributes; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof ProfileImpl)) { return false; } final ProfileImpl that = (ProfileImpl) obj; return Objects.equals(userId, that.userId) && getAttributes().equals(that.getAttributes()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(userId); hash = 31 * hash + getAttributes().hashCode(); return hash; } @Override public String toString() { return "ProfileImpl{" + "userId='" + userId + '\'' + ", attributes=" + attributes + '}'; } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/model/impl/UserImpl.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.model.impl; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; import org.eclipse.che.api.core.model.user.User; /** * Data object for the {@link User}. * * @author Yevhenii Voevodin */ @Entity(name = "Usr") @NamedQueries({ @NamedQuery( name = "User.getByAliasAndPassword", query = "SELECT u " + "FROM Usr u " + "WHERE :alias = u.name OR" + " :alias = u.email"), @NamedQuery( name = "User.getByAlias", query = "SELECT u FROM Usr u WHERE :alias MEMBER OF u.aliases"), @NamedQuery(name = "User.getByName", query = "SELECT u FROM Usr u WHERE u.name = :name"), @NamedQuery(name = "User.getByEmail", query = "SELECT u FROM Usr u WHERE u.email = :email"), @NamedQuery(name = "User.getAll", query = "SELECT u FROM Usr u"), @NamedQuery(name = "User.getTotalCount", query = "SELECT COUNT(u) FROM Usr u"), @NamedQuery( name = "User.getByEmailPart", query = "SELECT u FROM Usr u WHERE LOWER(u.email) LIKE CONCAT('%', :email, '%')"), @NamedQuery( name = "User.getByEmailPartCount", query = "SELECT COUNT(u) FROM Usr u WHERE LOWER(u.email) LIKE CONCAT('%', :email, '%')"), @NamedQuery( name = "User.getByNamePart", query = "SELECT u FROM Usr u WHERE LOWER(u.name) LIKE CONCAT('%', :name, '%')"), @NamedQuery( name = "User.getByNamePartCount", query = "SELECT COUNT(u) FROM Usr u WHERE LOWER(u.name) LIKE CONCAT('%', :name, '%')") }) @Table(name = "usr") public class UserImpl implements User { @Id @Column(name = "id") private String id; @Column(nullable = false, name = "email") private String email; @Column(nullable = false, name = "name") private String name; @Column(name = "password") private String password; @ElementCollection @Column(name = "alias", nullable = false, unique = true) @CollectionTable( name = "user_aliases", indexes = @Index(columnList = "alias"), joinColumns = @JoinColumn(name = "user_id")) private List aliases; public UserImpl() {} public UserImpl(String id, String email, String name) { this.id = id; this.name = name; this.email = email; } public UserImpl( String id, String email, String name, String password, Collection aliases) { this(id, email, name); this.password = password; if (aliases != null) { this.aliases = new ArrayList<>(aliases); } } public UserImpl(User user) { this(user.getId(), user.getEmail(), user.getName(), user.getPassword(), user.getAliases()); } @Override public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public List getAliases() { if (aliases == null) { aliases = new ArrayList<>(); } return aliases; } public void setAliases(List aliases) { this.aliases = aliases; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof UserImpl)) { return false; } final UserImpl that = (UserImpl) obj; return Objects.equals(id, that.id) && Objects.equals(email, that.email) && Objects.equals(name, that.name) && Objects.equals(password, that.password) && getAliases().equals(that.getAliases()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(email); hash = 31 * hash + Objects.hashCode(name); hash = 31 * hash + Objects.hashCode(password); hash = 31 * hash + getAliases().hashCode(); return hash; } @Override public String toString() { return "UserImpl{" + "id='" + id + '\'' + ", email='" + email + '\'' + ", name='" + name + '\'' + ", password='" + password + '\'' + ", aliases=" + aliases + '}'; } } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/spi/PreferenceDao.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.spi; import java.util.Map; import org.eclipse.che.api.core.ServerException; /** * Defines data access object contract for user preferences. * * @author Yevhenii Voevodin */ public interface PreferenceDao { /** * Sets user preferences, overrides existing preferences if any. * * @param userId user identifier * @param preferences new preferences, if preferences are empty - removes user preferences * @throws NullPointerException when preferences or userId is null * @throws ServerException when any other error occurs */ void setPreferences(String userId, Map preferences) throws ServerException; /** * Gets user preferences. * *

    Note that this method must always return upgradable map, thus it may be used as: * *

    {@code
       * Map prefs = dao.getPreferences("user123");
       * prefs.put("key", "secret");
       * spi.setPreferences("user123", prefs);
       * }
    * * @param userId user identifier * @return user preferences, or empty map * @throws NullPointerException when userId is null * @throws ServerException when any error occurs */ Map getPreferences(String userId) throws ServerException; /** * Gets user preferences filtered with given regexp. * *

    Note that this method must always return upgradable map, thus it may be used as: * *

    {@code
       * Map prefs = spi.getPreferences("user123", ".*key.*");
       * prefs.put("new-key", "secret");
       * prefs.setPreferences("user123", prefs);
       * }
    * * @param userId user identifier * @param filter {@link java.util.regex.Pattern regexp} which is used to filter preferences by * their keys * @return user preferences with keys which match given {@code filter} or empty map if user * doesn't have any preferences * @throws NullPointerException {@code userId} or {@code filter} is null * @throws ServerException when any other error occurs */ Map getPreferences(String userId, String filter) throws ServerException; /** * Removes user preferences. * *

    Note that this method doesn't throw any exception if user doesn't exist or user doesn't have * any preferences * * @param userId user identifier * @throws NullPointerException when {@code userId} is null * @throws ServerException when any error occurs */ void remove(String userId) throws ServerException; } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/spi/ProfileDao.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.spi; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.Profile; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.user.server.model.impl.ProfileImpl; /** * Data access object contract for {@link Profile}. * * @author Yevhenii Voevodin */ public interface ProfileDao { /** * Creates user profile. * * @param profile new profile * @throws NullPointerException when {@code profile} is null * @throws ServerException when any error occurs * @throws ConflictException when profile for user {@code profile.getUserId()} already exists */ void create(ProfileImpl profile) throws ServerException, ConflictException; /** * Updates profile by replacing an existing entity with a new one. * * @param profile profile update * @throws NullPointerException when {@code profile} is null * @throws NotFoundException when profile with such id doesn't exist * @throws ServerException when any other error occurs */ void update(ProfileImpl profile) throws NotFoundException, ServerException; /** * Removes profile. * * @param id profile identifier * @throws NullPointerException when {@code id} is null * @throws ServerException when any other error occurs */ void remove(String id) throws ServerException; /** * Finds profile by its id. * *

    Due to {@link Profile#getEmail()} and {@link Profile#getUserId()} definition returned * profile must contain profile owner's {@link User#getEmail() email} and {@link User#getId()} * identifier. * * @param id profile identifier * @return profile with given {@code id} * @throws NullPointerException when {@code id} is null * @throws NotFoundException when profile with such {@code id} doesn't exist * @throws ServerException when any other error occurs */ ProfileImpl getById(String id) throws NotFoundException, ServerException; } ================================================ FILE: wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/spi/UserDao.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server.spi; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.user.server.model.impl.UserImpl; /** * Defines data access object contract for {@link UserImpl}. * *

    The implementation is not required to be responsible for persistent layer data dto integrity. * It simply transfers data from one layer to another, so if you're going to call any of implemented * methods it is considered that all needed verifications are already done. * *

    Note: This particularly does not mean that method call will not make any * inconsistency, but this mean that such kind of inconsistencies are expected by design and may be * treated further. * * @author Yevhenii Voevodin * @author Anton Korneta */ public interface UserDao { /** * Gets user by email or name and password * * @param emailOrName one of user attribute such as email/name(but not id) * @param password password * @return user identifier * @throws NullPointerException when either {@code emailOrName} or {@code password} is null * @throws NotFoundException when user with such {@code emailOrName} and {@code password} doesn't * exist * @throws ServerException when any other error occurs */ UserImpl getByAliasAndPassword(String emailOrName, String password) throws NotFoundException, ServerException; /** * Creates a new user. * * @param user user to create * @throws NullPointerException when {@code user} is null * @throws ConflictException when user with such id/alias/email/name already exists * @throws ServerException when any other error occurs */ void create(UserImpl user) throws ConflictException, ServerException; /** * Updates user by replacing an existing entity with a new one. * * @param user user to update * @throws NullPointerException when {@code user} is null * @throws NotFoundException when user with id {@code user.getId()} doesn't exist * @throws ConflictException when any of the id/alias/email/name updated with a value which is not * unique * @throws ServerException when any other error occurs */ void update(UserImpl user) throws NotFoundException, ServerException, ConflictException; /** * Removes user. * *

    It is up to implementation to do cascade removing of dependent data or to forbid removing at * all. * *

    Note that this method doesn't throw any exception when user doesn't exist. * * @param id user identifier * @throws NullPointerException when {@code id} is null * @throws ServerException when any other error occurs */ void remove(String id) throws ServerException; /** * Finds user by his alias. * *

    This method doesn't work for user's email or name. If it is necessary to get user by name * use {@link #getByName(String)} method instead. * * @param alias user name or alias * @return user instance, never null * @throws NullPointerException when {@code alias} is null * @throws NotFoundException when user with given {@code alias} doesn't exist * @throws ServerException when any other error occurs */ UserImpl getByAlias(String alias) throws NotFoundException, ServerException; /** * Finds user by his identifier. * * @param id user identifier * @return user instance, never null * @throws NullPointerException when {@code id} is null * @throws NotFoundException when user with given {@code id} doesn't exist * @throws ServerException when any other error occurs */ UserImpl getById(String id) throws NotFoundException, ServerException; /** * Finds user by his name. * * @param name user name * @return user instance, never null * @throws NullPointerException when {@code name} is null * @throws NotFoundException when user with such {@code name} doesn't exist * @throws ServerException when any other error occurs */ UserImpl getByName(String name) throws NotFoundException, ServerException; /** * Finds user by his email. * * @param email user email * @return user instance, never null * @throws NullPointerException when {@code email} is null * @throws NotFoundException when user with such {@code email} doesn't exist * @throws ServerException when any other error occurs */ UserImpl getByEmail(String email) throws NotFoundException, ServerException; /** * Gets all users from persistent layer. * * @param maxItems the maximum number of users to return * @param skipCount the number of users to skip * @return list of users POJO or empty list if no users were found * @throws IllegalArgumentException when {@code maxItems} or {@code skipCount} is negative * @throws ServerException when any other error occurs */ Page getAll(int maxItems, long skipCount) throws ServerException; /** * Returns all users whose name contains(case insensitively) specified {@code namePart}. * * @param namePart fragment of user's name * @param maxItems the maximum number of users to return * @param skipCount the number of users to skip * @return list of matched users * @throws NullPointerException when {@code namePart} is null * @throws IllegalArgumentException when {@code maxItems} or {@code skipCount} is negative or when * {@code skipCount} more than {@value Integer#MAX_VALUE} * @throws ServerException when any other error occurs */ Page getByNamePart(String namePart, int maxItems, long skipCount) throws ServerException; /** * Returns all users whose email address contains(case insensitively) specified {@code emailPart}. * *

    For example if email fragment would be 'CHE' then result of search will include the * following: * *

       *  |        emails          |  result  |
       *  | Cherkassy@example.com  |    +     |
       *  | preacher@example.com   |    +     |
       *  | user@ukr.che           |    +     |
       *  | johny@example.com      |    -     |
       *  | CoachEddie@example.com |    +     |
       * 
    * * @param emailPart fragment of user's email * @param maxItems the maximum number of users to return * @param skipCount the number of users to skip * @return list of matched users * @throws NullPointerException when {@code emailPart} is null * @throws IllegalArgumentException when {@code maxItems} or {@code skipCount} is negative or when * {@code skipCount} more than {@value Integer#MAX_VALUE} * @throws ServerException when any other error occurs */ Page getByEmailPart(String emailPart, int maxItems, long skipCount) throws ServerException; /** * Get count of all users from persistent layer. * * @return user count * @throws ServerException when any error occurs */ long getTotalCount() throws ServerException; } ================================================ FILE: wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/PreferenceManagerTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import static java.util.Arrays.asList; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.user.server.spi.PreferenceDao; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Ignore; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests for {@link PreferenceManager}. * * @author Yevhenii Voevodin. */ @Listeners(MockitoTestNGListener.class) public class PreferenceManagerTest { @Mock private PreferenceDao preferenceDao; @InjectMocks private PreferenceManager preferenceManager; @Captor private ArgumentCaptor> preferencesCaptor; @Test public void shouldUseMergeStrategyForPreferencesUpdate() throws Exception { // Preparing preferences final Map existingPreferences = new HashMap<>(); existingPreferences.put("pKey1", "pValue1"); existingPreferences.put("pKey2", "pValue2"); existingPreferences.put("pKey3", "pValue3"); existingPreferences.put("pKey4", "pValue4"); when(preferenceDao.getPreferences(any())).thenReturn(existingPreferences); // Updating preferences final Map newPreferences = new HashMap<>(); newPreferences.put("pKey5", "pValue5"); newPreferences.put("pKey1", "new-value"); preferenceManager.update("user123", newPreferences); // Checking verify(preferenceDao).setPreferences(anyString(), preferencesCaptor.capture()); assertEquals( preferencesCaptor.getValue(), ImmutableMap.of( "pKey1", "new-value", "pKey2", "pValue2", "pKey3", "pValue3", "pKey4", "pValue4", "pKey5", "pValue5")); } @Test public void shouldRemoveSpecifiedPreferences() throws Exception { // Preparing preferences final Map existingPreferences = new HashMap<>(); existingPreferences.put("pKey1", "pValue1"); existingPreferences.put("pKey2", "pValue2"); existingPreferences.put("pKey3", "pValue3"); existingPreferences.put("pKey4", "pValue4"); when(preferenceDao.getPreferences(any())).thenReturn(existingPreferences); // Removing preferenceManager.remove("user123", asList("pKey1", "pKey5", "odd-pref-name")); // Checking verify(preferenceDao).setPreferences(anyString(), preferencesCaptor.capture()); assertEquals( preferencesCaptor.getValue(), ImmutableMap.of( "pKey2", "pValue2", "pKey3", "pValue3", "pKey4", "pValue4")); } @Test @Ignore public void shouldGetPreferencesByUser() throws Exception { final Map preferences = ImmutableMap.of("name", "value"); when(preferenceDao.getPreferences("user123")).thenReturn(preferences); assertEquals(preferenceManager.find("user123"), preferences); } @Test @Ignore public void shouldGetPreferencesByUserAndFilter() throws Exception { final Map preferences = ImmutableMap.of("name", "value"); when(preferenceDao.getPreferences("user123", "name.*")).thenReturn(preferences); assertEquals(preferenceManager.find("user123", "name.*"), preferences); } @Test(expectedExceptions = NullPointerException.class) public void getPreferencesShouldThrowNpeWhenUserIdIsNull() throws Exception { preferenceManager.find(null); } @Test(expectedExceptions = NullPointerException.class) public void getPreferencesByUserAndFilterShouldThrowNpeWhenUserIdIsNull() throws Exception { preferenceManager.find(null, "name.*"); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNpeWhenRemovingPreferencesAndUserIdIsNull() throws Exception { preferenceManager.remove(null); } @Test public void shouldRemoveUserPreferences() throws Exception { preferenceManager.remove("user123"); verify(preferenceDao).remove("user123"); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNpeWhenSavePreferencesWithNullUser() throws Exception { preferenceManager.save(null, Collections.emptyMap()); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNpeWhenSavePreferencesWithNullPreferences() throws Exception { preferenceManager.save("user123", null); } @Test public void shouldSavePreferences() throws Exception { preferenceManager.save("user123", Collections.emptyMap()); verify(preferenceDao).setPreferences("user123", Collections.emptyMap()); } } ================================================ FILE: wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/ProfileManagerTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import org.eclipse.che.api.user.server.model.impl.ProfileImpl; import org.eclipse.che.api.user.server.spi.ProfileDao; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests for {@link ProfileManager}. * * @author Yevhenii Voevodin */ @Listeners(MockitoTestNGListener.class) public class ProfileManagerTest { @Mock private ProfileDao profileDao; @InjectMocks private ProfileManager profileManager; @Test public void shouldGetProfileById() throws Exception { final ProfileImpl profile = new ProfileImpl("user123"); when(profileDao.getById(profile.getUserId())).thenReturn(profile); assertEquals(profile, profileManager.getById(profile.getUserId())); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNpeWhenGettingProfileByNullId() throws Exception { profileManager.getById(null); } @Test public void shouldCreateProfile() throws Exception { final ProfileImpl profile = new ProfileImpl("user123"); profileManager.create(profile); final ArgumentCaptor captor = ArgumentCaptor.forClass(ProfileImpl.class); verify(profileDao).create(captor.capture()); assertEquals(captor.getValue(), profile); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNpeWhenCreatingNullProfile() throws Exception { profileManager.create(null); } @Test public void shouldUpdateProfile() throws Exception { final ProfileImpl profile = new ProfileImpl("user123"); profileManager.update(profile); final ArgumentCaptor captor = ArgumentCaptor.forClass(ProfileImpl.class); verify(profileDao).update(captor.capture()); assertEquals(captor.getValue(), profile); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNpeWhenUpdatingNull() throws Exception { profileManager.update(null); } @Test public void shouldRemoveProfile() throws Exception { profileManager.remove("user123"); verify(profileDao).remove("user123"); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNpeWhenRemovingNull() throws Exception { profileManager.remove(null); } } ================================================ FILE: wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserManagerTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import java.util.Arrays; import java.util.Collections; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.user.server.spi.PreferenceDao; import org.eclipse.che.api.user.server.spi.ProfileDao; import org.eclipse.che.api.user.server.spi.UserDao; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Ignore; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests for {@link UserManager}. * * @author Max Shaposhnik (mshaposhnik@codenvy.com) * @author Yevhenii Voevodin */ @Listeners(MockitoTestNGListener.class) public class UserManagerTest { @Mock private UserDao userDao; @Mock private ProfileDao profileDao; @Mock private PreferenceDao preferencesDao; @Mock private EventService eventService; private UserManager manager; @BeforeMethod public void setUp() { manager = new UserManager( userDao, profileDao, preferencesDao, eventService, new String[] {"reserved"}); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNpeWhenUpdatingUserWithNullEntity() throws Exception { manager.update(null); } @Test public void shouldUpdateUser() throws Exception { final UserImpl user = new UserImpl( "identifier", "test@email.com", "testName", "password", Collections.emptyList()); UserImpl user2 = new UserImpl(user); user2.setName("testName2"); manager.update(user2); verify(userDao).update(new UserImpl(user2)); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrownNpeWhenTryingToGetUserByNullId() throws Exception { manager.getById(null); } @Test public void shouldGetUserById() throws Exception { final User user = new UserImpl( "identifier", "test@email.com", "testName", "password", Collections.singletonList("alias")); when(manager.getById(user.getId())).thenReturn(user); assertEquals(manager.getById(user.getId()), user); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrownNpeWhenTryingToGetUserByNullAlias() throws Exception { manager.getByAlias(null); } @Test public void shouldGetUserByAlias() throws Exception { final User user = new UserImpl( "identifier", "test@email.com", "testName", "password", Collections.singletonList("alias")); when(manager.getByAlias("alias")).thenReturn(user); assertEquals(manager.getByAlias("alias"), user); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrownNpeWhenTryingToGetUserByNullName() throws Exception { manager.getByName(null); } @Test public void shouldGetUserByName() throws Exception { final User user = new UserImpl( "identifier", "test@email.com", "testName", "password", Collections.singletonList("alias")); when(manager.getByName(user.getName())).thenReturn(user); assertEquals(manager.getByName(user.getName()), user); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrownNpeWhenTryingToGetUserWithNullEmail() throws Exception { manager.getByEmail(null); } @Test public void shouldGetUserByEmail() throws Exception { final User user = new UserImpl( "identifier", "test@email.com", "testName", "password", Collections.singletonList("alias")); when(manager.getByEmail(user.getEmail())).thenReturn(user); assertEquals(manager.getByEmail(user.getEmail()), user); } @Test public void shouldGetTotalUserCount() throws Exception { when(userDao.getTotalCount()).thenReturn(5L); assertEquals(manager.getTotalCount(), 5); verify(userDao).getTotalCount(); } @Test public void shouldGetAllUsers() throws Exception { final Page users = new Page( Arrays.asList( new UserImpl( "identifier1", "test1@email.com", "testName1", "password", Collections.singletonList("alias1")), new UserImpl( "identifier2", "test2@email.com", "testName2", "password", Collections.singletonList("alias2")), new UserImpl( "identifier3", "test3@email.com", "testName3", "password", Collections.singletonList("alias3"))), 0, 30, 3); when(userDao.getAll(30, 0)).thenReturn(users); assertEquals(manager.getAll(30, 0), users); verify(userDao).getAll(30, 0); } @Test(expectedExceptions = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionsWhenGetAllUsersWithNegativeMaxItems() throws Exception { manager.getAll(-5, 0); } @Test(expectedExceptions = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionsWhenGetAllUsersWithNegativeSkipCount() throws Exception { manager.getAll(30, -11); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNpeWhenRemovingUserByNullId() throws Exception { manager.remove(null); } @Test public void shouldRemoveUser() throws Exception { manager.remove("user123"); verify(userDao).remove("user123"); } @Test(expectedExceptions = ConflictException.class) public void shouldThrowConflictExceptionOnCreationIfUserNameIsReserved() throws Exception { final User user = new UserImpl("id", "test@email.com", "reserved"); manager.create(user, false); } @Test public void shouldBeAbleToGetOrCreate_existed() throws Exception { // when User actual = manager.getOrCreateUser("identifier", "testName@che", "testName"); // then assertNotNull(actual); assertEquals(actual.getEmail(), "testName@che"); assertEquals(actual.getId(), "identifier"); assertEquals(actual.getName(), "testName"); } @Test public void shouldBeAbleToGetOrCreate_nonexisted() throws Exception { // when User actual = manager.getOrCreateUser("identifier", "testName@che", "testName"); // then assertNotNull(actual); assertEquals(actual.getEmail(), "testName@che"); assertEquals(actual.getId(), "identifier"); assertEquals(actual.getName(), "testName"); } @Ignore @Test public void shouldBeAbleToGetOrCreateWithoutEmail_existed() throws Exception { // given final User user = new UserImpl( "identifier", "test@email.com", "testName", "password", Collections.singletonList("alias")); when(manager.getById(user.getId())).thenReturn(user); // when User actual = manager.getOrCreateUser("identifier", "testName"); // then assertNotNull(actual); assertEquals(actual.getEmail(), "test@email.com"); assertEquals(actual.getId(), "identifier"); assertEquals(actual.getName(), "testName"); } @Ignore @Test public void shouldBeAbleToGetOrCreateWithoutEmail_nonexisted() throws Exception { // given when(manager.getById("identifier")).thenThrow(NotFoundException.class); // when User actual = manager.getOrCreateUser("identifier", "testName"); // then assertNotNull(actual); assertEquals(actual.getEmail(), "testName@che"); assertEquals(actual.getId(), "identifier"); assertEquals(actual.getName(), "testName"); } } ================================================ FILE: wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserValidatorTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.server; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import org.eclipse.che.account.spi.AccountValidator; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests of {@link UserValidator}. * * @author Sergii Leschenko */ @Listeners(MockitoTestNGListener.class) public class UserValidatorTest { @Mock private AccountValidator accountValidator; @InjectMocks private UserValidator userNameValidator; @Test public void shouldReturnNameNormalizedByAccountValidator() throws Exception { when(accountValidator.normalizeAccountName(anyString(), anyString())).thenReturn("testname"); assertEquals(userNameValidator.normalizeUserName("toNormalize"), "testname"); verify(accountValidator) .normalizeAccountName("toNormalize", UserValidator.GENERATED_NAME_PREFIX); } @Test public void shouldReturnTrueWhenInputIsValidAccountName() throws Exception { when(accountValidator.isValidName(any())).thenReturn(true); assertEquals(userNameValidator.isValidName("toTest"), true); verify(accountValidator).isValidName("toTest"); } @Test public void shouldReturnFalseWhenInputIsInvalidAccountName() throws Exception { when(accountValidator.isValidName(any())).thenReturn(false); assertEquals(userNameValidator.isValidName("toTest"), false); verify(accountValidator).isValidName("toTest"); } } ================================================ FILE: wsmaster/che-core-api-user/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule ================================================ org.eclipse.che.api.user.server.jpa.JpaTckModule ================================================ FILE: wsmaster/che-core-api-user-shared/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-user-shared Che Core :: API :: User :: Shared ${project.build.directory}/generated-sources/dto/ false com.google.code.gson gson io.swagger.core.v3 swagger-annotations-jakarta org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} process-sources generate org.eclipse.che.core che-core-api-user-shared ${project.version} org.eclipse.che.api.user.shared.dto ${dto-generator-out-directory} org.eclipse.che.api.user.server.dto.DtoServerImpls server maven-compiler-plugin pre-compile generate-sources compile org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} ================================================ FILE: wsmaster/che-core-api-user-shared/src/main/java/org/eclipse/che/api/user/shared/dto/ProfileDto.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.shared.dto; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.user.Profile; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.dto.shared.DTO; /** * This object used for transporting profile data to/from client. * * @author Yevhenii Voevodin * @see Profile * @see DtoFactory */ @DTO public interface ProfileDto extends Profile { void setUserId(String id); @Schema(description = "Profile ID") String getUserId(); ProfileDto withUserId(String id); @Schema(description = "Profile attributes") Map getAttributes(); void setAttributes(Map attributes); ProfileDto withAttributes(Map attributes); List getLinks(); void setLinks(List links); ProfileDto withLinks(List links); String getEmail(); ProfileDto withEmail(String email); } ================================================ FILE: wsmaster/che-core-api-user-shared/src/main/java/org/eclipse/che/api/user/shared/dto/UserDto.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.user.shared.dto; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.dto.shared.DTO; /** * This object used for transporting user data to/from client. * * @author Yevhenii Voevodin * @see User * @see DtoFactory */ @DTO public interface UserDto extends User { @Schema(description = "User ID") String getId(); void setId(String id); UserDto withId(String id); @ArraySchema( schema = @Schema( implementation = String.class, description = "User alias which is used for OAuth")) List getAliases(); void setAliases(List aliases); UserDto withAliases(List aliases); @Schema(description = "User email") String getEmail(); void setEmail(String email); UserDto withEmail(String email); @Schema(description = "User name") String getName(); void setName(String name); UserDto withName(String name); @Schema(description = "User password") String getPassword(); void setPassword(String password); UserDto withPassword(String password); List getLinks(); void setLinks(List links); UserDto withLinks(List links); } ================================================ FILE: wsmaster/che-core-api-workspace/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-workspace jar Che Core :: API :: Workspace false com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.core jackson-core com.fasterxml.jackson.core jackson-databind com.fasterxml.jackson.dataformat jackson-dataformat-yaml com.google.guava guava com.google.inject guice com.google.inject.extensions guice-assistedinject jakarta.annotation jakarta.annotation-api jakarta.inject jakarta.inject-api jakarta.validation jakarta.validation-api org.eclipse.che.core che-core-api-account org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-api-workspace-shared org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-commons-lang org.eclipse.che.core che-core-commons-observability org.leadpony.justify justify org.slf4j slf4j-api com.google.inject.extensions guice-persist provided jakarta.servlet jakarta.servlet-api provided jakarta.websocket jakarta.websocket-api provided jakarta.ws.rs jakarta.ws.rs-api provided org.eclipse.persistence jakarta.persistence provided org.eclipse.persistence org.eclipse.persistence.core provided ch.qos.logback logback-classic test commons-fileupload commons-fileupload test io.rest-assured rest-assured test org.eclipse.che.core che-core-api-ssh test org.eclipse.che.core che-core-commons-test test org.eclipse.che.core che-core-sql-schema test org.eclipse.persistence org.eclipse.persistence.jpa test org.everrest everrest-assured test org.everrest everrest-core test org.everrest everrest-test test org.flywaydb flyway-core test org.hamcrest hamcrest test org.mockito mockito-core test org.mockito mockito-testng test org.testng testng test org.apache.maven.plugins maven-jar-plugin test-jar **/spi/tck/*.* ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceLockService.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import com.google.inject.Singleton; import org.eclipse.che.commons.lang.concurrent.StripedLocks; import org.eclipse.che.commons.lang.concurrent.Unlocker; /** * Default implementation of {@link WorkspaceLockService} that uses {@link StripedLocks}. * * @author Anton Korneta */ @Singleton public class DefaultWorkspaceLockService implements WorkspaceLockService { private final StripedLocks delegate; public DefaultWorkspaceLockService() { this.delegate = new StripedLocks(16); } @Override public Unlocker readLock(String key) { return delegate.readLock(key); } @Override public Unlocker writeLock(String key) { return delegate.writeLock(key); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceStatusCache.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; /** * Default implementation of {@link WorkspaceStatusCache} based on {@link ConcurrentHashMap}. * * @author Anton Korneta */ public class DefaultWorkspaceStatusCache implements WorkspaceStatusCache { private final ConcurrentHashMap delegate = new ConcurrentHashMap<>(); @Override public WorkspaceStatus get(String workspaceId) { return delegate.get(workspaceId); } @Override public WorkspaceStatus replace(String workspaceId, WorkspaceStatus newStatus) { return delegate.replace(workspaceId, newStatus); } @Override public boolean replace( String workspaceId, WorkspaceStatus prevStatus, WorkspaceStatus newStatus) { return delegate.replace(workspaceId, prevStatus, newStatus); } @Override public WorkspaceStatus remove(String workspaceId) { return delegate.remove(workspaceId); } @Override public WorkspaceStatus putIfAbsent(String workspaceId, WorkspaceStatus status) { return delegate.putIfAbsent(workspaceId, status); } @Override public Map asMap() { return new HashMap<>(delegate); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static org.eclipse.che.dto.server.DtoFactory.newDto; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.Runtime; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.config.Environment; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; import org.eclipse.che.api.core.model.workspace.config.ProjectConfig; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.config.SourceStorage; import org.eclipse.che.api.core.model.workspace.config.Volume; import org.eclipse.che.api.core.model.workspace.devfile.Action; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.api.core.model.workspace.devfile.Endpoint; import org.eclipse.che.api.core.model.workspace.devfile.Entrypoint; import org.eclipse.che.api.core.model.workspace.devfile.Env; import org.eclipse.che.api.core.model.workspace.devfile.Metadata; import org.eclipse.che.api.core.model.workspace.devfile.PreviewUrl; import org.eclipse.che.api.core.model.workspace.devfile.Project; import org.eclipse.che.api.core.model.workspace.devfile.Source; import org.eclipse.che.api.core.model.workspace.runtime.Machine; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.api.workspace.shared.dto.CommandDto; import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; import org.eclipse.che.api.workspace.shared.dto.MachineConfigDto; import org.eclipse.che.api.workspace.shared.dto.MachineDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.RecipeDto; import org.eclipse.che.api.workspace.shared.dto.RuntimeDto; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.api.workspace.shared.dto.ServerConfigDto; import org.eclipse.che.api.workspace.shared.dto.ServerDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.api.workspace.shared.dto.VolumeDto; import org.eclipse.che.api.workspace.shared.dto.WarningDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.che.api.workspace.shared.dto.devfile.ComponentDto; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileActionDto; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileCommandDto; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileVolumeDto; import org.eclipse.che.api.workspace.shared.dto.devfile.EndpointDto; import org.eclipse.che.api.workspace.shared.dto.devfile.EntrypointDto; import org.eclipse.che.api.workspace.shared.dto.devfile.EnvDto; import org.eclipse.che.api.workspace.shared.dto.devfile.MetadataDto; import org.eclipse.che.api.workspace.shared.dto.devfile.PreviewUrlDto; import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; /** * Helps to convert to/from DTOs related to workspace. * * @author Yevhenii Voevodin */ public final class DtoConverter { /** Converts {@link Workspace} to {@link WorkspaceDto}. */ public static WorkspaceDto asDto(Workspace workspace) { WorkspaceDto workspaceDto = newDto(WorkspaceDto.class) .withId(workspace.getId()) .withStatus(workspace.getStatus()) .withNamespace(workspace.getNamespace()) .withTemporary(workspace.isTemporary()) .withAttributes(workspace.getAttributes()); if (workspace.getConfig() != null) { workspaceDto.setConfig(asDto(workspace.getConfig())); } if (workspace.getDevfile() != null) { workspaceDto.setDevfile(asDto(workspace.getDevfile())); } if (workspace.getRuntime() != null) { RuntimeDto runtime = asDto(workspace.getRuntime()); workspaceDto.setRuntime(runtime); } return workspaceDto; } public static DevfileDto asDto(Devfile devfile) { List commands = devfile.getCommands().stream().map(DtoConverter::asDto).collect(toList()); List components = devfile.getComponents().stream().map(DtoConverter::asDto).collect(toList()); List projects = devfile.getProjects().stream().map(DtoConverter::asDto).collect(toList()); return newDto(DevfileDto.class) .withApiVersion(devfile.getApiVersion()) .withCommands(commands) .withComponents(components) .withProjects(projects) .withAttributes(devfile.getAttributes()) .withMetadata(asDto(devfile.getMetadata())); } private static ProjectDto asDto(Project project) { Source source = project.getSource(); return newDto(ProjectDto.class) .withName(project.getName()) .withClonePath(project.getClonePath()) .withSource( newDto(SourceDto.class) .withType(source.getType()) .withLocation(source.getLocation()) .withBranch(source.getBranch()) .withStartPoint(source.getStartPoint()) .withTag(source.getTag()) .withCommitId(source.getCommitId()) .withSparseCheckoutDir(source.getSparseCheckoutDir())); } private static ComponentDto asDto(Component component) { return newDto(ComponentDto.class) .withType(component.getType()) .withAlias(component.getAlias()) .withAutomountWorkspaceSecrets(component.getAutomountWorkspaceSecrets()) // chePlugin/cheEditor .withId(component.getId()) .withRegistryUrl(component.getRegistryUrl()) // chePlugin .withPreferences(component.getPreferences()) // dockerimage .withImage(component.getImage()) .withMemoryLimit(component.getMemoryLimit()) .withMemoryRequest(component.getMemoryRequest()) .withCpuLimit(component.getCpuLimit()) .withCpuRequest(component.getCpuRequest()) .withCommand(component.getCommand()) .withArgs(component.getArgs()) .withEndpoints(component.getEndpoints().stream().map(DtoConverter::asDto).collect(toList())) .withEnv(component.getEnv().stream().map(DtoConverter::asDto).collect(toList())) .withMountSources(component.getMountSources()) .withVolumes(component.getVolumes().stream().map(DtoConverter::asDto).collect(toList())) // k8s/os .withReference(component.getReference()) .withReferenceContent(component.getReferenceContent()) .withSelector(component.getSelector()) .withEntrypoints( component.getEntrypoints().stream().map(DtoConverter::asDto).collect(toList())); } private static EntrypointDto asDto(Entrypoint entrypoint) { return newDto(EntrypointDto.class) .withContainerName(entrypoint.getContainerName()) .withParentName(entrypoint.getParentName()) .withParentSelector(entrypoint.getParentSelector()) .withCommand(entrypoint.getCommand()) .withArgs(entrypoint.getArgs()); } private static DevfileVolumeDto asDto( org.eclipse.che.api.core.model.workspace.devfile.Volume volume) { return newDto(DevfileVolumeDto.class) .withName(volume.getName()) .withContainerPath(volume.getContainerPath()); } private static EnvDto asDto(Env env) { return newDto(EnvDto.class).withName(env.getName()).withValue(env.getValue()); } private static EndpointDto asDto(Endpoint endpoint) { return newDto(EndpointDto.class) .withName(endpoint.getName()) .withPort(endpoint.getPort()) .withAttributes(endpoint.getAttributes()); } private static DevfileCommandDto asDto( org.eclipse.che.api.core.model.workspace.devfile.Command command) { List actions = command.getActions().stream().map(DtoConverter::asDto).collect(toList()); DevfileCommandDto commandDto = newDto(DevfileCommandDto.class) .withName(command.getName()) .withActions(actions) .withAttributes(command.getAttributes()); if (command.getPreviewUrl() != null) { commandDto.setPreviewUrl(asDto(command.getPreviewUrl())); } return commandDto; } private static PreviewUrlDto asDto(PreviewUrl previewUrl) { final PreviewUrlDto previewUrlDto = newDto(PreviewUrlDto.class); if (previewUrl != null) { if (previewUrl.getPath() != null) { previewUrlDto.setPath(previewUrl.getPath()); } if (previewUrl.getPort() != 0) { previewUrlDto.setPort(previewUrl.getPort()); } } return previewUrlDto; } private static DevfileActionDto asDto(Action action) { return newDto(DevfileActionDto.class) .withComponent(action.getComponent()) .withType(action.getType()) .withWorkdir(action.getWorkdir()) .withCommand(action.getCommand()) .withReference(action.getReference()) .withReferenceContent(action.getReferenceContent()); } /** Converts {@link WorkspaceConfig} to {@link WorkspaceConfigDto}. */ public static WorkspaceConfigDto asDto(WorkspaceConfig workspace) { List commands = workspace.getCommands().stream().map(DtoConverter::asDto).collect(toList()); List projects = workspace.getProjects().stream().map(DtoConverter::asDto).collect(toList()); Map environments = workspace.getEnvironments().entrySet().stream() .collect(toMap(Map.Entry::getKey, entry -> asDto(entry.getValue()))); return newDto(WorkspaceConfigDto.class) .withName(workspace.getName()) .withDefaultEnv(workspace.getDefaultEnv()) .withCommands(commands) .withProjects(projects) .withEnvironments(environments) .withAttributes(workspace.getAttributes()) .withDescription(workspace.getDescription()); } /** Converts {@link Command} to {@link CommandDto}. */ public static CommandDto asDto(Command command) { return newDto(CommandDto.class) .withName(command.getName()) .withCommandLine(command.getCommandLine()) .withType(command.getType()) .withAttributes(command.getAttributes()); } /** Converts {@link ProjectConfig} to {@link ProjectConfigDto}. */ public static ProjectConfigDto asDto(ProjectConfig projectCfg) { final ProjectConfigDto projectConfigDto = newDto(ProjectConfigDto.class) .withName(projectCfg.getName()) .withDescription(projectCfg.getDescription()) .withPath(projectCfg.getPath()) .withType(projectCfg.getType()) .withAttributes(projectCfg.getAttributes()) .withMixins(projectCfg.getMixins()); final SourceStorage source = projectCfg.getSource(); if (source != null) { projectConfigDto.withSource( newDto(SourceStorageDto.class) .withLocation(source.getLocation()) .withType(source.getType()) .withParameters(source.getParameters())); } return projectConfigDto; } /** Converts {@link Environment} to {@link EnvironmentDto}. */ public static EnvironmentDto asDto(Environment env) { final EnvironmentDto envDto = newDto(EnvironmentDto.class); if (env.getMachines() != null) { envDto.withMachines( env.getMachines().entrySet().stream() .collect(toMap(Map.Entry::getKey, entry -> asDto(entry.getValue())))); } if (env.getRecipe() != null) { envDto.withRecipe( newDto(RecipeDto.class) .withType(env.getRecipe().getType()) .withContentType(env.getRecipe().getContentType()) .withLocation(env.getRecipe().getLocation()) .withContent(env.getRecipe().getContent())); } return envDto; } /** Converts {@link MachineConfig} to {@link MachineConfigDto}. */ public static MachineConfigDto asDto(MachineConfig machine) { MachineConfigDto machineDto = newDto(MachineConfigDto.class); if (machine.getServers() != null) { machineDto.setServers( machine.getServers().entrySet().stream() .collect(toMap(Map.Entry::getKey, entry -> asDto(entry.getValue())))); } if (machine.getAttributes() != null) { machineDto.setAttributes(machine.getAttributes()); } if (machine.getVolumes() != null) { machineDto.setVolumes( machine.getVolumes().entrySet().stream() .collect(toMap(Map.Entry::getKey, entry -> asDto(entry.getValue())))); } if (machine.getEnv() != null) { machineDto.setEnv(machine.getEnv()); } return machineDto; } /** Converts {@link ServerConfig} to {@link ServerConfigDto}. */ public static ServerConfigDto asDto(ServerConfig serverConf) { return newDto(ServerConfigDto.class) .withPort(serverConf.getPort()) .withProtocol(serverConf.getProtocol()) .withPath(serverConf.getPath()) .withAttributes(serverConf.getAttributes()); } /** Converts {@link Runtime} to {@link RuntimeDto}. */ public static RuntimeDto asDto(Runtime runtime) { if (runtime == null) { return null; } RuntimeDto runtimeDto = newDto(RuntimeDto.class).withActiveEnv(runtime.getActiveEnv()); if (runtime.getMachines() != null) { runtimeDto.setMachines( runtime.getMachines().entrySet().stream() .collect(toMap(Map.Entry::getKey, entry -> asDto(entry.getValue())))); } if (runtime.getWarnings() != null) { runtimeDto.setWarnings( runtime.getWarnings().stream().map(DtoConverter::asDto).collect(toList())); } if (runtime.getCommands() != null) { runtimeDto.setCommands( runtime.getCommands().stream().map(DtoConverter::asDto).collect(toList())); } return runtimeDto; } /** Converts {@link RuntimeIdentity} to {@link RuntimeIdentityDto}. */ public static RuntimeIdentityDto asDto(RuntimeIdentity identity) { return newDto(RuntimeIdentityDto.class) .withWorkspaceId(identity.getWorkspaceId()) .withEnvName(identity.getEnvName()) .withOwnerId(identity.getOwnerId()); } /** Converts {@link Volume} to {@link VolumeDto}. */ public static VolumeDto asDto(Volume volume) { return newDto(VolumeDto.class).withPath(volume.getPath()); } /** Converts {@link Warning} to {@link WarningDto}. */ public static WarningDto asDto(Warning warning) { return newDto(WarningDto.class).withCode(warning.getCode()).withMessage(warning.getMessage()); } /** Converts {@link Server} to {@link ServerDto}. */ public static ServerDto asDto(Server server) { return newDto(ServerDto.class) .withUrl(server.getUrl()) .withStatus(server.getStatus()) .withAttributes(server.getAttributes()); } /** Converts {@link Machine} to {@link MachineDto}. */ public static MachineDto asDto(Machine machine) { MachineDto machineDto = newDto(MachineDto.class) .withAttributes(machine.getAttributes()) .withStatus(machine.getStatus()); if (machine.getServers() != null) { machineDto.withServers( machine.getServers().entrySet().stream() .collect(toMap(Map.Entry::getKey, entry -> asDto(entry.getValue())))); } return machineDto; } /** Converts {@link Metadata} to {@link MetadataDto}. */ public static MetadataDto asDto(Metadata metadata) { return newDto(MetadataDto.class) .withName(metadata.getName()) .withGenerateName(metadata.getGenerateName()); } private DtoConverter() {} } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/NoEnvironmentFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe; import org.eclipse.che.api.workspace.server.spi.environment.MachineConfigsValidator; import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever; import org.eclipse.che.commons.annotation.Nullable; /** * Fake environment factory for a case when sidecar-based workspace has no environment. * * @author Alexander Garagatyi */ public class NoEnvironmentFactory extends InternalEnvironmentFactory { @Inject public NoEnvironmentFactory( RecipeRetriever recipeRetriever, MachineConfigsValidator machinesValidator) { super(recipeRetriever, machinesValidator); } @Override protected InternalEnvironment doCreate( @Nullable InternalRecipe recipe, Map machines, List warnings) throws InternalInfrastructureException { if (recipe != null) { throw new InternalInfrastructureException( "No environment factory doesn't accept non-null workspace recipes"); } return new NoEnvInternalEnvironment(); } public static class NoEnvInternalEnvironment extends InternalEnvironment {} } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/PreviewUrlLinksVariableGenerator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import static org.eclipse.che.api.core.model.workspace.config.Command.PREVIEW_URL_ATTRIBUTE; import jakarta.ws.rs.core.UriBuilder; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; /** * Helps to generate links for Preview URLs and update commands with these links. * *

    For preview URLs, we need to include following data in {@link * org.eclipse.che.api.workspace.shared.dto.WorkspaceDto}: *

  • Map with final preview url links where keys are composed from prefix {@link * PreviewUrlLinksVariableGenerator#PREVIEW_URL_VARIABLE_PREFIX}, modified command name and CRC * hash. *
  • Each command that has defined Preview url must have attribute `previewUrl` referencing to * correct key in the map in format that IDE will understand. */ @Singleton class PreviewUrlLinksVariableGenerator { private static final String PREVIEW_URL_VARIABLE_PREFIX = "previewurl/"; /** * Takes commands from given {@code workspace}. For all commands that have defined previewUrl, * creates variable name in defined format and final preview url link. It updates the command so * it's `previewUrl` attribute will contain variable in proper format. Method then returns map of * all preview url links with these variables as keys: * *
       *   links:
       *     "previewURl/run_123": http://your.domain
       *   command:
       *     attributes:
       *       previewUrl: '${previewUrl/run_123}/some/path'
       * 
    * * @return map of all */ Map genLinksMapAndUpdateCommands(WorkspaceImpl workspace, UriBuilder uriBuilder) { if (workspace == null || workspace.getRuntime() == null || workspace.getRuntime().getCommands() == null || uriBuilder == null) { return Collections.emptyMap(); } Map links = new HashMap<>(); for (Command command : workspace.getRuntime().getCommands()) { Map commandAttributes = command.getAttributes(); if (command.getPreviewUrl() != null && commandAttributes != null && commandAttributes.containsKey(PREVIEW_URL_ATTRIBUTE)) { String previewUrlLinkValue = createPreviewUrlLinkValue(uriBuilder, command); String previewUrlLinkKey = createPreviewUrlLinkKey(command); links.put(previewUrlLinkKey, previewUrlLinkValue); commandAttributes.replace( PREVIEW_URL_ATTRIBUTE, formatAttributeValue(previewUrlLinkKey, command.getPreviewUrl().getPath())); } } return links; } private String createPreviewUrlLinkValue(UriBuilder uriBuilder, Command command) { UriBuilder previewUriBuilder = uriBuilder.clone().host(command.getAttributes().get(PREVIEW_URL_ATTRIBUTE)); previewUriBuilder.replacePath(null); return previewUriBuilder.build().toString(); } /** * Creates link key for given command in format * `previewUrl/_` */ private String createPreviewUrlLinkKey(Command command) { return PREVIEW_URL_VARIABLE_PREFIX + command.getName().replaceAll(" ", "") + "_" + Math.abs(command.getName().hashCode()); } private String formatAttributeValue(String var, String path) { String previewUrlAttributeValue = "${" + var + "}"; if (path != null) { previewUrlAttributeValue += path; } return previewUrlAttributeValue; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/SidecarToolingWorkspaceUtil.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import com.google.common.base.Strings; import java.util.Map; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.workspace.shared.Constants; /** * Util class to deal with sidecar based workspaces. * * @author Alexander Garagatyi */ public class SidecarToolingWorkspaceUtil { /** * Checks whether provided workspace config attributes {@link WorkspaceConfig#getAttributes()} * contains configuration of sidecars.
    * This indicates whether this workspace is Che6 or Che7 compatible. */ public static boolean isSidecarBasedWorkspace(Map attributes) { boolean hasPlugins = !Strings.isNullOrEmpty( attributes.getOrDefault(Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, null)); boolean hasEditor = !Strings.isNullOrEmpty( attributes.getOrDefault(Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, null)); return hasPlugins || hasEditor; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/URLRewriter.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; /** * System specific strategy for rewriting URLs to use in rewriting Servers, Hyperlinks, etc For * example in a case when machines supposed to be accessible via reverse Proxy. * * @author gazarenkov */ public interface URLRewriter { /** * Rewrites URL according to URL rewriting strategy rules. May depend on * RuntimeIdentityImpl(workspace, owner,..) and name (some id) of this particular URL * * @param identity RuntimeIdentityImpl * @param machineName symbolic name of the machine * @param serverName symbolic name of the server * @param url URL to rewrite * @return rewritten URL (may be unchanged) * @throws InfrastructureException if URL rewriting failed */ String rewriteURL( @Nullable RuntimeIdentity identity, @Nullable String machineName, @Nullable String serverName, String url) throws InfrastructureException; /** No rewriting, just pass URL back */ class NoOpURLRewriter implements URLRewriter { @Override public String rewriteURL( @Nullable RuntimeIdentity identity, @Nullable String machineName, @Nullable String serverName, String url) throws InfrastructureException { return url; } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceAttributeValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import java.util.Map; import org.eclipse.che.api.core.ValidationException; /** * Adds an ability to extends logic of workspace attributes validation. * *

    It may needed since attributes may be used as configuration storage by different components. * And that components may need to validate attributes. * * @author Sergii Leshchenko */ public interface WorkspaceAttributeValidator { /** * Validates if the specified workspace attributes does not contain invalid attributes according * to implementors rules. * * @param attributes workspace attributes to validate * @throws ValidationException when the specified workspace attributes is not valid */ void validate(Map attributes) throws ValidationException; /** * Validates if the specified workspace attributes can be updated with new values. * *

    Note that this method must not allow updates that would not validate using the {@link * #validate(Map)} method. * *

    This check includes: * *

      *
    • format checking; *
    • checking if unmodifiable attributes are not changed,
      * if they are two scenarios depending on the implementation: *
        *
      • error is thrown; *
      • the needed value is provisioned into update; *
      *
    * * @param existing workspace attributes to validate * @param update new workspace attributes * @throws ValidationException when the specified workspace attributes is not valid */ void validateUpdate(Map existing, Map update) throws ValidationException; } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Produces; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.MessageBodyReader; import jakarta.ws.rs.ext.MessageBodyWriter; import jakarta.ws.rs.ext.Provider; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.eclipse.che.dto.server.DtoFactory; /** * Entity provider for {@link WorkspaceDto}. Performs schema validation of devfile part of the * workspace before actual {@link DevfileDto} creation. * * @author Max Shaposhnyk */ @Singleton @Provider @Produces({APPLICATION_JSON}) @Consumes({APPLICATION_JSON}) public class WorkspaceEntityProvider implements MessageBodyReader, MessageBodyWriter { private DevfileParser devfileParser; private ObjectMapper mapper = new ObjectMapper(); @Inject public WorkspaceEntityProvider(DevfileParser devfileParser) { this.devfileParser = devfileParser; } @Override public boolean isReadable( Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return type == WorkspaceDto.class; } @Override public WorkspaceDto readFrom( Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { try { JsonNode wsNode = mapper.readTree(entityStream); JsonNode devfileNode = wsNode.path("devfile"); if (!devfileNode.isNull() && !devfileNode.isMissingNode()) { devfileParser.parseJson(devfileNode.toString()); } return DtoFactory.getInstance().createDtoFromJson(wsNode.toString(), WorkspaceDto.class); } catch (DevfileFormatException e) { throw new BadRequestException(e.getMessage()); } catch (IOException e) { throw new WebApplicationException(e.getMessage(), e); } } @Override public boolean isWriteable( Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return WorkspaceDto.class.isAssignableFrom(type); } @Override public long getSize( WorkspaceDto workspaceDto, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } @Override public void writeTo( WorkspaceDto workspaceDto, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { httpHeaders.putSingle(HttpHeaders.CACHE_CONTROL, "public, no-cache, no-store, no-transform"); try (Writer w = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8)) { w.write(DtoFactory.getInstance().toJson(workspaceDto)); w.flush(); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceKeyValidator.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import static java.lang.String.format; import org.eclipse.che.api.core.BadRequestException; /** Helper class to validate workspace composite keys. */ public class WorkspaceKeyValidator { /** * Checks that key consists either from workspaceId or username:workspace_name string. * * @param key key string to validate * @throws BadRequestException if validation is failed */ public static void validateKey(String key) throws BadRequestException { String[] parts = key.split(":", -1); // -1 is to prevent skipping trailing part switch (parts.length) { case 1: { return; // consider it's id } case 2: { if (parts[1].isEmpty()) { throw new BadRequestException( "Wrong composite key format - workspace name required to be set."); } break; } default: { throw new BadRequestException( format("Wrong composite key %s. Format should be 'username:workspace_name'. ", key)); } } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceLockService.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import org.eclipse.che.commons.lang.concurrent.Unlocker; /** * Defines an abstract synchronization mechanism for operations associated with workspaces.(e.g. * sync statuses and runtime states). Note that different Che assemblies can provide various * implementations of the locking mechanism, these implementations are not required to follow the * contract defined by {@link java.util.concurrent.locks.ReadWriteLock}. For ease of use, acquired * locks are wrapped in {@link Unlocker}. * * @author Anton Korneta */ public interface WorkspaceLockService { /** * Acquires lock by given key. Returned instance may follow the read lock contract defined in * {@link java.util.concurrent.locks.ReadWriteLock}. * * @param key lock key * @return lock instance wrapped in {@link Unlocker} */ Unlocker readLock(String key); /** * Acquires lock by given key. Returned instance may follow the write lock contract defined in * {@link java.util.concurrent.locks.ReadWriteLock}. * * @param key lock key * @return lock instance wrapped in {@link Unlocker} */ Unlocker writeLock(String key); } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceSharedPool.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.inject.Inject; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import javax.inject.Singleton; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext; import org.eclipse.che.commons.observability.ExecutorServiceWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides a single non-daemon {@link ExecutorService} instance for workspace components. * * @author Yevhenii Voevodin */ @Singleton public class WorkspaceSharedPool { private final ExecutorService executor; @Inject public WorkspaceSharedPool(ExecutorServiceWrapper wrapper) { ThreadFactory factory = new ThreadFactoryBuilder() .setNameFormat("WorkspaceSharedPool-%d") .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) .setDaemon(false) .build(); int size = Runtime.getRuntime().availableProcessors(); executor = wrapper.wrap( Executors.newFixedThreadPool(size, factory), WorkspaceSharedPool.class.getName()); } /** * Returns an {@link ExecutorService} managed by this pool instance. The executor service is * tracing aware and will propagate the active tracing span, if any, to the submitted tasks. */ public ExecutorService getExecutor() { return executor; } /** * Delegates call to {@link ExecutorService#execute(Runnable)} and propagates thread locals to it * like defined by {@link ThreadLocalPropagateContext}. */ public void execute(Runnable runnable) { executor.execute(ThreadLocalPropagateContext.wrap(runnable)); } /** * Delegates call to {@link ExecutorService#submit(Callable)} and propagates thread locals to it * like defined by {@link ThreadLocalPropagateContext}. */ public Future submit(Callable callable) { return executor.submit(ThreadLocalPropagateContext.wrap(callable)); } /** * Asynchronously runs the given task wrapping it with {@link * ThreadLocalPropagateContext#wrap(Runnable)} * * @param runnable task to run * @return completable future bounded to the task */ public CompletableFuture runAsync(Runnable runnable) { return CompletableFuture.runAsync(ThreadLocalPropagateContext.wrap(runnable), executor); } /** Terminates this pool if it's not terminated yet. */ void shutdown() { if (!executor.isShutdown()) { Logger logger = LoggerFactory.getLogger(getClass()); executor.shutdown(); try { logger.info("Shutdown workspace threads pool, wait 30s to stop normally"); if (!executor.awaitTermination(30, TimeUnit.SECONDS)) { executor.shutdownNow(); logger.info("Interrupt workspace threads pool, wait 60s to stop"); if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { logger.error("Couldn't shutdown workspace threads pool"); } } } catch (InterruptedException x) { executor.shutdownNow(); Thread.currentThread().interrupt(); } logger.info("Workspace threads pool is terminated"); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceStatusCache.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import java.util.Map; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; /** * Defines a base set of operations that are used to manage {@link WorkspaceStatus}. * * @author Anton Korneta */ public interface WorkspaceStatusCache { /** * Returns {@link WorkspaceStatus} mapped to given workspace id. When no mapping for given * workspace id found then {@code null} will be returned. * * @param workspaceId workspace identifier * @return workspace status mapped to given workspace id */ WorkspaceStatus get(String workspaceId); /** * Replaces workspace status with given value and returns previous value mapped to given workspace * id. When no workspace status mapped to given workspace id then {@code null} will be returned. * * @param workspaceId workspace identifier * @param newStatus new workspace status * @return old workspace status or {@code null} if no previously mapped value */ WorkspaceStatus replace(String workspaceId, WorkspaceStatus newStatus); /** * Replaces workspace status with given new value only if currently mapped to the specified value. * * @param workspaceId workspace identifier * @param prevStatus expected workspace status mapped to given id * @param newStatus new workspace status that is going to be mapped to given id * @return {@code true} when workspace status is replaced */ boolean replace(String workspaceId, WorkspaceStatus prevStatus, WorkspaceStatus newStatus); /** * Removes workspace status mapped to given workspace id. * * @param workspaceId workspace identifier * @return workspace status mapped to given workspace id or {@code null} when no mapped value for * given workspace id */ WorkspaceStatus remove(String workspaceId); /** * Puts workspace status when specified key is not already mapped. * * @param workspaceId workspace identifier * @param status workspace status * @return previous workspace status or {@code null} mapped with given workspace id */ WorkspaceStatus putIfAbsent(String workspaceId, WorkspaceStatus status); /** Returns copy of this cache as map. */ Map asMap(); } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_REQUEST_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.SidecarToolingWorkspaceUtil.isSidecarBasedWorkspace; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.config.Environment; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; import org.eclipse.che.api.core.model.workspace.config.Recipe; import org.eclipse.che.api.core.model.workspace.config.Volume; /** * Validator for {@link Workspace}. * * @author Yevhenii Voevodin */ @Singleton public class WorkspaceValidator { /** * Must contain [3, 100] characters, first and last character is letter or digit, available * characters {A-Za-z0-9.-_}. */ private static final Pattern WS_NAME = Pattern.compile("[a-zA-Z0-9][-_.a-zA-Z0-9]{1,98}[a-zA-Z0-9]"); private static final Pattern VOLUME_NAME = Pattern.compile("[a-zA-Z][a-zA-Z0-9-_.]{0,18}[a-zA-Z0-9]"); private static final Pattern VOLUME_PATH = Pattern.compile("/.+"); private final Set attributeValidators; @Inject public WorkspaceValidator(Set attributeValidators) { this.attributeValidators = attributeValidators; } /** * Checks whether given workspace configuration object is in application valid state, so it * provides enough data to be processed by internal components, and the data it provides is valid * so consistency is not violated. * * @param config configuration to validate * @throws ValidationException if any of validation constraints is violated * @throws ServerException when any other error occurs during environment validation */ public void validateConfig(WorkspaceConfig config) throws ValidationException, ServerException { // configuration object properties checkNotNull(config.getName(), "Workspace name required"); check( WS_NAME.matcher(config.getName()).matches(), "Incorrect workspace name, it must be between 3 and 100 characters and may contain digits, " + "latin letters, underscores, dots, dashes and must start and end only with digits, " + "latin letters or underscores"); validateEnvironments(config); // commands for (Command command : config.getCommands()) { check( !isNullOrEmpty(command.getName()), "Workspace %s contains command with null or empty name", config.getName()); check( !isNullOrEmpty(command.getCommandLine()) || !isNullOrEmpty( command.getAttributes().get(Command.COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE)), "Command line or content required for command '%s' in workspace '%s'.", command.getName(), config.getName()); } } /** * Checks whether workspace attributes are valid. The attribute is valid if it's key is not null & * not empty & is not prefixed with 'codenvy'. * * @param attributes the map to check * @throws ValidationException when attributes are not valid */ public void validateAttributes(Map attributes) throws ValidationException { for (String attributeName : attributes.keySet()) { // attribute name should not be empty and should not start with codenvy check( attributeName != null && !attributeName.trim().isEmpty() && !attributeName.toLowerCase().startsWith("codenvy"), "Attribute name '%s' is not valid", attributeName); } for (WorkspaceAttributeValidator attributeValidator : attributeValidators) { attributeValidator.validate(attributes); } } /** * Checks whether workspace attributes are valid on updating. * * @param existing actual attributes * @param update new attributes that are going to be stored instead of existing * @throws ValidationException when attributes are not valid */ public void validateUpdateAttributes(Map existing, Map update) throws ValidationException { for (WorkspaceAttributeValidator attributeValidator : attributeValidators) { attributeValidator.validateUpdate(existing, update); } } private void validateEnvironments(WorkspaceConfig config) throws ValidationException { boolean environmentIsNotSet = (config.getEnvironments() == null || config.getEnvironments().isEmpty()) && isNullOrEmpty(config.getDefaultEnv()); boolean isSidecarWorkspace = isSidecarBasedWorkspace(config.getAttributes()); if (environmentIsNotSet && isSidecarWorkspace) { // sidecar based workspaces allowed not to have environment return; } check(!isNullOrEmpty(config.getDefaultEnv()), "Workspace default environment name required"); checkNotNull(config.getEnvironments(), "Workspace must contain at least one environment"); check( config.getEnvironments().containsKey(config.getDefaultEnv()), "Workspace default environment configuration required"); for (Environment environment : config.getEnvironments().values()) { checkNotNull(environment, "Environment must not be null"); Recipe recipe = environment.getRecipe(); checkNotNull(recipe, "Environment recipe must not be null"); checkNotNull(recipe.getType(), "Environment recipe type must not be null"); for (Entry machineEntry : environment.getMachines().entrySet()) { validateMachine(machineEntry.getKey(), machineEntry.getValue()); } } } private void validateMachine(String machineName, MachineConfig machine) throws ValidationException { validateLongAttribute( MEMORY_LIMIT_ATTRIBUTE, machine.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), machineName); validateLongAttribute( MEMORY_REQUEST_ATTRIBUTE, machine.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE), machineName); for (Entry volumeEntry : machine.getVolumes().entrySet()) { String volumeName = volumeEntry.getKey(); check( VOLUME_NAME.matcher(volumeName).matches(), "Volume name '%s' in machine '%s' is invalid", volumeName, machineName); Volume volume = volumeEntry.getValue(); check( volume != null && !isNullOrEmpty(volume.getPath()), "Path of volume '%s' in machine '%s' is invalid. It should not be empty", volumeName, machineName); check( VOLUME_PATH.matcher(volume.getPath()).matches(), "Path '%s' of volume '%s' in machine '%s' is invalid. It should be absolute", volume.getPath(), volumeName, machineName); } } private void validateLongAttribute( String attributeName, String attributeValue, String machineName) throws ValidationException { if (attributeValue != null) { try { Long.parseLong(attributeValue); } catch (NumberFormatException e) { throw new ValidationException( format( "Value '%s' of attribute '%s' in machine '%s' is illegal", attributeValue, attributeName, machineName)); } } } /** * Checks that object reference is not null, throws {@link ValidationException} in the case of * null {@code object} with given {@code message}. */ private static void checkNotNull(Object object, String message) throws ValidationException { if (object == null) { throw new ValidationException(message); } } /** * Checks that expression is true, throws {@link ValidationException} otherwise. * *

    Exception uses error message built from error message template and error message parameters. */ private static void check(boolean expression, String fmt, Object... args) throws ValidationException { if (!expression) { throw new ValidationException(format(fmt, args)); } } /** * Checks that expression is true, throws {@link ValidationException} otherwise. * *

    Exception uses error message built from error message template and error message parameters. */ private static void check(boolean expression, String message) throws ValidationException { if (!expression) { throw new ValidationException(message); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/Components.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static java.lang.String.format; import static org.eclipse.che.api.workspace.server.devfile.Constants.DOCKERIMAGE_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.OPENSHIFT_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.PLUGIN_COMPONENT_TYPE; import org.eclipse.che.api.core.model.workspace.devfile.Component; /** Utility methods for working with components */ public class Components { private Components() {} /** * Get a name that can be used to identify the component in the devfile. Either the component * alias is used or, if not defined, the identifying attribute corresponding to the component * type. * *

    The alias is unique in the devfile but the identifying attributes are only unique per * component type. Hence, it is required to use both type and the returned name to uniquely * identify a component in all cases. * * @throws IllegalStateException if the type of the component is not recognized which can be * classified as an implementation bug */ public static String getIdentifiableComponentName(Component component) throws IllegalStateException { if (component.getAlias() != null) { return component.getAlias(); } switch (component.getType()) { case EDITOR_COMPONENT_TYPE: case PLUGIN_COMPONENT_TYPE: return component.getId(); case DOCKERIMAGE_COMPONENT_TYPE: return component.getImage(); case KUBERNETES_COMPONENT_TYPE: case OPENSHIFT_COMPONENT_TYPE: String reference = component.getReference(); return reference == null ? component.getType() : reference; default: throw new IllegalStateException( format("Unhandled component type: %s", component.getType())); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/Constants.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; public class Constants { private Constants() {} public static final String SCHEMAS_LOCATION = "schema/"; public static final String SCHEMA_FILENAME = "devfile.json"; public static final String CURRENT_API_VERSION = "1.0.0"; public static final String EDITOR_COMPONENT_TYPE = "cheEditor"; public static final String PLUGIN_COMPONENT_TYPE = "chePlugin"; public static final String KUBERNETES_COMPONENT_TYPE = "kubernetes"; public static final String OPENSHIFT_COMPONENT_TYPE = "openshift"; public static final String DOCKERIMAGE_COMPONENT_TYPE = "dockerimage"; /** Action type that should be used for commands execution. */ public static final String EXEC_ACTION_TYPE = "exec"; /** Workspace command attributes that indicates with which component it is associated. */ public static final String COMPONENT_ALIAS_COMMAND_ATTRIBUTE = "componentAlias"; /** * {@link org.eclipse.che.api.core.model.workspace.devfile.Endpoint} attribute name which can * identify endpoint as public or internal. Attribute value {@code false} makes a endpoint * internal, any other value or lack of the attribute makes the endpoint public. */ public static final String PUBLIC_ENDPOINT_ATTRIBUTE = "public"; /** * {@link org.eclipse.che.api.core.model.workspace.devfile.Endpoint} attribute name which can * identify endpoint as discoverable(i.e. it is accessible by its name from workspace's * containers). Attribute value {@code true} makes a endpoint discoverable, any other value or * lack of the attribute makes the endpoint non-discoverable. */ public static final String DISCOVERABLE_ENDPOINT_ATTRIBUTE = "discoverable"; /** * The attribute of Devfile that should be devfile when no editor is needed and default one should * not be provisioned. Attributes value {@code true} deactivates provisioning of default editor, * any other value of lack of the attributes activates provisioning of default editor */ public static final String EDITOR_FREE_DEVFILE_ATTRIBUTE = "editorFree"; /** * Format used for composite (containing registry URL and id) editor and plugin components * workspace attribute values. */ public static final String COMPOSITE_EDITOR_PLUGIN_ATTRIBUTE_FORMAT = "%s#%s"; } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileBindings.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import com.google.inject.Binder; import com.google.inject.multibindings.MapBinder; import java.util.function.Consumer; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier; import org.eclipse.che.api.workspace.server.devfile.validator.ComponentIntegrityValidator; /** * Utility class to ease the adding of devfile-related bindings in a guice module configuration. * *

    Consult the individual methods on this class to see if you need to use them in a particular * module. */ public class DevfileBindings { /** * The {@code binder} consumer can be used to bind {@link ComponentToWorkspaceApplier} * implementations to component types. I.e. the keys of the binder are component type strings and * the bindings are the deemed implementations. * * @param baseBinder the binder available in the Guice module calling this method * @param binder a consumer to accept a new binder for the workspace appliers */ public static void onWorkspaceApplierBinder( Binder baseBinder, Consumer> binder) { binder.accept( MapBinder.newMapBinder(baseBinder, String.class, ComponentToWorkspaceApplier.class)); } /** * The {@code binder} consumer can be used to bind {@link ComponentIntegrityValidator} * implementations to component types. I.e. the keys of the binder are component type strings and * the bindings are the deemed implementations. * * @param baseBinder the binder available in the Guice module calling this method * @param binder a consumer to accept a new binder for the component integrity validators */ public static void onComponentIntegrityValidatorBinder( Binder baseBinder, Consumer> binder) { binder.accept( MapBinder.newMapBinder(baseBinder, String.class, ComponentIntegrityValidator.class)); } private DevfileBindings() {} } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static com.google.common.base.Strings.isNullOrEmpty; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static org.eclipse.che.api.workspace.server.DtoConverter.asDto; import com.google.common.io.CharStreams; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.NotSupportedException; import jakarta.ws.rs.Produces; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.MessageBodyReader; import jakarta.ws.rs.ext.MessageBodyWriter; import jakarta.ws.rs.ext.Provider; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.eclipse.che.dto.server.DtoFactory; /** * Parses {@link DevfileDto} either from Json or yaml content, and performs schema validation before * the actual DTO created. * * @author Max Shaposhnyk */ @Singleton @Provider @Produces({APPLICATION_JSON}) @Consumes({APPLICATION_JSON, "text/yaml", "text/x-yaml"}) public class DevfileEntityProvider implements MessageBodyReader, MessageBodyWriter { private DevfileParser devfileParser; @Inject public DevfileEntityProvider(DevfileParser devfileParser) { this.devfileParser = devfileParser; } @Override public boolean isReadable( Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return type == DevfileDto.class; } @Override public DevfileDto readFrom( Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { try { if (mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE)) { return asDto( devfileParser.parseJson( CharStreams.toString( new InputStreamReader(entityStream, getCharsetOrUtf8(mediaType))))); } else if (mediaType.isCompatible(MediaType.valueOf("text/yaml")) || mediaType.isCompatible(MediaType.valueOf("text/x-yaml"))) { return asDto( devfileParser.parseYaml( CharStreams.toString( new InputStreamReader(entityStream, getCharsetOrUtf8(mediaType))))); } } catch (DevfileFormatException e) { throw new BadRequestException(e.getMessage()); } throw new NotSupportedException("Unknown media type " + mediaType.toString()); } @Override public boolean isWriteable( Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return DevfileDto.class.isAssignableFrom(type); } @Override public long getSize( DevfileDto devfileDto, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } @Override public void writeTo( DevfileDto devfileDto, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { httpHeaders.putSingle(HttpHeaders.CACHE_CONTROL, "public, no-cache, no-store, no-transform"); try (Writer w = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8)) { w.write(DtoFactory.getInstance().toJson(devfileDto)); w.flush(); } } private String getCharsetOrUtf8(MediaType mediaType) { String charset = mediaType == null ? null : mediaType.getParameters().get("charset"); if (isNullOrEmpty(charset)) { charset = "UTF-8"; } return charset; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.PLUGIN_COMPONENT_TYPE; import com.google.inject.AbstractModule; import org.eclipse.che.api.workspace.server.devfile.convert.component.editor.EditorComponentToWorkspaceApplier; import org.eclipse.che.api.workspace.server.devfile.convert.component.plugin.PluginComponentToWorkspaceApplier; import org.eclipse.che.api.workspace.server.devfile.validator.ComponentIntegrityValidator.NoopComponentIntegrityValidator; /** * @author Sergii Leshchenko */ public class DevfileModule extends AbstractModule { @Override protected void configure() { bind(DevfileEntityProvider.class); DevfileBindings.onWorkspaceApplierBinder( binder(), binder -> { binder.addBinding(EDITOR_COMPONENT_TYPE).to(EditorComponentToWorkspaceApplier.class); binder.addBinding(PLUGIN_COMPONENT_TYPE).to(PluginComponentToWorkspaceApplier.class); }); DevfileBindings.onComponentIntegrityValidatorBinder( binder(), binder -> { binder.addBinding(PLUGIN_COMPONENT_TYPE).to(NoopComponentIntegrityValidator.class); binder.addBinding(EDITOR_COMPONENT_TYPE).to(NoopComponentIntegrityValidator.class); }); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileParser.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.Collections.emptyMap; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.OPENSHIFT_COMPONENT_TYPE; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.server.devfile.exception.OverrideParameterException; import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; /** * Facade for devfile related operations. * * @author Max Shaposhnyk */ @Beta @Singleton public class DevfileParser { private final ObjectMapper yamlMapper; private final ObjectMapper jsonMapper; private final DevfileIntegrityValidator integrityValidator; private final OverridePropertiesApplier overridePropertiesApplier; @Inject public DevfileParser(DevfileIntegrityValidator integrityValidator) { this(integrityValidator, new ObjectMapper(new YAMLFactory()), new ObjectMapper()); } @VisibleForTesting DevfileParser( DevfileIntegrityValidator integrityValidator, ObjectMapper yamlMapper, ObjectMapper jsonMapper) { this.integrityValidator = integrityValidator; this.yamlMapper = yamlMapper; this.jsonMapper = jsonMapper; this.overridePropertiesApplier = new OverridePropertiesApplier(); } /** * Creates {@link DevfileImpl} from given devfile content in YAML. Performs schema and integrity * validation of input data. * * @param devfileContent raw content of devfile * @return Devfile object created from the source content * @throws DevfileFormatException when any of schema or integrity validations fail * @throws DevfileFormatException when any yaml parsing error occurs */ public DevfileImpl parseYaml(String devfileContent) throws DevfileFormatException { try { return parse(parseYamlRaw(devfileContent), yamlMapper, emptyMap()); } catch (OverrideParameterException e) { // should never happen as we send empty overrides map throw new RuntimeException(e.getMessage()); } } /** * Tries to parse given `yaml` into {@link JsonNode}. * * @param yaml to parse * @return parsed yaml * @throws DevfileFormatException if given yaml is empty or invalid */ public JsonNode parseYamlRaw(String yaml) throws DevfileFormatException { try { return Optional.ofNullable(yamlMapper.readTree(yaml)) .orElseThrow( () -> new DevfileFormatException("Unable to parse Devfile - provided source is empty")); } catch (JsonProcessingException jpe) { throw new DevfileFormatException("Can't parse devfile yaml.", jpe); } } /** * converts given devfile in {@link JsonNode} into {@link Map}. * * @param devfileJson json with devfile content * @return devfile in simple Map structure */ public Map convertYamlToMap(JsonNode devfileJson) { return yamlMapper.convertValue(devfileJson, new TypeReference<>() {}); } /** * Parse given devfile in {@link JsonNode} format into our {@link DevfileImpl} and provides * possibility to override its values using key-value map, where key is an json-pointer-like * string and value is desired property value. NOTE: unlike json pointers, objects in arrays * should be pointed by their names, not by index. Examples: * *

      *
    • metadata.generateName : python-dev- *
    • projects.foo.source.type : git // foo is an project name *
    * *

    Performs schema and integrity validation of input data. * * @param devfile devfile parsed in Json * @param overrideProperties properties to override * @return devfile created from given {@link JsonNode} * @throws OverrideParameterException when any error when overriding parameters * @throws DevfileFormatException when given devfile is not valid devfile */ public DevfileImpl parseJsonNode(JsonNode devfile, Map overrideProperties) throws OverrideParameterException, DevfileFormatException { return parse(devfile, jsonMapper, overrideProperties); } /** * Creates {@link DevfileImpl} from given devfile content in JSON. Performs schema and integrity * validation of input data. * * @param devfileContent raw content of devfile * @return Devfile object created from the source content * @throws DevfileFormatException when any of schema or integrity validations fail * @throws DevfileFormatException when any yaml parsing error occurs */ public DevfileImpl parseJson(String devfileContent) throws DevfileFormatException { try { return parse(devfileContent, jsonMapper, emptyMap()); } catch (OverrideParameterException e) { // should never happen as we send empty overrides map throw new RuntimeException(e.getMessage()); } } /** * Resolve devfile component references into their reference content. * * @param devfile input devfile * @param fileContentProvider provider to fetch reference content */ public void resolveReference(DevfileImpl devfile, FileContentProvider fileContentProvider) throws DevfileException { List toResolve = devfile.getComponents().stream() .filter( c -> c.getType().equals(KUBERNETES_COMPONENT_TYPE) || c.getType().equals(OPENSHIFT_COMPONENT_TYPE)) .filter(c -> !isNullOrEmpty(c.getReference())) .collect(Collectors.toList()); for (ComponentImpl c : toResolve) { try { c.setReferenceContent(fileContentProvider.fetchContent(c.getReference())); } catch (IOException e) { throw new DevfileException( format( "Unable to resolve reference of component: %s", firstNonNull(c.getAlias(), c.getReference())), e); } } } private DevfileImpl parse( String content, ObjectMapper mapper, Map overrideProperties) throws DevfileFormatException, OverrideParameterException { try { return parse(mapper.readTree(content), mapper, overrideProperties); } catch (JsonProcessingException e) { throw new DevfileFormatException(e.getMessage()); } } private DevfileImpl parse( JsonNode parsed, ObjectMapper mapper, Map overrideProperties) throws DevfileFormatException, OverrideParameterException { if (parsed == null) { throw new DevfileFormatException("Unable to parse Devfile - provided source is empty"); } DevfileImpl devfile; try { parsed = overridePropertiesApplier.applyPropertiesOverride(parsed, overrideProperties); devfile = mapper.treeToValue(parsed, DevfileImpl.class); } catch (JsonProcessingException e) { throw new DevfileFormatException(e.getMessage()); } integrityValidator.validateDevfile(devfile); return devfile; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileRecipeFormatException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** Thrown when the provided content of recipe-type component is empty or invalid. */ public class DevfileRecipeFormatException extends DevfileException { public DevfileRecipeFormatException(String message) { super(message); } public DevfileRecipeFormatException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileVersionDetector.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import com.fasterxml.jackson.databind.JsonNode; import javax.inject.Singleton; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** Class that helps determine devfile versions. */ @Singleton public class DevfileVersionDetector { private final String DEVFILE_V1_VERSION_FIELD = "apiVersion"; private final String DEVFILE_V2_VERSION_FIELD = "schemaVersion"; /** * Gives exact version of the devfile. * * @param devfile to inspect * @return exact version of the devfile * @throws DevfileException when can't find the field with version */ public String devfileVersion(JsonNode devfile) throws DevfileException { final String version; if (devfile.has(DEVFILE_V1_VERSION_FIELD)) { version = devfile.get(DEVFILE_V1_VERSION_FIELD).asText(); } else if (devfile.has(DEVFILE_V2_VERSION_FIELD)) { version = devfile.get(DEVFILE_V2_VERSION_FIELD).asText(); } else { throw new DevfileException( "Neither of `apiVersion` or `schemaVersion` found. This is not a valid devfile."); } return version; } /** * Gives major version of the devfile. * *

       *   1 -> 1
       *   1.0.0 -> 1
       *   1.99 -> 1
       *   2.0.0 -> 2
       *   2.1 -> 2
       *   a.a -> DevfileException
       *   a -> DevfileException
       * 
    * * @param devfile to inspect * @return major version of the devfile * @throws DevfileException when can't find the field with version */ public int devfileMajorVersion(JsonNode devfile) throws DevfileException { String version = devfileVersion(devfile); int dot = version.indexOf("."); final String majorVersion = dot > 0 ? version.substring(0, dot) : version; try { return Integer.parseInt(majorVersion); } catch (NumberFormatException nfe) { throw new DevfileException( "Unable to parse devfile version. This is not a valid devfile.", nfe); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/FileContentProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import java.io.IOException; import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** * Interface for fetching devfile-related content from repositories or raw URL-s. Used for * retrieving devfile content as well as referenced files. * * @author Max Shaposhnyk * @author Sergii Leshchenko */ public interface FileContentProvider { /** * Fetches content of the specified file. * * @param fileURL absolute or devfile-relative file URL to fetch content * @return content of the specified file * @throws IOException when there is an error during content retrieval * @throws DevfileException when implementation does not support fetching of additional files * content */ String fetchContent(String fileURL) throws IOException, DevfileException; /** * Fetches content of the specified file without the authentication step. * * @param fileURL absolute or devfile-relative file URL to fetch content * @param credentials a string representation of credentials in format: : * @return content of the specified file * @throws IOException when there is an error during content retrieval * @throws DevfileException when implementation does not support fetching of additional files * content */ String fetchContent(String fileURL, String credentials) throws IOException, DevfileException; /** * Fetches content of the specified file without the authentication step. * * @param fileURL absolute or devfile-relative file URL to fetch content * @return content of the specified file * @throws IOException when there is an error during content retrieval * @throws DevfileException when implementation does not support fetching of additional files * content */ String fetchContentWithoutAuthentication(String fileURL) throws IOException, DevfileException; /** * Short for {@code new CachingProvider(contentProvider);}. If the {@code contentProvider} is * itself an instance of the {@link CachingProvider}, no new instance is produced. * * @param contentProvider the content provider to cache * @return a file content provider that caches the responses */ static FileContentProvider cached(FileContentProvider contentProvider) { if (contentProvider instanceof CachingProvider) { return contentProvider; } else { return new CachingProvider(contentProvider); } } /** * A file content provider that caches responses from the content provider it is wrapping. Useful * in situations where repeated calls to the {@link #fetchContent(String)} are necessary. */ class CachingProvider implements FileContentProvider { private final FileContentProvider provider; // we don't want to be holding on to large strings with content private final Map> cache = new HashMap<>(); public CachingProvider(FileContentProvider provider) { this.provider = provider; } private String fetchContentInternal(String fileURL) throws IOException, DevfileException { SoftReference ref = cache.get(fileURL); String ret = ref == null ? null : ref.get(); if (ret == null) { ret = provider.fetchContent(fileURL); cache.put(fileURL, new SoftReference<>(ret)); } return ret; } @Override public String fetchContent(String fileURL) throws IOException, DevfileException { return fetchContentInternal(fileURL); } @Override public String fetchContent(String fileURL, String credentials) throws IOException, DevfileException { return fetchContentInternal(fileURL); } @Override public String fetchContentWithoutAuthentication(String fileURL) throws IOException, DevfileException { return fetchContentInternal(fileURL); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/OverridePropertiesApplier.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static java.lang.String.format; import static java.lang.String.join; import static java.util.Arrays.asList; import static java.util.stream.Collectors.joining; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.eclipse.che.api.workspace.server.devfile.exception.OverrideParameterException; /** * Applies override properties to provided devfile {@link JsonNode}. The following set of rules will * be used during object modification: * *
      *
    • Only allowed top-level fields can be altered: apiVersion, metadata, project, attributes. *
    • The absent segment will be created as an empty object and next segment will be added as a * field of it *
    • The property identifier cannot ends with an array type reference *
    • The property identifier for object in an array should contain valid object name, error will * be thrown otherwise. *
    */ public class OverridePropertiesApplier { private final List allowedFirstSegments = asList("apiVersion", "metadata", "projects", "attributes", "devfileFilename"); private final List skipJsonSegments = asList("devfileFilename"); public JsonNode applyPropertiesOverride( JsonNode devfileNode, Map overrideProperties) throws OverrideParameterException { for (Map.Entry entry : overrideProperties.entrySet()) { // skip some segment if (skipJsonSegments.contains(entry.getKey())) { continue; } String[] pathSegments = parseSegments(entry.getKey()); if (pathSegments.length < 1) { continue; } validateFirstSegment(pathSegments); String lastSegment = pathSegments[pathSegments.length - 1]; JsonNode currentNode = devfileNode; Iterator pathSegmentsIterator = Stream.of(pathSegments).iterator(); do { String currentSegment = pathSegmentsIterator.next(); JsonNode nextNode = currentNode.path(currentSegment); if (nextNode.isMissingNode() && pathSegmentsIterator.hasNext()) { // no such intermediate node, let's create it as a empty object currentNode = ((ObjectNode) currentNode).putObject(currentSegment); continue; } else if (nextNode.isArray()) { // ok we have reference to array, so need to make sure that we have next path segment // and then try to retrieve it from array if (!pathSegmentsIterator.hasNext()) { throw new OverrideParameterException( format( "Override property reference '%s' points to an array type object. Please add an item qualifier by name.", entry.getKey())); } // retrieve object by name from array String arrayObjectName = pathSegmentsIterator.next(); currentNode = findNodeByName((ArrayNode) nextNode, arrayObjectName, currentSegment); continue; } else { // because it's impossible to change value of the current Json node, // so to set value, we should be 1 level up and do put(key, value), // so not set latest segment as an current node if (pathSegmentsIterator.hasNext()) { currentNode = nextNode; } } } while (pathSegmentsIterator.hasNext()); // end of path segments reached, now we can set value ((ObjectNode) currentNode).put(lastSegment, entry.getValue()); } return devfileNode; } private String[] parseSegments(String key) { return key.startsWith("attributes.") // for attributes we treat the rest as a attribute name so just need only 2 parts ? key.split("\\.", 2) : key.split("\\."); } private void validateFirstSegment(String[] pathSegments) throws OverrideParameterException { if (!allowedFirstSegments.contains(pathSegments[0])) { throw new OverrideParameterException( format( "Override path '%s' starts with an unsupported field pointer. Supported fields are %s.", join(".", pathSegments), allowedFirstSegments.stream().collect(joining("\",\"", "{\"", "\"}")))); } } private JsonNode findNodeByName(ArrayNode parentNode, String searchName, String parentNodeName) throws OverrideParameterException { return StreamSupport.stream(parentNode.spliterator(), false) .filter(node -> node.path("name").asText().equals(searchName)) .findFirst() .orElseThrow( () -> new OverrideParameterException( format( "Cannot apply override: object with name '%s' not found in array of %s.", searchName, parentNodeName))); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/PreferencesDeserializer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static java.lang.String.format; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.devfile.Component; /** * Helps to deserialize multi-type preferences into {@link Component} as a {@code Map}. * * @author Max Shaposhnyk */ public class PreferencesDeserializer extends JsonDeserializer> { @Override public Map deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException { Map result = new HashMap<>(); jsonParser.nextToken(); while (!JsonToken.END_OBJECT.equals(jsonParser.getCurrentToken())) { JsonToken currentToken = jsonParser.nextValue(); switch (currentToken) { case VALUE_NUMBER_INT: case VALUE_NUMBER_FLOAT: result.put(jsonParser.getCurrentName(), jsonParser.getNumberValue()); break; case VALUE_FALSE: case VALUE_TRUE: result.put(jsonParser.getCurrentName(), jsonParser.getValueAsBoolean()); break; case VALUE_STRING: result.put(jsonParser.getCurrentName(), jsonParser.getValueAsString()); break; case START_ARRAY: String key = jsonParser.getCurrentName(); ArrayList array = new ArrayList<>(); // Iterate over nested array JsonToken nextValue = jsonParser.nextValue(); while (!JsonToken.END_ARRAY.equals(nextValue)) { switch (nextValue) { case VALUE_NUMBER_INT: case VALUE_NUMBER_FLOAT: array.add(jsonParser.getNumberValue()); break; case VALUE_FALSE: case VALUE_TRUE: array.add(jsonParser.getValueAsBoolean()); break; case VALUE_STRING: array.add(jsonParser.getValueAsString()); break; default: throw new JsonParseException( jsonParser, format( "Unexpected value in the nested array of the preference with key '%s'.", key)); } nextValue = jsonParser.nextValue(); } result.put(key, array.toArray()); break; default: throw new JsonParseException( jsonParser, format( "Unexpected value of the preference with key '%s'.", jsonParser.getCurrentName())); } jsonParser.nextToken(); } return result; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/SerializableConverter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.persistence.AttributeConverter; import javax.persistence.Converter; import org.eclipse.che.api.core.model.workspace.devfile.Component; /** * Helps to store and read serializable values of the preferences map in {@link Component} to/from * database. * * @author Max Shaposhnyk */ @Converter(autoApply = true) public class SerializableConverter implements AttributeConverter { private ObjectMapper objectMapper; public SerializableConverter() { this.objectMapper = new ObjectMapper(); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE); } @Override public String convertToDatabaseColumn(Serializable value) { try { return objectMapper.writeValueAsString(value); } catch (JsonProcessingException e) { throw new RuntimeException("Unable to serialize preference value:" + e.getMessage(), e); } } @Override public Serializable convertToEntityAttribute(String dbData) { try { JsonNode node = objectMapper.readTree(dbData); if (node.isValueNode()) { return serializableNodeValue(node); } else if (node.isArray()) { List values = new ArrayList<>(); node.elements().forEachRemaining(n -> values.add(serializableNodeValue(n))); return values.toArray(); } else { throw new RuntimeException("Unable to deserialize preference value:" + dbData); } } catch (IOException e) { throw new RuntimeException("Unable to deserialize preference value:" + e.getMessage(), e); } } private Serializable serializableNodeValue(JsonNode node) { if (node.isNumber()) { return node.numberValue(); } else if (node.isBoolean()) { return node.booleanValue(); } else if (node.isTextual()) { return node.textValue(); } else { throw new RuntimeException("Unable to deserialize preference value:" + node.asText()); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/URLFetcher.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static com.google.common.base.Strings.isNullOrEmpty; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.io.ByteStreams; import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.core.HttpHeaders; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Allow to grab content from URL * * @author Florent Benoit */ @Singleton public class URLFetcher { /** Logger. */ private static final Logger LOG = LoggerFactory.getLogger(URLFetcher.class); /** timeout when reading */ @VisibleForTesting static final int CONNECTION_READ_TIMEOUT = 10 * 1000; // 10s /** The Compiled REGEX PATTERN that can be used for http|https git urls */ final Pattern GIT_HTTP_URL_PATTERN = Pattern.compile("(?^http[s]?://.*)\\.git$"); /** Maximum size of allowed data. */ protected long maximumReadBytes; @Inject public URLFetcher(@Named("che.factory.scm_file_fetcher_limit_bytes") long maxFetchBytes) { this.maximumReadBytes = maxFetchBytes; } /** * Fetches the url provided and return its content. To prevent DOS attack, limit the amount of the * collected data * * @param url the URL to fetch * @return the content of the requested URL or {@code null} if error happened */ public String fetchSafely(@NotNull final String url) { requireNonNull(url, "url parameter can't be null"); try { return fetch(url); } catch (IOException e) { return null; } } /** * Fetches the url provided and return its content. Uses default read connection timeout {@link * URLFetcher#CONNECTION_READ_TIMEOUT} * * @param url the URL to fetch * @return content of the requested URL * @throws IOException if fetch error occurs */ public String fetch(@NotNull final String url) throws IOException { return fetch(url, CONNECTION_READ_TIMEOUT); } /** * Fetches the url provided and return its content. Uses default read connection timeout {@link * URLFetcher#CONNECTION_READ_TIMEOUT} * * @param url the URL to fetch * @param authorization Authorisation string or {@code null} * @return content of the requested URL * @throws IOException if fetch error occurs */ public String fetch(@NotNull final String url, @Nullable String authorization) throws IOException { return fetch(url, CONNECTION_READ_TIMEOUT, authorization); } /** * Fetches the url provided and return its content. * * @param url the URL to fetch * @param timeout read and connection timeout (see {@link URLConnection#setConnectTimeout(int)} * and {@link URLConnection#setReadTimeout(int)} * @return content of the requested URL * @throws IOException if fetch error occurs */ String fetch(@NotNull final String url, int timeout) throws IOException { return fetch(url, timeout, null); } /** * Fetches the url provided and return its content. * * @param url the URL to fetch * @param authorization Authorisation string or {@code null} * @param timeout read and connection timeout (see {@link URLConnection#setConnectTimeout(int)} * and {@link URLConnection#setReadTimeout(int)} * @return content of the requested URL * @throws IOException if fetch error occurs */ String fetch(@NotNull final String url, int timeout, @Nullable String authorization) throws IOException { requireNonNull(url, "url parameter can't be null"); URLConnection connection = new URL(sanitized(url)).openConnection(); connection.setConnectTimeout(timeout); connection.setReadTimeout(timeout); if (!isNullOrEmpty(authorization)) { connection.setRequestProperty(HttpHeaders.AUTHORIZATION, authorization); } return fetch(connection); } /** * Fetch the urlConnection stream by using the urlconnection and return its content To prevent DOS * attack, limit the amount of the collected data * * @param urlConnection the URL connection to fetch * @return the content of the file * @throws IOException if fetch error occurs */ @VisibleForTesting String fetch(@NotNull URLConnection urlConnection) throws IOException { requireNonNull(urlConnection, "urlConnection parameter can't be null"); final String value; try (InputStream inputStream = urlConnection.getInputStream(); BufferedReader reader = new BufferedReader( new InputStreamReader(ByteStreams.limit(inputStream, getLimit()), UTF_8))) { value = reader.lines().collect(Collectors.joining("\n")); } catch (IOException e) { // we shouldn't fetch if check is done before LOG.debug("Invalid URL", e); throw e; } return value; } /** * Maximum size that can be read. * * @return maximum size. */ protected long getLimit() { return maximumReadBytes; } /** * Simple method to sanitize the Git urls like "https://github.com/demo.git" or * "http://myowngit.example.com/demo.git" * * @param url - the String format of the url * @return if the url ends with .git will return the url without .git otherwise return the url as * it is */ @VisibleForTesting String sanitized(String url) { if (url != null) { final Matcher matcher = GIT_HTTP_URL_PATTERN.matcher(url); if (matcher.find()) { return matcher.group("sanitized"); } } return url; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/URLFileContentProvider.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Base64; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.commons.annotation.Nullable; /** * A simple implementation of the FileContentProvider that merely uses the function resolve relative * paths and {@link URLFetcher} for retrieving the content, handling common error cases. */ public class URLFileContentProvider implements FileContentProvider { private final URI devfileLocation; private final URLFetcher urlFetcher; public URLFileContentProvider(@Nullable URI devfileLocation, URLFetcher urlFetcher) { this.devfileLocation = devfileLocation; this.urlFetcher = urlFetcher; } private String fetchContentInternal(String fileURL, @Nullable String credentials) throws IOException, DevfileException { URI fileURI; String requestURL; try { fileURI = new URI(fileURL); } catch (URISyntaxException e) { throw new DevfileException(e.getMessage(), e); } if (fileURI.isAbsolute()) { requestURL = fileURL; } else { if (devfileLocation == null) { throw new DevfileException( format( "It is unable to fetch a file %s as relative to devfile, since devfile location" + " is unknown. Try specifying absolute URL.", fileURL)); } requestURL = devfileLocation.resolve(fileURI).toString(); } try { return urlFetcher.fetch( requestURL, isNullOrEmpty(credentials) ? null : getCredentialsAuthorization(credentials)); } catch (IOException e) { throw new IOException( format( "Failed to fetch a file from URL %s. Make sure the URL" + " of the devfile points to the raw content of it (e.g. not to the webpage" + " showing it but really just its contents). Additionally, if you're using " + " relative form, make sure the referenced files are actually stored" + " relative to the devfile on the same host," + " or try to specify URL in absolute form. The current attempt to download" + " the file failed with the following error message: %s", fileURL, e.getMessage()), e); } } private String getCredentialsAuthorization(String credentials) { return "Basic " + new String(Base64.getEncoder().encode(credentials.getBytes())); } @Override public String fetchContent(String fileURL) throws IOException, DevfileException { return fetchContentInternal(fileURL, null); } @Override public String fetchContentWithoutAuthentication(String fileURL) throws IOException, DevfileException { return fetchContentInternal(fileURL, null); } @Override public String fetchContent(String fileURL, String credentials) throws IOException, DevfileException { return fetchContentInternal(fileURL, credentials); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/CommandConverter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert; import static java.lang.String.format; import static org.eclipse.che.api.core.model.workspace.config.Command.COMMAND_ACTION_REFERENCE_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.Command.COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.Command.WORKING_DIRECTORY_ATTRIBUTE; import java.io.IOException; import org.eclipse.che.api.core.model.workspace.devfile.Action; import org.eclipse.che.api.core.model.workspace.devfile.Command; import org.eclipse.che.api.workspace.server.devfile.Constants; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.server.devfile.exception.WorkspaceExportException; import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; /** * Helps to convert {@link CommandImpl workspace command} to {@link Command devfile command} and * vice versa. * * @author Sergii Leshchenko */ public class CommandConverter { /** * Converts the specified workspace command to devfile command. * * @param command source workspace command * @return created devfile command based on the specified workspace command * @throws WorkspaceExportException if workspace command does not has specified component name * attribute where it should be run */ public CommandImpl toDevfileCommand( org.eclipse.che.api.workspace.server.model.impl.CommandImpl command) throws WorkspaceExportException { String componentName = command.getAttributes().remove(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE); if (componentName == null) { throw new WorkspaceExportException( format( "Command `%s` has no specified component where it should be run", command.getName())); } CommandImpl devCommand = new CommandImpl(); devCommand.setName(command.getName()); ActionImpl action = new ActionImpl(); action.setCommand(command.getCommandLine()); action.setType(command.getType()); action.setWorkdir(command.getAttributes().remove(WORKING_DIRECTORY_ATTRIBUTE)); action.setComponent(componentName); action.setType(Constants.EXEC_ACTION_TYPE); devCommand.getActions().add(action); devCommand.setAttributes(command.getAttributes()); return devCommand; } /** * Converts the specified devfile command to workspace command. * * @param devfileCommand devfile command that should be converted * @return created workspace command based on the specified devfile command * @throws DevfileFormatException if devfile command does not have any action * @throws DevfileFormatException if devfile command has more than one action */ public org.eclipse.che.api.workspace.server.model.impl.CommandImpl toWorkspaceCommand( Command devfileCommand, FileContentProvider fileContentProvider) throws DevfileException { if (devfileCommand.getActions().size() != 1) { throw new DevfileFormatException( format("Command `%s` MUST has one and only one action", devfileCommand.getName())); } Action commandAction = devfileCommand.getActions().get(0); return toWorkspaceCommand(devfileCommand, commandAction, fileContentProvider); } private org.eclipse.che.api.workspace.server.model.impl.CommandImpl toWorkspaceCommand( Command devCommand, Action commandAction, FileContentProvider contentProvider) throws DevfileException { org.eclipse.che.api.workspace.server.model.impl.CommandImpl command = new org.eclipse.che.api.workspace.server.model.impl.CommandImpl(); command.setName(devCommand.getName()); command.setType(commandAction.getType()); command.setCommandLine(commandAction.getCommand()); command.setPreviewUrl(devCommand.getPreviewUrl()); if (commandAction.getWorkdir() != null) { command.getAttributes().put(WORKING_DIRECTORY_ATTRIBUTE, commandAction.getWorkdir()); } if (commandAction.getComponent() != null) { command .getAttributes() .put(Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE, commandAction.getComponent()); } if (commandAction.getReference() != null) { command.getAttributes().put(COMMAND_ACTION_REFERENCE_ATTRIBUTE, commandAction.getReference()); } if (commandAction.getReferenceContent() != null) { command .getAttributes() .put(COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE, commandAction.getReferenceContent()); } else if (commandAction.getReference() != null) { try { String referenceContent = contentProvider.fetchContent(commandAction.getReference()); command.getAttributes().put(COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE, referenceContent); } catch (IOException e) { throw new DevfileException( format( "Failed to fetch content of action from reference %s: %s", commandAction.getReference(), e.getMessage()), e); } } command.getAttributes().putAll(devCommand.getAttributes()); return command; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/DefaultEditorProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_FREE_DEVFILE_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.PLUGIN_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.shared.Constants.ASYNC_PERSIST_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.PERSIST_VOLUMES_ATTRIBUTE; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentFQNParser; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.PluginFQNParser; import org.eclipse.che.api.workspace.server.wsplugins.model.ExtendedPluginFQN; import org.eclipse.che.commons.annotation.Nullable; /** * Provision default editor if there is no any another editor and default plugins for it. * * @author Sergii Leshchenko */ public class DefaultEditorProvisioner { private final String defaultEditorRef; private final String defaultEditor; private final Map defaultPluginsToRefs; private final String asyncStoragePluginRef; private final ComponentFQNParser componentFQNParser; private final PluginFQNParser pluginFQNParser; @Inject public DefaultEditorProvisioner( @Named("che.workspace.devfile.default_editor") String defaultEditorRef, @Named("che.workspace.devfile.default_editor.plugins") @Nullable String defaultPluginsRefs, @Named("che.workspace.devfile.async.storage.plugin") String asyncStoragePluginRef, ComponentFQNParser componentFQNParser, PluginFQNParser pluginFQNParser) throws DevfileException { this.defaultEditorRef = isNullOrEmpty(defaultEditorRef) ? null : defaultEditorRef; this.asyncStoragePluginRef = asyncStoragePluginRef; this.componentFQNParser = componentFQNParser; this.pluginFQNParser = pluginFQNParser; this.defaultEditor = this.defaultEditorRef == null ? null : componentFQNParser.getPluginPublisherAndName(this.defaultEditorRef); Map map = new HashMap<>(); if (!isNullOrEmpty(defaultPluginsRefs)) { for (String defaultPluginsRef : defaultPluginsRefs.split(",")) { map.put(componentFQNParser.getPluginPublisherAndName(defaultPluginsRef), defaultPluginsRef); } } this.defaultPluginsToRefs = map; } /** * Provision default editor if there is no editor. Also provisions default plugins for default * editor regardless whether it is provisioned or set by user. * * @param devfile devfile where editor and plugins should be provisioned * @param contentProvider content provider for plugin references retrieval */ public void apply(DevfileImpl devfile, FileContentProvider contentProvider) throws DevfileException { if (defaultEditorRef == null) { // there is no default editor configured return; } if ("true".equals(devfile.getAttributes().get(EDITOR_FREE_DEVFILE_ATTRIBUTE))) { return; } List components = devfile.getComponents(); Optional editorOpt = components.stream().filter(t -> EDITOR_COMPONENT_TYPE.equals(t.getType())).findFirst(); boolean isDefaultEditorUsed; if (!editorOpt.isPresent()) { components.add(new ComponentImpl(EDITOR_COMPONENT_TYPE, defaultEditorRef)); isDefaultEditorUsed = true; } else { Component editor = editorOpt.get(); String editorPublisherAndName = getPluginPublisherAndName(editor, contentProvider); isDefaultEditorUsed = defaultEditor.equals(editorPublisherAndName); } if (isDefaultEditorUsed) { provisionDefaultPlugins(components, contentProvider); } if ("false".equals(devfile.getAttributes().get(PERSIST_VOLUMES_ATTRIBUTE)) && "true".equals(devfile.getAttributes().get(ASYNC_PERSIST_ATTRIBUTE))) { provisionAsyncStoragePlugin(components, contentProvider); } } /** * Provision the default editor plugins and add them to the the Devfile's component list * * @param components The set of components currently present in the Devfile * @param contentProvider content provider for plugin references retrieval * @throws DevfileException - A DevfileException containing any caught InfrastructureException */ private void provisionDefaultPlugins( List components, FileContentProvider contentProvider) throws DevfileException { Map missingPluginsIdToRef = new HashMap<>(defaultPluginsToRefs); removeAlreadyAddedPlugins(components, contentProvider, missingPluginsIdToRef); try { addMissingPlugins(components, contentProvider, missingPluginsIdToRef); } catch (InfrastructureException e) { throw new DevfileException(e.getMessage(), e); } } /** * Provision the for async storage service, it will provide ability backup and restore project * source using special storage. Will torn on only if workspace start in Ephemeral mode and has * attribute 'asyncPersist = true' * * @param components The set of components currently present in the Devfile * @param contentProvider content provider for plugin references retrieval * @throws DevfileException - A DevfileException containing any caught InfrastructureException */ private void provisionAsyncStoragePlugin( List components, FileContentProvider contentProvider) throws DevfileException { try { Map missingPluginsIdToRef = Collections.singletonMap( componentFQNParser.getPluginPublisherAndName(asyncStoragePluginRef), asyncStoragePluginRef); addMissingPlugins(components, contentProvider, missingPluginsIdToRef); } catch (InfrastructureException e) { throw new DevfileException(e.getMessage(), e); } } /** * Checks if any of the Devfile's components are also in the list of missing default plugins, and * removes them. * * @param devfileComponents - The list of Devfile components * @param contentProvider - The content provider to retrieve YAML * @param missingPluginsIdToRef - The list of default plugins that are not currently in the list * of Devfile components */ private void removeAlreadyAddedPlugins( List devfileComponents, FileContentProvider contentProvider, Map missingPluginsIdToRef) throws DevfileException { for (ComponentImpl component : devfileComponents) { if (PLUGIN_COMPONENT_TYPE.equals(component.getType())) { String pluginPublisherAndName = getPluginPublisherAndName(component, contentProvider); missingPluginsIdToRef.remove(pluginPublisherAndName); } } } /** * Tries to add default plugins to the Devfile components. Each plugin is initially parsed by * plugin ref. If the plugin does not have a reference, it is added to the component list, and its * plugin ID will be used to resolve it. If it has a reference, the Plugin is evaluated, so that * its meta.yaml can be retrieved. From the meta.yaml, the new Component's ID and reference are * properly set, and the Component is added to the list. * * @param devfileComponents - The list of Devfile components * @param contentProvider - The content provider to retrieve YAML * @param missingPluginsIdToRef - The list of default plugins that are not currently in the list * of devfile components * @throws InfrastructureException if the parser is unable to evaluate the FQN of the plugin. */ private void addMissingPlugins( List devfileComponents, FileContentProvider contentProvider, Map missingPluginsIdToRef) throws InfrastructureException { for (String pluginRef : missingPluginsIdToRef.values()) { ComponentImpl component; ExtendedPluginFQN fqn = pluginFQNParser.parsePluginFQN(pluginRef); if (!isNullOrEmpty(fqn.getId())) { component = new ComponentImpl(PLUGIN_COMPONENT_TYPE, pluginRef); } else { component = createReferencePluginComponent(pluginRef, contentProvider); } devfileComponents.add(component); } } /** * Evaluates a plugin FQN by retrieving it's meta.yaml, and sets it's name and reference to the * appropriate values. * * @param pluginRef - The formatted plugin reference (e.g. * eclipse/che-machine-exec-plugin/nightly) * @param contentProvider - The content provider used to read YAML data * @return - A {@link ComponentImpl} with it's ID and reference URL set. * @throws InfrastructureException when the parser cannot evalute the plugin's FQN. */ private ComponentImpl createReferencePluginComponent( String pluginRef, FileContentProvider contentProvider) throws InfrastructureException { ExtendedPluginFQN fqn = pluginFQNParser.evaluateFqn(pluginRef, contentProvider); ComponentImpl component = new ComponentImpl(); component.setType(PLUGIN_COMPONENT_TYPE); if (!isNullOrEmpty(fqn.getId())) { component.setId(fqn.getId()); } if (!isNullOrEmpty(fqn.getReference())) { component.setReference(fqn.getReference()); } return component; } private String getPluginPublisherAndName(Component component, FileContentProvider contentProvider) throws DevfileException { final ExtendedPluginFQN fqn = componentFQNParser.evaluateFQN(component, contentProvider); return fqn.getPublisherAndName(); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/DevfileConverter.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert; import static com.google.common.base.Preconditions.checkArgument; import static org.eclipse.che.api.workspace.server.devfile.Components.getIdentifiableComponentName; import java.util.Map; import javax.inject.Inject; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.devfile.Command; import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.api.workspace.server.devfile.DevfileRecipeFormatException; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.URLFileContentProvider; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; /** * Helps to convert Devfile to workspace config and back. * * @author Max Shaposhnyk * @author Sergii Leshchenko */ public class DevfileConverter { private final ProjectConverter projectConverter; private final CommandConverter commandConverter; private final Map componentTypeToApplier; private final DefaultEditorProvisioner defaultEditorProvisioner; private final URLFileContentProvider urlFileContentProvider; @Inject public DevfileConverter( ProjectConverter projectConverter, CommandConverter commandConverter, Map componentTypeToApplier, DefaultEditorProvisioner defaultEditorProvisioner, URLFetcher urlFetcher) { this.projectConverter = projectConverter; this.commandConverter = commandConverter; this.componentTypeToApplier = componentTypeToApplier; this.defaultEditorProvisioner = defaultEditorProvisioner; this.urlFileContentProvider = new URLFileContentProvider(null, urlFetcher); } /** * Converts given {@link Devfile} into {@link WorkspaceConfigImpl workspace config}. * *

    Note: some default components may be provisioned into devfile. The final devfile is * accessible via {@link WorkspaceConfigImpl#getDevfile()}. * * @param devfile initial devfile * @return constructed workspace config * @throws ServerException when general devfile error occurs */ public WorkspaceConfigImpl convert(Devfile devfile) throws ServerException { try { return devFileToWorkspaceConfig( new DevfileImpl(devfile), FileContentProvider.cached(urlFileContentProvider)); } catch (DevfileException e) { throw new ServerException(e.getMessage(), e); } } /** * Converts given {@link Devfile} into {@link WorkspaceConfigImpl workspace config}. * * @param devfile initial devfile * @param contentProvider content provider for recipe-type component or plugin references * @return constructed workspace config * @throws DevfileException when general devfile error occurs * @throws DevfileException when devfile requires additional files content but the specified * content provider does not support it * @throws DevfileFormatException when devfile format is invalid * @throws DevfileRecipeFormatException when content of the file specified in recipe type * component is empty or its format is invalid */ public WorkspaceConfigImpl devFileToWorkspaceConfig( DevfileImpl devfile, FileContentProvider contentProvider) throws DevfileException { checkArgument(devfile != null, "Devfile must not be null"); checkArgument(contentProvider != null, "Content provider must not be null"); // make copy to avoid modification of original devfile devfile = new DevfileImpl(devfile); defaultEditorProvisioner.apply(devfile, contentProvider); WorkspaceConfigImpl config = new WorkspaceConfigImpl(); config.setName(devfile.getName()); for (Command command : devfile.getCommands()) { CommandImpl com = commandConverter.toWorkspaceCommand(command, contentProvider); if (com != null) { config.getCommands().add(com); } } // note that component applier modifies commands in workspace config // so, commands should be already converted for (ComponentImpl component : devfile.getComponents()) { ComponentToWorkspaceApplier applier = componentTypeToApplier.get(component.getType()); if (applier == null) { throw new DevfileException( String.format( "Devfile contains component `%s` with type `%s` that can not be converted to workspace", getIdentifiableComponentName(component), component.getType())); } applier.apply(config, component, contentProvider); } for (ProjectImpl project : devfile.getProjects()) { ProjectConfigImpl projectConfig = projectConverter.toWorkspaceProject(project); config.getProjects().add(projectConfig); } config.getAttributes().putAll(devfile.getAttributes()); config.setDevfile(devfile); return config; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/ProjectConverter.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert; import static java.lang.String.format; import static org.eclipse.che.api.core.model.workspace.config.SourceStorage.BRANCH_PARAMETER_NAME; import static org.eclipse.che.api.core.model.workspace.config.SourceStorage.COMMIT_ID_PARAMETER_NAME; import static org.eclipse.che.api.core.model.workspace.config.SourceStorage.SPARSE_CHECKOUT_DIR_PARAMETER_NAME; import static org.eclipse.che.api.core.model.workspace.config.SourceStorage.START_POINT_PARAMETER_NAME; import static org.eclipse.che.api.core.model.workspace.config.SourceStorage.TAG_PARAMETER_NAME; import java.net.URI; import java.net.URISyntaxException; import org.eclipse.che.api.core.model.workspace.devfile.Project; import org.eclipse.che.api.core.model.workspace.devfile.Source; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; /** * Helps to convert {@link ProjectConfigImpl workspace project} to {@link Project devfile project} * and vice versa. * * @author Sergii Leshchenko */ public class ProjectConverter { /** * Converts the specified workspace project to devfile project. * * @param projectConfig source workspace project * @return created devfile project based on the specified workspace project */ public ProjectImpl toDevfileProject(ProjectConfigImpl projectConfig) { String branch = projectConfig.getSource().getParameters().get(BRANCH_PARAMETER_NAME); String startPoint = projectConfig.getSource().getParameters().get(START_POINT_PARAMETER_NAME); String tag = projectConfig.getSource().getParameters().get(TAG_PARAMETER_NAME); String commitId = projectConfig.getSource().getParameters().get(COMMIT_ID_PARAMETER_NAME); String sparseCheckoutDir = projectConfig.getSource().getParameters().get(SPARSE_CHECKOUT_DIR_PARAMETER_NAME); SourceImpl source = new SourceImpl( projectConfig.getSource().getType(), projectConfig.getSource().getLocation(), branch, startPoint, tag, commitId, sparseCheckoutDir); String path = projectConfig.getPath(); while (path != null && path.startsWith("/")) { path = path.substring(1); } // don't specify the clonePath if it is the same as the project name if (projectConfig.getName().equals(path)) { path = null; } return new ProjectImpl(projectConfig.getName(), source, path); } /** * Converts the specified devfile project to workspace project. * * @param devProject base devfile project * @return created workspace project based on the specified devfile project */ public ProjectConfigImpl toWorkspaceProject(Project devProject) throws DevfileException { String clonePath = devProject.getClonePath(); if (clonePath == null || clonePath.isEmpty()) { clonePath = "/" + devProject.getName(); } else { clonePath = "/" + sanitizeClonePath(clonePath); } ProjectConfigImpl projectConfig = new ProjectConfigImpl(); projectConfig.setName(devProject.getName()); projectConfig.setPath(clonePath); projectConfig.setSource(toSourceStorage(devProject.getSource())); return projectConfig; } private SourceStorageImpl toSourceStorage(Source source) throws DevfileException { SourceStorageImpl sourceStorage = new SourceStorageImpl(); sourceStorage.setType(source.getType()); sourceStorage.setLocation(source.getLocation()); updateSourceStorage(source, sourceStorage); return sourceStorage; } private String sanitizeClonePath(String clonePath) throws DevfileException { URI uri = clonePathToURI(clonePath); if (uri.isAbsolute()) { throw new DevfileException( "The clonePath must be a relative path. This seems to be an URI with a scheme: " + clonePath); } uri = uri.normalize(); String path = uri.getPath(); if (path.isEmpty()) { // the user tried to trick us with something like "blah/.." throw new DevfileException( "Cloning directly into projects root is not allowed." + " The clonePath that resolves to empty path: " + clonePath); } if (path.startsWith("/")) { // while technically we could allow this, because the project config contains what resembles // an absolute path, it is better to explicitly disallow this in devfile, which is // user-authored. Just don't give the user impression we can support absolute paths. throw new DevfileException( "The clonePath must be a relative. This seems to be an absolute path: " + clonePath); } if (path.startsWith("..")) { // an attempt to escape the projects root, e.g. "ich/../muss/../../raus". That's a no-no. throw new DevfileException( "The clonePath cannot escape the projects root. Don't use .. to try and do that." + " The invalid path was: " + clonePath); } return path; } /** * This merely re-throws the failure during the URI conversion as {@code DevfileException}. * * @param clonePath the path to convert to URI * @return the URI representing the clonePath * @throws DevfileException when the clonePath cannot be converted to an URI */ private URI clonePathToURI(String clonePath) throws DevfileException { try { return new URI(clonePath); } catch (URISyntaxException e) { throw new DevfileException("Failed to parse the clonePath.", e); } } private void updateSourceStorage(Source devfileSource, SourceStorageImpl sourceStorage) throws DevfileException { String startPoint = devfileSource.getStartPoint(); String tag = devfileSource.getTag(); String commitId = devfileSource.getCommitId(); if ((startPoint != null && tag != null) || (startPoint != null && commitId != null) || (tag != null && commitId != null)) { throw new DevfileException( format( "Only one of '%s', '%s', '%s' can be specified.", START_POINT_PARAMETER_NAME, TAG_PARAMETER_NAME, COMMIT_ID_PARAMETER_NAME)); } if (devfileSource.getBranch() != null) { sourceStorage.getParameters().put(BRANCH_PARAMETER_NAME, devfileSource.getBranch()); } // the order of importance is: startPoint > tag > commitId if (startPoint != null) { sourceStorage.getParameters().put(START_POINT_PARAMETER_NAME, startPoint); } else if (tag != null) { sourceStorage.getParameters().put(TAG_PARAMETER_NAME, tag); } else if (commitId != null) { sourceStorage.getParameters().put(COMMIT_ID_PARAMETER_NAME, commitId); } if (devfileSource.getSparseCheckoutDir() != null) { sourceStorage.getParameters().put("keepDir", devfileSource.getSparseCheckoutDir()); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/component/ComponentFQNParser.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert.component; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static org.eclipse.che.api.workspace.server.devfile.Constants.COMPOSITE_EDITOR_PLUGIN_ATTRIBUTE_FORMAT; import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.PLUGIN_COMPONENT_TYPE; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.PluginFQNParser; import org.eclipse.che.api.workspace.server.wsplugins.model.ExtendedPluginFQN; /** * Helper class to build {@code ExtendedPluginFQN} from editor/plugin type components and other FQN * utility functions. */ public class ComponentFQNParser { private final PluginFQNParser fqnParser; @Inject public ComponentFQNParser(PluginFQNParser fqnParser) { this.fqnParser = fqnParser; } public ExtendedPluginFQN evaluateFQN(Component component, FileContentProvider contentProvider) throws DevfileException { if (!component.getType().equals(EDITOR_COMPONENT_TYPE) && !component.getType().equals(PLUGIN_COMPONENT_TYPE)) { throw new DevfileException( "Invalid component type provided. Only editor or plugin is supported."); } try { if (!isNullOrEmpty(component.getReference())) { return fqnParser.evaluateFqn(component.getReference(), contentProvider); } else { return fqnParser.parsePluginFQN( getCompositeId(component.getRegistryUrl(), component.getId())); } } catch (InfrastructureException e) { throw new DevfileException(e.getMessage(), e); } } public String getCompositeId(String registryUrl, String id) { return registryUrl != null ? format(COMPOSITE_EDITOR_PLUGIN_ATTRIBUTE_FORMAT, registryUrl, id) : id; } public String getPluginPublisherAndName(String pluginId) throws DevfileException { try { ExtendedPluginFQN meta = fqnParser.parsePluginFQN(pluginId); return meta.getPublisherAndName(); } catch (InfrastructureException e) { throw new DevfileException(e.getMessage(), e); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/component/ComponentToWorkspaceApplier.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert.component; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.devfile.Endpoint; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; /** * Applies changes on workspace config according to the specified component. Different * implementations are specialized on the concrete component type. * * @author Sergii Leshchenko */ public interface ComponentToWorkspaceApplier { /** * Applies changes on workspace config according to the specified component. * * @param workspaceConfig workspace config on which changes should be applied * @param component component that should be applied * @param contentProvider optional content provider that may be used for external component * resource fetching * @throws IllegalArgumentException if the specified workspace config or devfile is null * @throws DevfileException if content provider is null while the specified component requires * external file content * @throws DevfileException if any exception occurs during content retrieving */ void apply( WorkspaceConfigImpl workspaceConfig, ComponentImpl component, FileContentProvider contentProvider) throws DevfileException; static Map convertEndpointsIntoServers( List endpoints, boolean requireSubdomain) { return endpoints.stream() .collect( Collectors.toMap( Endpoint::getName, e -> { var cfg = ServerConfigImpl.createFromEndpoint(e); ServerConfig.setRequireSubdomain(cfg.getAttributes(), requireSubdomain); return cfg; })); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/component/editor/EditorComponentToWorkspaceApplier.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert.component.editor; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static org.eclipse.che.api.core.model.workspace.config.Command.PLUGIN_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE; import javax.inject.Inject; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentFQNParser; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.wsplugins.model.ExtendedPluginFQN; /** * Applies changes on workspace config according to the specified editor component. * * @author Sergii Leshchenko */ public class EditorComponentToWorkspaceApplier implements ComponentToWorkspaceApplier { private final ComponentFQNParser componentFQNParser; @Inject public EditorComponentToWorkspaceApplier(ComponentFQNParser componentFQNParser) { this.componentFQNParser = componentFQNParser; } /** * Applies changes on workspace config according to the specified editor component. * * @param workspaceConfig workspace config on which changes should be applied * @param editorComponent plugin component that should be applied * @param contentProvider optional content provider that may be used for external component * resource fetching * @throws IllegalArgumentException if specified workspace config or plugin component is null * @throws IllegalArgumentException if specified component has type different from cheEditor */ @Override public void apply( WorkspaceConfigImpl workspaceConfig, ComponentImpl editorComponent, FileContentProvider contentProvider) throws DevfileException { checkArgument(workspaceConfig != null, "Workspace config must not be null"); checkArgument(editorComponent != null, "Component must not be null"); checkArgument( EDITOR_COMPONENT_TYPE.equals(editorComponent.getType()), format("Plugin must have `%s` type", EDITOR_COMPONENT_TYPE)); final String editorComponentAlias = editorComponent.getAlias(); final String editorId = editorComponent.getId(); final String registryUrl = editorComponent.getRegistryUrl(); final ExtendedPluginFQN fqn = componentFQNParser.evaluateFQN(editorComponent, contentProvider); if (!isNullOrEmpty(fqn.getReference())) { workspaceConfig.getAttributes().put(WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, fqn.getReference()); } else { workspaceConfig .getAttributes() .put( WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, componentFQNParser.getCompositeId(registryUrl, editorId)); } workspaceConfig.getCommands().stream() .filter( c -> editorComponentAlias != null && editorComponentAlias.equals( c.getAttributes().get(COMPONENT_ALIAS_COMMAND_ATTRIBUTE))) .forEach(c -> c.getAttributes().put(PLUGIN_ATTRIBUTE, fqn.getId())); // make sure id is set to be able to match component with plugin broker result // when referenceContent is used editorComponent.setId(fqn.getId()); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/convert/component/plugin/PluginComponentToWorkspaceApplier.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert.component.plugin; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static org.eclipse.che.api.core.model.workspace.config.Command.PLUGIN_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.PLUGIN_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE; import javax.inject.Inject; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentFQNParser; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.wsplugins.model.ExtendedPluginFQN; import org.eclipse.che.commons.annotation.Nullable; /** * Applies changes on workspace config according to the specified plugin component. * * @author Sergii Leshchenko */ public class PluginComponentToWorkspaceApplier implements ComponentToWorkspaceApplier { private final ComponentFQNParser componentFQNParser; @Inject public PluginComponentToWorkspaceApplier(ComponentFQNParser componentFQNParser) { this.componentFQNParser = componentFQNParser; } /** * Applies changes on workspace config according to the specified plugin component. * * @param workspaceConfig workspace config on which changes should be applied * @param pluginComponent plugin component that should be applied * @param contentProvider optional content provider that may be used for external component * resource fetching * @throws IllegalArgumentException if specified workspace config or plugin component is null * @throws IllegalArgumentException if specified component has type different from chePlugin */ @Override public void apply( WorkspaceConfigImpl workspaceConfig, ComponentImpl pluginComponent, @Nullable FileContentProvider contentProvider) throws DevfileException { checkArgument(workspaceConfig != null, "Workspace config must not be null"); checkArgument(pluginComponent != null, "Component must not be null"); checkArgument( PLUGIN_COMPONENT_TYPE.equals(pluginComponent.getType()), format("Plugin must have `%s` type", PLUGIN_COMPONENT_TYPE)); String workspacePluginsAttribute = workspaceConfig.getAttributes().get(WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE); final String pluginId = pluginComponent.getId(); final String registryUrl = pluginComponent.getRegistryUrl(); final ExtendedPluginFQN fqn = componentFQNParser.evaluateFQN(pluginComponent, contentProvider); if (!isNullOrEmpty(fqn.getReference())) { workspaceConfig .getAttributes() .put( WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, append(workspacePluginsAttribute, fqn.getReference())); } else { workspaceConfig .getAttributes() .put( WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, append( workspacePluginsAttribute, componentFQNParser.getCompositeId(registryUrl, pluginId))); } for (CommandImpl command : workspaceConfig.getCommands()) { String commandComponent = command.getAttributes().get(COMPONENT_ALIAS_COMMAND_ATTRIBUTE); if (commandComponent == null) { // command does not have component information continue; } if (!commandComponent.equals(pluginComponent.getAlias())) { continue; } command.getAttributes().put(PLUGIN_ATTRIBUTE, fqn.getId()); } // make sure id is set to be able to match component with plugin broker result // when referenceContent is used pluginComponent.setId(fqn.getId()); } private String append(String source, String toAppend) { if (isNullOrEmpty(source)) { return toAppend; } else { return source + "," + toAppend; } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/exception/DevfileException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.exception; /** Describes general devfile exception. */ public class DevfileException extends Exception { public DevfileException(String message, Throwable cause) { super(message, cause); } public DevfileException(String message) { super(message); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/exception/DevfileFormatException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.exception; /** Thrown when devfile schema or integrity validation is failed. */ public class DevfileFormatException extends DevfileException { public DevfileFormatException(String formatError) { super(formatError); } public DevfileFormatException(String formatError, Exception cause) { super(formatError, cause); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/exception/OverrideParameterException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.exception; public class OverrideParameterException extends Exception { public OverrideParameterException(String error) { super(error); } public OverrideParameterException(String error, Exception cause) { super(error, cause); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/exception/WorkspaceExportException.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.exception; /** Thrown when workspace can not be exported into devfile by some reason. */ public class WorkspaceExportException extends Exception { public WorkspaceExportException(String error) { super(error); } public WorkspaceExportException(String error, Exception cause) { super(error, cause); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/validator/ComponentIntegrityValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.validator; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; /** * There are several types of components that are handled in an infrastructure-specific way. This * interface provides the devfile validator with component-specific validators. */ public interface ComponentIntegrityValidator { /** * Validates the component. The component is guaranteed to be of the type that can be handled by * this validator. * * @param component the devfile component to validate * @param contentProvider content provider that can be used to resolve references */ void validateComponent(Component component, FileContentProvider contentProvider) throws DevfileFormatException; final class NoopComponentIntegrityValidator implements ComponentIntegrityValidator { @Override public void validateComponent(Component component, FileContentProvider contentProvider) {} } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileIntegrityValidator.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.validator; import static java.lang.String.format; import static org.eclipse.che.api.workspace.server.devfile.Components.getIdentifiableComponentName; import static org.eclipse.che.api.workspace.server.devfile.Constants.DOCKERIMAGE_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.OPENSHIFT_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.PLUGIN_COMPONENT_TYPE; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.devfile.Action; import org.eclipse.che.api.core.model.workspace.devfile.Command; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.api.core.model.workspace.devfile.Endpoint; import org.eclipse.che.api.core.model.workspace.devfile.Env; import org.eclipse.che.api.core.model.workspace.devfile.Project; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; /** Validates devfile logical integrity. */ @Singleton public class DevfileIntegrityValidator { /** * Checks than name may contain only letters, digits, symbols _.- and does not starts with * non-word character. */ private static final Pattern PROJECT_NAME_PATTERN = Pattern.compile("^[\\w\\d]+[\\w\\d_.-]*$"); private final Map validators; @Inject public DevfileIntegrityValidator(Map validators) { this.validators = validators; } /** * Performs the following checks: * *

       * 
      *
    • All listed items (projects, components, commands) have unique names
    • *
    • There is only one component of type cheEditor
    • *
    • All components exists which are referenced/required by command actions
    • *
    • Project names conforms naming rules
    • *
    *
    * *

    Note that this doesn't validate the selectors in the devfile. If you have access to the * {@link FileContentProvider} instance, you may want to also invoke {@link * #validateContentReferences(Devfile, FileContentProvider)} to perform further validation that is * otherwise impossible. * * @param devfile input devfile * @throws DevfileFormatException if some of the checks is failed * @see #validateContentReferences(Devfile, FileContentProvider) */ public void validateDevfile(Devfile devfile) throws DevfileFormatException { validateProjects(devfile); Set knownAliases = validateComponents(devfile); validateCommands(devfile, knownAliases); } /** * Validates that various selectors in the devfile reference something in the referenced content. * * @param devfile the validated devfile * @param provider the file content provider to fetch the referenced content with. * @throws DevfileFormatException when some selectors don't match any objects in the referenced * content or any other exception thrown during the validation */ public void validateContentReferences(Devfile devfile, FileContentProvider provider) throws DevfileFormatException { for (Component component : devfile.getComponents()) { ComponentIntegrityValidator validator = validators.get(component.getType()); if (validator == null) { throw new DevfileFormatException(format("Unknown component type: %s", component.getType())); } validator.validateComponent(component, provider); } } private Set validateComponents(Devfile devfile) throws DevfileFormatException { Set definedAliases = new HashSet<>(); Component editorComponent = null; Map> idsPerComponentType = new HashMap<>(); for (Component component : devfile.getComponents()) { if (component.getAlias() != null && !definedAliases.add(component.getAlias())) { throw new DevfileFormatException( format("Duplicate component alias found:'%s'", component.getAlias())); } Optional> duplicatedEndpoint = component.getEndpoints().stream() .map(Endpoint::getName) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) .entrySet() .stream() .filter(e -> e.getValue() > 1L) .findFirst(); if (duplicatedEndpoint.isPresent()) { throw new DevfileFormatException( format( "Duplicated endpoint name '%s' found in '%s' component", duplicatedEndpoint.get().getKey(), getIdentifiableComponentName(component))); } Set tempSet = new HashSet<>(); for (Env env : component.getEnv()) { if (!tempSet.add(env.getName())) { throw new DevfileFormatException( format( "Duplicate environment variable '%s' found in component '%s'", env.getName(), getIdentifiableComponentName(component))); } } if (!idsPerComponentType .computeIfAbsent(component.getType(), __ -> new HashSet<>()) .add(getIdentifiableComponentName(component))) { throw new DevfileFormatException( format( "There are multiple components '%s' of type '%s' that cannot be uniquely" + " identified. Please add aliases that would distinguish the components.", getIdentifiableComponentName(component), component.getType())); } if (component.getAutomountWorkspaceSecrets() != null && component.getAlias() == null) { throw new DevfileFormatException( format( "The 'automountWorkspaceSecrets' property cannot be used in component which doesn't have alias. " + "Please add alias to component '%s' that would allow to distinguish its containers.", getIdentifiableComponentName(component))); } switch (component.getType()) { case EDITOR_COMPONENT_TYPE: if (editorComponent != null) { throw new DevfileFormatException( format( "Multiple editor components found: '%s', '%s'", getIdentifiableComponentName(editorComponent), getIdentifiableComponentName(component))); } editorComponent = component; break; case PLUGIN_COMPONENT_TYPE: case KUBERNETES_COMPONENT_TYPE: case OPENSHIFT_COMPONENT_TYPE: case DOCKERIMAGE_COMPONENT_TYPE: // do nothing break; default: throw new DevfileFormatException( format( "One of the components has unsupported component type: '%s'", component.getType())); } } return definedAliases; } private void validateCommands(Devfile devfile, Set knownAliases) throws DevfileFormatException { Set existingNames = new HashSet<>(); for (Command command : devfile.getCommands()) { if (!existingNames.add(command.getName())) { throw new DevfileFormatException( format("Duplicate command name found:'%s'", command.getName())); } if (command.getActions().isEmpty()) { throw new DevfileFormatException( format("Command '%s' does not have actions.", command.getName())); } // It is temporary restriction while exec plugin is not able to handle multiple commands // in different containers as one Task. Later supporting of multiple actions in one command // may be implemented. if (command.getActions().size() > 1) { throw new DevfileFormatException( format("Multiple actions in command '%s' are not supported yet.", command.getName())); } Action action = command.getActions().get(0); if (action.getComponent() == null && (action.getReference() != null || action.getReferenceContent() != null)) { // ok, this action contains a reference to the file containing the definition. Such // actions don't have to have component alias defined. continue; } if (!knownAliases.contains(action.getComponent())) { throw new DevfileFormatException( format( "Command '%s' has action that refers to a component with unknown alias '%s'", command.getName(), action.getComponent())); } } } private void validateProjects(Devfile devfile) throws DevfileFormatException { Set existingNames = new HashSet<>(); for (Project project : devfile.getProjects()) { if (!existingNames.add(project.getName())) { throw new DevfileFormatException( format("Duplicate project name found:'%s'", project.getName())); } if (!PROJECT_NAME_PATTERN.matcher(project.getName()).matches()) { throw new DevfileFormatException( format( "Invalid project name found:'%s'. Name must contain only Latin letters," + "digits or these following special characters ._-", project.getName())); } } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/validator/ErrorMessageComposer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.validator; import static com.google.common.base.Strings.isNullOrEmpty; import java.util.List; import org.leadpony.justify.api.Problem; /** * Helps to convert json schema validation result list of {@link Problem} into an error message * string. */ public class ErrorMessageComposer { /** * Parses {@link Problem} list into an error string. Each problem is recursively parsed to extract * nested errors if any. * * @param validationErrors Schema validation problems list * @return composite error string */ public String extractMessages(List validationErrors, StringBuilder messageBuilder) { for (Problem problem : validationErrors) { int branchCount = problem.countBranches(); if (branchCount == 0) { messageBuilder.append(getMessage(problem)); } else { messageBuilder.append(problem.getMessage()).append(": ["); for (int i = 0; i < branchCount; i++) { extractMessages(problem.getBranch(i), messageBuilder); } messageBuilder.append("]"); } } return messageBuilder.toString(); } private String getMessage(Problem problem) { StringBuilder messageBuilder = new StringBuilder(); if (!isNullOrEmpty(problem.getPointer())) { messageBuilder.append("(").append(problem.getPointer()).append("):"); } messageBuilder.append(problem.getMessage()); return messageBuilder.toString(); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/MachineStatusJsonRpcMessenger.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.event; import static org.eclipse.che.api.workspace.shared.Constants.MACHINE_STATUS_CHANGED_METHOD; import jakarta.annotation.PostConstruct; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.notification.RemoteSubscriptionManager; import org.eclipse.che.api.workspace.shared.dto.event.MachineStatusEvent; /** Send workspace events using JSON RPC to the clients */ @Singleton public class MachineStatusJsonRpcMessenger { private final RemoteSubscriptionManager remoteSubscriptionManager; @Inject public MachineStatusJsonRpcMessenger(RemoteSubscriptionManager remoteSubscriptionManager) { this.remoteSubscriptionManager = remoteSubscriptionManager; } @PostConstruct private void postConstruct() { remoteSubscriptionManager.register( MACHINE_STATUS_CHANGED_METHOD, MachineStatusEvent.class, this::predicate); } private boolean predicate(MachineStatusEvent event, Map scope) { return event.getIdentity().getWorkspaceId().equals(scope.get("workspaceId")); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/RuntimeAbnormalStoppedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.event; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; /** * Should be propagated after Infrastructure stopped a runtime because of any fatal error. * * @author Sergii Leshchenko */ public class RuntimeAbnormalStoppedEvent { private RuntimeIdentity runtimeId; private String reason; public RuntimeAbnormalStoppedEvent(RuntimeIdentity runtimeId, String reason) { this.runtimeId = runtimeId; this.reason = reason; } public RuntimeIdentity getIdentity() { return runtimeId; } public String getReason() { return reason; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/RuntimeAbnormalStoppingEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.event; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; /** * Should be propagated before Infrastructure stops a runtime because of any fatal error. * * @author Sergii Leshchenko */ public class RuntimeAbnormalStoppingEvent { private RuntimeIdentity runtimeId; private String reason; public RuntimeAbnormalStoppingEvent(RuntimeIdentity runtimeId, String reason) { this.runtimeId = runtimeId; this.reason = reason; } public RuntimeIdentity getIdentity() { return runtimeId; } public String getReason() { return reason; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/RuntimeLogJsonRpcMessenger.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.event; import static org.eclipse.che.api.workspace.shared.Constants.MACHINE_LOG_METHOD; import static org.eclipse.che.api.workspace.shared.Constants.RUNTIME_LOG_METHOD; import jakarta.annotation.PostConstruct; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.notification.RemoteSubscriptionManager; import org.eclipse.che.api.workspace.shared.dto.event.RuntimeLogEvent; /** * Register subscriber on {@link RuntimeLogEvent runtime log event} for resending this type of event * via JSON-RPC to clients. * * @author Sergii Leshchenko */ @Singleton public class RuntimeLogJsonRpcMessenger { private final RemoteSubscriptionManager subscriptionManager; @Inject public RuntimeLogJsonRpcMessenger(RemoteSubscriptionManager subscriptionManager) { this.subscriptionManager = subscriptionManager; } @PostConstruct private void postConstruct() { subscriptionManager.register(RUNTIME_LOG_METHOD, RuntimeLogEvent.class, this::predicate); subscriptionManager.register( MACHINE_LOG_METHOD, RuntimeLogEvent.class, this::predicateMachineLog); } private boolean predicate(RuntimeLogEvent event, Map scope) { return event.getRuntimeId().getWorkspaceId().equals(scope.get("workspaceId")); } private boolean predicateMachineLog(RuntimeLogEvent event, Map scope) { return event.getMachineName() != null && event.getRuntimeId().getWorkspaceId().equals(scope.get("workspaceId")); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/RuntimeStatusJsonRpcMessenger.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.event; import jakarta.annotation.PostConstruct; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.notification.RemoteSubscriptionManager; import org.eclipse.che.api.workspace.shared.dto.event.RuntimeStatusEvent; /** Send workspace events using JSON RPC to the clients */ @Singleton public class RuntimeStatusJsonRpcMessenger { private final RemoteSubscriptionManager remoteSubscriptionManager; @Inject public RuntimeStatusJsonRpcMessenger(RemoteSubscriptionManager remoteSubscriptionManager) { this.remoteSubscriptionManager = remoteSubscriptionManager; } @PostConstruct private void postConstruct() { remoteSubscriptionManager.register( "runtime/statusChanged", RuntimeStatusEvent.class, this::predicate); } private boolean predicate(RuntimeStatusEvent event, Map scope) { return event.getIdentity().getWorkspaceId().equals(scope.get("workspaceId")); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/ServerIdleEvent.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.event; /** Event informing about idling the che server. */ public class ServerIdleEvent { private long timeout; /** Implements the handler to handle idling. */ public ServerIdleEvent(long timeout) { super(); this.timeout = timeout; } public long getTimeout() { return timeout; } public void setTimeout(long timeout) { this.timeout = timeout; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/ServerStatusJsonRpcMessenger.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.event; import static org.eclipse.che.api.workspace.shared.Constants.SERVER_STATUS_CHANGED_METHOD; import jakarta.annotation.PostConstruct; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.notification.RemoteSubscriptionManager; import org.eclipse.che.api.workspace.shared.dto.event.ServerStatusEvent; /** Send workspace events using JSON RPC to the clients */ @Singleton public class ServerStatusJsonRpcMessenger { private final RemoteSubscriptionManager remoteSubscriptionManager; @Inject public ServerStatusJsonRpcMessenger(RemoteSubscriptionManager remoteSubscriptionManager) { this.remoteSubscriptionManager = remoteSubscriptionManager; } @PostConstruct private void postConstruct() { remoteSubscriptionManager.register( SERVER_STATUS_CHANGED_METHOD, ServerStatusEvent.class, this::predicate); } private boolean predicate(ServerStatusEvent event, Map scope) { return event.getIdentity().getWorkspaceId().equals(scope.get("workspaceId")); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/WorkspaceJsonRpcMessenger.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.event; import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_STATUS_CHANGED_METHOD; import jakarta.annotation.PostConstruct; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.notification.RemoteSubscriptionManager; import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; /** Send workspace events using JSON RPC to the clients */ @Singleton public class WorkspaceJsonRpcMessenger { private final RemoteSubscriptionManager remoteSubscriptionManager; @Inject public WorkspaceJsonRpcMessenger(RemoteSubscriptionManager remoteSubscriptionManager) { this.remoteSubscriptionManager = remoteSubscriptionManager; } @PostConstruct private void postConstruct() { remoteSubscriptionManager.register( WORKSPACE_STATUS_CHANGED_METHOD, WorkspaceStatusEvent.class, this::predicate); } private boolean predicate(WorkspaceStatusEvent event, Map scope) { return event.getWorkspaceId().equals(scope.get("workspaceId")); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/HttpConnectionServerChecker.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc; import com.google.common.annotations.VisibleForTesting; import com.google.common.io.CharStreams; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Timer; import java.util.concurrent.TimeUnit; import org.eclipse.che.commons.proxy.ProxyAuthenticator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Server checker that uses http connection response code as a criteria of availability of a server. * If response code is not less than 200 and less than 400 server is treated as available. * * @author Alexander Garagatyi */ public class HttpConnectionServerChecker extends ServerChecker { private static final Logger LOG = LoggerFactory.getLogger(HttpConnectionServerChecker.class); private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String CONNECTION_HEADER = "Connection"; private static final String CONNECTION_CLOSE = "close"; private final URL url; private final String token; private final String serverRef; public HttpConnectionServerChecker( URL url, String machineName, String serverRef, long timeout, TimeUnit timeUnit, Timer timer, String token) { super(machineName, serverRef, timeout, timeUnit, timer); this.url = url; this.serverRef = serverRef; this.token = token; } @Override public boolean isAvailable() { HttpURLConnection httpURLConnection = null; try { ProxyAuthenticator.initAuthenticator(url.toString()); httpURLConnection = createConnection(url); // TODO consider how much time we should use as a limit httpURLConnection.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(3)); httpURLConnection.setReadTimeout((int) TimeUnit.SECONDS.toMillis(3)); httpURLConnection.setRequestProperty(CONNECTION_HEADER, CONNECTION_CLOSE); if (token != null) { httpURLConnection.setRequestProperty(AUTHORIZATION_HEADER, "Bearer " + token); } return isConnectionSuccessful(httpURLConnection); } catch (IOException e) { LOG.debug( "Failed to establish http connection to check server '{}'. Cause: {}", serverRef, e.getMessage()); return false; } finally { ProxyAuthenticator.resetAuthenticator(); if (httpURLConnection != null) { httpURLConnection.disconnect(); } } } boolean isConnectionSuccessful(HttpURLConnection conn) { try { int responseCode = conn.getResponseCode(); boolean success = isConnectionSuccessful(responseCode); if (!success && LOG.isDebugEnabled()) { String response; try { InputStream in = conn.getErrorStream(); if (in == null) { in = conn.getInputStream(); } try (Reader reader = new InputStreamReader(in)) { response = CharStreams.toString(reader); } } catch (Exception e) { response = "failed to ready response: " + e.getMessage(); } LOG.debug( "Server check for '{}:{}' request failed with code {}. Response: {}", serverRef, url, responseCode, response); } return success; } catch (IOException e) { LOG.debug( "Failed to establish http connection to check server '{}:{}'. Cause: {}", serverRef, url, e.getMessage()); return false; } } boolean isConnectionSuccessful(int responseCode) { return responseCode >= 200 && responseCode < 400; } @VisibleForTesting HttpURLConnection createConnection(URL url) throws IOException { return (HttpURLConnection) url.openConnection(); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/ServerChecker.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Function; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; /** * Checks availability of a server. * * @author Alexander Garagatyi */ public abstract class ServerChecker { private final String machineName; private final String serverRef; private final long period; private final long deadLine; private final CompletableFuture reportFuture; private final Timer timer; /** * Creates server checker instance. * * @param machineName name of machine to whom the server belongs * @param serverRef reference of the server * @param period period between unsuccessful availability checks, measured in {@code timeUnit} * treated as available * @param timeout max time allowed for the server availability checks to last before server is * treated unavailable, measured in {@code timeUnit} * @param timeUnit measurement unit for {@code period} and {@code timeout} parameters */ protected ServerChecker( String machineName, String serverRef, long timeout, TimeUnit timeUnit, Timer timer) { this.machineName = machineName; this.serverRef = serverRef; this.timer = timer; this.period = TimeUnit.MILLISECONDS.convert(1, timeUnit); this.reportFuture = new CompletableFuture<>(); this.deadLine = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(timeout, timeUnit); } /** * Starts server availability checking, which will be stopped when server become available or * checking times out. */ public void start() { timer.schedule(new ServerCheckingTask(0), 0); } /** * Checks server availability, throws {@link InfrastructureException} if the server is not * available. */ public void checkOnce(Consumer readinessHandler) throws InfrastructureException { if (!isAvailable()) { throw new InfrastructureException( String.format("Server '%s' in container '%s' not available.", serverRef, machineName)); } readinessHandler.accept(serverRef); } /** * Shows whether the server is treated as available. * * @return true if server is available, false otherwise */ public abstract boolean isAvailable(); /** * Returns {@code CompletableFuture} that will be completed when server become available or * unavailable. When server become available completable future returns server reference. *
    This completable future can be used to chain {@code CompletableFuture} stage to successful * or unsuccessful completion of the server availability check. * * @see CompletableFuture * @see CompletableFuture#thenAccept(Consumer) * @see CompletableFuture#exceptionally(Function) */ public CompletableFuture getReportCompFuture() { return reportFuture; } private boolean isTimedOut() { return System.currentTimeMillis() > deadLine; } private class ServerCheckingTask extends TimerTask { private int currentNumberOfSequentialSuccessfulPings; public ServerCheckingTask(int currentNumberOfSequentialSuccessfulPings) { this.currentNumberOfSequentialSuccessfulPings = currentNumberOfSequentialSuccessfulPings; } @Override public void run() { if (isTimedOut()) { reportFuture.completeExceptionally( new InfrastructureException( String.format( "Server '%s' in container '%s' not available.", serverRef, machineName))); } else if (isAvailable()) { currentNumberOfSequentialSuccessfulPings++; if (currentNumberOfSequentialSuccessfulPings == 1) { reportFuture.complete(serverRef); } else { timer.schedule(new ServerCheckingTask(currentNumberOfSequentialSuccessfulPings), period); } } else { timer.schedule(new ServerCheckingTask(0), period); } } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/ServersChecker.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc; import com.google.common.annotations.VisibleForTesting; import com.google.inject.assistedinject.Assisted; import jakarta.ws.rs.core.UriBuilder; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.server.token.MachineTokenProvider; /** * Checks readiness of servers of a machine. * * @author Alexander Garagatyi */ public class ServersChecker { private final RuntimeIdentity runtimeIdentity; private final String machineName; private final Map servers; private final MachineTokenProvider machineTokenProvider; private final Set livenessProbes; private Timer timer; private long resultTimeoutSeconds; private CompletableFuture result; /** * Creates instance of this class. * * @param machineName name of machine whose servers will be checked by this method * @param servers map of servers in a machine */ @Inject public ServersChecker( @Assisted RuntimeIdentity runtimeIdentity, @Assisted String machineName, @Assisted Map servers, MachineTokenProvider machineTokenProvider, @Named("che.workspace.server.liveness_probes") String[] livenessProbes) { this.runtimeIdentity = runtimeIdentity; this.machineName = machineName; this.servers = servers; this.timer = new Timer("ServersChecker", true); this.machineTokenProvider = machineTokenProvider; this.livenessProbes = Arrays.stream(livenessProbes).map(String::trim).collect(Collectors.toSet()); } /** * Asynchronously starts checking readiness of servers of a machine. Method {@link #await()} waits * the result of this asynchronous check. * * @param serverReadinessHandler consumer which will be called with server reference as the * argument when server become available * @throws InternalInfrastructureException if check of a server failed due to an unexpected error * @throws InfrastructureException if check of a server failed due to an error */ public CompletableFuture startAsync(Consumer serverReadinessHandler) throws InfrastructureException { timer = new Timer("ServersChecker", true); List serverCheckers = getServerCheckers(); // should be completed with an exception if a server considered unavailable CompletableFuture firstNonAvailable = new CompletableFuture<>(); CompletableFuture[] checkTasks = serverCheckers.stream() .map(ServerChecker::getReportCompFuture) .map( compFut -> compFut .thenAccept(serverReadinessHandler) .exceptionally( e -> { // cleanup checkers tasks timer.cancel(); firstNonAvailable.completeExceptionally(e); return null; })) .toArray(CompletableFuture[]::new); resultTimeoutSeconds = checkTasks.length * 180; // should complete when all servers checks reported availability CompletableFuture allAvailable = CompletableFuture.allOf(checkTasks); // should complete when all servers are available or any server is unavailable result = CompletableFuture.anyOf(allAvailable, firstNonAvailable); for (ServerChecker serverChecker : serverCheckers) { serverChecker.start(); } return result; } /** * Synchronously checks whether servers are available, throws {@link InfrastructureException} if * any is not. */ public void checkOnce(Consumer readyHandler) throws InfrastructureException { for (ServerChecker checker : getServerCheckers()) { checker.checkOnce(readyHandler); } } /** * Waits until servers are considered available or one of them is considered as unavailable. * * @throws InternalInfrastructureException if check of a server failed due to an unexpected error * @throws InfrastructureException if check of a server failed due to interruption * @throws InfrastructureException if check of a server failed because it reached timeout * @throws InfrastructureException if check of a server failed due to an error */ public void await() throws InfrastructureException, InterruptedException { try { // TODO how much time should we check? result.get(resultTimeoutSeconds, TimeUnit.SECONDS); } catch (TimeoutException e) { throw new InfrastructureException( "Servers readiness check of machine " + machineName + " timed out"); } catch (ExecutionException e) { try { throw e.getCause(); } catch (InfrastructureException rethrow) { throw rethrow; } catch (Throwable thr) { throw new InternalInfrastructureException( "Machine " + machineName + " servers readiness check failed. Error: " + thr.getMessage(), thr); } } } private List getServerCheckers() throws InfrastructureException { ArrayList checkers = new ArrayList<>(servers.size()); for (Map.Entry serverEntry : servers.entrySet()) { // TODO replace with correct behaviour // workaround needed because we don't have server readiness check in the model if (livenessProbes.contains(serverEntry.getKey())) { checkers.add(getChecker(serverEntry.getKey(), serverEntry.getValue())); } } return checkers; } private ServerChecker getChecker(String serverRef, Server server) throws InfrastructureException { // TODO replace with correct behaviour // workaround needed because we don't have server readiness check in the model // Create server readiness endpoint URL URL url; String token; try { String serverUrl = server.getUrl(); if ("terminal".equals(serverRef)) { serverUrl = serverUrl.replaceFirst("^ws", "http").replaceFirst("/pty$", "/"); } if ("wsagent/http".equals(serverRef) && !serverUrl.endsWith("/")) { // add trailing slash if it is not present serverUrl = serverUrl + '/'; } token = machineTokenProvider.getToken( runtimeIdentity.getOwnerId(), runtimeIdentity.getWorkspaceId()); url = UriBuilder.fromUri(serverUrl).build().toURL(); } catch (MalformedURLException e) { throw new InternalInfrastructureException( "Server " + serverRef + " URL is invalid. Error: " + e.getMessage(), e); } return doCreateChecker(url, serverRef, token); } @VisibleForTesting ServerChecker doCreateChecker(URL url, String serverRef, String token) { // TODO add readiness endpoint to terminal and remove this // workaround needed because terminal server doesn't have endpoint to check it readiness if ("terminal".equals(serverRef)) { return new TerminalHttpConnectionServerChecker( url, machineName, serverRef, TimeUnit.SECONDS.toMillis(180), TimeUnit.MILLISECONDS, timer, token); } // TODO do not hardcode timeouts, use server conf instead return new HttpConnectionServerChecker( url, machineName, serverRef, TimeUnit.SECONDS.toMillis(180), TimeUnit.MILLISECONDS, timer, token); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/ServersCheckerFactory.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.model.workspace.runtime.Server; /** * Creates {@link ServersChecker} for server readiness checking. * * @author Alexander Garagatyi * @author Sergii Leshchenko */ public interface ServersCheckerFactory { ServersChecker create( RuntimeIdentity runtimeIdentity, String machineName, Map servers); } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/TerminalHttpConnectionServerChecker.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc; import java.net.URL; import java.util.Timer; import java.util.concurrent.TimeUnit; /** * This class is used as {@link ServerChecker} for terminal server as it doesn't have an endpoint * that responds with 200 and can be used with default {@link HttpConnectionServerChecker} * * @author Alexander Garagatyi */ // TODO add readiness endpoint to terminal and remove this class class TerminalHttpConnectionServerChecker extends HttpConnectionServerChecker { TerminalHttpConnectionServerChecker( URL url, String machineName, String serverRef, long timeout, TimeUnit timeUnit, Timer timer, String token) { super(url, machineName, serverRef, timeout, timeUnit, timer, token); } @Override boolean isConnectionSuccessful(int responseCode) { return responseCode == 404; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/HttpProbe.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.util.HashMap; import java.util.Map; /** * Probes a HTTP(s) URL for a response with code >=200 and <400 * * @author Alexander Garagatyi */ public class HttpProbe extends Probe { private static final String CONNECTION_HEADER = "Connection"; private static final String CONNECTION_CLOSE = "close"; private final URL url; private final int timeout; private final Map headers; private HttpURLConnection httpURLConnection; /** * Creates probe * * @param url HTTP endpoint to probe * @param timeout connection and read timeouts */ public HttpProbe(URL url, int timeout, Map headers) { this.url = url; this.timeout = timeout; this.headers = new HashMap<>(); if (headers != null) { this.headers.putAll(headers); } this.headers.put(CONNECTION_HEADER, CONNECTION_CLOSE); } @Override public boolean doProbe() { try { httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setConnectTimeout(timeout); httpURLConnection.setReadTimeout(timeout); headers.forEach((name, value) -> httpURLConnection.setRequestProperty(name, value)); return isConnectionSuccessful(httpURLConnection); } catch (IOException e) { return false; } finally { if (httpURLConnection != null) { httpURLConnection.disconnect(); this.httpURLConnection = null; } } } /** * More effectively cancels the probe than cancellation inherited from {@link Probe}. * * @see Probe#cancel() */ @Override public void cancel() { httpURLConnection.disconnect(); this.httpURLConnection = null; } private boolean isConnectionSuccessful(HttpURLConnection conn) { try { int responseCode = conn.getResponseCode(); return responseCode >= 200 && responseCode < 400; } catch (IOException e) { return false; } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/HttpProbeConfig.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; import static java.util.Collections.emptyMap; import java.util.Map; /** * Configuration of a HTTP URL probe. * * @author Alexander Garagatyi */ public class HttpProbeConfig extends TcpProbeConfig { private final String scheme; private final String path; private final Map headers; /** * Creates probe configuration. * * @param scheme protocol of the HTTP server (http or https) * @param path path for the HTTP probe * @param headers optional headers to add into the HTTP probe request * @see TcpProbeConfig#TcpProbeConfig(int, int, int, int, int, int, String) */ public HttpProbeConfig( int port, String host, String scheme, String path, Map headers, int successThreshold, int failureThreshold, int timeoutSeconds, int periodSeconds, int initialDelaySeconds) { super( successThreshold, failureThreshold, timeoutSeconds, periodSeconds, initialDelaySeconds, port, host); if (!"http".equals(scheme) && !"https".equals(scheme)) { throw new IllegalArgumentException("HTTP probe scheme must be 'http' or 'https'"); } this.scheme = scheme; this.path = path; this.headers = headers != null ? headers : emptyMap(); } public String getScheme() { return scheme; } public String getPath() { return path; } public Map getHeaders() { return headers; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/HttpProbeFactory.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; import java.util.concurrent.TimeUnit; /** * Produces {@link HttpProbe} instances * * @see ProbeFactory * @author Alexander Garagatyi */ public class HttpProbeFactory extends ProbeFactory { private final URL url; private final int timeout; private final Map headers; private final HttpProbeConfig probeConfig; public HttpProbeFactory( String workspaceId, String machineName, String serverName, HttpProbeConfig probeConfig) throws MalformedURLException { super(workspaceId, machineName, serverName, probeConfig); url = new URL( probeConfig.getScheme(), probeConfig.getHost(), probeConfig.getPort(), probeConfig.getPath()); timeout = (int) TimeUnit.SECONDS.toMillis(probeConfig.getTimeoutSeconds()); headers = probeConfig.getHeaders(); this.probeConfig = probeConfig; } @Override public HttpProbeConfig getProbeConfig() { return probeConfig; } @Override public HttpProbe get() { return new HttpProbe(url, timeout, headers); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/Probe.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; /** * One-time probe for a server. Should not be used directly but rather by a probe scheduling * framework. * * @author Alexander Garagatyi */ public abstract class Probe { private Thread probeThread; /** * Checks {@link Probe}. Note that it must not be called more than one time. * * @return true if probe finishes successfully, false otherwise * @throws IllegalStateException if called second time */ final boolean probe() { if (probeThread != null) { throw new IllegalStateException( "This probe can be used only once, but second usage is detected!"); } probeThread = Thread.currentThread(); try { return doProbe(); } finally { // clear interrupted state Thread.interrupted(); } } /** * Returns {@code true} if probe finishes successfully, {@code false} otherwise. Must return false * when probe is interrupted even if interruption is not respected by probe implementation. */ protected abstract boolean doProbe(); /** * Interrupts execution of the probe. May be useful when probing takes too much time. Doesn't * guarantee that interruption is respected by the probe or will lead to the immediate stop of * usage of a thread where {@link #probe()} is called. */ public void cancel() { probeThread.interrupt(); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/ProbeConfig.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; /** * Configuration of a workspace server probe. * * @author Alexander Garagatyi */ public abstract class ProbeConfig { private int successThreshold; private int failureThreshold; private int timeoutSeconds; private int periodSeconds; private int initialDelaySeconds; /** * Creates probe configuration. * * @param successThreshold minimum consecutive successes for the probe to be considered successful * after having failed. Minimum value is 1. * @param failureThreshold consecutive failures of a probe needed to consider probe failed. * Minimum value is 1. * @param timeoutSeconds number of seconds after which the probe times out. Minimum value is 1. * @param periodSeconds how often to perform the probe. Minimum value is 1. * @param initialDelaySeconds number of seconds after the probe is submitted for checks before * probes are initiated. */ public ProbeConfig( int successThreshold, int failureThreshold, int timeoutSeconds, int periodSeconds, int initialDelaySeconds) { if (successThreshold < 1) { throw new IllegalArgumentException( "Success threshold value '" + successThreshold + "' is illegal. Should be not less than 1"); } this.successThreshold = successThreshold; if (failureThreshold < 1) { throw new IllegalArgumentException( "Failure threshold value '" + failureThreshold + "' is illegal. Should be not less than 1"); } this.failureThreshold = failureThreshold; if (timeoutSeconds < 1) { throw new IllegalArgumentException( "Timeout value '" + timeoutSeconds + "' is illegal. Should be not less than 1"); } this.timeoutSeconds = timeoutSeconds; if (periodSeconds < 1) { throw new IllegalArgumentException( "Period value '" + periodSeconds + "' is illegal. Should be not less than 1"); } this.periodSeconds = periodSeconds; this.initialDelaySeconds = initialDelaySeconds; } public int getInitialDelaySeconds() { return initialDelaySeconds; } public int getFailureThreshold() { return failureThreshold; } public int getSuccessThreshold() { return successThreshold; } public int getPeriodSeconds() { return periodSeconds; } public int getTimeoutSeconds() { return timeoutSeconds; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/ProbeFactory.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; /** * Produces {@link Probe} instances and holds information about probe configuration and Che * workspace server the probe corresponds to. * * @author Alexander Garagatyi */ public abstract class ProbeFactory { private final String workspaceId; private final String machineName; private final String serverName; private final ProbeConfig probeConfig; protected ProbeFactory( String workspaceId, String machineName, String serverName, ProbeConfig probeConfig) { this.workspaceId = workspaceId; this.machineName = machineName; this.serverName = serverName; this.probeConfig = probeConfig; } /** Returns an instance of a probe for a server in a workspace */ public abstract Probe get(); /** Returns ID of a workspace the probe corresponds to */ public String getWorkspaceId() { return workspaceId; } /** Returns name of a machine the probe corresponds to */ public String getMachineName() { return machineName; } /** Returns name of a workspace server the probe corresponds to */ public String getServerName() { return serverName; } /** Returns configuration of a probe */ public ProbeConfig getProbeConfig() { return probeConfig; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/ProbeResult.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; /** * Result of a probe. Should be fired in accordance with {@link ProbeConfig}, e.g. thresholds, * timeouts. In particular if a threshold if >1 then this result should be fired after reaching of * the threshold instead of on each probe check. * * @author Alexander Garagatyi */ public class ProbeResult { public enum ProbeStatus { PASSED, FAILED } private final String workspaceId; private final String machineName; private final String serverName; private final ProbeStatus status; public ProbeResult( String workspaceId, String machineName, String serverName, ProbeStatus status) { this.workspaceId = workspaceId; this.machineName = machineName; this.serverName = serverName; this.status = status; } /** Returns ID of a workspace the probe corresponds to */ public String getWorkspaceId() { return workspaceId; } /** Returns name of a machine the probe corresponds to */ public String getMachineName() { return machineName; } /** Returns name of a workspace server the probe corresponds to */ public String getServerName() { return serverName; } /** Status of a probe */ public ProbeStatus getStatus() { return status; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/ProbeScheduler.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.workspace.server.hc.probe.ProbeResult.ProbeStatus; import org.eclipse.che.commons.observability.ExecutorServiceWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Schedules workspace servers probes checks asynchronously. * * @author Alexander Garagatyi * @author Sergii Leshchenko */ @Singleton public class ProbeScheduler { private static final Logger LOG = LoggerFactory.getLogger(ProbeScheduler.class); private final ScheduledExecutorService probesExecutor; /** * Use single thread for a scheduling of tasks interruption by timeout. Single thread can be used * since it is supposed that interruption is a very quick call. Separate thread is needed to * prevent a situation when executor is full of jobs and current ones are hanging but we need to * time them out. */ private final Timer timeouts; /** Mapping of workspaceId to a list of futures with probes of a workspace. */ private final Map> probesFutures; @Inject public ProbeScheduler(ExecutorServiceWrapper executorServiceWrapper) { probesExecutor = executorServiceWrapper.wrap( new ScheduledThreadPoolExecutor( 10, new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat("ServerProbes-%s") .build()), ProbeScheduler.class.getName()); timeouts = new Timer("ServerProbesTimeouts", true); probesFutures = new ConcurrentHashMap<>(); } /** * Schedules provided {@link WorkspaceProbes} and uses provided {@code Consumer} to * send probes results. Respects {@link ProbeConfig} parameters such as thresholds, timeouts. Note * that probe execution is not deleted automatically when probe results are passed to probe * results consumer. To stop a probe execution method {@link #cancel(String)} should be used. * * @param probes probes to check * @param probeResultConsumer consumer of {@link ProbeResult} instances produced on retrieving * probe execution results * @throws RejectedExecutionException when {@link ProbeScheduler} is terminated */ public void schedule(WorkspaceProbes probes, Consumer probeResultConsumer) { probesFutures.putIfAbsent(probes.getWorkspaceId(), new ArrayList<>()); probes .getProbes() .forEach( probeFactory -> schedule(probes.getWorkspaceId(), probeFactory, probeResultConsumer)); } /** * Schedules provided {@link WorkspaceProbes} when a workspace becomes {@link * WorkspaceStatus#RUNNING}. * *

    Note that probes scheduling will be canceled when {@link #cancel(String)} is called or when * a workspace becomes {@link WorkspaceStatus#STOPPING} or {@link WorkspaceStatus#STOPPED}. * * @param probes probes to schedule * @param probeResultConsumer consumer of {@link ProbeResult} instances produced on retrieving * probe execution results * @param statusSupplier supplier to retrieve workspace status. Scheduling will be delayed if * {@link RuntimeException} is thrown * @see #schedule(WorkspaceProbes, Consumer) */ public void schedule( WorkspaceProbes probes, Consumer probeResultConsumer, Supplier statusSupplier) { DelayedSchedulingTask task = new DelayedSchedulingTask(statusSupplier, probes, probeResultConsumer); // scheduleWithFixedDelay is used in favor of scheduleAtFixedRate because in case of big amount // of scheduled probes start time of tasks may shift and this may lead to a situation when // another probeConfig is needed immediately after the previous one is finished which doesn't // seem a good thing ScheduledFuture scheduledFuture = probesExecutor.scheduleWithFixedDelay(task, 10L, 10L, TimeUnit.SECONDS); probesFutures.compute( probes.getWorkspaceId(), (key, scheduledFutures) -> { List target = scheduledFutures; if (target == null) { target = new ArrayList<>(); } target.add(scheduledFuture); return target; }); } /** * Dismisses following and if possible current executions of probes of a workspace with a * specified ID. */ public void cancel(String workspaceId) { List tasks = probesFutures.remove(workspaceId); if (tasks != null) { tasks.forEach(task -> task.cancel(true)); } } /** Denies starting of new probes and terminates active one if scheduler not terminated yet. */ public void shutdown() { if (!probesExecutor.isShutdown()) { probesExecutor.shutdown(); try { LOG.info("Shutdown probe scheduler, wait 30s to stop normally"); if (!probesExecutor.awaitTermination(30, TimeUnit.SECONDS)) { probesExecutor.shutdownNow(); LOG.info("Interrupt probe scheduler, wait 60s to stop"); if (!probesExecutor.awaitTermination(60, TimeUnit.SECONDS)) { LOG.error("Couldn't shutdown probe scheduler threads pool"); } else { LOG.info("Probe scheduler threads pool is interrupted"); } } else { LOG.info("Probe scheduler threads pool is shut down"); } } catch (InterruptedException x) { probesExecutor.shutdownNow(); Thread.currentThread().interrupt(); } } } private void schedule( String workspaceId, ProbeFactory probeFactory, Consumer probeResultConsumer) { ProbeConfig probeConfig = probeFactory.getProbeConfig(); Task task = new Task(probeFactory, probeResultConsumer); // scheduleWithFixedDelay is used in favor of scheduleAtFixedRate because in case of big amount // of scheduled probes start time of tasks may shift and this may lead to a situation when // another probeConfig is needed immediately after the previous one is finished which doesn't // seem a good thing ScheduledFuture scheduledFuture = probesExecutor.scheduleWithFixedDelay( task, probeConfig.getInitialDelaySeconds(), probeConfig.getPeriodSeconds(), TimeUnit.SECONDS); List workspaceProbes = probesFutures.computeIfPresent( workspaceId, (key, scheduledFutures) -> { scheduledFutures.add(scheduledFuture); return scheduledFutures; }); // check whether workspace probes were cancelled concurrently which led to removal of the value // in the map if (workspaceProbes == null) { scheduledFuture.cancel(true); task.cancel(); } } private class Task implements Runnable { private final ProbeFactory probeFactory; private final Consumer probeResultConsumer; private final ProbeConfig probeConfig; private int failures = 0; private int successes = 0; private AtomicBoolean cancelled = new AtomicBoolean(false); public Task(ProbeFactory probeFactory, Consumer probeResultConsumer) { this.probeFactory = probeFactory; this.probeConfig = probeFactory.getProbeConfig(); this.probeResultConsumer = probeResultConsumer; } @Override public void run() { if (cancelled.get()) { return; } Probe probe = probeFactory.get(); TimeoutProbeTask timeoutProbeTask = new TimeoutProbeTask(probe); timeouts.schedule( timeoutProbeTask, TimeUnit.SECONDS.toMillis(probeConfig.getTimeoutSeconds())); boolean success = probe.probe(); timeoutProbeTask.cancel(); if (success) { // current success increases successes count and clears failures count successes++; failures = 0; if (successes >= probeConfig.getSuccessThreshold()) { // TODO make them completable futures? Then we should ensure that // consecutive calls won't use executors thread time but rather use common thread // pool to perform processing of passed probe result if (cancelled.get()) { return; } // Health check satisfies probeConfig health conditions probeResultConsumer.accept( new ProbeResult( probeFactory.getWorkspaceId(), probeFactory.getMachineName(), probeFactory.getServerName(), ProbeStatus.PASSED)); } } else { // current failure increases failures count and clears successes count failures++; successes = 0; if (failures >= probeConfig.getFailureThreshold()) { if (cancelled.get()) { return; } // Health check satisfies probeConfig failure conditions probeResultConsumer.accept( new ProbeResult( probeFactory.getWorkspaceId(), probeFactory.getMachineName(), probeFactory.getServerName(), ProbeStatus.FAILED)); } } } public void cancel() { cancelled.set(true); } } private class DelayedSchedulingTask implements Runnable { private final String workspaceId; private final Supplier statusSupplier; private final WorkspaceProbes probes; private final Consumer probeResultConsumer; DelayedSchedulingTask( Supplier statusSupplier, WorkspaceProbes probes, Consumer probeResultConsumer) { this.workspaceId = probes.getWorkspaceId(); this.statusSupplier = statusSupplier; this.probes = probes; this.probeResultConsumer = probeResultConsumer; } @Override public void run() { WorkspaceStatus status; try { status = statusSupplier.get(); } catch (RuntimeException e) { // delay return; } switch (status) { case STARTING: // delay return; case RUNNING: ProbeScheduler.this.cancel(workspaceId); schedule(probes, probeResultConsumer); return; case STOPPED: case STOPPING: default: ProbeScheduler.this.cancel(workspaceId); } } } private class TimeoutProbeTask extends TimerTask { private final Probe probe; public TimeoutProbeTask(Probe probe) { this.probe = probe; } @Override public void run() { probe.cancel(); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/TcpProbeConfig.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; import static com.google.common.base.Strings.isNullOrEmpty; /** * @author Alexander Garagatyi */ public class TcpProbeConfig extends ProbeConfig { private final int port; private final String host; /** * Creates probe configuration. * * @param port port of the TCP server to probe * @param host hostname of the TCP server to probe * @see ProbeConfig#ProbeConfig(int, int, int, int, int) */ public TcpProbeConfig( int successThreshold, int failureThreshold, int timeoutSeconds, int periodSeconds, int initialDelaySeconds, int port, String host) { super(successThreshold, failureThreshold, timeoutSeconds, periodSeconds, initialDelaySeconds); if (port < 1) { throw new IllegalArgumentException( "Port '" + port + "' is illegal. Port should not be less than 1"); } this.port = port; if (isNullOrEmpty(host)) { throw new IllegalArgumentException( "Host '" + host + "' is illegal. Host should not be empty"); } this.host = host; } public String getHost() { return host; } public int getPort() { return port; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/WorkspaceProbes.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; import java.util.List; /** * Holds {@link ProbeFactory} instances for probes of a workspace runtime * * @author Alexander Garagatyi */ public class WorkspaceProbes { private String workspaceId; private List probesFactories; public WorkspaceProbes(String workspaceId, List probesFactories) { this.workspaceId = workspaceId; this.probesFactories = probesFactories; } public List getProbes() { return probesFactories; } public String getWorkspaceId() { return workspaceId; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/WorkspaceProbesFactory.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; import com.google.common.collect.ImmutableMap; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.Runtime; import org.eclipse.che.api.core.model.workspace.runtime.Machine; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.api.workspace.server.hc.probe.server.ExecServerLivenessProbeConfigFactory; import org.eclipse.che.api.workspace.server.hc.probe.server.HttpProbeConfigFactory; import org.eclipse.che.api.workspace.server.hc.probe.server.TerminalServerLivenessProbeConfigFactory; import org.eclipse.che.api.workspace.server.hc.probe.server.WsAgentServerLivenessProbeConfigFactory; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.server.token.MachineTokenProvider; import org.eclipse.che.api.workspace.shared.Constants; /** * Produces instances of {@link WorkspaceProbes} according to provided servers of a workspace * runtime. * * @author Alexander Garagatyi */ public class WorkspaceProbesFactory { // Is used to define servers which will be checked by this server checker class. // It is also a workaround to set correct paths for servers readiness checks. private final Map probeConfigFactories; @Inject public WorkspaceProbesFactory(MachineTokenProvider machineTokenProvider) { probeConfigFactories = ImmutableMap.of( Constants.SERVER_WS_AGENT_HTTP_REFERENCE, new WsAgentServerLivenessProbeConfigFactory(machineTokenProvider, 1), Constants.SERVER_EXEC_AGENT_HTTP_REFERENCE, new ExecServerLivenessProbeConfigFactory(1), Constants.SERVER_TERMINAL_REFERENCE, new TerminalServerLivenessProbeConfigFactory(1)); } /** * Get {@link WorkspaceProbes} for a whole workspace runtime * * @throws InfrastructureException when the operation fails */ public WorkspaceProbes getProbes(RuntimeIdentity runtimeId, Runtime runtime) throws InfrastructureException { return getProbes(runtimeId, runtime.getMachines()); } /** * Get {@link WorkspaceProbes} for servers of the specified machines * * @throws InfrastructureException when the operation fails */ public WorkspaceProbes getProbes( RuntimeIdentity runtimeId, Map machines) throws InfrastructureException { List factories = new ArrayList<>(); try { for (Entry entry : machines.entrySet()) { fillProbes(runtimeId, entry.getKey(), factories, entry.getValue().getServers()); } } catch (MalformedURLException e) { throw new InternalInfrastructureException( "Server liveness probes creation failed. Error: " + e.getMessage()); } return new WorkspaceProbes(runtimeId.getWorkspaceId(), factories); } /** * Get {@link WorkspaceProbes} for servers of a machine from a workspace runtime * * @throws InfrastructureException when the operation fails */ public WorkspaceProbes getProbes( RuntimeIdentity runtimeId, String machineName, Map servers) throws InfrastructureException { List factories = new ArrayList<>(); try { fillProbes(runtimeId, machineName, factories, servers); } catch (MalformedURLException e) { throw new InternalInfrastructureException( "Server liveness probes creation failed. Error: " + e.getMessage()); } return new WorkspaceProbes(runtimeId.getWorkspaceId(), factories); } private void fillProbes( RuntimeIdentity runtimeId, String machineName, List factories, Map servers) throws InfrastructureException, MalformedURLException { for (Entry entry : servers.entrySet()) { ProbeFactory probeFactory = getProbeFactory( runtimeId.getOwnerId(), runtimeId.getWorkspaceId(), machineName, entry.getKey(), entry.getValue()); if (probeFactory != null) { factories.add(probeFactory); } } } private ProbeFactory getProbeFactory( String userId, String workspaceId, String machineName, String serverRef, Server server) throws InfrastructureException, MalformedURLException { // workaround needed because we don't have server readiness check in the model HttpProbeConfigFactory configFactory = probeConfigFactories.get(serverRef); if (configFactory == null) { return null; } final HttpProbeConfig httpProbeConfig = configFactory.get(userId, workspaceId, server); return new HttpProbeFactory(workspaceId, machineName, serverRef, httpProbeConfig); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/server/ExecServerLivenessProbeConfigFactory.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe.server; import java.net.MalformedURLException; import java.net.URL; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.api.workspace.server.hc.probe.HttpProbeConfig; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.commons.env.EnvironmentContext; /** * Produces {@link HttpProbeConfig} for exec agent liveness probes. * * @author Alexander Garagatyi */ public class ExecServerLivenessProbeConfigFactory implements HttpProbeConfigFactory { private final int successThreshold; public ExecServerLivenessProbeConfigFactory(int successThreshold) { this.successThreshold = successThreshold; } @Override public HttpProbeConfig get(String workspaceId, Server server) throws InternalInfrastructureException { return get(EnvironmentContext.getCurrent().getSubject().getUserId(), workspaceId, server); } @Override public HttpProbeConfig get(String userId, String workspaceId, Server server) throws InternalInfrastructureException { try { URL url = new URL(server.getUrl()); return new HttpProbeConfig( url.getPort() == -1 ? url.getDefaultPort() : url.getPort(), url.getHost(), url.getProtocol(), url.getPath().replaceFirst("/process$", "/liveness"), null, successThreshold, 3, 120, 10, 10); } catch (MalformedURLException e) { throw new InternalInfrastructureException( "Exec agent server liveness probe url is invalid. Error: " + e.getMessage()); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/server/HttpProbeConfigFactory.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe.server; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.api.workspace.server.hc.probe.HttpProbeConfig; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; /** * Produces {@link HttpProbeConfig} for a server liveness probes. * * @author Alexander Garagatyi * @apiNote this is a workaround needed because Che agents doesn't have a way to specify liveness * probes configuration. We should remove this when it is possible to configure ws-agent, exec * and terminal agents probes configuration */ public interface HttpProbeConfigFactory { /** * Returns liveness probe config for a specified server * * @throws InternalInfrastructureException when server probe creation failed */ HttpProbeConfig get(String workspaceId, Server server) throws InternalInfrastructureException; HttpProbeConfig get(String userId, String workspaceId, Server server) throws InternalInfrastructureException; } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/server/TerminalServerLivenessProbeConfigFactory.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe.server; import java.net.URI; import java.net.URISyntaxException; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.api.workspace.server.hc.probe.HttpProbeConfig; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.commons.env.EnvironmentContext; /** * Produces {@link HttpProbeConfig} for terminal agent liveness probes. * * @author Alexander Garagatyi */ public class TerminalServerLivenessProbeConfigFactory implements HttpProbeConfigFactory { private final int successThreshold; public TerminalServerLivenessProbeConfigFactory(int successThreshold) { this.successThreshold = successThreshold; } @Override public HttpProbeConfig get(String workspaceId, Server server) throws InternalInfrastructureException { return get(EnvironmentContext.getCurrent().getSubject().getUserId(), workspaceId, server); } @Override public HttpProbeConfig get(String userId, String workspaceId, Server server) throws InternalInfrastructureException { URI uri; try { uri = new URI(server.getUrl()); } catch (URISyntaxException e) { throw new InternalInfrastructureException( "Terminal agent server liveness probe url is invalid. Error: " + e.getMessage()); } String protocol; if ("wss".equals(uri.getScheme())) { protocol = "https"; } else { protocol = "http"; } int port; if (uri.getPort() == -1) { if ("http".equals(protocol)) { port = 80; } else { port = 443; } } else { port = uri.getPort(); } String path = uri.getPath().replaceFirst("/pty$", "/liveness"); return new HttpProbeConfig( port, uri.getHost(), protocol, path, null, successThreshold, 3, 120, 10, 10); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/hc/probe/server/WsAgentServerLivenessProbeConfigFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe.server; import static java.util.Collections.singletonMap; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriBuilderException; import java.net.URI; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.api.workspace.server.hc.probe.HttpProbeConfig; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.server.token.MachineTokenException; import org.eclipse.che.api.workspace.server.token.MachineTokenProvider; import org.eclipse.che.commons.env.EnvironmentContext; /** * Produces {@link HttpProbeConfig} for ws-agent liveness probes. * * @author Alexander Garagatyi */ public class WsAgentServerLivenessProbeConfigFactory implements HttpProbeConfigFactory { private final MachineTokenProvider machineTokenProvider; private final int successThreshold; @Inject public WsAgentServerLivenessProbeConfigFactory( MachineTokenProvider machineTokenProvider, int successThreshold) { this.machineTokenProvider = machineTokenProvider; this.successThreshold = successThreshold; } @Override public HttpProbeConfig get(String workspaceId, Server server) throws InternalInfrastructureException { return get(EnvironmentContext.getCurrent().getSubject().getUserId(), workspaceId, server); } @Override public HttpProbeConfig get(String userId, String workspaceId, Server server) throws InternalInfrastructureException { try { // add check path URI uri = UriBuilder.fromUri(server.getUrl()).path("/liveness").build(); int port; if (uri.getPort() == -1) { if ("http".equals(uri.getScheme())) { port = 80; } else { port = 443; } } else { port = uri.getPort(); } return new HttpProbeConfig( port, uri.getHost(), uri.getScheme(), uri.getPath(), singletonMap( HttpHeaders.AUTHORIZATION, "Bearer " + machineTokenProvider.getToken(userId, workspaceId)), successThreshold, 3, 120, 10, 10); } catch (MachineTokenException e) { throw new InternalInfrastructureException( "Failed to retrieve workspace token for ws-agent server liveness probe. Error: " + e.getMessage()); } catch (UriBuilderException e) { throw new InternalInfrastructureException( "Wsagent server liveness probe url is invalid. Error: " + e.getMessage()); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/JpaWorkspaceDao.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.jpa; import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import com.google.inject.persist.Transactional; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; import org.eclipse.che.api.workspace.shared.event.WorkspaceRemovedEvent; /** * JPA based implementation of {@link WorkspaceDao}. * * @author Yevhenii Voevodin */ @Singleton public class JpaWorkspaceDao implements WorkspaceDao { @Inject private EventService eventService; @Inject private Provider managerProvider; @Override public WorkspaceImpl create(WorkspaceImpl workspace) throws ConflictException, ServerException { requireNonNull(workspace, "Required non-null workspace"); try { doCreate(workspace); } catch (RuntimeException x) { throw new ServerException(x.getMessage(), x); } return new WorkspaceImpl(workspace); } @Override public WorkspaceImpl update(WorkspaceImpl update) throws NotFoundException, ConflictException, ServerException { requireNonNull(update, "Required non-null update"); try { return new WorkspaceImpl(doUpdate(update)); } catch (RuntimeException x) { throw new ServerException(x.getMessage(), x); } } @Override public Optional remove(String id) throws ServerException { requireNonNull(id, "Required non-null id"); Optional workspaceOpt; try { workspaceOpt = doRemove(id); workspaceOpt.ifPresent( workspace -> eventService.publish(new WorkspaceRemovedEvent(workspace))); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } return workspaceOpt; } @Override @Transactional public WorkspaceImpl get(String id) throws NotFoundException, ServerException { requireNonNull(id, "Required non-null id"); try { final WorkspaceImpl workspace = managerProvider.get().find(WorkspaceImpl.class, id); if (workspace == null) { throw new NotFoundException(format("Workspace with id '%s' doesn't exist", id)); } return new WorkspaceImpl(workspace); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public WorkspaceImpl get(String name, String namespace) throws NotFoundException, ServerException { requireNonNull(name, "Required non-null name"); requireNonNull(namespace, "Required non-null namespace"); try { return new WorkspaceImpl( managerProvider .get() .createNamedQuery("Workspace.getByName", WorkspaceImpl.class) .setParameter("namespace", namespace) .setParameter("name", name) .getSingleResult()); } catch (NoResultException noResEx) { throw new NotFoundException( format("Workspace with name '%s' in namespace '%s' doesn't exist", name, namespace)); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public Page getByNamespace(String namespace, int maxItems, long skipCount) throws ServerException { requireNonNull(namespace, "Required non-null namespace"); try { final EntityManager manager = managerProvider.get(); final List list = manager .createNamedQuery("Workspace.getByNamespace", WorkspaceImpl.class) .setParameter("namespace", namespace) .setMaxResults(maxItems) .setFirstResult((int) skipCount) .getResultList() .stream() .map(WorkspaceImpl::new) .collect(Collectors.toList()); final long count = manager .createNamedQuery("Workspace.getByNamespaceCount", Long.class) .setParameter("namespace", namespace) .getSingleResult(); return new Page<>(list, skipCount, maxItems, count); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public Page getWorkspaces(String userId, int maxItems, long skipCount) throws ServerException { try { final List list = managerProvider .get() .createNamedQuery("Workspace.getAll", WorkspaceImpl.class) .setMaxResults(maxItems) .setFirstResult((int) skipCount) .getResultList() .stream() .map(WorkspaceImpl::new) .collect(Collectors.toList()); final long count = managerProvider .get() .createNamedQuery("Workspace.getAllCount", Long.class) .getSingleResult(); return new Page<>(list, skipCount, maxItems, count); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Override @Transactional public Page getWorkspaces(boolean isTemporary, int maxItems, long skipCount) throws ServerException { checkArgument(maxItems >= 0, "The number of items to return can't be negative."); checkArgument( skipCount >= 0, "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE); try { final List list = managerProvider .get() .createNamedQuery("Workspace.getByTemporary", WorkspaceImpl.class) .setParameter("temporary", isTemporary) .setMaxResults(maxItems) .setFirstResult((int) skipCount) .getResultList() .stream() .map(WorkspaceImpl::new) .collect(toList()); final long count = managerProvider .get() .createNamedQuery("Workspace.getByTemporaryCount", Long.class) .setParameter("temporary", isTemporary) .getSingleResult(); return new Page<>(list, skipCount, maxItems, count); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } @Transactional protected void doCreate(WorkspaceImpl workspace) { if (workspace.getConfig() != null) { workspace.getConfig().getProjects().forEach(ProjectConfigImpl::prePersistAttributes); } EntityManager manager = managerProvider.get(); manager.persist(workspace); manager.flush(); } @Transactional(rollbackOn = {RuntimeException.class, ServerException.class}) protected Optional doRemove(String id) throws ServerException { final WorkspaceImpl workspace = managerProvider.get().find(WorkspaceImpl.class, id); if (workspace == null) { return Optional.empty(); } final EntityManager manager = managerProvider.get(); manager.remove(workspace); manager.flush(); return Optional.of(workspace); } @Transactional protected WorkspaceImpl doUpdate(WorkspaceImpl update) throws NotFoundException { EntityManager manager = managerProvider.get(); if (manager.find(WorkspaceImpl.class, update.getId()) == null) { throw new NotFoundException(format("Workspace with id '%s' doesn't exist", update.getId())); } if (update.getConfig() != null) { update.getConfig().getProjects().forEach(ProjectConfigImpl::prePersistAttributes); } WorkspaceImpl merged = manager.merge(update); manager.flush(); return merged; } @Override @Transactional public long getWorkspacesTotalCount() throws ServerException { try { return managerProvider .get() .createNamedQuery("Workspace.getWorkspacesTotalCount", Long.class) .getSingleResult(); } catch (RuntimeException x) { throw new ServerException(x.getLocalizedMessage(), x); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceJpaModule.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.jpa; import com.google.inject.AbstractModule; import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; /** * @author Yevhenii Voevodin */ public class WorkspaceJpaModule extends AbstractModule { @Override protected void configure() { bind(WorkspaceDao.class).to(JpaWorkspaceDao.class); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/CommandImpl.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.StringJoiner; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.devfile.PreviewUrl; import org.eclipse.che.api.workspace.server.model.impl.devfile.PreviewUrlImpl; /** * Data object for {@link Command}. * * @author Eugene Voevodin */ @Entity(name = "Command") @Table(name = "command") public class CommandImpl implements Command { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "name", nullable = false) private String name; @Column(name = "commandline", nullable = false, columnDefinition = "TEXT") private String commandLine; @Column(name = "type", nullable = false) private String type; @Embedded private PreviewUrlImpl previewUrl; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "command_attributes", joinColumns = @JoinColumn(name = "command_id")) @MapKeyColumn(name = "name") @Column(name = "value_param", columnDefinition = "TEXT") private Map attributes; public CommandImpl() {} public CommandImpl(String name, String commandLine, String type) { this.name = name; this.commandLine = commandLine; this.type = type; } public CommandImpl( String name, String commandLine, String type, PreviewUrlImpl previewUrl, Map attributes) { this.name = name; this.commandLine = commandLine; this.type = type; this.previewUrl = previewUrl; this.attributes = new HashMap<>(attributes); } public CommandImpl(Command command) { this.name = command.getName(); this.commandLine = command.getCommandLine(); this.type = command.getType(); if (command.getPreviewUrl() != null) { this.previewUrl = new PreviewUrlImpl(command.getPreviewUrl()); } this.attributes = new HashMap<>(command.getAttributes()); } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String getCommandLine() { return commandLine; } public void setCommandLine(String commandLine) { this.commandLine = commandLine; } public PreviewUrlImpl getPreviewUrl() { return previewUrl; } public void setPreviewUrl(PreviewUrl previewUrl) { if (previewUrl != null) { this.previewUrl = new PreviewUrlImpl(previewUrl); } else { this.previewUrl = null; } } @Override public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } CommandImpl command = (CommandImpl) o; return Objects.equals(id, command.id) && Objects.equals(name, command.name) && Objects.equals(commandLine, command.commandLine) && Objects.equals(type, command.type) && Objects.equals(previewUrl, command.previewUrl) && Objects.equals(attributes, command.attributes); } @Override public int hashCode() { return Objects.hash(id, name, commandLine, type, previewUrl, attributes); } @Override public String toString() { return new StringJoiner(", ", CommandImpl.class.getSimpleName() + "[", "]") .add("id=" + id) .add("name='" + name + "'") .add("commandLine='" + commandLine + "'") .add("type='" + type + "'") .add("previewUrl=" + previewUrl) .add("attributes=" + attributes) .toString(); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.OneToMany; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.config.Environment; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; import org.eclipse.che.api.core.model.workspace.config.Recipe; /** * Data object for {@link Environment}. * * @author Yevhenii Voevodin */ @Entity(name = "Environment") @Table(name = "environment") public class EnvironmentImpl implements Environment { @Id @GeneratedValue @Column(name = "id") private Long id; @Embedded private RecipeImpl recipe; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "machines_id") @MapKeyColumn(name = "machines_key") private Map machines; public EnvironmentImpl() {} public EnvironmentImpl(Recipe recipe, Map machines) { if (recipe != null) { this.recipe = new RecipeImpl(recipe); } if (machines != null) { this.machines = machines.entrySet().stream() .collect( Collectors.toMap( Map.Entry::getKey, entry -> new MachineConfigImpl(entry.getValue()))); } } public EnvironmentImpl(Environment environment) { this(environment.getRecipe(), environment.getMachines()); } @Override public RecipeImpl getRecipe() { return recipe; } public void setRecipe(RecipeImpl environmentRecipe) { this.recipe = environmentRecipe; } @Override public Map getMachines() { if (machines == null) { machines = new HashMap<>(); } return machines; } public void setMachines(Map machines) { this.machines = machines; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof EnvironmentImpl)) return false; EnvironmentImpl that = (EnvironmentImpl) o; return Objects.equals(id, that.id) && Objects.equals(getRecipe(), that.getRecipe()) && Objects.equals(getMachines(), that.getMachines()); } @Override public int hashCode() { return Objects.hash(id, getRecipe(), getMachines()); } @Override public String toString() { return "EnvironmentImpl{" + "id=" + id + ", recipe=" + recipe + ", machines=" + machines + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/MachineConfigImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.OneToMany; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.config.Volume; /** * @author Alexander Garagatyi */ @Entity(name = "ExternalMachine") @Table(name = "externalmachine") public class MachineConfigImpl implements MachineConfig { @Id @GeneratedValue @Column(name = "id") private Long id; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "externalmachine_attributes", joinColumns = @JoinColumn(name = "externalmachine_id")) @MapKeyColumn(name = "attributes_key") @Column(name = "attributes") private Map attributes; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "externalmachine_env", joinColumns = @JoinColumn(name = "externalmachine_id")) @MapKeyColumn(name = "env_key") @Column(name = "env_value") private Map env; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "servers_id") @MapKeyColumn(name = "servers_key") private Map servers; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "machine_id") @MapKeyColumn(name = "name") private Map volumes; public MachineConfigImpl() {} public MachineConfigImpl( Map servers, Map env, Map attributes, Map volumes) { if (servers != null) { this.servers = servers.entrySet().stream() .collect( Collectors.toMap( Map.Entry::getKey, entry -> new ServerConfigImpl(entry.getValue()))); } if (env != null) { this.env = new HashMap<>(env); } if (attributes != null) { this.attributes = new HashMap<>(attributes); } if (volumes != null) { this.volumes = volumes.entrySet().stream() .collect( Collectors.toMap(Map.Entry::getKey, entry -> new VolumeImpl(entry.getValue()))); } } public MachineConfigImpl(MachineConfig machine) { this(machine.getServers(), machine.getEnv(), machine.getAttributes(), machine.getVolumes()); } @Override public Map getServers() { if (servers == null) { servers = new HashMap<>(); } return servers; } public void setServers(Map servers) { this.servers = servers; } public MachineConfigImpl withServers(Map servers) { this.servers = servers; return this; } @Override public Map getEnv() { if (env == null) { env = new HashMap<>(); } return env; } public void setEnv(Map env) { this.env = env; } public MachineConfigImpl withEnv(Map env) { this.env = env; return this; } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } public MachineConfigImpl withAttributes(Map attributes) { this.attributes = attributes; return this; } @Override public Map getVolumes() { if (volumes == null) { volumes = new HashMap<>(); } return volumes; } public void setVolumes(Map volumes) { this.volumes = volumes; } public MachineConfigImpl withVolumes(Map volumes) { this.volumes = volumes; return this; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof MachineConfigImpl)) { return false; } final MachineConfigImpl that = (MachineConfigImpl) obj; return Objects.equals(id, that.id) && getEnv().equals(that.getEnv()) && getAttributes().equals(that.getAttributes()) && getServers().equals(that.getServers()) && getVolumes().equals(that.getVolumes()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + getEnv().hashCode(); hash = 31 * hash + getAttributes().hashCode(); hash = 31 * hash + getServers().hashCode(); hash = 31 * hash + getVolumes().hashCode(); return hash; } @Override public String toString() { return "MachineConfigImpl{" + "id=" + id + ", env=" + env + ", attributes=" + attributes + ", servers=" + servers + ", volumes=" + volumes + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/MachineImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import java.util.HashMap; import java.util.Map; import java.util.Objects; import org.eclipse.che.api.core.model.workspace.runtime.Machine; import org.eclipse.che.api.core.model.workspace.runtime.MachineStatus; import org.eclipse.che.api.core.model.workspace.runtime.Server; /** * Data object for {@link Machine}. * * @author Alexander Garagatyi */ public class MachineImpl implements Machine { private Map attributes; private Map servers; private MachineStatus status; public MachineImpl(Machine machineRuntime) { this(machineRuntime.getAttributes(), machineRuntime.getServers(), machineRuntime.getStatus()); } public MachineImpl( Map attributes, Map servers, MachineStatus status) { this(servers); this.attributes = new HashMap<>(attributes); this.status = status; } public MachineImpl(Map servers) { if (servers != null) { this.servers = servers.entrySet().stream() .collect( HashMap::new, (map, entry) -> map.put(entry.getKey(), new ServerImpl(entry.getValue())), HashMap::putAll); } } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } @Override public Map getServers() { if (servers == null) { servers = new HashMap<>(); } return servers; } @Override public MachineStatus getStatus() { return status; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof MachineImpl)) { return false; } MachineImpl machine = (MachineImpl) o; return Objects.equals(getAttributes(), machine.getAttributes()) && Objects.equals(getServers(), machine.getServers()) && getStatus() == machine.getStatus(); } @Override public int hashCode() { return Objects.hash(getAttributes(), getServers(), getStatus()); } @Override public String toString() { return "MachineImpl{" + "attributes=" + attributes + ", servers=" + servers + ", status=" + status + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ProjectConfigImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import static java.util.stream.Collectors.toMap; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKey; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.PostLoad; import javax.persistence.PostPersist; import javax.persistence.PostUpdate; import javax.persistence.PrePersist; import javax.persistence.PreUpdate; import javax.persistence.Table; import javax.persistence.Transient; import org.eclipse.che.api.core.model.workspace.config.ProjectConfig; import org.eclipse.che.api.core.model.workspace.config.SourceStorage; import org.eclipse.che.api.workspace.shared.ProjectProblemImpl; /** * Data object for {@link ProjectConfig}. * * @author Eugene Voevodin * @author Dmitry Shnurenko */ @Entity(name = "ProjectConfig") @Table(name = "projectconfig") public class ProjectConfigImpl implements ProjectConfig { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "path", nullable = false) private String path; @Column(name = "name") private String name; @Column(name = "type") private String type; @Column(name = "description", columnDefinition = "TEXT") private String description; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "source_id") private SourceStorageImpl source; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "projectconfig_mixins", joinColumns = @JoinColumn(name = "projectconfig_id")) @Column(name = "mixins") private List mixins; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "dbattributes_id") @MapKey(name = "name") private Map dbAttributes; // Mapping delegated to 'dbAttributes' field // as it is impossible to map nested list directly @Transient private Map> attributes; @Transient private List problems; public ProjectConfigImpl() {} public ProjectConfigImpl(ProjectConfig projectConfig) { name = projectConfig.getName(); path = projectConfig.getPath(); description = projectConfig.getDescription(); type = projectConfig.getType(); mixins = new ArrayList<>(projectConfig.getMixins()); attributes = projectConfig.getAttributes().entrySet().stream() .collect(toMap(Map.Entry::getKey, e -> new ArrayList<>(e.getValue()))); SourceStorage sourceStorage = projectConfig.getSource(); if (sourceStorage != null) { source = new SourceStorageImpl( sourceStorage.getType(), sourceStorage.getLocation(), sourceStorage.getParameters()); } if (projectConfig.getProblems() != null) { problems = projectConfig.getProblems().stream() .map(problem -> new ProjectProblemImpl(problem.getCode(), problem.getMessage())) .collect(Collectors.toList()); } else { problems = Collections.emptyList(); } } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String getPath() { return path; } public void setPath(String path) { this.path = path; } @Override public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } @Override public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public List getMixins() { if (mixins == null) { mixins = new ArrayList<>(); } return mixins; } public void setMixins(List mixins) { this.mixins = mixins; } @Override public Map> getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map> attributes) { this.attributes = attributes; } @Override public SourceStorageImpl getSource() { return source; } @Override public List getProblems() { return problems; } public void setSource(SourceStorageImpl sourceStorage) { this.source = sourceStorage; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof ProjectConfigImpl)) { return false; } final ProjectConfigImpl that = (ProjectConfigImpl) obj; return Objects.equals(id, that.id) && Objects.equals(path, that.path) && Objects.equals(name, that.name) && Objects.equals(type, that.type) && Objects.equals(description, that.description) && Objects.equals(source, that.source) && getMixins().equals(that.getMixins()) && getAttributes().equals(that.getAttributes()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(path); hash = 31 * hash + Objects.hashCode(name); hash = 31 * hash + Objects.hashCode(type); hash = 31 * hash + Objects.hashCode(description); hash = 31 * hash + Objects.hashCode(source); hash = 31 * hash + getMixins().hashCode(); hash = 31 * hash + getAttributes().hashCode(); return hash; } @Override public String toString() { return "ProjectConfigImpl{" + "id=" + id + ", path='" + path + '\'' + ", name='" + name + '\'' + ", type='" + type + '\'' + ", description='" + description + '\'' + ", source=" + source + ", mixins=" + mixins + ", attributes=" + attributes + '}'; } /** * Synchronizes instance attributes with db attributes, should be called by internal components in * needed places, this can't be done neither by {@link PrePersist} nor by {@link PreUpdate} as * when the entity is merged the transient attribute won't be passed to event handlers. */ public void prePersistAttributes() { if (dbAttributes == null) { dbAttributes = new HashMap<>(); } final Map dbAttrsCopy = new HashMap<>(dbAttributes); dbAttributes.clear(); for (Map.Entry> entry : getAttributes().entrySet()) { Attribute attribute = dbAttrsCopy.get(entry.getKey()); if (attribute == null) { attribute = new Attribute(entry.getKey(), entry.getValue()); } else if (!Objects.equals(attribute.values, entry.getValue())) { attribute.values = entry.getValue(); } dbAttributes.put(entry.getKey(), attribute); } } @PostLoad @PostUpdate @PostPersist private void postLoadAttributes() { if (dbAttributes != null) { attributes = dbAttributes.values().stream().collect(toMap(attr -> attr.name, attr -> attr.values)); } } @Entity(name = "ProjectAttribute") @Table(name = "projectattribute") private static class Attribute { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "name") private String name; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "projectattribute_values", joinColumns = @JoinColumn(name = "projectattribute_id")) @Column(name = "attribute_values") private List values; public Attribute() {} public Attribute(String name, List values) { this.name = name; this.values = values; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Attribute)) { return false; } final Attribute that = (Attribute) obj; return Objects.equals(id, that.id) && Objects.equals(name, that.name) && values.equals(that.values); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(name); hash = 31 * hash + values.hashCode(); return hash; } @Override public String toString() { return "Attribute{" + "values=" + values + ", name='" + name + '\'' + ", id=" + id + '}'; } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/RecipeImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Embeddable; import org.eclipse.che.api.core.model.workspace.config.Recipe; /** * @author Alexander Garagatyi */ @Embeddable public class RecipeImpl implements Recipe { @Column(name = "type") private String type; @Column(name = "contenttype") private String contentType; @Column(name = "content", columnDefinition = "TEXT") private String content; @Column(name = "location", columnDefinition = "TEXT") private String location; public RecipeImpl() {} public RecipeImpl(String type, String contentType, String content, String location) { this.type = type; this.contentType = contentType; this.content = content; this.location = location; } public RecipeImpl(Recipe recipe) { this.type = recipe.getType(); this.contentType = recipe.getContentType(); this.content = recipe.getContent(); this.location = recipe.getLocation(); } @Override public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public String getContentType() { return contentType; } public void setContentType(String contentType) { this.contentType = contentType; } @Override public String getContent() { return content; } public void setContent(String content) { this.content = content; } @Override public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof RecipeImpl)) return false; RecipeImpl that = (RecipeImpl) o; return Objects.equals(type, that.type) && Objects.equals(contentType, that.contentType) && Objects.equals(content, that.content) && Objects.equals(location, that.location); } @Override public int hashCode() { return Objects.hash(type, contentType, content, location); } @Override public String toString() { return "RecipeImpl{" + "type='" + type + '\'' + ", contentType='" + contentType + '\'' + ", content='" + content + '\'' + ", location='" + location + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/RuntimeIdentityImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import java.util.Objects; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; /** * @author gazarenkov */ public final class RuntimeIdentityImpl implements RuntimeIdentity { private final String workspaceId; private final String envName; private final String ownerId; private final String infrastructureNamespace; public RuntimeIdentityImpl(RuntimeIdentity id) { this(id.getWorkspaceId(), id.getEnvName(), id.getOwnerId(), id.getInfrastructureNamespace()); } public RuntimeIdentityImpl( String workspaceId, String envName, String ownerId, String infrastructureNamespace) { this.workspaceId = workspaceId; this.envName = envName; this.ownerId = ownerId; this.infrastructureNamespace = infrastructureNamespace; } @Override public String getWorkspaceId() { return workspaceId; } @Override public String getEnvName() { return envName; } @Override public String getOwnerId() { return ownerId; } @Override public String getInfrastructureNamespace() { return infrastructureNamespace; } @Override public int hashCode() { return Objects.hash(workspaceId, envName, ownerId, infrastructureNamespace); } @Override public boolean equals(Object obj) { if (!(obj instanceof RuntimeIdentityImpl)) { return false; } RuntimeIdentityImpl other = (RuntimeIdentityImpl) obj; return workspaceId.equals(other.workspaceId) && Objects.equals(envName, other.envName) && Objects.equals(ownerId, other.ownerId) && Objects.equals(infrastructureNamespace, other.infrastructureNamespace); } @Override public String toString() { return "RuntimeIdentityImpl{" + "workspaceId='" + workspaceId + '\'' + ", envName='" + envName + '\'' + ", ownerId='" + ownerId + '\'' + ", infrastructureNamespace='" + infrastructureNamespace + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/RuntimeImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import org.eclipse.che.api.core.model.workspace.Runtime; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.runtime.Machine; /** * Data object for {@link Runtime}. * * @author Yevhenii Voevodin */ public class RuntimeImpl implements Runtime { private final String activeEnv; private final String owner; private final Map machines; private List warnings; private List commands; public RuntimeImpl(String activeEnv, Map machines, String owner) { this.activeEnv = activeEnv; this.machines = machines; this.owner = owner; } public RuntimeImpl( String activeEnv, Map machines, String owner, List commands, List warnings) { this.activeEnv = activeEnv; this.machines = machines; this.owner = owner; if (commands != null) { this.commands = commands.stream().map(CommandImpl::new).collect(Collectors.toList()); } this.warnings = warnings.stream().map(WarningImpl::new).collect(Collectors.toList()); } public RuntimeImpl(Runtime runtime) { this.activeEnv = runtime.getActiveEnv(); this.machines = runtime.getMachines().entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> new MachineImpl(e.getValue()))); this.owner = runtime.getOwner(); this.warnings = runtime.getWarnings().stream().map(WarningImpl::new).collect(Collectors.toList()); this.commands = runtime.getCommands().stream().map(CommandImpl::new).collect(Collectors.toList()); } @Override public String getActiveEnv() { return activeEnv; } @Override public String getOwner() { return owner; } @Override public List getWarnings() { if (warnings == null) { warnings = new ArrayList<>(); } return warnings; } @Override public List getCommands() { if (commands == null) { commands = new ArrayList<>(); } return commands; } @Override public Map getMachines() { return machines; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof RuntimeImpl)) return false; RuntimeImpl that = (RuntimeImpl) o; return Objects.equals(activeEnv, that.activeEnv) && Objects.equals(machines, that.machines) && Objects.equals(owner, that.owner) && Objects.equals(commands, that.commands) && Objects.equals(warnings, that.warnings); } @Override public int hashCode() { return Objects.hash(activeEnv, machines, owner, commands, warnings); } @Override public String toString() { return "RuntimeImpl{" + "activeEnv='" + activeEnv + '\'' + ", owner='" + owner + '\'' + ", machines=" + machines + ", commands=" + commands + ", warnings=" + warnings + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ServerConfigImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.api.workspace.server.devfile.Constants.PUBLIC_ENDPOINT_ATTRIBUTE; import java.util.HashMap; import java.util.Map; import java.util.Objects; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.devfile.Endpoint; /** * @author Alexander Garagatyi */ @Entity(name = "ServerConf") @Table(name = "serverconf") public class ServerConfigImpl implements ServerConfig { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "port") private String port; @Column(name = "protocol") private String protocol; @Column(name = "path") private String path; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "serverconf_attributes", joinColumns = @JoinColumn(name = "serverconf_id")) @MapKeyColumn(name = "attributes_key") @Column(name = "attributes") private Map attributes; public ServerConfigImpl() {} public ServerConfigImpl( String port, String protocol, String path, Map attributes) { this.port = port; this.protocol = protocol; this.path = path; if (attributes != null) { this.attributes = new HashMap<>(attributes); } } public ServerConfigImpl(ServerConfig serverConf) { this( serverConf.getPort(), serverConf.getProtocol(), serverConf.getPath(), serverConf.getAttributes()); } @Override public String getPort() { return port; } public void setPort(String port) { this.port = port; } public ServerConfigImpl withPort(String port) { this.port = port; return this; } @Override public String getProtocol() { return protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } public ServerConfigImpl withProtocol(String protocol) { this.protocol = protocol; return this; } @Override public String getPath() { return path; } public void setPath(String path) { this.path = path; } public ServerConfigImpl withPath(String path) { this.path = path; return this; } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { if (attributes != null) { this.attributes = new HashMap<>(attributes); } else { this.attributes = new HashMap<>(); } } public ServerConfigImpl withAttributes(Map attributes) { setAttributes(attributes); return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ServerConfigImpl)) { return false; } ServerConfigImpl that = (ServerConfigImpl) o; return Objects.equals(id, that.id) && Objects.equals(getPort(), that.getPort()) && Objects.equals(getProtocol(), that.getProtocol()) && Objects.equals(getPath(), that.getPath()) && Objects.equals(getAttributes(), that.getAttributes()); } @Override public int hashCode() { return Objects.hash(id, getPort(), getProtocol(), getPath(), getAttributes()); } @Override public String toString() { return "ServerConfigImpl{" + "id=" + id + ", port='" + port + '\'' + ", protocol='" + protocol + '\'' + ", path='" + path + '\'' + ", attributes=" + attributes + '}'; } public static ServerConfigImpl createFromEndpoint(Endpoint endpoint) { HashMap attributes = new HashMap<>(endpoint.getAttributes()); attributes.put(SERVER_NAME_ATTRIBUTE, endpoint.getName()); String protocol = attributes.remove("protocol"); if (isNullOrEmpty(protocol)) { protocol = "http"; } String path = attributes.remove("path"); String isPublic = attributes.remove(PUBLIC_ENDPOINT_ATTRIBUTE); if ("false".equals(isPublic)) { ServerConfig.setInternal(attributes, true); } return new ServerConfigImpl(Integer.toString(endpoint.getPort()), protocol, path, attributes); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ServerImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import java.util.HashMap; import java.util.Map; import java.util.Objects; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; /** * @author gazarenkov */ public class ServerImpl implements Server { private String url; private ServerStatus status; private Map attributes; public ServerImpl() {} public ServerImpl(String url, ServerStatus status, Map attributes) { this.url = url; this.status = status; if (attributes != null) { this.attributes = new HashMap<>(attributes); } else { this.attributes = new HashMap<>(); } } public ServerImpl(Server server) { this.url = server.getUrl(); this.status = server.getStatus(); if (server.getAttributes() != null) { this.attributes = new HashMap<>(server.getAttributes()); } else { this.attributes = new HashMap<>(); } } @Override public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public ServerImpl withUrl(String url) { this.url = url; return this; } @Override public ServerStatus getStatus() { return this.status; } public void setStatus(ServerStatus status) { this.status = status; } public ServerImpl withStatus(ServerStatus status) { this.status = status; return this; } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { if (attributes != null) { this.attributes = attributes; } else { this.attributes = new HashMap<>(); } } public ServerImpl withAttributes(Map attributes) { setAttributes(attributes); return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ServerImpl)) { return false; } ServerImpl server = (ServerImpl) o; return Objects.equals(getUrl(), server.getUrl()) && getStatus() == server.getStatus() && Objects.equals(getAttributes(), server.getAttributes()); } @Override public int hashCode() { return Objects.hash(getUrl(), getStatus(), getAttributes()); } @Override public String toString() { return "ServerImpl{" + "url='" + url + '\'' + ", status=" + status + ", attributes=" + attributes + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/SourceStorageImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import static java.lang.String.format; import java.util.HashMap; import java.util.Map; import java.util.Objects; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.PrePersist; import javax.persistence.PreUpdate; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.config.SourceStorage; /** * Data object for {@link SourceStorage}. * * @author Yevhenii Voevodin */ @Entity(name = "SourceStorage") @Table(name = "sourcestorage") public class SourceStorageImpl implements SourceStorage { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "type") private String type; @Column(name = "location", columnDefinition = "TEXT") private String location; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "sourcestorage_parameters", joinColumns = @JoinColumn(name = "sourcestorage_id")) @MapKeyColumn(name = "parameters_key") @Column(name = "parameters") private Map parameters; public SourceStorageImpl() {} public SourceStorageImpl(String type, String location, Map parameters) { this.type = type; this.location = location; if (parameters != null) { this.parameters = new HashMap<>(parameters); } } @PrePersist @PreUpdate public void validate() { if (parameters != null) { for (Map.Entry e : parameters.entrySet()) { if (e.getValue() == null) { throw new IllegalStateException( format( "Parameter '%s' of the source %s is null. This is illegal.", e.getKey(), this)); } } } } @Override public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } @Override public Map getParameters() { if (parameters == null) { parameters = new HashMap<>(); } return parameters; } public void setParameters(Map parameters) { this.parameters = parameters; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof SourceStorageImpl)) { return false; } final SourceStorageImpl that = (SourceStorageImpl) obj; return Objects.equals(id, that.id) && Objects.equals(type, that.type) && Objects.equals(location, that.location) && getParameters().equals(that.getParameters()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(type); hash = 31 * hash + Objects.hashCode(location); hash = 31 * hash + getParameters().hashCode(); return hash; } @Override public String toString() { return "SourceStorageImpl{" + "id=" + id + ", type='" + type + '\'' + ", location='" + location + '\'' + ", parameters=" + parameters + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/VolumeImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.config.Volume; /** * @author Alexander Garagatyi */ @Entity(name = "MachineVolume") @Table(name = "machine_volume") public class VolumeImpl implements Volume { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "path") private String path; public VolumeImpl() {} public VolumeImpl(Volume value) { path = value.getPath(); } @Override public String getPath() { return path; } public void setPath(String path) { this.path = path; } public VolumeImpl withPath(String path) { this.path = path; return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof VolumeImpl)) { return false; } VolumeImpl volume = (VolumeImpl) o; return Objects.equals(id, volume.id) && Objects.equals(getPath(), volume.getPath()); } @Override public int hashCode() { return Objects.hash(id, getPath()); } @Override public String toString() { return "VolumeImpl{" + "id=" + id + ", path='" + path + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WarningImpl.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import java.util.Objects; import org.eclipse.che.api.core.model.workspace.Warning; /** * Data object for {@link Warning}. * * @author Yevhenii Voevodin */ public class WarningImpl implements Warning { private final int code; private final String message; public WarningImpl(int code, String message) { this.code = code; this.message = message; } public WarningImpl(Warning warning) { this.code = warning.getCode(); this.message = warning.getMessage(); } @Override public int getCode() { return code; } @Override public String getMessage() { return message; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof WarningImpl)) { return false; } final WarningImpl that = (WarningImpl) obj; return code == that.code && Objects.equals(message, that.message); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + code; hash = 31 * hash + Objects.hashCode(message); return hash; } @Override public String toString() { return "Warning{" + "code=" + code + ", message='" + message + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceConfigImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Transient; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.config.Environment; import org.eclipse.che.api.core.model.workspace.config.ProjectConfig; import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.commons.annotation.Nullable; /** * Data object for {@link WorkspaceConfig}. * * @author Alexander Garagatyi * @author Yevhenii Voevodin */ @Entity(name = "WorkspaceConfig") @Table(name = "workspaceconfig") public class WorkspaceConfigImpl implements WorkspaceConfig { public static WorkspaceConfigImplBuilder builder() { return new WorkspaceConfigImplBuilder(); } @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "name", nullable = false) private String name; @Column(name = "description", columnDefinition = "TEXT") private String description; @Column(name = "defaultenv", nullable = true) private String defaultEnv; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "commands_id") private List commands; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "projects_id") private List projects; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "environments_id") @MapKeyColumn(name = "environments_key") private Map environments; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "che_workspace_cfg_attributes", joinColumns = @JoinColumn(name = "workspace_id")) @MapKeyColumn(name = "attributes_key") @Column(name = "attributes") private Map attributes; // we do not store converted workspace configs, // so it's not needed to store devfile from which this workspace config is generated @Transient private DevfileImpl devfile; public WorkspaceConfigImpl() {} public WorkspaceConfigImpl( String name, String description, String defaultEnv, List commands, List projects, Map environments, Map attributes) { this(name, description, defaultEnv, commands, projects, environments, attributes, null); } public WorkspaceConfigImpl( String name, String description, String defaultEnv, List commands, List projects, Map environments, Map attributes, Devfile devfile) { this.name = name; this.defaultEnv = defaultEnv; this.description = description; if (environments != null) { this.environments = environments.entrySet().stream() .collect(toMap(Map.Entry::getKey, entry -> new EnvironmentImpl(entry.getValue()))); } if (commands != null) { this.commands = commands.stream().map(CommandImpl::new).collect(toList()); } if (projects != null) { this.projects = projects.stream().map(ProjectConfigImpl::new).collect(toList()); } if (attributes != null) { this.attributes = new HashMap<>(attributes); } if (devfile != null) { this.devfile = new DevfileImpl(devfile); } } public WorkspaceConfigImpl(WorkspaceConfig workspaceConfig) { this( workspaceConfig.getName(), workspaceConfig.getDescription(), workspaceConfig.getDefaultEnv(), workspaceConfig.getCommands(), workspaceConfig.getProjects(), workspaceConfig.getEnvironments(), workspaceConfig.getAttributes(), workspaceConfig.getDevfile()); } @Override public String getName() { return name; } public void setName(String name) { this.name = requireNonNull(name, "Non-null name required"); } @Override @Nullable public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } @Override public String getDefaultEnv() { return defaultEnv; } public void setDefaultEnv(String defaultEnv) { this.defaultEnv = defaultEnv; } @Override public List getCommands() { if (commands == null) { commands = new ArrayList<>(); } return commands; } public void setCommands(List commands) { this.commands = commands; } @Override public List getProjects() { if (projects == null) { projects = new ArrayList<>(); } return projects; } public void setProjects(List projects) { this.projects = projects; } @Override public Map getEnvironments() { if (environments == null) { environments = new HashMap<>(); } return environments; } public void setEnvironments(Map environments) { this.environments = environments; } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } public WorkspaceConfigImpl setDevfile(DevfileImpl devfile) { this.devfile = devfile; return this; } @Nullable @Override public DevfileImpl getDevfile() { return devfile; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof WorkspaceConfigImpl)) { return false; } final WorkspaceConfigImpl that = (WorkspaceConfigImpl) obj; return Objects.equals(id, that.id) && Objects.equals(name, that.name) && Objects.equals(description, that.description) && Objects.equals(defaultEnv, that.defaultEnv) && Objects.equals(devfile, that.devfile) && getCommands().equals(that.getCommands()) && getProjects().equals(that.getProjects()) && getEnvironments().equals(that.getEnvironments()) && getAttributes().equals(that.getAttributes()); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(name); hash = 31 * hash + Objects.hashCode(description); hash = 31 * hash + Objects.hashCode(defaultEnv); hash = 31 * hash + Objects.hashCode(devfile); hash = 31 * hash + getCommands().hashCode(); hash = 31 * hash + getProjects().hashCode(); hash = 31 * hash + getEnvironments().hashCode(); hash = 31 * hash + getAttributes().hashCode(); return hash; } @Override public String toString() { return "WorkspaceConfigImpl{" + "id=" + id + ", name='" + name + '\'' + ", description='" + description + '\'' + ", defaultEnv='" + defaultEnv + '\'' + ", commands=" + commands + ", projects=" + projects + ", environments=" + environments + ", attributes=" + attributes + ", devfile=" + devfile + '}'; } /** * Helps to build complex {@link WorkspaceConfigImpl users workspace instance}. * * @see WorkspaceConfigImpl#builder() */ public static class WorkspaceConfigImplBuilder { private String name; private String defaultEnvName; private String description; private List commands; private List projects; private Map environments; private Map attributes; private WorkspaceConfigImplBuilder() {} public WorkspaceConfigImpl build() { return new WorkspaceConfigImpl( name, description, defaultEnvName, commands, projects, environments, attributes); } public WorkspaceConfigImplBuilder fromConfig(WorkspaceConfig workspaceConfig) { this.name = workspaceConfig.getName(); this.description = workspaceConfig.getDescription(); this.defaultEnvName = workspaceConfig.getDefaultEnv(); this.projects = workspaceConfig.getProjects(); this.commands = workspaceConfig.getCommands(); this.environments = workspaceConfig.getEnvironments(); return this; } public WorkspaceConfigImplBuilder setName(String name) { this.name = name; return this; } public WorkspaceConfigImplBuilder setDefaultEnv(String defaultEnvName) { this.defaultEnvName = defaultEnvName; return this; } public WorkspaceConfigImplBuilder setCommands(List commands) { this.commands = commands; return this; } public WorkspaceConfigImplBuilder setProjects(List projects) { this.projects = projects; return this; } public WorkspaceConfigImplBuilder setEnvironments( Map environments) { this.environments = environments; return this; } public WorkspaceConfigImplBuilder setDescription(String description) { this.description = description; return this; } public WorkspaceConfigImplBuilder setAttributes(Map attributes) { this.attributes = attributes; return this; } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import java.util.HashMap; import java.util.Map; import java.util.Objects; import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.EntityListeners; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.MapKeyColumn; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToOne; import javax.persistence.PrePersist; import javax.persistence.PreUpdate; import javax.persistence.Table; import javax.persistence.Transient; import org.eclipse.che.account.shared.model.Account; import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.core.model.workspace.Runtime; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.persistence.descriptors.DescriptorEvent; import org.eclipse.persistence.descriptors.DescriptorEventAdapter; /** * Data object for {@link Workspace}. * * @author Yevhenii Voevodin */ @Entity(name = "Workspace") @Table(name = "workspace") @NamedQueries({ @NamedQuery( name = "Workspace.getByNamespace", query = "SELECT w FROM Workspace w WHERE w.account.name = :namespace"), @NamedQuery( name = "Workspace.getByName", query = "SELECT w FROM Workspace w WHERE w.account.name = :namespace AND w.name = :name"), @NamedQuery(name = "Workspace.getAll", query = "SELECT w FROM Workspace w"), @NamedQuery( name = "Workspace.getByTemporary", query = "SELECT w " + "FROM Workspace w " + "WHERE w.isTemporary = :temporary "), @NamedQuery(name = "Workspace.getAllCount", query = "SELECT COUNT(w) FROM Workspace w"), @NamedQuery( name = "Workspace.getByNamespaceCount", query = "SELECT COUNT(w) " + "FROM Workspace w " + "WHERE w.account.name = :namespace "), @NamedQuery( name = "Workspace.getWorkspacesTotalCount", query = "SELECT COUNT(w) FROM Workspace w"), @NamedQuery( name = "Workspace.getByTemporaryCount", query = "SELECT COUNT(w) " + "FROM Workspace w " + "WHERE w.isTemporary = :temporary ") }) @EntityListeners(WorkspaceImpl.SyncNameOnUpdateAndPersistEventListener.class) public class WorkspaceImpl implements Workspace { public static WorkspaceImplBuilder builder() { return new WorkspaceImplBuilder(); } @Id @Column(name = "id") private String id; /** * The original workspace name is workspace.config.name this attribute is stored for unique * constraint with account id. See {@link #syncName()}. */ @Column(name = "name") private String name; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "config_id") private WorkspaceConfigImpl config; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "devfile_id") private DevfileImpl devfile; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "workspace_attributes", joinColumns = @JoinColumn(name = "workspace_id")) @MapKeyColumn(name = "attributes_key") @Column(name = "attributes") private Map attributes; @Column(name = "istemporary") private boolean isTemporary; @ManyToOne @JoinColumn(name = "accountid", nullable = false) private AccountImpl account; @Transient private WorkspaceStatus status; @Transient private Runtime runtime; public WorkspaceImpl() {} public WorkspaceImpl(String id, Account account, WorkspaceConfig config) { this(id, account, config, null, null, false, null); } public WorkspaceImpl(String id, Account account, Devfile devfile) { this(id, account, devfile, null, null, false, null); } public WorkspaceImpl( String id, Account account, WorkspaceConfig config, Runtime runtime, Map attributes, boolean isTemporary, WorkspaceStatus status) { this(id, account, config, null, runtime, attributes, isTemporary, status); } public WorkspaceImpl( String id, Account account, Devfile devfile, Runtime runtime, Map attributes, boolean isTemporary, WorkspaceStatus status) { this(id, account, null, devfile, runtime, attributes, isTemporary, status); } public WorkspaceImpl( String id, Account account, WorkspaceConfig config, Devfile devfile, Runtime runtime, Map attributes, boolean isTemporary, WorkspaceStatus status) { this.id = id; if (account != null) { this.account = new AccountImpl(account); } if (config != null && devfile != null) { throw new IllegalArgumentException("Only config or devfile must be specified."); } if (config != null) { this.config = new WorkspaceConfigImpl(config); } if (devfile != null) { this.devfile = new DevfileImpl(devfile); } if (runtime != null) { this.runtime = new RuntimeImpl( runtime.getActiveEnv(), runtime.getMachines(), runtime.getOwner(), runtime.getCommands(), runtime.getWarnings()); } if (attributes != null) { this.attributes = new HashMap<>(attributes); } this.isTemporary = isTemporary; this.status = status; } public WorkspaceImpl(Workspace workspace, Account account) { this( workspace.getId(), account, workspace.getConfig(), workspace.getDevfile(), workspace.getRuntime(), workspace.getAttributes(), workspace.isTemporary(), workspace.getStatus()); } public WorkspaceImpl(WorkspaceImpl workspace) { this(workspace, workspace.account); } @Override public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String getNamespace() { if (account != null) { return account.getName(); } return null; } /** Returns the name of workspace. It can be stored by workspace config or devfile. */ public String getName() { if (devfile != null) { return devfile.getMetadata().getName(); } else if (config != null) { return config.getName(); } else { return null; } } public void setAccount(AccountImpl account) { this.account = account; } public AccountImpl getAccount() { return account; } @Nullable @Override public WorkspaceConfigImpl getConfig() { return config; } public void setConfig(WorkspaceConfigImpl config) { this.config = config; } @Nullable @Override public DevfileImpl getDevfile() { return devfile; } public WorkspaceImpl setDevfile(DevfileImpl devfile) { this.devfile = devfile; return this; } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } @Override public boolean isTemporary() { return isTemporary; } public void setTemporary(boolean temporary) { isTemporary = temporary; } @Override public WorkspaceStatus getStatus() { return status; } public void setStatus(WorkspaceStatus status) { this.status = status; } @Override public Runtime getRuntime() { return runtime; } public void setRuntime(Runtime runtime) { this.runtime = runtime; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof WorkspaceImpl)) return false; final WorkspaceImpl other = (WorkspaceImpl) obj; return Objects.equals(id, other.id) && Objects.equals(getNamespace(), other.getNamespace()) && Objects.equals(status, other.status) && isTemporary == other.isTemporary && getAttributes().equals(other.getAttributes()) && Objects.equals(config, other.config) && Objects.equals(devfile, other.devfile) && Objects.equals(runtime, other.runtime); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(getNamespace()); hash = 31 * hash + Objects.hashCode(status); hash = 31 * hash + Objects.hashCode(config); hash = 31 * hash + Objects.hashCode(devfile); hash = 31 * hash + getAttributes().hashCode(); hash = 31 * hash + Boolean.hashCode(isTemporary); hash = 31 * hash + Objects.hashCode(runtime); return hash; } @Override public String toString() { return "WorkspaceImpl{" + "id='" + id + '\'' + ", namespace='" + getNamespace() + '\'' + ", name='" + name + '\'' + ", config=" + config + ", devfile=" + devfile + ", isTemporary=" + isTemporary + ", status=" + status + ", attributes=" + attributes + ", runtime=" + runtime + '}'; } /** Syncs {@link #name} with config name or devfile name. */ private void syncName() { name = getName(); } /** * {@link PreUpdate} and {@link PrePersist} methods are not called when the configuration is * updated and workspace object is not, while listener methods are always called, even if * workspace instance is not changed. */ public static class SyncNameOnUpdateAndPersistEventListener extends DescriptorEventAdapter { @Override public void preUpdate(DescriptorEvent event) { ((WorkspaceImpl) event.getObject()).syncName(); } @Override public void prePersist(DescriptorEvent event) { ((WorkspaceImpl) event.getObject()).syncName(); } @Override public void preUpdateWithChanges(DescriptorEvent event) { ((WorkspaceImpl) event.getObject()).syncName(); } } /** * Helps to build complex {@link WorkspaceImpl workspace} instance. * * @see WorkspaceImpl#builder() */ public static class WorkspaceImplBuilder { private String id; private Account account; private boolean isTemporary; private WorkspaceStatus status; private WorkspaceConfig config; private Devfile devfile; private Runtime runtime; private Map attributes; public WorkspaceImpl build() { return new WorkspaceImpl( id, account, config, devfile, runtime, attributes, isTemporary, status); } public WorkspaceImplBuilder generateId() { id = NameGenerator.generate("workspace", 16); return this; } public WorkspaceImplBuilder setConfig(WorkspaceConfig workspaceConfig) { this.config = workspaceConfig; return this; } public WorkspaceImplBuilder setDevfile(Devfile devfile) { this.devfile = devfile; return this; } public WorkspaceImplBuilder setId(String id) { this.id = id; return this; } public WorkspaceImplBuilder setAccount(Account account) { this.account = account; return this; } public WorkspaceImplBuilder setTemporary(boolean isTemporary) { this.isTemporary = isTemporary; return this; } public WorkspaceImplBuilder setStatus(WorkspaceStatus status) { this.status = status; return this; } public WorkspaceImplBuilder setAttributes(Map attributes) { this.attributes = attributes; return this; } public WorkspaceImplBuilder setRuntime(Runtime runtime) { this.runtime = runtime; return this; } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/ActionImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.devfile.Action; /** * @author Sergii Leshchenko */ @Entity(name = "DevfileAction") @Table(name = "devfile_action") public class ActionImpl implements Action { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "type", nullable = false) private String type; @Column(name = "component", nullable = false) private String component; @Column(name = "command", nullable = false) private String command; @Column(name = "workdir") private String workdir; @Column(name = "reference") private String reference; @Column(name = "reference_content") private String referenceContent; public ActionImpl() {} public ActionImpl( String type, String component, String command, String workdir, String reference, String referenceContent) { this.type = type; this.component = component; this.command = command; this.workdir = workdir; this.reference = reference; this.referenceContent = referenceContent; } public ActionImpl(Action action) { this( action.getType(), action.getComponent(), action.getCommand(), action.getWorkdir(), action.getReference(), action.getReferenceContent()); } @Override public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public String getComponent() { return component; } public void setComponent(String component) { this.component = component; } @Override public String getCommand() { return command; } public void setCommand(String command) { this.command = command; } @Override public String getWorkdir() { return workdir; } public void setWorkdir(String workdir) { this.workdir = workdir; } @Override public String getReference() { return reference; } public void setReference(String reference) { this.reference = reference; } @Override public String getReferenceContent() { return referenceContent; } public void setReferenceContent(String referenceContent) { this.referenceContent = referenceContent; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ActionImpl)) { return false; } ActionImpl action = (ActionImpl) o; return Objects.equals(id, action.id) && Objects.equals(type, action.type) && Objects.equals(component, action.component) && Objects.equals(command, action.command) && Objects.equals(workdir, action.workdir); } @Override public int hashCode() { return Objects.hash(id, type, component, command, workdir); } @Override public String toString() { return "ActionImpl{" + "id='" + id + '\'' + ", type='" + type + '\'' + ", component='" + component + '\'' + ", command='" + command + '\'' + ", workdir='" + workdir + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/CommandImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import static java.util.stream.Collectors.toCollection; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.StringJoiner; import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.OneToMany; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.devfile.Action; import org.eclipse.che.api.core.model.workspace.devfile.Command; import org.eclipse.che.api.core.model.workspace.devfile.PreviewUrl; /** * @author Sergii Leshchenko */ @Entity(name = "DevfileCommand") @Table(name = "devfile_command") public class CommandImpl implements Command { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "name", nullable = false) private String name; @Embedded private PreviewUrlImpl previewUrl; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "devfile_command_id") private List actions; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "devfile_command_attributes", joinColumns = @JoinColumn(name = "devfile_command_id")) @MapKeyColumn(name = "name") @Column(name = "value_param", columnDefinition = "TEXT") private Map attributes; public CommandImpl() {} public CommandImpl( String name, List actions, Map attributes, PreviewUrl previewUrl) { this.name = name; if (actions != null) { this.actions = actions.stream().map(ActionImpl::new).collect(toCollection(ArrayList::new)); } if (attributes != null) { this.attributes = new HashMap<>(attributes); } if (previewUrl != null) { this.previewUrl = new PreviewUrlImpl(previewUrl.getPort(), previewUrl.getPath()); } } public CommandImpl(Command command) { this(command.getName(), command.getActions(), command.getAttributes(), command.getPreviewUrl()); } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public PreviewUrlImpl getPreviewUrl() { return previewUrl; } public void setPreviewUrl(PreviewUrlImpl previewUrl) { this.previewUrl = previewUrl; } @Override public List getActions() { if (actions == null) { actions = new ArrayList<>(); } return actions; } public void setActions(List actions) { this.actions = actions; } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } @Override public String toString() { return new StringJoiner(", ", CommandImpl.class.getSimpleName() + "[", "]") .add("id=" + id) .add("name='" + name + "'") .add("previewURL=" + previewUrl) .add("actions=" + actions) .add("attributes=" + attributes) .toString(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } CommandImpl command = (CommandImpl) o; return Objects.equals(id, command.id) && Objects.equals(name, command.name) && Objects.equals(previewUrl, command.previewUrl) && Objects.equals(actions, command.actions) && Objects.equals(attributes, command.attributes); } @Override public int hashCode() { return Objects.hash(id, name, previewUrl, actions, attributes); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/ComponentImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import static java.util.stream.Collectors.toCollection; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.Convert; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.OneToMany; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.core.model.workspace.devfile.Endpoint; import org.eclipse.che.api.core.model.workspace.devfile.Entrypoint; import org.eclipse.che.api.core.model.workspace.devfile.Env; import org.eclipse.che.api.core.model.workspace.devfile.Volume; import org.eclipse.che.api.workspace.server.devfile.PreferencesDeserializer; import org.eclipse.che.api.workspace.server.devfile.SerializableConverter; /** * @author Sergii Leshchenko */ @Entity(name = "DevfileComponent") @Table(name = "devfile_component") public class ComponentImpl implements Component { @Id @GeneratedValue @Column(name = "id") private Long generatedId; @Column(name = "component_id", nullable = false) private String componentId; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "devfile_component_preferences", joinColumns = @JoinColumn(name = "devfile_component_id")) @MapKeyColumn(name = "preference_key") @Convert(converter = SerializableConverter.class) @Column(name = "preference") @JsonDeserialize(using = PreferencesDeserializer.class) private Map preferences; @Column(name = "alias") private String alias; @Column(name = "type", nullable = false) private String type; @Column(name = "registry_url") private String registryUrl; @Column(name = "reference") private String reference; @Column(name = "reference_content") private String referenceContent; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "devfile_component_selector", joinColumns = @JoinColumn(name = "devfile_component_id")) @MapKeyColumn(name = "selector_key") @Column(name = "selector") private Map selector; @Column(name = "image") private String image; @Column(name = "memory_limit") private String memoryLimit; @Column(name = "memory_request") private String memoryRequest; @Column(name = "cpu_limit") private String cpuLimit; @Column(name = "cpu_request") private String cpuRequest; @Column(name = "mount_sources") private Boolean mountSources; @Column(name = "automount_secrets") private Boolean automountWorkspaceSecrets; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "devfile_component_command", joinColumns = @JoinColumn(name = "devfile_component_id")) @Column(name = "command") private List command; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "devfile_component_arg", joinColumns = @JoinColumn(name = "devfile_component_id")) @Column(name = "args") private List args; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "devfile_component_id") private List entrypoints; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "devfile_component_id") private List volumes; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "devfile_component_id") private List env; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "devfile_component_id") private List endpoints; public ComponentImpl() {} public ComponentImpl(String type, String id) { this.type = type; this.componentId = id; } public ComponentImpl(String type, String id, Map preferences) { this.type = type; this.componentId = id; if (preferences != null) { this.preferences = new HashMap<>(preferences); } } public ComponentImpl( String type, String id, String reference, String referenceContent, Map selector, List entrypoints) { this.type = type; this.componentId = id; this.reference = reference; this.referenceContent = referenceContent; if (selector != null) { this.selector = new HashMap<>(selector); } if (entrypoints != null) { this.entrypoints = entrypoints.stream().map(EntrypointImpl::new).collect(toCollection(ArrayList::new)); } } public ComponentImpl( String type, String alias, String id, Map preferences, String registryUrl, String reference, String referenceContent, Map selector, List entrypoints, String image, String memoryLimit, String memoryRequest, String cpuLimit, String cpuRequest, Boolean mountSources, Boolean automountWorkspaceSecrets, List command, List args, List volumes, List env, List endpoints) { this.alias = alias; this.type = type; this.componentId = id; this.registryUrl = registryUrl; if (preferences != null) { this.preferences = new HashMap<>(preferences); } this.reference = reference; this.referenceContent = referenceContent; if (selector != null) { this.selector = new HashMap<>(selector); } if (entrypoints != null) { this.entrypoints = entrypoints.stream().map(EntrypointImpl::new).collect(toCollection(ArrayList::new)); } this.image = image; this.memoryLimit = memoryLimit; this.memoryRequest = memoryRequest; this.cpuLimit = cpuLimit; this.cpuRequest = cpuRequest; this.mountSources = mountSources; this.automountWorkspaceSecrets = automountWorkspaceSecrets; this.command = command; this.args = args; if (volumes != null) { this.volumes = volumes.stream().map(VolumeImpl::new).collect(toCollection(ArrayList::new)); } if (env != null) { this.env = env.stream().map(EnvImpl::new).collect(toCollection(ArrayList::new)); } if (endpoints != null) { this.endpoints = endpoints.stream().map(EndpointImpl::new).collect(toCollection(ArrayList::new)); } } public ComponentImpl(Component component) { this( component.getType(), component.getAlias(), component.getId(), component.getPreferences(), component.getRegistryUrl(), component.getReference(), component.getReferenceContent(), component.getSelector(), component.getEntrypoints(), component.getImage(), component.getMemoryLimit(), component.getMemoryRequest(), component.getCpuLimit(), component.getCpuRequest(), component.getMountSources(), component.getAutomountWorkspaceSecrets(), component.getCommand(), component.getArgs(), component.getVolumes(), component.getEnv(), component.getEndpoints()); } @Override public String getAlias() { return alias; } public void setAlias(String alias) { this.alias = alias; } @Override public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public String getId() { return componentId; } public void setId(String id) { this.componentId = id; } @Override public String getRegistryUrl() { return registryUrl; } public void setRegistryUrl(String registryUrl) { this.registryUrl = registryUrl; } public Map getPreferences() { if (preferences == null) { preferences = new HashMap<>(); } return preferences; } public void setPreferences(Map preferences) { this.preferences = preferences; } @Override public String getReference() { return reference; } public void setReference(String reference) { this.reference = reference; } @Override public String getReferenceContent() { return referenceContent; } public void setReferenceContent(String referenceContent) { this.referenceContent = referenceContent; } @Override public Map getSelector() { if (selector == null) { selector = new HashMap<>(); } return selector; } public void setSelector(Map selector) { this.selector = selector; } @Override public List getEntrypoints() { if (entrypoints == null) { entrypoints = new ArrayList<>(); } return entrypoints; } public void setEntrypoints(List entrypoints) { this.entrypoints = entrypoints; } @Override public String getImage() { return image; } public void setImage(String image) { this.image = image; } @Override public String getMemoryLimit() { return memoryLimit; } public void setMemoryLimit(String memoryLimit) { this.memoryLimit = memoryLimit; } @Override public String getMemoryRequest() { return memoryRequest; } public void setMemoryRequest(String memoryRequest) { this.memoryRequest = memoryRequest; } @Override public String getCpuLimit() { return cpuLimit; } public void setCpuLimit(String cpuLimit) { this.cpuLimit = cpuLimit; } @Override public String getCpuRequest() { return cpuRequest; } public void setCpuRequest(String cpuRequest) { this.cpuRequest = cpuRequest; } @Override public Boolean getMountSources() { return mountSources; } public void setMountSources(Boolean mountSources) { this.mountSources = mountSources; } @Override public Boolean getAutomountWorkspaceSecrets() { return automountWorkspaceSecrets; } public void setAutomountWorkspaceSecrets(Boolean automountWorkspaceSecrets) { this.automountWorkspaceSecrets = automountWorkspaceSecrets; } @Override public List getCommand() { if (command == null) { command = new ArrayList<>(); } return command; } public void setCommand(List command) { this.command = command; } @Override public List getArgs() { if (args == null) { args = new ArrayList<>(); } return args; } public void setArgs(List args) { this.args = args; } @Override public List getVolumes() { if (volumes == null) { volumes = new ArrayList<>(); } return volumes; } public void setVolumes(List volumes) { this.volumes = volumes; } @Override public List getEnv() { if (env == null) { env = new ArrayList<>(); } return env; } public void setEnv(List env) { this.env = env; } @Override public List getEndpoints() { if (endpoints == null) { endpoints = new ArrayList<>(); } return endpoints; } public void setEndpoints(List endpoints) { this.endpoints = endpoints; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ComponentImpl)) { return false; } ComponentImpl component = (ComponentImpl) o; return Objects.equals(getMountSources(), component.getMountSources()) && Objects.equals(getAutomountWorkspaceSecrets(), component.getAutomountWorkspaceSecrets()) && Objects.equals(generatedId, component.generatedId) && Objects.equals(alias, component.alias) && Objects.equals(type, component.type) && Objects.equals(componentId, component.componentId) && Objects.equals(registryUrl, component.registryUrl) && Objects.equals(reference, component.reference) && Objects.equals(referenceContent, component.referenceContent) && Objects.equals(image, component.image) && Objects.equals(memoryLimit, component.memoryLimit) && Objects.equals(getPreferences(), component.getPreferences()) && Objects.equals(getSelector(), component.getSelector()) && Objects.equals(getEntrypoints(), component.getEntrypoints()) && Objects.equals(getCommand(), component.getCommand()) && Objects.equals(getArgs(), component.getArgs()) && Objects.equals(getVolumes(), component.getVolumes()) && Objects.equals(getEnv(), component.getEnv()) && Objects.equals(getEndpoints(), component.getEndpoints()); } @Override public int hashCode() { return Objects.hash( generatedId, alias, type, componentId, registryUrl, reference, referenceContent, image, memoryLimit, getPreferences(), getSelector(), getEntrypoints(), getMountSources(), getAutomountWorkspaceSecrets(), getCommand(), getArgs(), getVolumes(), getEnv(), getEndpoints()); } @Override public String toString() { return "ComponentImpl{" + "id='" + componentId + '\'' + ", alias='" + alias + '\'' + ", type='" + type + '\'' + ", preferences=" + preferences + ", registryUrl='" + registryUrl + '\'' + ", reference='" + reference + '\'' + ", referenceContent='" + referenceContent + '\'' + ", selector=" + selector + ", entrypoints=" + entrypoints + ", image='" + image + '\'' + ", memoryLimit='" + memoryLimit + '\'' + ", mountSources=" + mountSources + ", automountWorkspaceSecrets=" + automountWorkspaceSecrets + ", command=" + command + ", args=" + args + ", volumes=" + volumes + ", env=" + env + ", endpoints=" + endpoints + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/DevfileImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import static java.util.stream.Collectors.toCollection; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.OneToMany; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.devfile.Command; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.api.core.model.workspace.devfile.Metadata; import org.eclipse.che.api.core.model.workspace.devfile.Project; /** * @author Sergii Leshchenko */ @Entity(name = "Devfile") @Table(name = "devfile") public class DevfileImpl implements Devfile { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "api_version", nullable = false) private String apiVersion; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "devfile_id") private List projects; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "devfile_id") private List components; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "devfile_id") private List commands; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "devfile_attributes", joinColumns = @JoinColumn(name = "devfile_id")) @MapKeyColumn(name = "name") @Column(name = "value_param", columnDefinition = "TEXT") private Map attributes; @Embedded private MetadataImpl metadata; public DevfileImpl() {} public DevfileImpl( String apiVersion, List projects, List components, List commands, Map attributes, Metadata metadata) { this.apiVersion = apiVersion; if (projects != null) { this.projects = projects.stream().map(ProjectImpl::new).collect(toCollection(ArrayList::new)); } if (components != null) { this.components = components.stream().map(ComponentImpl::new).collect(toCollection(ArrayList::new)); } if (commands != null) { this.commands = commands.stream().map(CommandImpl::new).collect(toCollection(ArrayList::new)); } if (attributes != null) { this.attributes = new HashMap<>(attributes); } if (metadata != null) { this.metadata = new MetadataImpl(metadata); } } public DevfileImpl(Devfile devfile) { this( devfile.getApiVersion(), devfile.getProjects(), devfile.getComponents(), devfile.getCommands(), devfile.getAttributes(), devfile.getMetadata()); } @Override public String getApiVersion() { return apiVersion; } public void setApiVersion(String apiVersion) { this.apiVersion = apiVersion; } public void setName(String name) { getMetadata().setName(name); } @Override public List getProjects() { if (projects == null) { projects = new ArrayList<>(); } return projects; } public void setProjects(List projects) { this.projects = projects; } @Override public List getComponents() { if (components == null) { components = new ArrayList<>(); } return components; } public void setComponents(List components) { this.components = components; } @Override public List getCommands() { if (commands == null) { commands = new ArrayList<>(); } return commands; } public void setCommands(List commands) { this.commands = commands; } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } @Override public MetadataImpl getMetadata() { if (metadata == null) { metadata = new MetadataImpl(); } return metadata; } public void setMetadata(MetadataImpl metadata) { this.metadata = metadata; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof DevfileImpl)) { return false; } DevfileImpl devfile = (DevfileImpl) o; return Objects.equals(id, devfile.id) && Objects.equals(apiVersion, devfile.apiVersion) && Objects.equals(getProjects(), devfile.getProjects()) && Objects.equals(getComponents(), devfile.getComponents()) && Objects.equals(getCommands(), devfile.getCommands()) && Objects.equals(getAttributes(), devfile.getAttributes()) && Objects.equals(getMetadata(), devfile.getMetadata()); } @Override public int hashCode() { return Objects.hash( id, apiVersion, getProjects(), getComponents(), getCommands(), getAttributes(), getMetadata()); } @Override public String toString() { return "DevfileImpl{" + "id='" + id + '\'' + ", apiVersion='" + apiVersion + '\'' + ", projects=" + projects + ", components=" + components + ", commands=" + commands + ", attributes=" + attributes + ", metadata=" + metadata + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/EndpointImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import java.util.HashMap; import java.util.Map; import java.util.Objects; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.devfile.Endpoint; /** * @author Sergii Leshchenko */ @Entity(name = "DevfileEndpoint") @Table(name = "devfile_endpoint") public class EndpointImpl implements Endpoint { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "name", nullable = false) private String name; @Column(name = "port", nullable = false) private Integer port; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "devfile_endpoint_attributes", joinColumns = @JoinColumn(name = "devfile_endpoint_id")) @MapKeyColumn(name = "name") @Column(name = "value_param", columnDefinition = "TEXT") private Map attributes; public EndpointImpl() {} public EndpointImpl(String name, Integer port, Map attributes) { this.name = name; this.port = port; this.attributes = attributes; } public EndpointImpl(Endpoint endpoint) { this(endpoint.getName(), endpoint.getPort(), endpoint.getAttributes()); } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } @Override public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof EndpointImpl)) { return false; } EndpointImpl endpoint = (EndpointImpl) o; return Objects.equals(id, endpoint.id) && Objects.equals(name, endpoint.name) && Objects.equals(port, endpoint.port) && Objects.equals(getAttributes(), endpoint.getAttributes()); } @Override public int hashCode() { return Objects.hash(id, name, port, getAttributes()); } @Override public String toString() { return "EndpointImpl{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", port=" + port + ", attributes=" + attributes + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/EntrypointImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.devfile.Entrypoint; /** * @author Sergii Leshchenko */ @Entity(name = "DevfileEntrypoint") @Table(name = "devfile_entrypoint") public class EntrypointImpl implements Entrypoint { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "parent_name", nullable = false) private String parentName; @Column(name = "container_name", nullable = false) private String containerName; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "devfile_entrypoint_commands", joinColumns = @JoinColumn(name = "devfile_entrypoint_id")) @Column(name = "command") private List command; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "devfile_entrypoint_arg", joinColumns = @JoinColumn(name = "devfile_entrypoint_id")) @Column(name = "arg") private List args; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "devfile_entrypoint_selector", joinColumns = @JoinColumn(name = "devfile_entrypoint_id")) @MapKeyColumn(name = "selector_key") @Column(name = "selector") private Map parentSelector; public EntrypointImpl() {} public EntrypointImpl( String parentName, Map parentSelector, String containerName, List command, List args) { this.parentName = parentName; this.parentSelector = parentSelector; this.containerName = containerName; this.command = command; this.args = args; } public EntrypointImpl(Entrypoint entrypoint) { this( entrypoint.getParentName(), entrypoint.getParentSelector(), entrypoint.getContainerName(), entrypoint.getCommand(), entrypoint.getArgs()); } @Override public String getParentName() { return parentName; } public void setParentName(String parentName) { this.parentName = parentName; } @Override public Map getParentSelector() { if (parentSelector == null) { parentSelector = new HashMap<>(); } return parentSelector; } public void setParentSelector(Map parentSelector) { this.parentSelector = parentSelector; } @Override public String getContainerName() { return containerName; } public void setContainerName(String containerName) { this.containerName = containerName; } @Override public List getCommand() { if (command == null) { command = new ArrayList<>(); } return command; } public void setCommand(List command) { this.command = command; } @Override public List getArgs() { if (args == null) { args = new ArrayList<>(); } return args; } public void setArgs(List args) { this.args = args; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof EntrypointImpl)) { return false; } EntrypointImpl that = (EntrypointImpl) o; return Objects.equals(parentName, that.parentName) && Objects.equals(containerName, that.containerName) && Objects.equals(getParentSelector(), that.getParentSelector()) && Objects.equals(getCommand(), that.getCommand()) && Objects.equals(getArgs(), that.getArgs()); } @Override public int hashCode() { return Objects.hash( id, parentName, getParentSelector(), getContainerName(), getCommand(), getArgs()); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/EnvImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.devfile.Env; /** * @author Sergii Leshchenko */ @Entity(name = "DevfileEnv") @Table(name = "devfile_env") public class EnvImpl implements Env { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "name", nullable = false) private String name; @Column(name = "value_param", nullable = false) private String value; public EnvImpl() {} public EnvImpl(String name, String value) { this.name = name; this.value = value; } public EnvImpl(Env env) { this(env.getName(), env.getValue()); } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof EnvImpl)) { return false; } EnvImpl env = (EnvImpl) o; return Objects.equals(id, env.id) && Objects.equals(name, env.name) && Objects.equals(value, env.value); } @Override public int hashCode() { return Objects.hash(id, name, value); } @Override public String toString() { return "EnvImpl{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", value='" + value + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/MetadataImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Embeddable; import javax.persistence.Transient; import org.eclipse.che.api.core.model.workspace.devfile.Metadata; @Embeddable public class MetadataImpl implements Metadata { @Column(name = "meta_name") private String name; /** * generateName is used just at workspace create time, when name is generated from it and stored * into {@link MetadataImpl#name}, thus it's not needed to persist */ @Transient private String generateName; public MetadataImpl() {} public MetadataImpl(String name) { this.name = name; } public MetadataImpl(String name, String generateName) { this.name = name; this.generateName = generateName; } public MetadataImpl(Metadata metadata) { this.name = metadata.getName(); this.generateName = metadata.getGenerateName(); } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String getGenerateName() { return generateName; } public void setGenerateName(String generateName) { this.generateName = generateName; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MetadataImpl metadata = (MetadataImpl) o; return Objects.equals(name, metadata.name) && Objects.equals(generateName, metadata.generateName); } @Override public int hashCode() { return Objects.hash(name, generateName); } @Override public String toString() { return "MetadataImpl{" + "name='" + name + '\'' + ", generateName='" + generateName + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/PreviewUrlImpl.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import java.util.Objects; import java.util.StringJoiner; import javax.persistence.Column; import javax.persistence.Embeddable; import org.eclipse.che.api.core.model.workspace.devfile.PreviewUrl; @Embeddable public class PreviewUrlImpl implements PreviewUrl { @Column(name = "preview_url_port") private int port; @Column(name = "preview_url_path") private String path; public PreviewUrlImpl() {} public PreviewUrlImpl(PreviewUrl previewUrl) { this(previewUrl.getPort(), previewUrl.getPath()); } public PreviewUrlImpl(int port, String path) { this.port = port; this.path = path; } @Override public int getPort() { return port; } @Override public String getPath() { return path; } @Override public String toString() { return new StringJoiner(", ", PreviewUrlImpl.class.getSimpleName() + "[", "]") .add("port=" + port) .add("path='" + path + "'") .toString(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } PreviewUrlImpl that = (PreviewUrlImpl) o; return port == that.port && Objects.equals(path, that.path); } @Override public int hashCode() { return Objects.hash(port, path); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/ProjectImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.devfile.Project; import org.eclipse.che.api.core.model.workspace.devfile.Source; /** * @author Sergii Leshchenko */ @Entity(name = "DevfileProject") @Table(name = "devfile_project") public class ProjectImpl implements Project { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "name", nullable = false) private String name; @Embedded private SourceImpl source; @Column(name = "clone_path", nullable = false) private String clonePath; public ProjectImpl() {} public ProjectImpl(String name, Source source, String clonePath) { this.name = name; if (source != null) { this.source = new SourceImpl(source); } this.clonePath = clonePath; } public ProjectImpl(Project project) { this(project.getName(), project.getSource(), project.getClonePath()); } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public SourceImpl getSource() { return source; } public void setSource(SourceImpl source) { this.source = source; } @Override public String getClonePath() { return clonePath; } public void setClonePath(String clonePath) { this.clonePath = clonePath; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ProjectImpl)) { return false; } ProjectImpl project = (ProjectImpl) o; return Objects.equals(id, project.id) && Objects.equals(name, project.name) && Objects.equals(source, project.source) && Objects.equals(clonePath, project.clonePath); } @Override public int hashCode() { return Objects.hash(id, name, source, clonePath); } @Override public String toString() { return "ProjectImpl{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", source=" + source + ", clonePath='" + clonePath + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/SourceImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Embeddable; import org.eclipse.che.api.core.model.workspace.devfile.Source; /** * @author Sergii Leshchenko */ @Embeddable public class SourceImpl implements Source { @Column(name = "type", nullable = false) private String type; @Column(name = "location", nullable = false) private String location; @Column(name = "branch") private String branch; @Column(name = "start_point") private String startPoint; @Column(name = "tag") private String tag; @Column(name = "commit_id") private String commitId; @Column(name = "sparse_checkout_dir") private String sparseCheckoutDir; public SourceImpl() {} public SourceImpl( String type, String location, String branch, String startPoint, String tag, String commitId, String sparseCheckoutDir) { this.type = type; this.location = location; this.branch = branch; this.startPoint = startPoint; this.tag = tag; this.commitId = commitId; this.sparseCheckoutDir = sparseCheckoutDir; } public SourceImpl(Source source) { this( source.getType(), source.getLocation(), source.getBranch(), source.getStartPoint(), source.getTag(), source.getCommitId(), source.getSparseCheckoutDir()); } @Override public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } @Override public String getBranch() { return branch; } public void setBranch(String branch) { this.branch = branch; } @Override public String getStartPoint() { return startPoint; } public void setStartPoint(String startPoint) { this.startPoint = startPoint; } @Override public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } @Override public String getCommitId() { return commitId; } public void setCommitId(String commitId) { this.commitId = commitId; } @Override public String getSparseCheckoutDir() { return sparseCheckoutDir; } public void setSparseCheckoutDir(String sparseCheckoutDir) { this.sparseCheckoutDir = sparseCheckoutDir; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof SourceImpl)) { return false; } SourceImpl source = (SourceImpl) o; return Objects.equals(type, source.type) && Objects.equals(location, source.location) && Objects.equals(branch, source.branch) && Objects.equals(startPoint, source.startPoint) && Objects.equals(tag, source.tag) && Objects.equals(commitId, source.commitId) && Objects.equals(sparseCheckoutDir, source.sparseCheckoutDir); } @Override public int hashCode() { return Objects.hash(type, location, branch, startPoint, tag, commitId, sparseCheckoutDir); } @Override public String toString() { return "SourceImpl{" + "type='" + type + '\'' + ", location='" + location + '\'' + ", branch='" + branch + '\'' + ", startPoint='" + startPoint + '\'' + ", tag='" + tag + '\'' + ", commitId='" + commitId + '\'' + ", sparseCheckoutDir='" + sparseCheckoutDir + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/VolumeImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl.devfile; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.eclipse.che.api.core.model.workspace.devfile.Volume; /** * @author Sergii Leshchenko */ @Entity(name = "DevfileVolume") @Table(name = "devfile_volume") public class VolumeImpl implements Volume { @Id @GeneratedValue @Column(name = "id") private Long id; @Column(name = "name", nullable = false) private String name; @Column(name = "container_path", nullable = false) private String containerPath; public VolumeImpl() {} public VolumeImpl(String name, String containerPath) { this.name = name; this.containerPath = containerPath; } public VolumeImpl(Volume volume) { this(volume.getName(), volume.getContainerPath()); } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String getContainerPath() { return containerPath; } public void setContainerPath(String containerPath) { this.containerPath = containerPath; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof VolumeImpl)) { return false; } VolumeImpl volume = (VolumeImpl) o; return Objects.equals(id, volume.id) && Objects.equals(name, volume.name) && Objects.equals(containerPath, volume.containerPath); } @Override public int hashCode() { return Objects.hash(id, name, containerPath); } @Override public String toString() { return "VolumeImpl{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", containerPath='" + containerPath + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/InfrastructureException.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi; /** * An exception thrown by RuntimeInfrastructure and related components. Indicates that an * infrastructure operation can't be performed or an error occurred during operation execution. * * @author Yevhenii Voevodin */ public class InfrastructureException extends Exception { public InfrastructureException(String message) { super(message); } public InfrastructureException(Exception e) { super(e); } public InfrastructureException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/InternalInfrastructureException.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi; /** * Thrown by RuntimeInfrastructure and related components when unexpected internal error occurs. * * @author Yevhenii Voevodin */ public class InternalInfrastructureException extends InfrastructureException { public InternalInfrastructureException(String message) { super(message); } public InternalInfrastructureException(String message, Throwable cause) { super(message, cause); } public InternalInfrastructureException(Exception e) { super(e); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/NamespaceResolutionContext.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi; import java.util.Objects; import org.eclipse.che.commons.subject.Subject; /** * Holds information needed for resolving placeholders in the namespace name. The {@code * persistAfterCreate} attribute indicates whether namespace name should be persisted after * resolution (if the infrastructure supports it). * * @author Lukas Krejci * @author Sergii Leshchenko */ public class NamespaceResolutionContext { private final String workspaceId; private final String userId; private final String userName; public NamespaceResolutionContext(Subject subject) { this(null, subject.getUserId(), subject.getUserName()); } public NamespaceResolutionContext(String workspaceId, String userId, String userName) { this.workspaceId = workspaceId; this.userId = userId; this.userName = userName; } public String getWorkspaceId() { return workspaceId; } public String getUserId() { return userId; } public String getUserName() { return userName; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof NamespaceResolutionContext)) { return false; } final NamespaceResolutionContext that = (NamespaceResolutionContext) obj; return Objects.equals(workspaceId, that.workspaceId) && Objects.equals(userId, that.userId) && Objects.equals(userName, that.userName); } @Override public int hashCode() { return Objects.hash(workspaceId, userId, userName); } @Override public String toString() { return "NamespaceResolutionContext{" + "workspaceId='" + workspaceId + '\'' + ", userId='" + userId + '\'' + ", userName='" + userName + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/RuntimeStartInterruptedException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi; import static java.lang.String.format; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; /** * Thrown when start of the runtime is interrupted. * * @author Anton Korneta */ public class RuntimeStartInterruptedException extends InfrastructureException { public RuntimeStartInterruptedException(RuntimeIdentity identity) { super( format( "Runtime start for identity 'workspace: %s, environment: %s, ownerId: %s' is interrupted", identity.getWorkspaceId(), identity.getEnvName(), identity.getOwnerId())); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/StateException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi; /** * Thrown by {@link RuntimeContext} and related components. Indicates that an operation cannot be * performed due to context state violation(e.g. workspace cannot be stopped while it is starting). * * @author Yevhenii Voevodin */ public class StateException extends InfrastructureException { public StateException(String message) { super(message); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/WorkspaceDao.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi; import java.util.Optional; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; /** * Defines data access object contract for {@link WorkspaceImpl}. * * @author Eugene Voevodin */ public interface WorkspaceDao { /** * Creates workspace. * * @param workspace workspace to create * @return created workspace * @throws NullPointerException when {@code workspace} is null * @throws ConflictException when workspace with given name already exists for given namespace * @throws ServerException when any other error occurs during workspace creation */ WorkspaceImpl create(WorkspaceImpl workspace) throws ConflictException, ServerException; /** * Updates workspace with new entity, actually replaces(not merges) existing workspaces. * *

    Workspace will be fully updated(replaced), all data which was present before update will not * be accessible with {@link WorkspaceDao this} class anymore. Expected update usage: * *

       *     UsersWorkspaceImpl workspace = workspaceDao.get("workspace123");
       *     ...
       *     workspace.setName("new-workspace-name");
       *     ...
       *     workspaceDao.update(workspace);
       * 
    * * @param update workspace update * @return updated workspace * @throws NullPointerException when {@code update} is null * @throws NotFoundException when workspace with given {@link WorkspaceImpl#getId() identifier} * was not found * @throws ConflictException when workspace with given name already exists in given namespace * @throws ServerException when any other error occurs during workspace updating */ WorkspaceImpl update(WorkspaceImpl update) throws NotFoundException, ConflictException, ServerException; /** * Removes workspace. * *

    It is up to implementation to do cascade removing of related to workspace data or to forbid * remove operation at all * *

    Doesn't throw an exception when workspace with given {@code id} does not exist * * @param id workspace identifier * @return removed workspace * @throws NullPointerException when {@code id} is null * @throws ServerException when any other error occurs during workspace removing */ Optional remove(String id) throws ServerException; /** * Gets workspace by identifier. * * @param id workspace identifier * @return workspace instance, never null * @throws NullPointerException when {@code id} is null * @throws NotFoundException when workspace with given {@code id} was not found * @throws ServerException when any other error occurs during workspace fetching */ WorkspaceImpl get(String id) throws NotFoundException, ServerException; /** * Gets workspace by name in namespace. * * @param namespace namespace of workspace * @param name workspace identifier * @return workspace instance, never null * @throws NullPointerException when {@code name} or {@code owner} is null * @throws NotFoundException when workspace with given name & owner was not found * @throws ServerException when any other error occurs during workspace fetching */ WorkspaceImpl get(String name, String namespace) throws NotFoundException, ServerException; /** * Gets list of workspaces in given namespace. * * @param namespace workspace namespace * @return list of workspaces in given namespace. Always returns list(even when there are no * workspace in given namespace), never null * @throws NullPointerException when {@code owner} is null * @throws ServerException when any other error occurs during workspaces fetching */ Page getByNamespace(String namespace, int maxItems, long skipCount) throws ServerException; /** * Gets list of workspaces which user can read * * @param userId id of user * @return list of workspaces which user can read * @throws ServerException when any other error occurs during workspaces fetching */ Page getWorkspaces(String userId, int maxItems, long skipCount) throws ServerException; /** * Gets workspaces by temporary attribute. * * @param isTemporary When {@code true}, only temporary workspaces should be retrieved. When * {@code false}, only non-temporary workspaces should be retrieved. * @param skipCount the number of workspaces to skip * @param maxItems the maximum number of workspaces to return * @return list of workspaces or empty list if no workspaces were found * @throws ServerException when any other error occurs during workspaces fetching * @throws IllegalArgumentException when {@code maxItems} or {@code skipCount} is negative */ Page getWorkspaces(boolean isTemporary, int maxItems, long skipCount) throws ServerException; /** * Get the count of all workspaces from the persistent layer. * * @return workspace count * @throws ServerException when any error occurs */ long getWorkspacesTotalCount() throws ServerException; } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/environment/InternalEnvironment.java ================================================ /* * Copyright (c) 2012-2026 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.environment; import com.google.common.annotations.Beta; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.core.model.workspace.config.Environment; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.commons.annotation.Nullable; /** * Representation of {@link Environment} which holds internal representations of environment * components to ease implementation of RuntimeInfrastructure. * *

    It is related but not really bound to some specific infrastructure. It lets an infrastructure * apply multiple different implementations, some of which can be considered as a "native format", * while others as rather "supported, adopted formats". * * @author Alexander Garagatyi * @author gazarenkov */ public abstract class InternalEnvironment { private String type; private InternalRecipe recipe; private Map machines; private final List warnings; private Map attributes; private List commands; private DevfileImpl devfile; protected InternalEnvironment() { this.warnings = new CopyOnWriteArrayList<>(); } protected InternalEnvironment( InternalRecipe recipe, Map machines, List warnings) { this.recipe = recipe; this.machines = machines; this.warnings = new CopyOnWriteArrayList<>(); if (warnings != null) { this.warnings.addAll(warnings); } this.type = recipe != null ? recipe.getType() : null; } protected InternalEnvironment(InternalEnvironment internalEnvironment) { this.recipe = internalEnvironment.getRecipe(); this.type = recipe != null ? recipe.getType() : null; this.machines = internalEnvironment.getMachines(); this.warnings = new CopyOnWriteArrayList<>(internalEnvironment.getWarnings()); this.attributes = internalEnvironment.getAttributes(); this.commands = internalEnvironment.getCommands(); this.devfile = internalEnvironment.getDevfile(); } /** * Returns internal environment type - an identifier of the type of the environment. * *

    It can differ from the type of {@link InternalRecipe#getType()} in certain cases. An example * of such a case is converting of an environment from one type to another for the purposes of an * infrastructure. In this case, {@link InternalRecipe#getType()} shows an origin type of the * environment whereas this method might return the type of the environment after the conversion. */ @Nullable public String getType() { return type; } public InternalEnvironment setType(String type) { this.type = type; return this; } /** Returns environment recipe which includes recipe content. */ @Nullable public InternalRecipe getRecipe() { return recipe; } public InternalEnvironment setRecipe(InternalRecipe recipe) { this.recipe = recipe; return this; } /** * Returns unmodifiable map of internal machines configs which include all information about * machine configuration which may be needed by infrastructure implementation. */ public Map getMachines() { if (machines == null) { machines = new HashMap<>(); } return machines; } public InternalEnvironment setMachines(Map machines) { this.machines = machines; return this; } /** * Returns the list of the warnings indicating that the environment violates some non-critical * constraints or some preferable configuration is missing so defaults are used. */ public List getWarnings() { return warnings; } /** Adds an {@link Warning}. */ public void addWarning(Warning warning) { warnings.add(warning); } public InternalEnvironment setWarnings(List warnings) { this.warnings.clear(); if (warnings != null) { this.warnings.addAll(warnings); } return this; } /** * Note that this field is in Beta and is subject to be removed at any point of time without any * notification. * *

    Returns map of workspace config attributes that can be used for workspace runtime creation. * * @see WorkspaceConfig#getAttributes() */ @Beta public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } /** * Note that this field is in Beta and is subject to be removed at any point of time without any * notification. * * @param attributes workspace config attributes that might be used in creation of workspace * runtime * @see WorkspaceConfig#getAttributes() */ @Beta public InternalEnvironment setAttributes(Map attributes) { this.attributes = attributes; return this; } @Beta public List getCommands() { if (commands == null) { commands = new ArrayList<>(); } return commands; } @Beta public InternalEnvironment setCommands(List commands) { if (commands != null) { this.commands = commands.stream().map(CommandImpl::new).collect(Collectors.toList()); } return this; } /** Return the devfile that was used for the environment constructing. */ public DevfileImpl getDevfile() { return devfile; } public void setDevfile(DevfileImpl devfile) { this.devfile = devfile; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/environment/InternalEnvironmentFactory.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.environment; import static org.eclipse.che.api.workspace.shared.Constants.CONTAINER_SOURCE_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.RECIPE_CONTAINER_SOURCE; import com.google.common.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.api.core.model.workspace.config.Environment; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.annotation.Nullable; /** * Creates a valid instance of InternalEnvironment. * *

    Expected to be bound with a MapBinder with unique String key that contains recipe type, like: * *

     *   MapBinder envFactories =
     *       MapBinder.newMapBinder(binder(), String.class, InternalEnvironmentFactory.class);
     *   envFactories.addBinding("recipe_type_1").to(SubclassOfInternalEnvironmentFactory.class);
     * 
    * * @author gazarenkov * @author Sergii Leshchenko */ public abstract class InternalEnvironmentFactory { private final RecipeRetriever recipeRetriever; private final MachineConfigsValidator machinesValidator; public InternalEnvironmentFactory( RecipeRetriever recipeRetriever, MachineConfigsValidator machinesValidator) { this.recipeRetriever = recipeRetriever; this.machinesValidator = machinesValidator; } /** * Creates a valid instance of InternalEnvironment. * *

    To construct a valid instance it performs the following actions: * *

      *
    • download recipe content if it is needed; *
    • retrieve the configured installers from installers registry; *
    • normalize servers port by adding default protocol in port if it is absent; *
    • validate the environment machines; *
    • invoke implementation specific method that should validate and parse recipe; *
    • ensure there are environment variables pointing to machine names; *
    * * @param sourceEnv the environment * @return InternalEnvironment a valid InternalEnvironment instance * @throws InfrastructureException if exception occurs on recipe downloading * @throws InfrastructureException if infrastructure specific error occurs * @throws ValidationException if validation fails */ public T create(@Nullable final Environment sourceEnv) throws InfrastructureException, ValidationException { Map machines = new HashMap<>(); List warnings = new ArrayList<>(); InternalRecipe recipe = null; if (sourceEnv != null) { recipe = recipeRetriever.getRecipe(sourceEnv.getRecipe()); for (Map.Entry machineEntry : sourceEnv.getMachines().entrySet()) { MachineConfig machineConfig = machineEntry.getValue(); machines.put( machineEntry.getKey(), new InternalMachineConfig( normalizeServers(machineConfig.getServers()), machineConfig.getEnv(), machineConfig.getAttributes(), machineConfig.getVolumes())); } machinesValidator.validate(machines); } T internalEnv = doCreate(recipe, machines, warnings); internalEnv .getMachines() .values() .forEach(m -> m.getAttributes().put(CONTAINER_SOURCE_ATTRIBUTE, RECIPE_CONTAINER_SOURCE)); return internalEnv; } /** * Implementation validates downloaded recipe and creates specific InternalEnvironment. * *

    Returned InternalEnvironment must contains all machine that are defined in recipe and in * source machines collection. Also, if memory limitation is supported, it may add memory limit * attribute {@link MachineConfig#MEMORY_LIMIT_ATTRIBUTE} from recipe or configured system-wide * default value. * * @param recipe downloaded recipe * @param machines machines configuration * @param warnings list of warnings * @return InternalEnvironment * @throws InfrastructureException if infrastructure specific error occurs * @throws ValidationException if validation fails */ protected abstract T doCreate( @Nullable InternalRecipe recipe, Map machines, List warnings) throws InfrastructureException, ValidationException; @VisibleForTesting Map normalizeServers(Map servers) { return servers.entrySet().stream() .collect(Collectors.toMap(Entry::getKey, e -> normalizeServer(e.getValue()))); } private ServerConfig normalizeServer(ServerConfig serverConfig) { String port = serverConfig.getPort(); if (port != null && !port.contains("/")) { port = port + "/tcp"; } return new ServerConfigImpl( port, serverConfig.getProtocol(), serverConfig.getPath(), serverConfig.getAttributes()); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/environment/InternalMachineConfig.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.environment; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.config.Volume; /** * Machine Config to use inside infrastructure. * *

    It contains: * *

      *
    • retrieved full information about installers; *
    • normalized server configs. *
    * * @author gazarenkov */ public class InternalMachineConfig { private final Map servers; private final Map env; private final Map attributes; private final Map volumes; public InternalMachineConfig() { this.servers = new HashMap<>(); this.env = new HashMap<>(); this.attributes = new HashMap<>(); this.volumes = new HashMap<>(); } public InternalMachineConfig( Map servers, Map env, Map attributes, Map volumes) { this(); if (servers != null) { this.servers.putAll(servers); } if (env != null) { this.env.putAll(env); } if (attributes != null) { this.attributes.putAll(attributes); } if (volumes != null) { this.volumes.putAll(volumes); } } /** Returns modifiable map of servers configured in the machine. */ public Map getServers() { return servers; } /** Returns modifiable map of machine environment variables. */ public Map getEnv() { return env; } /** Returns modifiable map of machine attributes. */ public Map getAttributes() { return attributes; } /** Returns modifiable map of machine volumes. */ public Map getVolumes() { return volumes; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/environment/InternalRecipe.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.environment; import org.eclipse.che.api.core.model.workspace.config.Environment; import org.eclipse.che.api.core.model.workspace.config.Recipe; import org.eclipse.che.commons.annotation.Nullable; /** * Recipe of {@link Environment} with content either provided by {@link Recipe#getContent()} or * downloaded from {@link Recipe#getLocation()}. * * @author Alexander Garagatyi * @author gazarenkov * @author Sergii Leshchenko */ public class InternalRecipe { private final String type; private final String contentType; private final String content; InternalRecipe(String type, String contentType, String content) { this.type = type; this.contentType = contentType; this.content = content; } /** Type of the recipe. It is mandatory. */ public String getType() { return type; } /** Content type. It is optional. */ @Nullable public String getContentType() { return contentType; } /** The context of the recipe. It is mandatory. */ public String getContent() { return content; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/environment/MachineConfigsValidator.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.environment; import static java.lang.String.format; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_REQUEST_ATTRIBUTE; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; /** * @author Sergii Leshchenko */ public class MachineConfigsValidator { private static final String MACHINE_NAME_REGEXP = "[a-zA-Z0-9]+([a-zA-Z0-9_/-]*[a-zA-Z0-9])?"; private static final Pattern MACHINE_NAME_PATTERN = Pattern.compile("^" + MACHINE_NAME_REGEXP + "$"); private static final Pattern SERVER_PORT_PATTERN = Pattern.compile("^[1-9]+[0-9]*(/(tcp|udp))?$"); private static final Pattern SERVER_PROTOCOL_PATTERN = Pattern.compile("^[a-z][a-z0-9-+.]*$"); /** * Validates the specified machine configs. * * @param machines machines configs to validate * @throws ValidationException when the specified environment is not valid */ public void validate(Map machines) throws ValidationException { for (Entry machineConfigEntry : machines.entrySet()) { validateMachine(machineConfigEntry.getKey(), machineConfigEntry.getValue()); } } private void validateMachine(String machineName, InternalMachineConfig machineConfig) throws ValidationException { checkArgument( MACHINE_NAME_PATTERN.matcher(machineName).matches(), "Name of machine '%s' in environment is invalid", machineName); if (machineConfig.getServers() != null) { for (Map.Entry serverEntry : machineConfig.getServers().entrySet()) { String serverName = serverEntry.getKey(); ServerConfig server = serverEntry.getValue(); checkArgument( server.getPort() != null && SERVER_PORT_PATTERN.matcher(server.getPort()).matches(), "Machine '%s' in environment contains server conf '%s' with invalid port '%s'", machineName, serverName, server.getPort()); checkArgument( server.getProtocol() == null || SERVER_PROTOCOL_PATTERN.matcher(server.getProtocol()).matches(), "Machine '%s' in environment contains server conf '%s' with invalid protocol '%s'", machineName, serverName, server.getProtocol()); } } if (machineConfig.getAttributes() != null) { String memoryLimit = machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE); String memoryRequest = machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE); if (memoryLimit != null && memoryRequest != null) { checkArgument( Long.parseLong(memoryLimit) >= Long.parseLong(memoryRequest), "Machine '%s' in environment contains inconsistent memory attributes: Memory limit: '%s', Memory request: '%s'", machineName, Long.parseLong(memoryLimit), Long.parseLong(memoryRequest)); } } } private static void checkArgument( boolean expression, String errorMessageTemplate, Object... errorMessageParams) throws ValidationException { if (!expression) { throw new ValidationException(format(errorMessageTemplate, errorMessageParams)); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/environment/RecipeRetriever.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.environment; import static org.slf4j.LoggerFactory.getLogger; import jakarta.ws.rs.core.UriBuilder; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Objects; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.config.Recipe; import org.eclipse.che.api.core.util.FileCleaner; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.IoUtil; import org.slf4j.Logger; /** * Handle how recipe is retrieved, either by downloading it with external location or by using the * provided content. * * @author Alexander Garagatyi */ public class RecipeRetriever { private static final Logger LOG = getLogger(RecipeRetriever.class); private final URI apiEndpoint; @Inject public RecipeRetriever(@Named("che.api") URI apiEndpoint) { this.apiEndpoint = apiEndpoint; } /** * Returns content of recipe either by getting it from provided {@link Recipe} object or by * retrieving it from location set in provided recipe. * * @param recipe recipe of {@link org.eclipse.che.api.core.model.workspace.config.Environment} * @return recipe content * @throws NullPointerException when recipe is null * @throws IllegalArgumentException when both recipe content and location are null or empty */ public InternalRecipe getRecipe(Recipe recipe) throws InfrastructureException { Objects.requireNonNull(recipe, "Recipe should not be null"); Objects.requireNonNull(recipe.getType(), "Recipe type should not be null"); return new InternalRecipe(recipe.getType(), recipe.getContentType(), retrieveContent(recipe)); } private String retrieveContent(Recipe recipe) throws InfrastructureException { if (recipe.getContent() != null && !recipe.getContent().isEmpty()) { // no downloading is needed return recipe.getContent(); } if (recipe.getLocation() == null || recipe.getLocation().isEmpty()) { throw new IllegalArgumentException("Neither content nor location are present in recipe"); } URL url = prepareURL(recipe.getLocation()); return downloadContent(url); } private URL prepareURL(String location) throws InfrastructureException { URI uri; try { uri = new URI(location); } catch (URISyntaxException e) { LOG.debug(e.getLocalizedMessage(), e); throw new InfrastructureException( "Location of recipe downloading is not supported because it is not a valid URI"); } // if URI to this server add token to access protected API boolean addToken = isTokenNeeded(uri); UriBuilder uriBuilder = makeURIAbsolute(uri); if (addToken) { addToken(uriBuilder); } try { return uriBuilder.build().toURL(); } catch (MalformedURLException e) { LOG.debug(e.getLocalizedMessage(), e); throw new InfrastructureException("Constructing URL for downloading recipe failed"); } } private String downloadContent(URL url) throws InfrastructureException { File file = null; try { file = IoUtil.downloadFileWithRedirect(null, "recipe", null, url); return IoUtil.readAndCloseQuietly(new FileInputStream(file)); } catch (IOException e) { LOG.debug(e.getLocalizedMessage(), e); throw new InfrastructureException("Failed to download recipe content"); } finally { if (file != null && !file.delete()) { FileCleaner.addFile(file); } } } private UriBuilder makeURIAbsolute(URI uri) { UriBuilder uriBuilder = UriBuilder.fromUri(uri); if (!uri.isAbsolute() && uri.getHost() == null) { uriBuilder .scheme(apiEndpoint.getScheme()) .host(apiEndpoint.getHost()) .port(apiEndpoint.getPort()) .replacePath(apiEndpoint.getPath() + uri.toString()); } return uriBuilder; } private boolean isTokenNeeded(URI uri) { // relative URI or host is the same as CHE API host - token is needed return (!uri.isAbsolute() && uri.getHost() == null) || (apiEndpoint.getHost().equals(uri.getHost()) && apiEndpoint.getPort() == uri.getPort()); } private void addToken(UriBuilder ub) { if (EnvironmentContext.getCurrent().getSubject().getToken() != null) { ub.queryParam("token", EnvironmentContext.getCurrent().getSubject().getToken()); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/environment/ResourceLimitAttributesProvisioner.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.environment; import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_REQUEST_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_REQUEST_ATTRIBUTE; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Configures resource limits attributes for a given machine, if they are not present in {@link * MachineConfig} the attributes are taken from recipe, when available, by the specific * infrastructure implementation, or from wsmaster properties as a fallback * *

    if default requested memory allocation is greater then default memory limit, requested memory * allocation is set to be equal to memory limit. */ public class ResourceLimitAttributesProvisioner { private static final Logger LOG = LoggerFactory.getLogger(ResourceLimitAttributesProvisioner.class); /** * Configures memory attributes, if they are missing in {@link MachineConfig} * * @param machineConfig - given machine configuration * @param memoryLimit - resource limit parameter configured by user in specific infra recipe. Can * be 0 if defaults should be used * @param memoryRequest - memory request parameter configured by user in specific infra recipe. * Can be 0 if defaults should be used * @param defaultMemoryLimit - default memory limit resource parameter value * @param defaultMemoryRequest - default memory request resource parameter value */ public static void provisionMemory( InternalMachineConfig machineConfig, long memoryLimit, long memoryRequest, long defaultMemoryLimit, long defaultMemoryRequest) { if (defaultMemoryRequest > defaultMemoryLimit) { defaultMemoryRequest = defaultMemoryLimit; LOG.warn( "Requested default container resource limit is less than default request. Request parameter will be ignored."); } if (memoryLimit <= 0 && defaultMemoryLimit > 0) { memoryLimit = defaultMemoryLimit; } if (memoryRequest <= 0 && defaultMemoryRequest > 0) { memoryRequest = defaultMemoryRequest; } if (memoryRequest > memoryLimit) { // if both properties are defined, but not consistent memoryRequest = memoryLimit; } final Map attributes = machineConfig.getAttributes(); String configuredLimit = attributes.get(MEMORY_LIMIT_ATTRIBUTE); String configuredRequest = attributes.get(MEMORY_REQUEST_ATTRIBUTE); // Added < 0 check to avoid bypassing limit if someone is set negative value into attribute. if (isNullOrEmpty(configuredLimit) || Long.parseLong(configuredLimit) <= 0) { attributes.put(MEMORY_LIMIT_ATTRIBUTE, String.valueOf(memoryLimit)); } if (isNullOrEmpty(configuredRequest) || Long.parseLong(configuredRequest) <= 0) { attributes.put(MEMORY_REQUEST_ATTRIBUTE, String.valueOf(memoryRequest)); } } /** * Configures CPU attributes, if they are missing in {@link MachineConfig} * * @param machineConfig - given machine configuration * @param cpuLimit - CPU resource limit parameter configured by user in specific infra recipe. Can * be 0 if defaults should be used * @param cpuRequest - CPU resource request parameter configured by user in specific infra recipe. * Can be 0 if defaults should be used * @param defaultCPULimit - default CPU limit resource parameter value * @param defaultCPURequest - default CPU request resource parameter value */ public static void provisionCPU( InternalMachineConfig machineConfig, float cpuLimit, float cpuRequest, float defaultCPULimit, float defaultCPURequest) { if (defaultCPURequest > defaultCPULimit) { defaultCPURequest = defaultCPULimit; LOG.warn( "Requested default container resource limit is less than default request. Request parameter will be ignored."); } if (cpuLimit <= 0 && defaultCPULimit > 0) { cpuLimit = defaultCPULimit; } if (cpuRequest <= 0 && defaultCPURequest > 0) { cpuRequest = defaultCPURequest; } if (cpuRequest > cpuLimit) { // if both properties are defined, but not consistent cpuRequest = cpuLimit; } final Map attributes = machineConfig.getAttributes(); String configuredLimit = attributes.get(CPU_LIMIT_ATTRIBUTE); String configuredRequest = attributes.get(CPU_REQUEST_ATTRIBUTE); // Added < 0 check to avoid bypassing limit if someone is set negative value into attribute. if (isNullOrEmpty(configuredLimit) || Float.parseFloat(configuredLimit) <= 0) { attributes.put(CPU_LIMIT_ATTRIBUTE, Float.toString(cpuLimit)); } if (isNullOrEmpty(configuredRequest) || Float.parseFloat(configuredRequest) <= 0) { attributes.put(CPU_REQUEST_ATTRIBUTE, Float.toString(cpuRequest)); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/AgentAuthEnableEnvVarProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.commons.lang.Pair; /** * Provides environment variable that is used for enabling/disabling authentication on agents. * * @author Sergii Leshchenko */ public class AgentAuthEnableEnvVarProvider implements LegacyEnvVarProvider { public static final String CHE_AUTH_ENABLED_ENV = "CHE_AUTH_ENABLED"; private final boolean agentsAuthEnabled; @Inject public AgentAuthEnableEnvVarProvider( @Named("che.agents.auth_enabled") boolean agentsAuthEnabled) { this.agentsAuthEnabled = agentsAuthEnabled; } @Override public Pair get(RuntimeIdentity runtimeIdentity) { return Pair.of(CHE_AUTH_ENABLED_ENV, Boolean.toString(agentsAuthEnabled)); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/CheApiEnvVarProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.lang.Pair; /** * CHE_API endpoint var provided. For all currently supported infrastructures, it reuses {@link * CheApiInternalEnvVarProvider} to provide the value. * * @deprecated this class shall soon be removed, as this variable is provided only for backward * compatibility * @author Sergii Leshchenko * @author Mykhailo Kuznietsov */ @Deprecated public class CheApiEnvVarProvider implements EnvVarProvider { /** Che API url */ public static final String CHE_API_VARIABLE = "CHE_API"; private final CheApiInternalEnvVarProvider cheApiInternalEnvVarProvider; private final CheApiExternalEnvVarProvider cheApiExternalEnvVarProvider; @Inject public CheApiEnvVarProvider( CheApiInternalEnvVarProvider cheApiInternalEnvVarProvider, CheApiExternalEnvVarProvider cheApiExternalEnvVarProvider) { this.cheApiInternalEnvVarProvider = cheApiInternalEnvVarProvider; this.cheApiExternalEnvVarProvider = cheApiExternalEnvVarProvider; } /** * Returns Che API environment variable which should be injected into machines. * * @param runtimeIdentity which may be needed to evaluate environment variable value */ @Override public Pair get(RuntimeIdentity runtimeIdentity) throws InfrastructureException { if (cheApiInternalEnvVarProvider.get(runtimeIdentity) != null) { return Pair.of(CHE_API_VARIABLE, cheApiInternalEnvVarProvider.get(runtimeIdentity).second); } return Pair.of(CHE_API_VARIABLE, cheApiExternalEnvVarProvider.get(runtimeIdentity).second); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/CheApiExternalEnvVarProvider.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.lang.Pair; /** * @author Mykhailo Kuznietsov */ public interface CheApiExternalEnvVarProvider extends EnvVarProvider { /** Env variable for machine that contains url of Che API */ String CHE_API_EXTERNAL_VARIABLE = "CHE_API_EXTERNAL"; /** * Returns Che API environment variable which should be injected into machines. External API URL * is meant to be used by external clients like browsers. * * @param runtimeIdentity which may be needed to evaluate environment variable value */ @Override Pair get(RuntimeIdentity runtimeIdentity) throws InfrastructureException; } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/CheApiInternalEnvVarProvider.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.lang.Pair; /** * @author Mykhailo Kuznietsov */ public interface CheApiInternalEnvVarProvider extends EnvVarProvider { /** Env variable for machine that contains url of Che API */ String CHE_API_INTERNAL_VARIABLE = "CHE_API_INTERNAL"; /** * Returns Che API environment variable which should be injected into machines. Internal API URL * is meant to be used from the inside of other machines. * * @param runtimeIdentity which may be needed to evaluate environment variable value */ @Override Pair get(RuntimeIdentity runtimeIdentity) throws InfrastructureException; } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/EnvVarProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.commons.lang.Pair; /** * Provides an environment variable which is needed for servers used by Che for providing IDE * features. * * @author Sergii Leshchenko */ public interface EnvVarProvider { /** * Returns environment variable which should be injected into machine environment. If null is * returned, that means that no variable will be provided * * @param runtimeIdentity which may be needed to evaluate environment variable value */ Pair get(RuntimeIdentity runtimeIdentity) throws InfrastructureException; } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/JavaOptsEnvVariableProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.Pair; /** * Add env variable to machines with java opts. * * @author Roman Iuvshyn * @author Alexander Garagatyi */ public class JavaOptsEnvVariableProvider implements LegacyEnvVarProvider { /** Env variable for jvm settings */ public static final String JAVA_OPTS_VARIABLE = "JAVA_OPTS"; private String javaOpts; @Inject public JavaOptsEnvVariableProvider( @Named("che.workspace.java_options") String javaOpts, @Nullable @Named("che.workspace.http_proxy_java_options") String httpProxyJavaOptions) { this.javaOpts = httpProxyJavaOptions == null ? javaOpts : javaOpts + " " + httpProxyJavaOptions; } @Override public Pair get(RuntimeIdentity runtimeIdentity) { return Pair.of(JAVA_OPTS_VARIABLE, javaOpts); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/LegacyEnvVarProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; /** * Marker interface for environment variable providers which keeps backward-compatibility with Che 6 * workspaces. */ public interface LegacyEnvVarProvider extends EnvVarProvider {} ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/MachineTokenEnvVarProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import javax.inject.Inject; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.token.MachineTokenException; import org.eclipse.che.api.workspace.server.token.MachineTokenProvider; import org.eclipse.che.commons.lang.Pair; /** * Provides environment variable with a token that should be used by servers in a container to * access Che master API. * * @author Alexander Garagatyi * @author Sergii Leshchenko */ public class MachineTokenEnvVarProvider implements EnvVarProvider { /** Environment variable that will be setup in machines and contains machine token. */ public static final String MACHINE_TOKEN = "CHE_MACHINE_TOKEN"; private final MachineTokenProvider machineTokenProvider; @Inject public MachineTokenEnvVarProvider(MachineTokenProvider machineTokenProvider) { this.machineTokenProvider = machineTokenProvider; } @Override public Pair get(RuntimeIdentity runtimeIdentity) throws MachineTokenException { return Pair.of( MACHINE_TOKEN, machineTokenProvider.getToken( runtimeIdentity.getOwnerId(), runtimeIdentity.getWorkspaceId())); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/MavenOptsEnvVariableProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.Pair; /** * Provides MAVEN_OPTS environment variable with a value set to either maven options from * configuration or java options if maven options are not set. * * @author Yevhenii Voevodin */ public class MavenOptsEnvVariableProvider implements LegacyEnvVarProvider { private final String javaOpts; @Inject public MavenOptsEnvVariableProvider( @Named("che.workspace.maven_options") String javaOpts, @Nullable @Named("che.workspace.http_proxy_java_options") String httpProxyJavaOptions) { this.javaOpts = httpProxyJavaOptions == null ? javaOpts : javaOpts + " " + httpProxyJavaOptions; } @Override public Pair get(RuntimeIdentity runtimeIdentity) { return Pair.of("MAVEN_OPTS", javaOpts); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/WorkspaceIdEnvVarProvider.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.commons.lang.Pair; /** * @author Sergii Leshchenko */ public class WorkspaceIdEnvVarProvider implements EnvVarProvider { /** * Environment variable that will be setup in machine will contain ID of a workspace for which * this machine has been created */ public static final String WORKSPACE_ID_ENV_VAR = "CHE_WORKSPACE_ID"; @Override public Pair get(RuntimeIdentity runtimeIdentity) { return Pair.of(WORKSPACE_ID_ENV_VAR, runtimeIdentity.getWorkspaceId()); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/WorkspaceNameEnvVarProvider.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import javax.inject.Inject; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; import org.eclipse.che.commons.lang.Pair; /** * Provides environment variable with workspace name * * @author Sergii Kabashniuk */ public class WorkspaceNameEnvVarProvider implements EnvVarProvider { /** Env variable for workspace name */ public static final String CHE_WORKSPACE_NAME = "CHE_WORKSPACE_NAME"; private final WorkspaceDao workspaceDao; @Inject public WorkspaceNameEnvVarProvider(WorkspaceDao workspaceDao) { this.workspaceDao = workspaceDao; } @Override public Pair get(RuntimeIdentity runtimeIdentity) throws InfrastructureException { try { WorkspaceImpl workspace = workspaceDao.get(runtimeIdentity.getWorkspaceId()); return Pair.of(CHE_WORKSPACE_NAME, workspace.getName()); } catch (NotFoundException | ServerException e) { throw new InfrastructureException( "Not able to get workspace name for workspace with id " + runtimeIdentity.getWorkspaceId(), e); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/WorkspaceNamespaceNameEnvVarProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import javax.inject.Inject; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; import org.eclipse.che.commons.lang.Pair; /** * Provides environment variable with workspace namespace * * @author Sergii Kabashniuk */ public class WorkspaceNamespaceNameEnvVarProvider implements EnvVarProvider { /** Env variable for workspace name */ public static final String CHE_WORKSPACE_NAMESPACE = "CHE_WORKSPACE_NAMESPACE"; private final WorkspaceDao workspaceDao; @Inject public WorkspaceNamespaceNameEnvVarProvider(WorkspaceDao workspaceDao) { this.workspaceDao = workspaceDao; } @Override public Pair get(RuntimeIdentity runtimeIdentity) throws InfrastructureException { try { WorkspaceImpl workspace = workspaceDao.get(runtimeIdentity.getWorkspaceId()); return Pair.of(CHE_WORKSPACE_NAMESPACE, workspace.getNamespace()); } catch (NotFoundException | ServerException e) { throw new InfrastructureException( "Not able to get workspace namespace for workspace with id " + runtimeIdentity.getWorkspaceId(), e); } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/token/MachineAccessForbidden.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.token; /** * An exception should be thrown by {@link MachineTokenProvider} when an user doesn't have the * needed permissions. * * @author Sergii Leshchenko */ public class MachineAccessForbidden extends MachineTokenException { public MachineAccessForbidden(String message) { super(message); } public MachineAccessForbidden(Exception e) { super(e); } public MachineAccessForbidden(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/token/MachineTokenException.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.token; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; /** * An exception should be thrown by {@link MachineTokenProvider} when an error occurred during token * fetching. * * @author Sergii Leshchenko */ public class MachineTokenException extends InfrastructureException { public MachineTokenException(String message) { super(message); } public MachineTokenException(Exception e) { super(e); } public MachineTokenException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/token/MachineTokenProvider.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.token; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; /** * Provides machine token that should be used for accessing workspace master from a machine. * * @author Sergii Leshchenko */ public interface MachineTokenProvider { /** * Returns the machine's token for the specified workspace and user from {@link * EnvironmentContext#getSubject()}. * * @param workspaceId identifier of workspace to fetch token * @throws IllegalStateException when the current subject in context is {@link Subject#ANONYMOUS} * @throws MachineAccessForbidden when the current subject doesn't have the needed permissions * @throws MachineTokenException when any exception occurs on token fetching */ String getToken(String workspaceId) throws MachineTokenException; /** * Returns the machine's token for the specified pair: user, workspace. * * @param workspaceId identifier of workspace to fetch token * @throws MachineAccessForbidden when the specified user doesn't have the needed permissions * @throws MachineTokenException when any exception occurs on token fetching */ String getToken(String userId, String workspaceId) throws MachineTokenException; /** Returns empty string as machine token. */ class EmptyMachineTokenProvider implements MachineTokenProvider { @Override public String getToken(String workspaceId) { return ""; } @Override public String getToken(String userId, String workspaceId) { return ""; } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/ChePluginsApplier.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins; import com.google.common.annotations.Beta; import java.util.Collection; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; /** * Applies Che plugins tooling configuration to an internal runtime object that represents workspace * runtime configuration on an infrastructure level. * * @author Oleksander Garagatyi */ @Beta public interface ChePluginsApplier { /** * Applies Che plugins tooling configuration to internal environment. * * @param runtimeIdentity the runtime identity of the workspace that the plugins are being applied * to * @param internalEnvironment infrastructure specific representation of workspace runtime * environment * @param chePlugins Che plugins tooling configuration to apply to {@code internalEnvironment} * @throws InfrastructureException when applying Che plugins tooling fails */ void apply( RuntimeIdentity runtimeIdentity, InternalEnvironment internalEnvironment, Collection chePlugins) throws InfrastructureException; } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/PluginFQNParser.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.Collections.emptyList; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.common.annotations.Beta; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ExtendedPluginFQN; import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.api.workspace.shared.Constants; /** * Parses workspace attributes into a list of {@link PluginFQN}. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksander Garagatyi * @author Angel Misevski */ @Beta public class PluginFQNParser { private ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); private static final String INCORRECT_PLUGIN_FORMAT_TEMPLATE = "Plugin '%s' has incorrect format. Should be: 'registryURL#publisher/name/version' or 'publisher/name/version' or `referenceURL`"; private static final String URL_PATTERN = "https?://[-.\\w]+(:[0-9]+)?(/[-.\\w]+)*"; private static final String PUBLISHER_PATTERN = "[-a-z0-9]+"; private static final String NAME_PATTERN = "[-a-z0-9]+"; private static final String VERSION_PATTERN = "[-.a-z0-9]+"; private static final String ID_PATTERN = "(?" + PUBLISHER_PATTERN + ")/(?" + NAME_PATTERN + ")/(?" + VERSION_PATTERN + ")"; private static final Pattern PLUGIN_PATTERN = Pattern.compile("((?" + URL_PATTERN + ")/?#?)?(?" + ID_PATTERN + ")?"); /** * Parses a workspace attributes map into a collection of {@link PluginFQN}. * * @param attributes workspace attributes containing plugin and/or editor fields * @return a Collection of PluginFQN containing the editor and all plugins for this attributes * @throws InfrastructureException if attributes defines more than one editor */ public Collection parsePlugins(Map attributes) throws InfrastructureException { if (attributes == null) { return emptyList(); } String pluginsAttribute = attributes.getOrDefault(Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, null); String editorAttribute = attributes.getOrDefault(Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, null); List metaFQNs = new ArrayList<>(); if (!isNullOrEmpty(pluginsAttribute)) { metaFQNs.addAll(parsePluginFQNs(pluginsAttribute)); } if (!isNullOrEmpty(editorAttribute)) { Collection editorsFQNs = parsePluginFQNs(editorAttribute); if (editorsFQNs.size() > 1) { throw new InfrastructureException( "Multiple editors found in workspace config attributes. " + "Only one editor is supported per workspace."); } metaFQNs.addAll(editorsFQNs); } return metaFQNs; } private Collection parsePluginFQNs(String attribute) throws InfrastructureException { String[] plugins = splitAttribute(attribute); if (plugins.length == 0) { return emptyList(); } List collectedFQNs = new ArrayList<>(); for (String plugin : plugins) { PluginFQN pFQN = parsePluginFQN(plugin); String pluginKey = firstNonNull(pFQN.getReference(), pFQN.getId()); if (collectedFQNs.stream() .anyMatch(p -> pluginKey.equals(p.getId()) || pluginKey.equals(p.getReference()))) { throw new InfrastructureException( format( "Invalid Che tooling plugins configuration: plugin %s is duplicated", pluginKey)); // even if different registries } collectedFQNs.add(pFQN); } return collectedFQNs; } public ExtendedPluginFQN parsePluginFQN(String plugin) throws InfrastructureException { String url; String id; String publisher; String name; String version; URI registryURI = null; Matcher matcher = PLUGIN_PATTERN.matcher(plugin); if (matcher.matches()) { url = matcher.group("url"); id = matcher.group("id"); publisher = matcher.group("publisher"); name = matcher.group("name"); version = matcher.group("version"); } else { throw new InfrastructureException(format(INCORRECT_PLUGIN_FORMAT_TEMPLATE, plugin)); } if (!isNullOrEmpty(url)) { if (isNullOrEmpty(id)) { // reference only return new ExtendedPluginFQN(url); } else { // registry + id try { registryURI = new URI(url); } catch (URISyntaxException e) { throw new InfrastructureException( format( "Plugin registry URL '%s' is invalid. Problematic plugin entry: '%s'", url, plugin)); } } } return new ExtendedPluginFQN(registryURI, id, publisher, name, version); } private String[] splitAttribute(String attribute) { String[] plugins = attribute.split(","); return Arrays.stream(plugins).map(String::trim).toArray(String[]::new); } /** * Evaluates plugin FQN from provided reference by trying to fetch and parse its meta information. * * @param reference plugin reference to evaluate FQN from * @param fileContentProvider content provider instance to perform plugin meta requests * @return plugin FQN evaluated from given reference * @throws InfrastructureException if plugin reference is invalid or inaccessible */ public ExtendedPluginFQN evaluateFqn(String reference, FileContentProvider fileContentProvider) throws InfrastructureException { JsonNode contentNode; try { String pluginMetaContent = fileContentProvider.fetchContent(reference); contentNode = yamlReader.readTree(pluginMetaContent); } catch (DevfileException | IOException e) { throw new InfrastructureException( format("Plugin reference URL '%s' is invalid.", reference), e); } JsonNode publisher = contentNode.path("publisher"); if (publisher.isMissingNode()) { throw new InfrastructureException(formatMessage(reference, "publisher")); } JsonNode name = contentNode.get("name"); if (name.isMissingNode()) { throw new InfrastructureException(formatMessage(reference, "name")); } JsonNode version = contentNode.get("version"); if (version.isMissingNode()) { throw new InfrastructureException(formatMessage(reference, "version")); } if (!version.isValueNode()) { throw new InfrastructureException( format( "Plugin specified by reference URL '%s' has version field that cannot be parsed to string", reference)); } return new ExtendedPluginFQN( reference, publisher.textValue(), name.textValue(), version.asText()); } private String formatMessage(String reference, String field) { return format( "Plugin specified by reference URL '%s' have missing required field '" + field + "'.", reference); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/CheContainer.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** Represents sidecar container in Che workspace. */ @JsonIgnoreProperties(ignoreUnknown = true) public class CheContainer { private String image = null; private String name = null; private List env = new ArrayList<>(); @JsonProperty("commands") private List commands = new ArrayList<>(); private List volumes = new ArrayList<>(); private List ports = new ArrayList<>(); @JsonProperty("memoryLimit") private String memoryLimit = null; @JsonProperty("memoryRequest") private String memoryRequest = null; @JsonProperty("cpuLimit") private String cpuLimit = null; @JsonProperty("cpuRequest") private String cpuRequest = null; @JsonProperty("mountSources") private boolean mountSources = false; @JsonProperty("command") private List command; @JsonProperty("args") private List args; @JsonProperty("lifecycle") private Lifecycle lifecycle; public CheContainer image(String image) { this.image = image; return this; } public String getImage() { return image; } public void setImage(String image) { this.image = image; } public CheContainer name(String name) { this.name = name; return this; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** List of environment variables to set in the container. Cannot be updated. */ public CheContainer env(List env) { this.env = env; return this; } public List getEnv() { if (env == null) { env = new ArrayList<>(); } return env; } public void setEnv(List env) { this.env = env; } /** List of container commands */ public CheContainer commands(List commands) { this.commands = commands; return this; } public List getCommands() { if (commands == null) { commands = new ArrayList<>(); } return commands; } public void setCommands(List commands) { this.commands = commands; } /** List of container volumes */ public CheContainer volumes(List volumes) { this.volumes = volumes; return this; } public List getVolumes() { if (volumes == null) { volumes = new ArrayList<>(); } return volumes; } public void setVolumes(List volumes) { this.volumes = volumes; } public CheContainer ports(List ports) { this.ports = ports; return this; } public List getPorts() { if (ports == null) { ports = new ArrayList<>(); } return ports; } public void setPorts(List ports) { this.ports = ports; } public CheContainer memoryLimit(String memoryLimit) { this.memoryLimit = memoryLimit; return this; } public String getMemoryLimit() { return memoryLimit; } public void setMemoryLimit(String memoryLimit) { this.memoryLimit = memoryLimit; } public CheContainer memoryRequest(String memoryRequest) { this.memoryRequest = memoryRequest; return this; } public String getMemoryRequest() { return memoryRequest; } public void setMemoryRequest(String memoryRequest) { this.memoryRequest = memoryRequest; } public CheContainer cpuLimit(String cpuLimit) { this.cpuLimit = cpuLimit; return this; } public String getCpuLimit() { return cpuLimit; } public void setCpuLimit(String cpuLimit) { this.cpuLimit = cpuLimit; } public CheContainer cpuRequest(String cpuRequest) { this.cpuRequest = cpuRequest; return this; } public String getCpuRequest() { return cpuRequest; } public void setCpuRequest(String cpuRequest) { this.cpuRequest = cpuRequest; } public CheContainer mountSources(boolean mountSources) { this.mountSources = mountSources; return this; } public boolean isMountSources() { return mountSources; } public void setMountSources(boolean mountSources) { this.mountSources = mountSources; } public CheContainer command(List command) { this.command = command; return this; } public List getCommand() { if (command == null) { return new ArrayList<>(); } return command; } public void setCommand(List command) { this.command = command; } public CheContainer args(List args) { this.args = args; return this; } public List getArgs() { if (args == null) { return new ArrayList<>(); } return args; } public void setLifecycle(Lifecycle lifecycle) { this.lifecycle = lifecycle; } public CheContainer lifecycle(Lifecycle lifecycle) { this.lifecycle = lifecycle; return this; } public Lifecycle getLifecycle() { return lifecycle; } public void setArgs(List args) { this.args = args; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof CheContainer)) { return false; } CheContainer that = (CheContainer) o; return Objects.equals(getImage(), that.getImage()) && Objects.equals(getEnv(), that.getEnv()) && Objects.equals(getCommands(), that.getCommands()) && Objects.equals(getVolumes(), that.getVolumes()) && Objects.equals(getPorts(), that.getPorts()) && Objects.equals(getMemoryLimit(), that.getMemoryLimit()) && Objects.equals(getMemoryRequest(), that.getMemoryRequest()) && Objects.equals(getCpuLimit(), that.getCpuLimit()) && Objects.equals(getCpuRequest(), that.getCpuRequest()) && Objects.equals(getName(), that.getName()) && isMountSources() == that.isMountSources() && Objects.equals(getCommand(), that.getCommand()) && Objects.equals(getArgs(), that.getArgs()); } @Override public int hashCode() { return Objects.hash( getImage(), getEnv(), getCommands(), getVolumes(), getPorts(), getMemoryLimit(), getMemoryRequest(), getCpuLimit(), getCpuRequest(), getName(), isMountSources(), getCommand(), getArgs()); } @Override public String toString() { return "CheContainer{" + "image='" + image + '\'' + ", env=" + env + ", commands=" + commands + ", volumes=" + volumes + ", ports=" + ports + ", memoryLimit=" + memoryLimit + ", memoryRequest=" + memoryRequest + ", cpuLimit=" + cpuLimit + ", cpuRequest=" + cpuRequest + ", name=" + name + ", mountSources=" + mountSources + ", command=" + command + ", args=" + args + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/CheContainerPort.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import java.util.Objects; public class CheContainerPort { public int exposedPort = 0; public CheContainerPort exposedPort(int exposedPort) { this.exposedPort = exposedPort; return this; } public int getExposedPort() { return exposedPort; } public void setExposedPort(int exposedPort) { this.exposedPort = exposedPort; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof CheContainerPort)) { return false; } CheContainerPort that = (CheContainerPort) o; return Objects.equals(getExposedPort(), that.getExposedPort()); } @Override public int hashCode() { return Objects.hash(getExposedPort()); } @Override public String toString() { return "CheContainerPort{" + "exposedPort='" + exposedPort + '\'' + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/ChePlugin.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** Represents Che plugin in sidecar-powered workspace. */ @JsonIgnoreProperties(ignoreUnknown = true) public class ChePlugin { private String name = null; private String publisher = null; private String id = null; private String version = null; private List containers = new ArrayList<>(); private List initContainers; private List endpoints = new ArrayList<>(); @JsonProperty("workspaceEnv") private List workspaceEnv = new ArrayList<>(); /** Object name. Name must be unique. */ public ChePlugin name(String name) { this.name = name; return this; } public String getName() { return name; } public void setName(String name) { this.name = name; } public ChePlugin id(String id) { this.id = id; return this; } public String getId() { return id; } public void setId(String id) { this.id = id; } public ChePlugin version(String version) { this.version = version; return this; } public String getPublisher() { return publisher; } public void setPublisher(String publisher) { this.publisher = publisher; } public ChePlugin publisher(String publisher) { this.publisher = publisher; return this; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public ChePlugin containers(List containers) { this.containers = containers; return this; } public List getContainers() { if (containers == null) { containers = new ArrayList<>(); } return containers; } public void setContainers(List containers) { this.containers = containers; } public ChePlugin initContainers(List initContainers) { this.initContainers = initContainers; return this; } public List getInitContainers() { if (initContainers == null) { initContainers = new ArrayList<>(); } return initContainers; } public void setInitContainers(List initContainers) { this.initContainers = initContainers; } public ChePlugin endpoints(List endpoints) { this.endpoints = endpoints; return this; } public List getEndpoints() { if (endpoints == null) { endpoints = new ArrayList<>(); } return endpoints; } public void setEndpoints(List endpoints) { this.endpoints = endpoints; } /** List of environment variables to set in all the containers of a workspace */ public ChePlugin workspaceEnv(List workspaceEnv) { this.workspaceEnv = workspaceEnv; return this; } public List getWorkspaceEnv() { if (workspaceEnv == null) { workspaceEnv = new ArrayList<>(); } return workspaceEnv; } public void setWorkspaceEnv(List workspaceEnv) { this.workspaceEnv = workspaceEnv; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ChePlugin)) { return false; } ChePlugin chePlugin = (ChePlugin) o; return Objects.equals(getName(), chePlugin.getName()) && Objects.equals(getPublisher(), chePlugin.getPublisher()) && Objects.equals(getId(), chePlugin.getId()) && Objects.equals(getVersion(), chePlugin.getVersion()) && Objects.equals(getContainers(), chePlugin.getContainers()) && Objects.equals(getEndpoints(), chePlugin.getEndpoints()) && Objects.equals(getWorkspaceEnv(), chePlugin.getWorkspaceEnv()) && Objects.equals(getInitContainers(), chePlugin.getInitContainers()); } @Override public int hashCode() { return Objects.hash( getName(), getPublisher(), getId(), getVersion(), getContainers(), getInitContainers(), getEndpoints(), getWorkspaceEnv()); } @Override public String toString() { return "ChePlugin{" + "name='" + name + '\'' + ", publisher='" + publisher + '\'' + ", id='" + id + '\'' + ", version='" + version + '\'' + ", containers=" + containers + ", init containers=" + initContainers + ", endpoints=" + endpoints + ", workspaceEnv=" + workspaceEnv + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/ChePluginEndpoint.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * Represents a network endpoint that can be accessed by clients inside or outside of workspace. * *

    Whether an endpoint is accessible from the outside of a workspace is defined by {@link * #isPublic()} method. */ @JsonIgnoreProperties(ignoreUnknown = true) public class ChePluginEndpoint { private String name = null; @JsonProperty("public") private boolean isPublic = false; private int targetPort = 0; private Map attributes = new HashMap<>(); public ChePluginEndpoint name(String name) { this.name = name; return this; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isPublic() { return isPublic; } public ChePluginEndpoint setPublic(boolean isPublic) { this.isPublic = isPublic; return this; } public ChePluginEndpoint targetPort(int targetPort) { this.targetPort = targetPort; return this; } public int getTargetPort() { return targetPort; } public void setTargetPort(int targetPort) { this.targetPort = targetPort; } public ChePluginEndpoint attributes(Map attributes) { this.attributes = attributes; return this; } public Map getAttributes() { if (attributes == null) { attributes = new HashMap<>(); } return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ChePluginEndpoint)) { return false; } ChePluginEndpoint that = (ChePluginEndpoint) o; return isPublic() == that.isPublic() && Objects.equals(getName(), that.getName()) && Objects.equals(getTargetPort(), that.getTargetPort()) && Objects.equals(getAttributes(), that.getAttributes()); } @Override public int hashCode() { return Objects.hash(getName(), isPublic(), getTargetPort(), getAttributes()); } @Override public String toString() { return "ChePluginEndpoint{" + "name='" + name + '\'' + ", isPlugin=" + isPublic + ", targetPort='" + targetPort + '\'' + ", attributes=" + attributes + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/Command.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.List; import java.util.Objects; @JsonIgnoreProperties(ignoreUnknown = true) public class Command { private String name = null; @JsonProperty("workingDir") private String workingDir = null; private List command = new ArrayList(); /** */ public Command name(String name) { this.name = name; return this; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** */ public Command workingDir(String workingDir) { this.workingDir = workingDir; return this; } public String getWorkingDir() { return workingDir; } public void setWorkingDir(String workingDir) { this.workingDir = workingDir; } /** */ public Command command(List command) { this.command = command; return this; } public List getCommand() { if (command == null) { command = new ArrayList<>(); } return command; } public void setCommand(List command) { this.command = command; } @Override public boolean equals(java.lang.Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Command command = (Command) o; return Objects.equals(name, command.name) && Objects.equals(workingDir, command.workingDir) && Objects.equals(command, command.command); } @Override public int hashCode() { return Objects.hash(name, workingDir, command); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class Command {\n"); sb.append(" name: ").append(toIndentedString(name)).append("\n"); sb.append(" workingDir: ").append(toIndentedString(workingDir)).append("\n"); sb.append(" command: ").append(toIndentedString(command)).append("\n"); sb.append("}"); return sb.toString(); } /** * Convert the given object to string with each line indented by 4 spaces (except the first line). */ private String toIndentedString(java.lang.Object o) { if (o == null) { return "null"; } return o.toString().replace("\n", "\n "); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/EnvVar.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import java.util.Objects; /** EnvVar represents an environment variable present in a Container. */ public class EnvVar { private String name = null; private String value = null; /** Name of the environment variable. Must be a C_IDENTIFIER. */ public EnvVar name(String name) { this.name = name; return this; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * Variable references $(VAR_NAME) are expanded using the previous defined environment variables * in the container and any service environment variables. If a variable cannot be resolved, the * reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a * double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether * the variable exists or not. Defaults to \"\". */ public EnvVar value(String value) { this.value = value; return this; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public boolean equals(java.lang.Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } EnvVar envVar = (EnvVar) o; return Objects.equals(name, envVar.name) && Objects.equals(value, envVar.value); } @Override public int hashCode() { return Objects.hash(name, value); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class EnvVar {\n"); sb.append(" name: ").append(toIndentedString(name)).append("\n"); sb.append(" value: ").append(toIndentedString(value)).append("\n"); sb.append("}"); return sb.toString(); } /** * Convert the given object to string with each line indented by 4 spaces (except the first line). */ private String toIndentedString(java.lang.Object o) { if (o == null) { return "null"; } return o.toString().replace("\n", "\n "); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/Exec.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * Exec describes a "run in container" action. Command is the command line to execute inside the * container, the working directory for the command is root ('/') in the container's filesystem. T * he command is simply exec'd, it is not run inside a shell, so traditional shell instructions * ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status * of 0 is treated as live/healthy and non-zero is unhealthy. */ public class Exec { @JsonProperty("command") private List command; public Exec command(List commands) { this.command = commands; return this; } public List getCommand() { if (command == null) { command = new ArrayList<>(); } return command; } public void setCommand(List command) { this.command = command; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Exec that = (Exec) o; return Objects.equals(getCommand(), that.getCommand()); } @Override public int hashCode() { return Objects.hash(command); } @Override public String toString() { return "Exec{" + "command=" + command + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/ExtendedPluginFQN.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import com.fasterxml.jackson.annotation.JsonIgnore; import java.net.URI; import java.util.Objects; /** * Represents extended information about plugin identification. * * @author Oleksandr Garagatyi * @see PluginFQN */ public class ExtendedPluginFQN extends PluginFQN { private String name; private String version; private String publisher; public ExtendedPluginFQN(URI registry, String id, String publisher, String name, String version) { super(registry, id); this.publisher = publisher; this.name = name; this.version = version; } /** In this constructor, id is composed from given params */ public ExtendedPluginFQN(String reference, String publisher, String name, String version) { super(reference, publisher + "/" + name + "/" + version); this.publisher = publisher; this.name = name; this.version = version; } public ExtendedPluginFQN(String reference) { super(reference); } public String getPublisher() { return publisher; } public void setPublisher(String publisher) { this.publisher = publisher; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } @JsonIgnore public String getPublisherAndName() { return publisher + "/" + name; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ExtendedPluginFQN)) { return false; } if (!super.equals(o)) { return false; } ExtendedPluginFQN that = (ExtendedPluginFQN) o; return Objects.equals(getName(), that.getName()) && Objects.equals(getVersion(), that.getVersion()) && Objects.equals(getPublisher(), that.getPublisher()); } @Override public int hashCode() { return Objects.hash(super.hashCode(), getName(), getVersion(), getPublisher()); } @Override public String toString() { return String.format( "{id:%s, registry:%s, publisher:%s, name:%s, version:%s, reference:%s}", getId(), getRegistry(), getPublisher(), getName(), getVersion(), getReference()); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/Handler.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; /** Handler defines a specific action that should be taken on postStart or preStop. */ public class Handler { @JsonProperty("exec") private Exec exec; public Handler exec(Exec exec) { this.exec = exec; return this; } public Exec getExec() { return exec; } public void setExec(Exec exec) { this.exec = exec; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Handler that = (Handler) o; return Objects.equals(getExec(), that.getExec()); } @Override public int hashCode() { return Objects.hash(exec); } @Override public String toString() { return "Handler{" + "exec=" + exec + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/Lifecycle.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; /** * Lifecycle describes actions that the management system should take in response to container * lifecycle events. * *

    For the PostStart and PreStop lifecycle handlers, management of the container blocks until the * action is complete, unless the container process fails, in which case the handler is aborted. * *

    PostStart is called immediately after a container is created. If the handler fails, the * container is terminated and restarted according to its restart policy. Other management of the * container blocks until the hook completes. More info: * https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks * *

    PreStop is called immediately before a container is terminated due to an API request or * management event such as liveness/startup probe failure, preemption, resource contention, etc. * The handler is not called if the container crashes or exits. The reason for termination is passed * to the handler. The Pod's termination grace period countdown begins before the PreStop hooked is * executed. Regardless of the outcome of the handler, the container will eventually terminate * within the Pod's termination grace period. Other management of the container blocks until the * hook completes or until the termination grace period is reached. More info: * https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks */ public class Lifecycle { @JsonProperty("preStop") private Handler preStop = new Handler(); @JsonProperty("postStart") private Handler postStart = new Handler(); public Lifecycle preStop(Handler preStop) { this.preStop = preStop; return this; } public Handler getPreStop() { return preStop; } public void setPreStop(Handler preStop) { this.preStop = preStop; } public Lifecycle postStart(Handler postStart) { this.postStart = postStart; return this; } public Handler getPostStart() { return postStart; } public void setPostStart(Handler postStart) { this.postStart = postStart; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Lifecycle that = (Lifecycle) o; return Objects.equals(preStop, that.preStop) && Objects.equals(postStart, that.postStart); } @Override public int hashCode() { return Objects.hash(postStart, preStop); } @Override public String toString() { return "Lifecycle{" + "postStart=" + postStart + ", preStop=" + preStop + '}'; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/PluginFQN.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import java.net.URI; import java.util.Objects; /** * Represents full information about plugin, including registry address and id, or direct reference * to plugin descriptor. When {@link PluginFQN#reference} and {@link PluginFQN#id} are set * simultaneously, {@link PluginFQN#reference} should take precedence. * * @author Max Shaposhnyk */ @JsonInclude(Include.NON_NULL) public class PluginFQN { private URI registry; private String id; private String reference; public PluginFQN(URI registry, String id) { this.registry = registry; this.id = id; } protected PluginFQN(String reference, String id) { this.reference = reference; this.id = id; } public PluginFQN(String reference) { this.reference = reference; } public URI getRegistry() { return registry; } public void setRegistry(URI registry) { this.registry = registry; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getReference() { return reference; } public void setReference(String reference) { this.reference = reference; } @Override public int hashCode() { return Objects.hash(getRegistry(), getId(), getReference()); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof PluginFQN)) { return false; } PluginFQN other = (PluginFQN) obj; return Objects.equals(getId(), other.getId()) && Objects.equals(getRegistry(), other.getRegistry()) && Objects.equals(getReference(), other.getReference()); } @Override public String toString() { return String.format( "{id:%s, registry:%s, reference:%s}", this.id, this.registry, this.reference); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/Volume.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins.model; import java.util.Objects; public class Volume { private String name = null; private String mountPath = null; private boolean ephemeral; /** */ public Volume name(String name) { this.name = name; return this; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** mountPath of the volume in running container */ public Volume mountPath(String path) { this.mountPath = path; return this; } public String getMountPath() { return mountPath; } public void setMountPath(String mountPath) { this.mountPath = mountPath; } public Volume ephemeral(boolean ephemeral) { this.ephemeral = ephemeral; return this; } public boolean isEphemeral() { return ephemeral; } public void setEphemeral(boolean ephemeral) { this.ephemeral = ephemeral; } @Override public boolean equals(java.lang.Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Volume volume = (Volume) o; return Objects.equals(name, volume.name) && Objects.equals(mountPath, volume.mountPath) && Objects.equals(ephemeral, volume.ephemeral); } @Override public int hashCode() { return Objects.hash(name, mountPath, ephemeral); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class Volume {\n"); sb.append(" name: ").append(toIndentedString(name)).append("\n"); sb.append(" mountPath: ").append(toIndentedString(mountPath)).append("\n"); sb.append(" ephemeral: ").append(toIndentedString(ephemeral)).append("\n"); sb.append("}"); return sb.toString(); } /** * Convert the given object to string with each line indented by 4 spaces (except the first line). */ private String toIndentedString(java.lang.Object o) { if (o == null) { return "null"; } return o.toString().replace("\n", "\n "); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/PreviewUrlLinksVariableGeneratorTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import static java.util.Collections.singletonList; import static org.testng.Assert.*; import jakarta.ws.rs.core.UriBuilder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.RuntimeImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.PreviewUrlImpl; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class PreviewUrlLinksVariableGeneratorTest { private PreviewUrlLinksVariableGenerator generator; private UriBuilder uriBuilder; @BeforeMethod public void setUp() { generator = new PreviewUrlLinksVariableGenerator(); uriBuilder = UriBuilder.fromUri("http://host/path"); } @Test public void shouldDoNothingWhenSomethingIsNull() { assertTrue(generator.genLinksMapAndUpdateCommands(null, null).isEmpty()); } @Test public void shouldDoNothingWhenRuntimeIsNull() { WorkspaceImpl w = new WorkspaceImpl(); w.setRuntime(null); assertTrue(generator.genLinksMapAndUpdateCommands(w, uriBuilder).isEmpty()); } @Test public void shouldDoNothingWhenCommandsIsNull() { WorkspaceImpl w = createWorkspaceWithCommands(null); assertTrue(generator.genLinksMapAndUpdateCommands(w, uriBuilder).isEmpty()); } @Test public void shouldDoNothingWhenNoCommandWithPreviewUrl() { WorkspaceImpl w = createWorkspaceWithCommands(singletonList(new CommandImpl("a", "a", "a"))); assertTrue(generator.genLinksMapAndUpdateCommands(w, uriBuilder).isEmpty()); } @Test public void shouldDoNothingWhenNoCommandWithPreviewurlAttribute() { CommandImpl command = new CommandImpl("a", "a", "a", new PreviewUrlImpl(123, null), Collections.emptyMap()); WorkspaceImpl w = createWorkspaceWithCommands(singletonList(command)); assertTrue(generator.genLinksMapAndUpdateCommands(w, uriBuilder).isEmpty()); } @Test public void shouldUpdateCommandAndReturnLinkMapWhenPreviewUrlFound() { Map commandAttrs = new HashMap<>(); commandAttrs.put(Command.PREVIEW_URL_ATTRIBUTE, "preview_url_host"); CommandImpl command = new CommandImpl("a", "a", "a", new PreviewUrlImpl(123, null), commandAttrs); WorkspaceImpl w = createWorkspaceWithCommands(Arrays.asList(command, new CommandImpl("b", "b", "b"))); Map linkMap = generator.genLinksMapAndUpdateCommands(w, uriBuilder); assertEquals(linkMap.size(), 1); assertEquals(linkMap.values().iterator().next(), "http://preview_url_host"); String varKey = linkMap.keySet().iterator().next(); assertTrue(varKey.startsWith("previewurl/")); Command aCommand = w.getRuntime().getCommands().stream() .filter(c -> c.getName().equals("a")) .findFirst() .get(); assertTrue(aCommand.getAttributes().get(Command.PREVIEW_URL_ATTRIBUTE).contains(varKey)); assertEquals(aCommand.getAttributes().get(Command.PREVIEW_URL_ATTRIBUTE), "${" + varKey + "}"); } @Test public void variableNamesForTwoCommandsWithSimilarNameMustBeDifferent() { Map commandAttrs = new HashMap<>(); commandAttrs.put(Command.PREVIEW_URL_ATTRIBUTE, "preview_url_host"); CommandImpl command = new CommandImpl("run command", "a", "a", new PreviewUrlImpl(123, null), commandAttrs); CommandImpl command2 = new CommandImpl(command); command2.setName("runcommand"); WorkspaceImpl w = createWorkspaceWithCommands(Arrays.asList(command, command2)); Map linkMap = generator.genLinksMapAndUpdateCommands(w, uriBuilder); assertEquals(linkMap.size(), 2); List commandsAfter = w.getRuntime().getCommands(); assertNotEquals( commandsAfter.get(1).getAttributes().get(Command.PREVIEW_URL_ATTRIBUTE), commandsAfter.get(0).getAttributes().get(Command.PREVIEW_URL_ATTRIBUTE)); } @Test public void shouldGetHttpsWhenUriBuilderHasHttps() { UriBuilder httpsUriBuilder = UriBuilder.fromUri("https://host/path"); Map commandAttrs = new HashMap<>(); commandAttrs.put(Command.PREVIEW_URL_ATTRIBUTE, "preview_url_host"); CommandImpl command = new CommandImpl("run command", "a", "a", new PreviewUrlImpl(123, null), commandAttrs); Map linkMap = generator.genLinksMapAndUpdateCommands( createWorkspaceWithCommands(singletonList(command)), httpsUriBuilder); assertTrue(linkMap.values().iterator().next().startsWith("https://")); } @Test public void shouldAppendPathWhenDefinedInPreviewUrl() { Map commandAttrs = new HashMap<>(); commandAttrs.put(Command.PREVIEW_URL_ATTRIBUTE, "preview_url_host"); CommandImpl command = new CommandImpl("run command", "a", "a", new PreviewUrlImpl(123, "testpath"), commandAttrs); WorkspaceImpl workspace = createWorkspaceWithCommands(singletonList(command)); Map linkMap = generator.genLinksMapAndUpdateCommands(workspace, uriBuilder); assertTrue(linkMap.values().iterator().next().endsWith("preview_url_host")); String linkKey = linkMap.keySet().iterator().next(); assertEquals( workspace .getRuntime() .getCommands() .get(0) .getAttributes() .get(Command.PREVIEW_URL_ATTRIBUTE), "${" + linkKey + "}testpath"); } @Test public void shouldAppendQueryParamsWhenDefinedInPreviewUrl() { Map commandAttrs = new HashMap<>(); commandAttrs.put(Command.PREVIEW_URL_ATTRIBUTE, "preview_url_host"); CommandImpl command = new CommandImpl("run command", "a", "a", new PreviewUrlImpl(123, "?a=b"), commandAttrs); WorkspaceImpl workspace = createWorkspaceWithCommands(singletonList(command)); Map linkMap = generator.genLinksMapAndUpdateCommands(workspace, uriBuilder); assertTrue(linkMap.values().iterator().next().endsWith("preview_url_host")); String linkKey = linkMap.keySet().iterator().next(); assertEquals( workspace .getRuntime() .getCommands() .get(0) .getAttributes() .get(Command.PREVIEW_URL_ATTRIBUTE), "${" + linkKey + "}?a=b"); } @Test public void shouldAppendMultipleQueryParamsWhenDefinedInPreviewUrl() { Map commandAttrs = new HashMap<>(); commandAttrs.put(Command.PREVIEW_URL_ATTRIBUTE, "preview_url_host"); CommandImpl command = new CommandImpl("run command", "a", "a", new PreviewUrlImpl(123, "?a=b&c=d"), commandAttrs); WorkspaceImpl workspace = createWorkspaceWithCommands(singletonList(command)); Map linkMap = generator.genLinksMapAndUpdateCommands(workspace, uriBuilder); assertTrue(linkMap.values().iterator().next().endsWith("preview_url_host")); String linkKey = linkMap.keySet().iterator().next(); assertEquals( workspace .getRuntime() .getCommands() .get(0) .getAttributes() .get(Command.PREVIEW_URL_ATTRIBUTE), "${" + linkKey + "}?a=b&c=d"); } @Test public void shouldAppendPathWithQueryParamsWhenDefinedInPreviewUrl() { Map commandAttrs = new HashMap<>(); commandAttrs.put(Command.PREVIEW_URL_ATTRIBUTE, "preview_url_host"); CommandImpl command = new CommandImpl( "run command", "a", "a", new PreviewUrlImpl(123, "/hello?a=b"), commandAttrs); WorkspaceImpl workspace = createWorkspaceWithCommands(singletonList(command)); Map linkMap = generator.genLinksMapAndUpdateCommands(workspace, uriBuilder); assertTrue(linkMap.values().iterator().next().endsWith("preview_url_host")); String linkKey = linkMap.keySet().iterator().next(); assertEquals( workspace .getRuntime() .getCommands() .get(0) .getAttributes() .get(Command.PREVIEW_URL_ATTRIBUTE), "${" + linkKey + "}/hello?a=b"); } private WorkspaceImpl createWorkspaceWithCommands(List commands) { RuntimeImpl runtime = new RuntimeImpl("", Collections.emptyMap(), "", commands, new ArrayList<>()); WorkspaceImpl w = new WorkspaceImpl(); w.setRuntime(runtime); return w; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProviderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedHashMap; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.eclipse.che.dto.server.DtoFactory; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class WorkspaceEntityProviderTest { @Mock private DevfileParser devfileParser; @InjectMocks private WorkspaceEntityProvider workspaceEntityProvider; @Test public void shouldBuildDtoFromValidJson() throws Exception { when(devfileParser.parseJson(anyString())).thenReturn(new DevfileImpl()); WorkspaceDto actual = newDto(WorkspaceDto.class).withDevfile(newDto(DevfileDto.class)); workspaceEntityProvider.readFrom( WorkspaceDto.class, WorkspaceDto.class, null, MediaType.APPLICATION_JSON_TYPE, new MultivaluedHashMap<>(), new ByteArrayInputStream( DtoFactory.getInstance().toJson(actual).getBytes(StandardCharsets.UTF_8))); verify(devfileParser).parseJson(anyString()); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceValidatorTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_REQUEST_ATTRIBUTE; import static org.eclipse.che.dto.server.DtoFactory.newDto; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.workspace.shared.Constants; import org.eclipse.che.api.workspace.shared.dto.CommandDto; import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; import org.eclipse.che.api.workspace.shared.dto.MachineConfigDto; import org.eclipse.che.api.workspace.shared.dto.RecipeDto; import org.eclipse.che.api.workspace.shared.dto.ServerConfigDto; import org.eclipse.che.api.workspace.shared.dto.VolumeDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.mockito.InjectMocks; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests for {@link WorkspaceValidator}. * * @author Alexander Reshetnyak */ @Listeners(MockitoTestNGListener.class) public class WorkspaceValidatorTest { @InjectMocks private WorkspaceValidator wsValidator; @Test public void shouldValidateCorrectWorkspace() throws Exception { final WorkspaceConfigDto config = createConfig(); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Workspace name required") public void shouldFailValidationIfNameIsNull() throws Exception { final WorkspaceConfigDto config = createConfig(); config.withName(null); wsValidator.validateConfig(config); } @Test( dataProvider = "invalidNameProvider", expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Incorrect workspace name, it must be between 3 and 100 " + "characters and may contain digits, latin letters, underscores, dots, dashes and must " + "start and end only with digits, latin letters or underscores") public void shouldFailValidationIfNameIsInvalid(String name) throws Exception { final WorkspaceConfigDto config = createConfig(); config.withName(name); wsValidator.validateConfig(config); } @DataProvider(name = "invalidNameProvider") public static Object[][] invalidNameProvider() { return new Object[][] { {".name"}, {"name."}, {"-name"}, {"name-"}, { "long-name1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" }, {"_name"}, {"name_"} }; } @Test(dataProvider = "validNameProvider") public void shouldValidateCorrectWorkspaceName(String name) throws Exception { final WorkspaceConfigDto config = createConfig(); config.withName(name); wsValidator.validateConfig(config); } @DataProvider(name = "validNameProvider") public static Object[][] validNameProvider() { return new Object[][] { {"name"}, {"quiteLongName1234567"}, {"name-with-dashes"}, {"name.with.dots"}, {"name0with1digits"}, {"mixed-symbols.name12"}, {"123456"}, {"name_name"}, {"123-456.78"} }; } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Attribute name 'null' is not valid") public void shouldFailValidationIfAttributeNameIsNull() throws Exception { Map attributes = new HashMap<>(); attributes.put(null, "value1"); wsValidator.validateAttributes(attributes); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Attribute name '' is not valid") public void shouldFailValidationIfAttributeNameIsEmpty() throws Exception { wsValidator.validateAttributes(ImmutableMap.of("", "value1")); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Attribute name 'codenvy_key' is not valid") public void shouldFailValidationIfAttributeNameStartsWithWordCodenvy() throws Exception { wsValidator.validateAttributes(ImmutableMap.of("codenvy_key", "value1")); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Workspace default environment name required") public void shouldFailValidationOfChe6WSIfDefaultEnvNameIsNull() throws Exception { final WorkspaceConfigDto config = createConfig(); config.setDefaultEnv(null); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Workspace default environment name required") public void shouldFailValidationOfChe6WSIfDefaultEnvNameIsEmpty() throws Exception { final WorkspaceConfigDto config = createConfig(); config.setDefaultEnv(""); wsValidator.validateConfig(config); } @Test public void shouldNotFailValidationOfChe7WSIfDefaultEnvNameIsNullAndNoEnvIsPresent() throws Exception { final WorkspaceConfigDto config = createConfig(); config.setDefaultEnv(null); config.getEnvironments().clear(); config.getAttributes().put(Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, "something"); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Workspace default environment configuration required") public void shouldFailValidationIfEnvWithDefaultEnvNameIsNull() throws Exception { final WorkspaceConfigDto config = createConfig(); config.setEnvironments(null); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Workspace ws-name contains command with null or empty name") public void shouldFailValidationIfCommandNameIsNull() throws Exception { final WorkspaceConfigDto config = createConfig(); config.getCommands().get(0).withName(null); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Value '.*' of attribute '" + MEMORY_LIMIT_ATTRIBUTE + "' in machine '.*' is illegal", dataProvider = "illegalMemoryAttributeValueProvider") public void shouldFailValidationIfMemoryLimitMachineAttributeHasIllegalValue( String attributeValue) throws Exception { final WorkspaceConfigDto config = createConfig(); EnvironmentDto env = config.getEnvironments().values().iterator().next(); MachineConfigDto machine = env.getMachines().values().iterator().next(); machine.getAttributes().put(MEMORY_LIMIT_ATTRIBUTE, attributeValue); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Value '.*' of attribute '" + MEMORY_REQUEST_ATTRIBUTE + "' in machine '.*' is illegal", dataProvider = "illegalMemoryAttributeValueProvider") public void shouldFailValidationIfMemoryRequestMachineAttributeHasIllegalValue( String attributeValue) throws Exception { final WorkspaceConfigDto config = createConfig(); EnvironmentDto env = config.getEnvironments().values().iterator().next(); MachineConfigDto machine = env.getMachines().values().iterator().next(); machine.getAttributes().put(MEMORY_REQUEST_ATTRIBUTE, attributeValue); wsValidator.validateConfig(config); } @DataProvider(name = "illegalMemoryAttributeValueProvider") public static Object[][] illegalMemoryAttributeValueProvider() { return new Object[][] {{"text"}, {""}, {"123MB"}, {"123GB"}, {"123KB"}}; } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Workspace ws-name contains command with null or empty name") public void shouldFailValidationIfCommandNameIsEmpty() throws Exception { final WorkspaceConfigDto config = createConfig(); config.getCommands().get(0).withName(null); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Command line or content required for command '.*' in workspace '.*'\\.") public void shouldFailValidationIfCommandLineIsNull() throws Exception { final WorkspaceConfigDto config = createConfig(); config.getCommands().get(0).withCommandLine(null); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Command line or content required for command '.*' in workspace '.*'\\.") public void shouldFailValidationIfCommandLineIsEmpty() throws Exception { final WorkspaceConfigDto config = createConfig(); config.getCommands().get(0).withCommandLine(""); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Volume name '.*' in machine '.*' is invalid", dataProvider = "illegalVolumeNameProvider") public void shouldFailValidationIfVolumeNameDoesNotMatchCriteria(String volumeName) throws Exception { final WorkspaceConfigDto config = createConfig(); EnvironmentDto env = config.getEnvironments().values().iterator().next(); MachineConfigDto machine = env.getMachines().values().iterator().next(); machine.getVolumes().put(volumeName, newDto(VolumeDto.class).withPath("/path")); wsValidator.validateConfig(config); } @DataProvider(name = "illegalVolumeNameProvider") public static Object[][] illegalVolumeNameProvider() { return new Object[][] { {"0begin_with_number"}, {"begin_with_dot."}, {"begin_with_underscore_"}, {"begin_with_hyphen-"}, {"with_@_special_char"}, {"with_@_special_char"}, {"veryveryveryveryveryveryverylongname"}, {"volume/name"}, }; } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Path of volume '.*' in machine '.*' is invalid. It should not be empty") public void shouldFailValidationIfVolumeValueIsEmpty() throws Exception { final WorkspaceConfigDto config = createConfig(); EnvironmentDto env = config.getEnvironments().values().iterator().next(); MachineConfigDto machine = env.getMachines().values().iterator().next(); machine.getVolumes().put("volume1", null); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Path of volume '.*' in machine '.*' is invalid. It should not be empty") public void shouldFailValidationIfVolumePathIsEmpty() throws Exception { final WorkspaceConfigDto config = createConfig(); EnvironmentDto env = config.getEnvironments().values().iterator().next(); MachineConfigDto machine = env.getMachines().values().iterator().next(); machine.getVolumes().put("volume1", newDto(VolumeDto.class).withPath("")); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Path of volume '.*' in machine '.*' is invalid. It should not be empty") public void shouldFailValidationIfVolumePathIsNull() throws Exception { final WorkspaceConfigDto config = createConfig(); EnvironmentDto env = config.getEnvironments().values().iterator().next(); MachineConfigDto machine = env.getMachines().values().iterator().next(); machine.getVolumes().put("volume1", newDto(VolumeDto.class).withPath(null)); wsValidator.validateConfig(config); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Path '.*' of volume '.*' in machine '.*' is invalid. It should be absolute") public void shouldFailValidationIfVolumePathIsNotAbsolute() throws Exception { final WorkspaceConfigDto config = createConfig(); EnvironmentDto env = config.getEnvironments().values().iterator().next(); MachineConfigDto machine = env.getMachines().values().iterator().next(); machine.getVolumes().put("volume1", newDto(VolumeDto.class).withPath("not/absolute/path")); wsValidator.validateConfig(config); } private static WorkspaceConfigDto createConfig() { final WorkspaceConfigDto workspaceConfigDto = newDto(WorkspaceConfigDto.class).withName("ws-name").withDefaultEnv("dev-env"); MachineConfigDto machineConfig = newDto(MachineConfigDto.class) .withServers( singletonMap( "ref1", newDto(ServerConfigDto.class) .withPort("8080/tcp") .withProtocol("https") .withAttributes(singletonMap("key", "value")))) .withAttributes(new HashMap<>(singletonMap(MEMORY_LIMIT_ATTRIBUTE, "1000000"))); EnvironmentDto env = newDto(EnvironmentDto.class) .withMachines(singletonMap("devmachine1", machineConfig)) .withRecipe( newDto(RecipeDto.class) .withType("type") .withContent("content") .withContentType("content type")); workspaceConfigDto.setEnvironments(new HashMap<>(singletonMap("dev-env", env))); List commandDtos = new ArrayList<>(); commandDtos.add( newDto(CommandDto.class) .withName("command_name") .withType("maven") .withCommandLine("mvn clean install") .withAttributes( new HashMap<>(singletonMap("cmd-attribute-name", "cmd-attribute-value")))); workspaceConfigDto.setCommands(commandDtos); return workspaceConfigDto; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProviderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import jakarta.ws.rs.NotSupportedException; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedHashMap; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class DevfileEntityProviderTest { @Mock private DevfileParser devfileParser; @InjectMocks private DevfileEntityProvider devfileEntityProvider; @Test public void shouldBuildDtoFromValidYaml() throws Exception { when(devfileParser.parseYaml(anyString())).thenReturn(new DevfileImpl()); devfileEntityProvider.readFrom( DevfileDto.class, DevfileDto.class, null, MediaType.valueOf("text/x-yaml"), new MultivaluedHashMap<>(), getClass().getClassLoader().getResourceAsStream("devfile/devfile.yaml")); verify(devfileParser).parseYaml(anyString()); } @Test public void shouldBuildDtoFromValidJson() throws Exception { when(devfileParser.parseJson(anyString())).thenReturn(new DevfileImpl()); devfileEntityProvider.readFrom( DevfileDto.class, DevfileDto.class, null, MediaType.APPLICATION_JSON_TYPE, new MultivaluedHashMap<>(), getClass().getClassLoader().getResourceAsStream("devfile/devfile.json")); verify(devfileParser).parseJson(anyString()); } @Test( expectedExceptions = NotSupportedException.class, expectedExceptionsMessageRegExp = "Unknown media type text/plain") public void shouldThrowErrorOnInvalidMediaType() throws Exception { devfileEntityProvider.readFrom( DevfileDto.class, DevfileDto.class, null, MediaType.TEXT_PLAIN_TYPE, new MultivaluedHashMap<>(), getClass().getClassLoader().getResourceAsStream("devfile/devfile.json")); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileParserTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator; import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class DevfileParserTest { private static final String DEVFILE_YAML_CONTENT = "devfile yaml stub"; @Mock private DevfileIntegrityValidator integrityValidator; @Mock private ObjectMapper jsonMapper; @Mock private ObjectMapper yamlMapper; @Mock private FileContentProvider contentProvider; @Mock private JsonNode devfileJsonNode; private DevfileImpl devfile; private DevfileParser devfileParser; @BeforeMethod public void setUp() throws Exception { devfile = new DevfileImpl(); devfileParser = new DevfileParser(integrityValidator, yamlMapper, jsonMapper); lenient().when(jsonMapper.treeToValue(any(), eq(DevfileImpl.class))).thenReturn(devfile); lenient().when(yamlMapper.treeToValue(any(), eq(DevfileImpl.class))).thenReturn(devfile); lenient().when(yamlMapper.readTree(anyString())).thenReturn(devfileJsonNode); } @Test public void testValidateAndParse() throws Exception { // when DevfileImpl parsed = devfileParser.parseYaml(DEVFILE_YAML_CONTENT); // then assertEquals(parsed, devfile); verify(yamlMapper).treeToValue(devfileJsonNode, DevfileImpl.class); verify(integrityValidator).validateDevfile(devfile); } @Test public void testParseRaw() throws DevfileFormatException { JsonNode parsed = devfileParser.parseYamlRaw(DEVFILE_YAML_CONTENT); assertEquals(parsed, devfileJsonNode); } @Test(expectedExceptions = DevfileFormatException.class) public void testParseRawThrowsExceptionWhenNothinParsed() throws DevfileFormatException, JsonProcessingException { when(yamlMapper.readTree(DEVFILE_YAML_CONTENT)).thenReturn(null); devfileParser.parseYamlRaw(DEVFILE_YAML_CONTENT); } @Test(expectedExceptions = DevfileFormatException.class) public void testParseRawThrowsDevfilExceptionWhenJsonParsingFails() throws JsonProcessingException, DevfileFormatException { when(yamlMapper.readTree(DEVFILE_YAML_CONTENT)).thenThrow(JsonProcessingException.class); devfileParser.parseYamlRaw(DEVFILE_YAML_CONTENT); } @Test public void testInitializingDevfileMapsAfterParsing() throws Exception { // given CommandImpl command = new CommandImpl(); command.getActions().add(new ActionImpl()); devfile.getCommands().add(command); ComponentImpl component = new ComponentImpl(); component.getEndpoints().add(new EndpointImpl()); devfile.getComponents().add(component); // when DevfileImpl parsed = devfileParser.parseYaml(DEVFILE_YAML_CONTENT); // then assertNotNull(parsed.getCommands().get(0).getAttributes()); assertNotNull(parsed.getComponents().get(0).getSelector()); assertNotNull(parsed.getComponents().get(0).getEndpoints().get(0).getAttributes()); } @Test public void shouldResolveReferencesIntoReferenceContentForFactories() throws Exception { String referenceContent = "my_content_yaml_v3"; when(contentProvider.fetchContent(anyString())).thenReturn(referenceContent); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference("myfile.yaml"); devfile.getComponents().add(component); // when devfileParser.resolveReference(devfile, contentProvider); // then verify(contentProvider).fetchContent(eq("myfile.yaml")); assertEquals(devfile.getComponents().get(0).getReferenceContent(), referenceContent); } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Unable to parse Devfile - provided source is empty") public void shouldThrowDevfileExceptionWhenEmptyObjectProvided() throws Exception { // when devfileParser.parseJson("{}"); } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Unable to parse Devfile - provided source is empty") public void shouldThrowDevfileExceptionWhenEmptySourceProvided() throws Exception { // when devfileParser.parseJson(""); } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Unable to resolve reference of component: test") public void shouldThrowDevfileExceptionWhenReferenceIsNotResolvable() throws Exception { when(contentProvider.fetchContent(anyString())).thenThrow(IOException.class); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setAlias("test"); component.setReference("myfile.yaml"); devfile.getComponents().add(component); // when devfileParser.resolveReference(devfile, contentProvider); // then exception is thrown } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "non valid") public void shouldThrowExceptionWhenErrorOccurredDuringDevfileParsing() throws Exception { // given JsonProcessingException jsonException = mock(JsonProcessingException.class); when(jsonException.getMessage()).thenReturn("non valid"); when(jsonMapper.readTree(anyString())).thenReturn(devfileJsonNode); doThrow(jsonException).when(jsonMapper).treeToValue(any(), eq(DevfileImpl.class)); // when devfileParser.parseJson(DEVFILE_YAML_CONTENT); } @Test public void shouldConvertYamlToMapAndValidate() throws Exception { // when devfileParser.convertYamlToMap(devfileJsonNode); // then verify(yamlMapper).convertValue(eq(devfileJsonNode), any(TypeReference.class)); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileVersionTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static org.testng.Assert.*; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.testng.annotations.Test; public class DevfileVersionTest { private final DevfileVersionDetector devfileVersionDetector = new DevfileVersionDetector(); @Test(expectedExceptions = DevfileException.class) public void shouldThrowExceptionWhenEmptyDevfile() throws DevfileException { JsonNode devfile = new ObjectNode(JsonNodeFactory.instance); devfileVersionDetector.devfileVersion(devfile); } @Test public void shouldReturnApiVersion() throws DevfileException { ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance); devfile.put("apiVersion", "1.1.1"); assertEquals(devfileVersionDetector.devfileVersion(devfile), "1.1.1"); } @Test public void shouldReturnSchemaVersion() throws DevfileException { ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance); devfile.put("schemaVersion", "1.1.1"); assertEquals(devfileVersionDetector.devfileVersion(devfile), "1.1.1"); } @Test public void shouldReturnApiVersionWhenBothDefined() throws DevfileException { ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance); devfile.put("apiVersion", "1"); devfile.put("schemaVersion", "2"); assertEquals(devfileVersionDetector.devfileVersion(devfile), "1"); } @Test public void shouldReturnMainVersionFromSchemaVersion() throws DevfileException { ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance); devfile.put("schemaVersion", "10.1.1"); assertEquals(devfileVersionDetector.devfileMajorVersion(devfile), 10); } @Test public void shouldReturnMainVersionFromApiVersion() throws DevfileException { ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance); devfile.put("apiVersion", "11.1.1"); assertEquals(devfileVersionDetector.devfileMajorVersion(devfile), 11); } @Test(expectedExceptions = DevfileException.class) public void shouldThrowExceptionWhenVersionNotDefined() throws DevfileException { ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance); devfileVersionDetector.devfileMajorVersion(devfile); } @Test public void shouldReturnMajorVersionWhenIsNumberString() throws DevfileException { ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance); devfile.put("apiVersion", "2"); assertEquals(devfileVersionDetector.devfileMajorVersion(devfile), 2); } @Test public void shouldReturnMajorVersionWhenIsNumber() throws DevfileException { ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance); devfile.put("apiVersion", 2); assertEquals(devfileVersionDetector.devfileMajorVersion(devfile), 2); } @Test(expectedExceptions = DevfileException.class) public void shouldThrowExceptionWhenVersionIsNotNumber() throws DevfileException { ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance); devfile.put("apiVersion", "a"); devfileVersionDetector.devfileMajorVersion(devfile); } @Test(expectedExceptions = DevfileException.class) public void shouldThrowExceptionWhenVersionIsNotSemverNumber() throws DevfileException { ObjectNode devfile = new ObjectNode(JsonNodeFactory.instance); devfile.put("apiVersion", "a.a"); devfileVersionDetector.devfileMajorVersion(devfile); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/OverridePropertiesApplierTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.workspace.server.devfile.exception.OverrideParameterException; import org.testng.annotations.Test; public class OverridePropertiesApplierTest { private final OverridePropertiesApplier applier = new OverridePropertiesApplier(); private final ObjectMapper jsonMapper = new ObjectMapper(); @Test public void shouldOverrideExistingPropertiesInDevfile() throws Exception { String json = "{" + "\"apiVersion\": \"1.0.0\"," + "\"metadata\": {" + " \"generateName\": \"python\"" + " }" + "}"; Map overrides = new HashMap<>(); overrides.put("apiVersion", "2.0.0"); overrides.put("metadata.generateName", "go"); // when JsonNode result = applier.applyPropertiesOverride(jsonMapper.readTree(json), overrides); // then assertEquals(result.get("apiVersion").textValue(), "2.0.0"); assertEquals(result.get("metadata").get("generateName").textValue(), "go"); } @Test public void shouldNotOverrideDevfileFilenameInDevfile() throws Exception { String json = "{" + "\"apiVersion\": \"2.0.0\"," + "\"metadata\": {" + " \"generateName\": \"python\"" + " }" + "}"; Map overrides = new HashMap<>(); overrides.put("devfileFilename", "devfile.v2"); overrides.put("metadata.generateName", "go"); // when JsonNode result = applier.applyPropertiesOverride(jsonMapper.readTree(json), overrides); // then assertEquals(result.get("metadata").get("generateName").textValue(), "go"); // this parameter is accepted but not added into the JSON assertNull(result.get("devfileFilename")); } @Test public void shouldCreateUnExistingOverridePropertiesInDevfile() throws Exception { String json = "{" + "\"apiVersion\": \"1.0.0\"" + "}"; Map overrides = new HashMap<>(); overrides.put("metadata.generateName", "go"); // when JsonNode result = applier.applyPropertiesOverride(jsonMapper.readTree(json), overrides); assertEquals(result.get("metadata").get("generateName").textValue(), "go"); } @Test public void shouldCreateUnExistingAttributesInDevfile() throws Exception { String json = "{" + "\"apiVersion\": \"1.0.0\"" + "}"; Map overrides = new HashMap<>(); overrides.put("attributes.persistVolumes", "true"); // when JsonNode result = applier.applyPropertiesOverride(jsonMapper.readTree(json), overrides); assertEquals(result.get("attributes").get("persistVolumes").textValue(), "true"); } @Test public void shouldUpdateExistingAttributesInDevfile() throws Exception { String json = "{" + "\"apiVersion\": \"1.0.0\"," + "\"attributes\": {" + " \"persistVolumes\": \"false\"" + " }" + "}"; Map overrides = new HashMap<>(); overrides.put("attributes.persistVolumes", "true"); // when JsonNode result = applier.applyPropertiesOverride(jsonMapper.readTree(json), overrides); assertEquals(result.get("attributes").get("persistVolumes").textValue(), "true"); } @Test public void shouldUpdateExistingAttributesWithDotsInDevfile() throws Exception { String json = "{" + "\"apiVersion\": \"1.0.0\"," + "\"attributes\": {" + " \"che.persistVolumes\": \"false\"" + " }" + "}"; Map overrides = new HashMap<>(); overrides.put("attributes.che.persistVolumes", "true"); // when JsonNode result = applier.applyPropertiesOverride(jsonMapper.readTree(json), overrides); assertEquals(result.get("attributes").get("che.persistVolumes").textValue(), "true"); } @Test public void shouldRewriteValueInProjectsArrayElementByName() throws Exception { String json = "{" + "\"apiVersion\": \"1.0.0\"," + "\"projects\": [" + " {" + " \"name\": \"test1\"," + " \"clonePath\": \"/foo/bar1\"" + " }," + " {" + " \"name\": \"test2\"," + " \"clonePath\": \"/foo/bar2\"" + " }" + " ]" + "}"; Map overrides = new HashMap<>(); overrides.put("projects.test1.clonePath", "baz1"); overrides.put("projects.test2.clonePath", "baz2"); // when JsonNode result = applier.applyPropertiesOverride(jsonMapper.readTree(json), overrides); // then assertEquals(result.get("projects").get(0).get("clonePath").textValue(), "baz1"); assertEquals(result.get("projects").get(1).get("clonePath").textValue(), "baz2"); } @Test( expectedExceptions = OverrideParameterException.class, expectedExceptionsMessageRegExp = "Cannot apply override: object with name 'test3' not found in array of projects.") public void shouldThrowExceptionIfOverrideArrayObjectNotFoundByName() throws Exception { String json = "{" + "\"apiVersion\": \"1.0.0\"," + "\"projects\": [" + " {" + " \"name\": \"test1\"," + " \"clonePath\": \"/foo/bar1\"" + " }," + " {" + " \"name\": \"test2\"," + " \"clonePath\": \"/foo/bar2\"" + " }" + " ]" + "}"; Map overrides = new HashMap<>(); overrides.put("projects.test3.clonePath", "baz1"); // when applier.applyPropertiesOverride(jsonMapper.readTree(json), overrides); } @Test( expectedExceptions = OverrideParameterException.class, expectedExceptionsMessageRegExp = "Override property reference 'projects' points to an array type object. Please add an item qualifier by name.") public void shouldThrowExceptionIfOverrideReferenceEndsWithArray() throws Exception { String json = "{" + "\"apiVersion\": \"1.0.0\"," + "\"projects\": [" + " {" + " \"name\": \"test1\"," + " \"clonePath\": \"/foo/bar1\"" + " }," + " {" + " \"name\": \"test2\"," + " \"clonePath\": \"/foo/bar2\"" + " }" + " ]" + "}"; Map overrides = new HashMap<>(); overrides.put("projects", "baz1"); // when applier.applyPropertiesOverride(jsonMapper.readTree(json), overrides); } @Test( expectedExceptions = OverrideParameterException.class, expectedExceptionsMessageRegExp = "Override property reference 'projects' points to an array type object. Please add an item qualifier by name.") public void shouldThrowExceptionIfOverrideReferenceIsJustWithArray() throws Exception { String json = "{" + "\"apiVersion\": \"1.0.0\"," + "\"projects\": [" + " {" + " \"name\": \"test1\"," + " \"clonePath\": \"/foo/bar1\"" + " }" + " ]" + "}"; Map overrides = new HashMap<>(); overrides.put("projects", "baz1"); // when applier.applyPropertiesOverride(jsonMapper.readTree(json), overrides); } @Test( expectedExceptions = OverrideParameterException.class, expectedExceptionsMessageRegExp = "Override path 'commands.run.foo.bar' starts with an unsupported field pointer. Supported fields are \\{\"apiVersion\",\"metadata\",\"projects\"\\,\"attributes\"\\,\"devfileFilename\"\\}.") public void shouldThrowExceptionIfOverrideReferenceUsesUnsupportedField() throws Exception { String json = "{" + "\"apiVersion\": \"1.0.0\"," + "\"projects\": [" + " {" + " \"name\": \"test1\"," + " \"clonePath\": \"/foo/bar1\"" + " }" + " ]" + "}"; Map overrides = new HashMap<>(); overrides.put("commands.run.foo.bar", "baz1"); // when applier.applyPropertiesOverride(jsonMapper.readTree(json), overrides); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/PreferencesDeserializerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static org.testng.Assert.assertEquals; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import java.io.Serializable; import java.util.Map; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class PreferencesDeserializerTest { @Mock private DeserializationContext ctxt; private final JsonFactory factory = new JsonFactory(); /** Instance to test. */ private PreferencesDeserializer preferencesDeserializer = new PreferencesDeserializer(); @Test public void shouldParseSimpleTypesCorrectly() throws Exception { String json = "{" + "\"valid.string\": \"/usr/bin/value\"," + "\"valid.boolean\": false," + "\"valid.integer\": 777555888," + "\"valid.float\": 3.1415926," + "\"valid.string.array\": [\"foo\",\"bar\",\"baz\"]," + "\"valid.int.array\": [12,13,0]," + "\"valid.bool.array\": [true,false,false]" + "}"; final JsonParser parser = factory.createParser(json); Map result = preferencesDeserializer.deserialize(parser, ctxt); assertEquals(7, result.size()); assertEquals(result.get("valid.integer"), 777555888); assertEquals(result.get("valid.float"), 3.1415926); assertEquals(result.get("valid.boolean"), false); assertEquals(result.get("valid.string"), "/usr/bin/value"); assertEquals(result.get("valid.string.array"), new String[] {"foo", "bar", "baz"}); assertEquals(result.get("valid.int.array"), new int[] {12, 13, 0}); assertEquals(result.get("valid.bool.array"), new boolean[] {true, false, false}); } @Test( expectedExceptions = JsonParseException.class, expectedExceptionsMessageRegExp = "Unexpected value of the preference with key 'invalid.object'.\n" + " at \\[Source: REDACTED \\(`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled\\); line: 1, column: 54]") public void shouldThrowExceptionOnUnsupportedPreferenceValue() throws Exception { String json = "{" + "\"valid.string\": \"/usr/bin/value\"," + "\"invalid.object\": {\"someobject\": true}" + "}"; final JsonParser parser = factory.createParser(json); preferencesDeserializer.deserialize(parser, ctxt); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/SerializableConverterTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static org.testng.Assert.assertEquals; import java.io.Serializable; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class SerializableConverterTest { private final SerializableConverter converter = new SerializableConverter(); @DataProvider public static Object[][] SerializableProvider() { return new Object[][] { {"foo"}, {"bar"}, {true}, {false}, {Integer.MAX_VALUE}, {0}, {Integer.MIN_VALUE}, {"{\"java.home\": \"/home/user/jdk11\", \"java.jdt.ls.vmargs\": \"-Xmx1G\"}"}, {new String[] {"single"}}, {new String[] {"--enable-all", "--new"}}, {new int[] {213, 456, 459}}, {new boolean[] {true, false, false}} }; } @Test(dataProvider = "SerializableProvider") public void testConvertToDatabaseColumnAndBack(Serializable initialObj) { String res = converter.convertToDatabaseColumn(initialObj); Serializable backObj = converter.convertToEntityAttribute(res); assertEquals(initialObj, backObj); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/URLFetcherTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.che.api.workspace.server.devfile.URLFetcher.CONNECTION_READ_TIMEOUT; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import com.google.common.base.Strings; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLConnection; import java.util.function.Consumer; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Testing {@link org.eclipse.che.api.workspace.server.devfile.URLFetcher} * * @author Florent Benoit */ @Listeners(MockitoTestNGListener.class) public class URLFetcherTest { /** Instance to test. */ private URLFetcher urlFetcher = new URLFetcher(1024); /** Check that when url is null, NPE is thrown */ @Test(expectedExceptions = NullPointerException.class) public void checkNullURL() { urlFetcher.fetchSafely(null); } /** Check that when url exists the content is retrieved */ @Test public void checkGetContent() { // test to download this class object URL urlJson = getClass().getClassLoader().getResource("devfile/url_fetcher_test_resource.json"); Assert.assertNotNull(urlJson); String content = urlFetcher.fetchSafely(urlJson.toString()); assertEquals(content, "Hello"); } /** Check when url is invalid */ @Test public void checkUrlFileIsInvalid() { String result = urlFetcher.fetchSafely("hello world"); assertNull(result); } /** Check when url is invalid */ @Test( expectedExceptions = IOException.class, expectedExceptionsMessageRegExp = "no protocol: hello_world") public void checkUnsafeGetUrlFileIsInvalid() throws Exception { String result = urlFetcher.fetch("hello_world"); assertNull(result); } /** Check Sanitizing of Git URL works */ @Test public void checkDotGitRemovedFromURL() { String result = urlFetcher.sanitized("https://github.com/acme/demo.git"); assertEquals("https://github.com/acme/demo", result); result = urlFetcher.sanitized("http://github.com/acme/demo.git"); assertEquals("http://github.com/acme/demo", result); } /** Check that when url doesn't exist */ @Test public void checkMissingContent() { // test to download this class object URL urlJson = getClass().getClassLoader().getResource("devfile/url_fetcher_test_resource.json"); Assert.assertNotNull(urlJson); // add extra path to make url not found String content = urlFetcher.fetchSafely(urlJson.toString() + "-invalid"); assertNull(content); } /** Check that when url doesn't exist */ @Test( expectedExceptions = IOException.class, expectedExceptionsMessageRegExp = ".*url_fetcher_test_resource.json-invalid \\(No such file or directory\\)") public void checkMissingContentUnsafeGet() throws Exception { // test to download this class object URL urlJson = getClass().getClassLoader().getResource("devfile/url_fetcher_test_resource.json"); Assert.assertNotNull(urlJson); // add extra path to make url not found String content = urlFetcher.fetch(urlJson.toString() + "-invalid"); assertNull(content); } /** Check when we reach custom limit */ @Test public void checkPartialContent() { URL urlJson = getClass().getClassLoader().getResource("devfile/url_fetcher_test_resource.json"); Assert.assertNotNull(urlJson); String content = new OneByteURLFetcher(1).fetchSafely(urlJson.toString()); assertEquals(content, "Hello".substring(0, 1)); } /** Check when we reach custom limit */ @Test public void checkDefaultPartialContent() throws IOException { URLConnection urlConnection = Mockito.mock(URLConnection.class); String originalContent = Strings.padEnd("", 1024, 'a'); String extraContent = originalContent + "----"; when(urlConnection.getInputStream()) .thenReturn(new ByteArrayInputStream(extraContent.getBytes(UTF_8))); String readcontent = urlFetcher.fetch(urlConnection); // check extra content has been removed as we keep only first values assertEquals(readcontent, originalContent); } @Test public void testDefaultFetchTimeoutIsSet() throws IOException { URLFetcher fetcher = new TimeoutCheckURLFetcher( timeout -> assertEquals(timeout.intValue(), CONNECTION_READ_TIMEOUT)); fetcher.fetch("http://eclipse.org/che"); } @Test public void testFetchTimeoutIsSet() throws IOException { URLFetcher fetcher = new TimeoutCheckURLFetcher(timeout -> assertEquals(timeout.intValue(), 123)); fetcher.fetch("http://eclipse.org/che", 123); } @Test(expectedExceptions = IOException.class) public void testExceptionIsThrownOnTimeout() throws IOException { URLFetcher fetcher = new URLFetcher(1024); URLConnection connection = new URLConnection(new URL("http://eclipse.org/che")) { @Override public void connect() throws IOException { // noop } @Override public InputStream getInputStream() throws IOException { throw new SocketTimeoutException("yes"); } }; fetcher.fetch(connection); } /** Limit to only one Byte. */ static class OneByteURLFetcher extends URLFetcher { public OneByteURLFetcher(long maxFetchBytes) { super(maxFetchBytes); } /** Override the limit */ @Override protected long getLimit() { return 1; } } private static class TimeoutCheckURLFetcher extends URLFetcher { private final Consumer assertion; public TimeoutCheckURLFetcher(Consumer assertion) { super(500); this.assertion = assertion; } @Override String fetch(URLConnection urlConnection) { assertion.accept(urlConnection.getReadTimeout()); assertion.accept(urlConnection.getConnectTimeout()); return "NOOP"; } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/URLFileContentProviderTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; import java.net.URI; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class URLFileContentProviderTest { @Mock private URLFetcher urlFetcher; @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "It is unable to fetch a file /relative/dev.yaml as relative to devfile, since devfile location is unknown. Try specifying absolute URL.") public void shouldThrowExceptionWhenNoDevfileLocationKnownAndURLIsRelative() throws Exception { URLFileContentProvider provider = new URLFileContentProvider(null, urlFetcher); provider.fetchContent("/relative/dev.yaml"); } @Test public void shouldFetchByAbsoluteURL() throws Exception { String url = "http://myhost.com/relative/dev.yaml"; URLFileContentProvider provider = new URLFileContentProvider(null, urlFetcher); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); provider.fetchContent(url); verify(urlFetcher).fetch(captor.capture(), eq(null)); assertEquals(captor.getValue(), url); } @Test public void shouldMergeDevfileLocationAndRelativeURL() throws Exception { String devfileUrl = "http://myhost.com/relative/devile.yaml"; String relativeUrl = "relative.yaml"; URLFileContentProvider provider = new URLFileContentProvider(new URI(devfileUrl), urlFetcher); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); provider.fetchContent(relativeUrl); verify(urlFetcher).fetch(captor.capture(), eq(null)); assertEquals(captor.getValue(), "http://myhost.com/relative/relative.yaml"); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/convert/CommandConverterTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert; import static org.eclipse.che.api.core.model.workspace.config.Command.WORKING_DIRECTORY_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.EXEC_ACTION_TYPE; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import com.google.common.collect.ImmutableMap; import java.util.HashMap; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.server.devfile.exception.WorkspaceExportException; import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * @author Sergii Leshchenko */ public class CommandConverterTest { private CommandConverter commandConverter; @BeforeMethod public void setUp() { commandConverter = new CommandConverter(); } @Test public void shouldConvertWorkspaceCommandToDevfileCommand() throws Exception { // given org.eclipse.che.api.workspace.server.model.impl.CommandImpl workspaceCommand = new org.eclipse.che.api.workspace.server.model.impl.CommandImpl( "build", "mvn clean install", "custom"); workspaceCommand.getAttributes().put(COMPONENT_ALIAS_COMMAND_ATTRIBUTE, "dockerimageComponent"); workspaceCommand.getAttributes().put(WORKING_DIRECTORY_ATTRIBUTE, "/tmp"); workspaceCommand.getAttributes().put("anotherAttribute", "value"); // when CommandImpl devfileCommand = commandConverter.toDevfileCommand(workspaceCommand); // then assertEquals(devfileCommand.getName(), "build"); assertEquals(devfileCommand.getActions().size(), 1); assertEquals(devfileCommand.getAttributes().size(), 1); assertEquals(devfileCommand.getAttributes().get("anotherAttribute"), "value"); ActionImpl action = devfileCommand.getActions().get(0); assertEquals(action.getComponent(), "dockerimageComponent"); assertEquals(action.getWorkdir(), "/tmp"); assertEquals(action.getCommand(), "mvn clean install"); assertEquals(action.getType(), EXEC_ACTION_TYPE); } @Test( expectedExceptions = WorkspaceExportException.class, expectedExceptionsMessageRegExp = "Command `build` has no specified component where it should be run") public void shouldThrowAnExceptionIfWorkspaceCommandDoesNotHaveComponentNameAttribute() throws Exception { // given org.eclipse.che.api.workspace.server.model.impl.CommandImpl workspaceCommand = new org.eclipse.che.api.workspace.server.model.impl.CommandImpl( "build", "mvn clean install", "custom"); // when commandConverter.toDevfileCommand(workspaceCommand); } @Test public void shouldConvertDevfileCommandToWorkspaceCommands() throws Exception { // given CommandImpl devfileCommand = new CommandImpl(); devfileCommand.setName("build"); devfileCommand.setAttributes(ImmutableMap.of("attr", "value")); ActionImpl action = new ActionImpl(); action.setComponent("dockerimageComponent"); action.setType(EXEC_ACTION_TYPE); action.setWorkdir("/tmp"); action.setCommand("mvn clean install"); devfileCommand.getActions().add(action); // when org.eclipse.che.api.workspace.server.model.impl.CommandImpl workspaceCommand = commandConverter.toWorkspaceCommand(devfileCommand, null); // then assertEquals(workspaceCommand.getName(), "build"); assertEquals(workspaceCommand.getType(), EXEC_ACTION_TYPE); assertEquals(workspaceCommand.getAttributes().size(), 3); assertEquals(workspaceCommand.getAttributes().get("attr"), "value"); assertEquals(workspaceCommand.getAttributes().get(WORKING_DIRECTORY_ATTRIBUTE), "/tmp"); assertEquals( workspaceCommand.getAttributes().get(COMPONENT_ALIAS_COMMAND_ATTRIBUTE), "dockerimageComponent"); assertEquals(workspaceCommand.getCommandLine(), "mvn clean install"); } @Test public void shouldNotSetWorkingDirAttributeIfItIsMissingInDevfileCommandDuringConvertingDevfileCommandToWorkspaceCommands() throws Exception { // given CommandImpl devfileCommand = new CommandImpl(); ActionImpl action = new ActionImpl(); action.setWorkdir(null); devfileCommand.getActions().add(action); devfileCommand.setAttributes(new HashMap<>()); // when org.eclipse.che.api.workspace.server.model.impl.CommandImpl workspaceCommand = commandConverter.toWorkspaceCommand(devfileCommand, null); // then assertFalse(workspaceCommand.getAttributes().containsKey(WORKING_DIRECTORY_ATTRIBUTE)); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Command `build` MUST has one and only one action") public void shouldThrowAnExceptionIfDevfileCommandHasMultipleActions() throws Exception { // given CommandImpl devfileCommand = new CommandImpl(); devfileCommand.setName("build"); devfileCommand.setAttributes(ImmutableMap.of("attr", "value")); devfileCommand.getActions().add(new ActionImpl()); devfileCommand.getActions().add(new ActionImpl()); // when commandConverter.toWorkspaceCommand(devfileCommand, null); } @Test public void shouldAcceptActionWithCommand() throws Exception { // given CommandImpl devfileCommand = new CommandImpl(); devfileCommand.setName("build"); ActionImpl action = new ActionImpl(); action.setType("exec"); action.setCommand("blah"); devfileCommand.getActions().add(action); // when Command command = commandConverter.toWorkspaceCommand(devfileCommand, null); // then assertEquals(command.getCommandLine(), "blah"); } @Test public void shouldAcceptActionWithReference() throws Exception { // given CommandImpl devfileCommand = new CommandImpl(); devfileCommand.setName("build"); ActionImpl action = new ActionImpl(); action.setType("exec"); action.setReference("blah"); devfileCommand.getActions().add(action); FileContentProvider fileContentProvider = mock(FileContentProvider.class); when(fileContentProvider.fetchContent(anyString())).thenReturn("content"); // when Command command = commandConverter.toWorkspaceCommand(devfileCommand, fileContentProvider); // then assertNull(command.getCommandLine()); assertEquals("blah", command.getAttributes().get(Command.COMMAND_ACTION_REFERENCE_ATTRIBUTE)); assertEquals( "content", command.getAttributes().get(Command.COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE)); } @Test public void shouldAcceptActionWithReferenceContent() throws Exception { // given CommandImpl devfileCommand = new CommandImpl(); devfileCommand.setName("build"); ActionImpl action = new ActionImpl(); action.setType("exec"); action.setReference("blah"); action.setReferenceContent("content"); devfileCommand.getActions().add(action); // when Command command = commandConverter.toWorkspaceCommand(devfileCommand, null); // then assertNull(command.getCommandLine()); assertEquals("blah", command.getAttributes().get(Command.COMMAND_ACTION_REFERENCE_ATTRIBUTE)); assertEquals( "content", command.getAttributes().get(Command.COMMAND_ACTION_REFERENCE_CONTENT_ATTRIBUTE)); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/convert/DefaultEditorProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert; import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_FREE_DEVFILE_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.PLUGIN_COMPONENT_TYPE; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import java.util.List; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentFQNParser; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.wsplugins.PluginFQNParser; import org.eclipse.che.api.workspace.shared.Constants; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link DefaultEditorProvisioner}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class DefaultEditorProvisionerTest { @Mock private FileContentProvider fileContentProvider; private static final String EDITOR_NAME = "theia"; private static final String EDITOR_VERSION = "1.0.0"; private static final String EDITOR_PUBLISHER = "eclipse"; private static final String EDITOR_REF = EDITOR_PUBLISHER + "/" + EDITOR_NAME + "/" + EDITOR_VERSION; private static final String TERMINAL_PLUGIN_NAME = "theia-terminal"; private static final String TERMINAL_PLUGIN_VERSION = "0.0.4"; private static final String ASYNC_STORAGE_PLUGIN_REF = "eclipse/che-async-pv-plugin/nightly"; private static final String TERMINAL_PLUGIN_REF = EDITOR_PUBLISHER + "/" + TERMINAL_PLUGIN_NAME + "/" + TERMINAL_PLUGIN_VERSION; private static final String COMMAND_PLUGIN_NAME = "theia-command"; private static final String COMMAND_PLUGIN_REF = EDITOR_PUBLISHER + "/" + COMMAND_PLUGIN_NAME + "/v1.0.0"; private DefaultEditorProvisioner provisioner; private PluginFQNParser pluginFQNParser = new PluginFQNParser(); private ComponentFQNParser fqnParser = new ComponentFQNParser(pluginFQNParser); @Test public void shouldNotProvisionDefaultEditorIfItIsNotConfigured() throws Exception { // given provisioner = new DefaultEditorProvisioner( null, "", ASYNC_STORAGE_PLUGIN_REF, fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); // when provisioner.apply(devfile, fileContentProvider); // then assertTrue(devfile.getComponents().isEmpty()); } @Test public void shouldProvisionDefaultEditorWithPluginsWhenDevfileDoNotHaveAny() throws Exception { // given provisioner = new DefaultEditorProvisioner( EDITOR_REF, TERMINAL_PLUGIN_REF + "," + COMMAND_PLUGIN_REF, "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 3); assertTrue(components.contains(new ComponentImpl(EDITOR_COMPONENT_TYPE, EDITOR_REF))); assertTrue(components.contains(new ComponentImpl(PLUGIN_COMPONENT_TYPE, COMMAND_PLUGIN_REF))); assertTrue(components.contains(new ComponentImpl(PLUGIN_COMPONENT_TYPE, TERMINAL_PLUGIN_REF))); } @Test public void shouldProvisionDefaultPluginsIfTheyAreNotSpecifiedAndDefaultEditorIsConfigured() throws Exception { // given provisioner = new DefaultEditorProvisioner( EDITOR_REF, TERMINAL_PLUGIN_REF, "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); ComponentImpl defaultEditorWithDifferentVersion = new ComponentImpl(EDITOR_COMPONENT_TYPE, EDITOR_PUBLISHER + "/" + EDITOR_NAME + "/latest"); devfile.getComponents().add(defaultEditorWithDifferentVersion); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 2); assertTrue(components.contains(defaultEditorWithDifferentVersion)); assertTrue(components.contains(new ComponentImpl(PLUGIN_COMPONENT_TYPE, TERMINAL_PLUGIN_REF))); } @Test public void shouldProvisionDefaultPluginsIfTheyAreNotSpecifiedAndDefaultEditorFromCustomRegistryIsConfigured() throws Exception { // given provisioner = new DefaultEditorProvisioner( EDITOR_REF, TERMINAL_PLUGIN_REF, "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); ComponentImpl defaultEditorWithDifferentVersion = new ComponentImpl( EDITOR_COMPONENT_TYPE, "https://my-custom-registry#" + EDITOR_PUBLISHER + "/" + EDITOR_NAME + "/latest"); devfile.getComponents().add(defaultEditorWithDifferentVersion); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 2); assertTrue(components.contains(defaultEditorWithDifferentVersion)); assertTrue(components.contains(new ComponentImpl(PLUGIN_COMPONENT_TYPE, TERMINAL_PLUGIN_REF))); } @Test public void shouldNotProvisionDefaultPluginsIfCustomEditorIsConfiguredWhichStartWithDefaultId() throws Exception { // given provisioner = new DefaultEditorProvisioner( EDITOR_REF, TERMINAL_PLUGIN_REF, "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); ComponentImpl editorWithNameSimilarToDefault = new ComponentImpl( EDITOR_COMPONENT_TYPE, EDITOR_PUBLISHER + "/" + EDITOR_NAME + "-dev/dev-version"); devfile.getComponents().add(editorWithNameSimilarToDefault); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 1); assertTrue(components.contains(editorWithNameSimilarToDefault)); assertNull(findById(components, EDITOR_NAME)); } @Test public void shouldNotProvisionDefaultPluginsIfDevfileContainsEditorFreeAttribute() throws Exception { // given provisioner = new DefaultEditorProvisioner( EDITOR_REF, TERMINAL_PLUGIN_REF, "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); devfile.getAttributes().put(EDITOR_FREE_DEVFILE_ATTRIBUTE, "true"); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertTrue(components.isEmpty()); } @Test public void shouldProvisionDefaultPluginIfDevfileAlreadyContainPluginWithNameWhichStartWithDefaultOne() throws Exception { // given provisioner = new DefaultEditorProvisioner( EDITOR_REF, TERMINAL_PLUGIN_REF, "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); ComponentImpl pluginWithNameSimilarToDefault = new ComponentImpl( PLUGIN_COMPONENT_TYPE, EDITOR_PUBLISHER + "/" + TERMINAL_PLUGIN_NAME + "-dummy/latest"); devfile.getComponents().add(pluginWithNameSimilarToDefault); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 3); assertTrue(components.contains(new ComponentImpl(EDITOR_COMPONENT_TYPE, EDITOR_REF))); assertTrue(components.contains(new ComponentImpl(PLUGIN_COMPONENT_TYPE, TERMINAL_PLUGIN_REF))); assertTrue(components.contains(pluginWithNameSimilarToDefault)); } @Test public void shouldNotProvisionDefaultEditorOrDefaultPluginsIfDevfileAlreadyHasNonDefaultEditor() throws Exception { // given provisioner = new DefaultEditorProvisioner(EDITOR_REF, "", "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); ComponentImpl nonDefaultEditor = new ComponentImpl(EDITOR_COMPONENT_TYPE, "anypublisher/anyname/v" + EDITOR_VERSION); devfile.getComponents().add(nonDefaultEditor); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 1); assertTrue(components.contains(nonDefaultEditor)); } @Test public void shouldNotProvisionDefaultEditorIfDevfileAlreadyContainsSuchButWithDifferentVersion() throws Exception { // given provisioner = new DefaultEditorProvisioner(EDITOR_REF, "", "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); ComponentImpl myTheiaEditor = new ComponentImpl( EDITOR_COMPONENT_TYPE, EDITOR_PUBLISHER + "/" + EDITOR_NAME + "/my-custom"); devfile.getComponents().add(myTheiaEditor); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 1); assertTrue(components.contains(myTheiaEditor)); } @Test public void shouldNotProvisionDefaultEditorIfDevfileAlreadyContainsSuchByReference() throws Exception { // given provisioner = new DefaultEditorProvisioner(EDITOR_REF, "", "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); ComponentImpl myTheiaEditor = new ComponentImpl( EDITOR_COMPONENT_TYPE, null, "https://myregistry.com/abc/meta.yaml", null, null, null); String meta = "apiVersion: v2\n" + "publisher: " + EDITOR_PUBLISHER + "\n" + "name: " + EDITOR_NAME + "\n" + "version: " + EDITOR_VERSION + "\n" + "type: Che Editor"; devfile.getComponents().add(myTheiaEditor); when(fileContentProvider.fetchContent(anyString())).thenReturn(meta); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 1); assertTrue(components.contains(myTheiaEditor)); } @Test public void shouldNotProvisionDefaultPluginIfDevfileAlreadyContainsSuchButWithDifferentVersion() throws Exception { // given provisioner = new DefaultEditorProvisioner( EDITOR_REF, TERMINAL_PLUGIN_REF, "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); ComponentImpl myTerminal = new ComponentImpl( PLUGIN_COMPONENT_TYPE, EDITOR_PUBLISHER + "/" + TERMINAL_PLUGIN_NAME + "/my-custom"); devfile.getComponents().add(myTerminal); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 2); assertTrue(components.contains(new ComponentImpl(EDITOR_COMPONENT_TYPE, EDITOR_REF))); assertTrue(components.contains(myTerminal)); } @Test public void shouldNotProvisionDefaultPluginIfDevfileAlreadyContainsSuchByReference() throws Exception { // given provisioner = new DefaultEditorProvisioner( EDITOR_REF, TERMINAL_PLUGIN_REF, "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); String meta = "apiVersion: v2\n" + "publisher: " + EDITOR_PUBLISHER + "\n" + "name: " + TERMINAL_PLUGIN_NAME + "\n" + "version: " + TERMINAL_PLUGIN_VERSION + "\n" + "type: Che Plugin"; ComponentImpl myTerminal = new ComponentImpl( PLUGIN_COMPONENT_TYPE, null, "https://myregistry.com/abc/meta.yaml", null, null, null); when(fileContentProvider.fetchContent(anyString())).thenReturn(meta); devfile.getComponents().add(myTerminal); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 2); assertTrue(components.contains(new ComponentImpl(EDITOR_COMPONENT_TYPE, EDITOR_REF))); assertTrue(components.contains(myTerminal)); } @Test public void shouldGenerateDefaultPluginNameIfIdIsNotUnique() throws Exception { // given provisioner = new DefaultEditorProvisioner( EDITOR_REF, EDITOR_PUBLISHER + "/" + "my-plugin/v2.0", "", fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); ComponentImpl myPlugin = new ComponentImpl( PLUGIN_COMPONENT_TYPE, EDITOR_PUBLISHER + "/" + "my-custom-plugin/v0.0.3"); devfile.getComponents().add(myPlugin); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 3); assertTrue(components.contains(new ComponentImpl(EDITOR_COMPONENT_TYPE, EDITOR_REF))); assertTrue(components.contains(myPlugin)); ComponentImpl defaultPlugin = findByRef(components, EDITOR_PUBLISHER + "/" + "my-plugin/v2.0"); assertNotNull(defaultPlugin); assertNull(defaultPlugin.getAlias()); } @Test public void shouldResolveDefaultReferencePlugins() throws Exception { // given String referencePluginRef = "https://remotepluginregistry.com/plugins/abc/meta.yaml"; provisioner = new DefaultEditorProvisioner( EDITOR_REF, EDITOR_PUBLISHER + "/" + "my-plugin/v2.0" + "," + referencePluginRef, "", fqnParser, pluginFQNParser); String meta = "apiVersion: v2\n" + "publisher: " + EDITOR_PUBLISHER + "\n" + "name: " + COMMAND_PLUGIN_NAME + "\n" + "version: v1.0.0" + "\n" + "type: Che Plugin"; DevfileImpl devfile = new DevfileImpl(); when(fileContentProvider.fetchContent(anyString())).thenReturn(meta); ComponentImpl myPlugin = new ComponentImpl(PLUGIN_COMPONENT_TYPE, COMMAND_PLUGIN_REF); myPlugin.setReference(referencePluginRef); myPlugin.setId(COMMAND_PLUGIN_REF); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 3); assertTrue(components.contains(myPlugin)); } @Test public void shouldProvisionAsyncStoragePluginsIfWorkspaceHasOnlyOneAttribute() throws Exception { // given provisioner = new DefaultEditorProvisioner( EDITOR_REF, TERMINAL_PLUGIN_REF, ASYNC_STORAGE_PLUGIN_REF, fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); devfile.setAttributes(ImmutableMap.of(Constants.ASYNC_PERSIST_ATTRIBUTE, "true")); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 2); assertFalse( components.contains(new ComponentImpl(PLUGIN_COMPONENT_TYPE, ASYNC_STORAGE_PLUGIN_REF))); } @Test public void shouldProvisionAsyncStoragePluginsIfWorkspaceHasBothAttributes() throws Exception { // given provisioner = new DefaultEditorProvisioner( EDITOR_REF, TERMINAL_PLUGIN_REF, ASYNC_STORAGE_PLUGIN_REF, fqnParser, pluginFQNParser); DevfileImpl devfile = new DevfileImpl(); devfile.setAttributes( ImmutableMap.of( Constants.ASYNC_PERSIST_ATTRIBUTE, "true", Constants.PERSIST_VOLUMES_ATTRIBUTE, "false")); // when provisioner.apply(devfile, fileContentProvider); // then List components = devfile.getComponents(); assertEquals(components.size(), 3); assertTrue( components.contains(new ComponentImpl(PLUGIN_COMPONENT_TYPE, ASYNC_STORAGE_PLUGIN_REF))); } private ComponentImpl findById(List components, String id) { return components.stream() .filter(c -> c.getId() != null && c.getId().startsWith(id + ':')) .findAny() .orElse(null); } private ComponentImpl findByRef(List components, String ref) { return components.stream().filter(c -> ref.equals(c.getId())).findAny().orElse(null); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/convert/DevfileConverterTest.java ================================================ /* * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert; import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; import static org.eclipse.che.api.workspace.shared.Constants.PERSIST_VOLUMES_ATTRIBUTE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertSame; import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentToWorkspaceApplier; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class DevfileConverterTest { public static final String COMPONENT_TYPE = "componentType"; @Mock private ProjectConverter projectConverter; @Mock private CommandConverter commandConverter; @Mock private ComponentToWorkspaceApplier componentToWorkspaceApplier; @Mock private DefaultEditorProvisioner defaultEditorToolApplier; @Mock private URLFetcher urlFetcher; private DevfileConverter devfileConverter; @BeforeMethod public void setUp() { devfileConverter = new DevfileConverter( projectConverter, commandConverter, ImmutableMap.of(COMPONENT_TYPE, componentToWorkspaceApplier), defaultEditorToolApplier, urlFetcher); } @Test public void shouldUseDevfileNameForWorkspaceNameDuringConvertingDevfileToWorkspaceConfig() throws Exception { // given FileContentProvider fileContentProvider = mock(FileContentProvider.class); DevfileImpl devfile = newDevfile("petclinic"); // when WorkspaceConfigImpl workspaceConfig = devfileConverter.devFileToWorkspaceConfig(devfile, fileContentProvider); // then assertEquals(workspaceConfig.getName(), "petclinic"); } @Test public void shouldInvokeDefaultEditorProvisionerDuringConvertingDevfileToWorkrspaceConfig() throws Exception { // given FileContentProvider fileContentProvider = mock(FileContentProvider.class); DevfileImpl devfile = newDevfile("petclinic"); // when devfileConverter.devFileToWorkspaceConfig(devfile, fileContentProvider); // then verify(defaultEditorToolApplier).apply(devfile, fileContentProvider); } @Test public void shouldProvisionDevfileAttributesAsConfigAttributesDuringConvertingDevfileToWorkspaceConfig() throws Exception { // given FileContentProvider fileContentProvider = mock(FileContentProvider.class); Map devfileAttributes = new HashMap<>(); devfileAttributes.put(PERSIST_VOLUMES_ATTRIBUTE, "false"); devfileAttributes.put("anotherAttribute", "value"); DevfileImpl devfile = newDevfile("petclinic"); devfile.getAttributes().putAll(devfileAttributes); // when WorkspaceConfigImpl config = devfileConverter.devFileToWorkspaceConfig(devfile, fileContentProvider); // then assertEquals(config.getAttributes(), devfileAttributes); } @Test public void shouldConvertCommandsDuringConvertingDevfileToWorkspaceConfig() throws Exception { // given FileContentProvider fileContentProvider = mock(FileContentProvider.class); DevfileImpl devfile = newDevfile("petclinic"); CommandImpl devfileCommand = mock(CommandImpl.class); devfile.getCommands().add(devfileCommand); org.eclipse.che.api.workspace.server.model.impl.CommandImpl workspaceCommand = mock(org.eclipse.che.api.workspace.server.model.impl.CommandImpl.class); when(commandConverter.toWorkspaceCommand(any(), any())).thenReturn(workspaceCommand); // when WorkspaceConfigImpl workspaceConfig = devfileConverter.devFileToWorkspaceConfig(devfile, fileContentProvider); // then assertEquals(workspaceConfig.getCommands().size(), 1); assertSame(workspaceConfig.getCommands().get(0), workspaceCommand); } @Test public void shouldConvertProjectsDuringConvertingDevfileToWorkspaceConfig() throws Exception { // given FileContentProvider fileContentProvider = mock(FileContentProvider.class); DevfileImpl devfile = newDevfile("petclinic"); ProjectImpl devfileProject = mock(ProjectImpl.class); devfile.getProjects().add(devfileProject); ProjectConfigImpl workspaceProject = mock(ProjectConfigImpl.class); when(projectConverter.toWorkspaceProject(any())).thenReturn(workspaceProject); // when WorkspaceConfigImpl workspaceConfig = devfileConverter.devFileToWorkspaceConfig(devfile, fileContentProvider); // then assertEquals(workspaceConfig.getProjects().size(), 1); assertSame(workspaceConfig.getProjects().get(0), workspaceProject); } @Test public void shouldConvertComponentsDuringConvertingDevfileToWorkspaceConfig() throws Exception { // given DevfileImpl devfile = newDevfile("petclinic"); ComponentImpl component = new ComponentImpl(); component.setType(COMPONENT_TYPE); devfile.getComponents().add(component); FileContentProvider fileContentProvider = mock(FileContentProvider.class); // when WorkspaceConfigImpl workspaceConfig = devfileConverter.devFileToWorkspaceConfig(devfile, fileContentProvider); // then verify(componentToWorkspaceApplier).apply(workspaceConfig, component, fileContentProvider); } @Test public void shouldConvertDevfileToWorkspaceConfig() throws Exception { devfileConverter = spy(devfileConverter); WorkspaceConfigImpl wsConfig = new WorkspaceConfigImpl(); wsConfig.setName("converted"); wsConfig.getAttributes().put("att", "value"); doReturn(wsConfig).when(devfileConverter).devFileToWorkspaceConfig(any(), any()); WorkspaceConfig converted = devfileConverter.convert(new DevfileImpl()); assertEquals(converted, wsConfig); } @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "error") public void shouldThrowServerExceptionIfAnyDevfileExceptionOccursOnConvertingDevfileToWorkspaceConfig() throws Exception { devfileConverter = spy(devfileConverter); doThrow(new DevfileException("error")) .when(devfileConverter) .devFileToWorkspaceConfig(any(), any()); devfileConverter.convert(new DevfileImpl()); } private DevfileImpl newDevfile(String name) { DevfileImpl devfile = new DevfileImpl(); devfile.setApiVersion(CURRENT_API_VERSION); devfile.setName(name); return devfile; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/convert/ProjectConverterTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert; import static org.eclipse.che.api.core.model.workspace.config.SourceStorage.BRANCH_PARAMETER_NAME; import static org.eclipse.che.api.core.model.workspace.config.SourceStorage.COMMIT_ID_PARAMETER_NAME; import static org.eclipse.che.api.core.model.workspace.config.SourceStorage.SPARSE_CHECKOUT_DIR_PARAMETER_NAME; import static org.eclipse.che.api.core.model.workspace.config.SourceStorage.START_POINT_PARAMETER_NAME; import static org.eclipse.che.api.core.model.workspace.config.SourceStorage.TAG_PARAMETER_NAME; import static org.testng.Assert.assertEquals; import static org.testng.AssertJUnit.assertFalse; import com.google.common.collect.ImmutableMap; import org.eclipse.che.api.core.model.workspace.devfile.Project; import org.eclipse.che.api.core.model.workspace.devfile.Source; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * @author Sergii Leshchenko */ public class ProjectConverterTest { private ProjectConverter projectConverter; @BeforeMethod public void setUp() { projectConverter = new ProjectConverter(); } @Test public void testConvertingDevfileProjectToProjectConfig() throws Exception { ProjectImpl devfileProject = new ProjectImpl( "myProject", new SourceImpl( "git", "https://github.com/eclipse/che.git", "master", "3434d", null, null, "core"), null); ProjectConfigImpl workspaceProject = projectConverter.toWorkspaceProject(devfileProject); assertEquals(workspaceProject.getName(), "myProject"); assertEquals(workspaceProject.getPath(), "/myProject"); SourceStorageImpl source = workspaceProject.getSource(); assertEquals(source.getType(), "git"); assertEquals(source.getLocation(), "https://github.com/eclipse/che.git"); assertEquals(source.getParameters().get(BRANCH_PARAMETER_NAME), "master"); assertEquals(source.getParameters().get(START_POINT_PARAMETER_NAME), "3434d"); assertEquals(source.getParameters().get("keepDir"), "core"); } @Test public void testConvertingProjectConfigToDevfileProject() { ProjectConfigImpl workspaceProject = new ProjectConfigImpl(); workspaceProject.setName("myProject"); workspaceProject.setPath("/clone/path"); SourceStorageImpl sourceStorage = new SourceStorageImpl(); sourceStorage.setType("git"); sourceStorage.setLocation("https://github.com/eclipse/che.git"); sourceStorage.setParameters( ImmutableMap.of(TAG_PARAMETER_NAME, "v1.0", BRANCH_PARAMETER_NAME, "develop")); workspaceProject.setSource(sourceStorage); Project devfileProject = projectConverter.toDevfileProject(workspaceProject); assertEquals(devfileProject.getName(), "myProject"); Source source = devfileProject.getSource(); assertEquals(source.getType(), "git"); assertEquals(source.getLocation(), "https://github.com/eclipse/che.git"); assertEquals(source.getBranch(), "develop"); assertEquals(source.getTag(), "v1.0"); assertEquals(devfileProject.getClonePath(), "clone/path"); } @Test public void testClonePathSetWhenConvertingDevfileToProjectConfig() throws Exception { ProjectImpl devfileProject = new ProjectImpl( "myProject", new SourceImpl( "git", "https://github.com/eclipse/che.git", "master", null, null, null, null), "down/the/rabbit/hole/myProject"); ProjectConfigImpl workspaceProject = projectConverter.toWorkspaceProject(devfileProject); assertEquals(workspaceProject.getName(), "myProject"); assertEquals(workspaceProject.getPath(), "/down/the/rabbit/hole/myProject"); SourceStorageImpl source = workspaceProject.getSource(); assertEquals(source.getType(), "git"); assertEquals(source.getLocation(), "https://github.com/eclipse/che.git"); } @Test(expectedExceptions = DevfileException.class) public void testClonePathCannotEscapeProjectsRoot() throws Exception { ProjectImpl devfileProject = new ProjectImpl( "myProject", new SourceImpl( "git", "https://github.com/eclipse/che.git", "master", null, null, null, null), "cant/hack/../../../usr/bin"); projectConverter.toWorkspaceProject(devfileProject); } @Test(expectedExceptions = DevfileException.class) public void testClonePathCannotBeAbsolute() throws Exception { ProjectImpl devfileProject = new ProjectImpl( "myProject", new SourceImpl( "git", "https://github.com/eclipse/che.git", "master", null, null, null, null), "/usr/bin"); projectConverter.toWorkspaceProject(devfileProject); } @Test public void testUpDirOkInClonePathAsLongAsItDoesntEscapeProjectsRoot() throws Exception { ProjectImpl devfileProject = new ProjectImpl( "myProject", new SourceImpl( "git", "https://github.com/eclipse/che.git", "master", null, null, null, null), "cant/hack/../../usr/bin"); ProjectConfigImpl workspaceProject = projectConverter.toWorkspaceProject(devfileProject); // this is OK, because the absolute-looking path is applied to the projects root assertEquals(workspaceProject.getPath(), "/usr/bin"); } @Test(expectedExceptions = DevfileException.class) public void testCloningIntoProjectsRootFails() throws Exception { ProjectImpl devfileProject = new ProjectImpl( "myProject", new SourceImpl( "git", "https://github.com/eclipse/che.git", "master", null, null, null, null), "not/../in/root/../.."); projectConverter.toWorkspaceProject(devfileProject); } @Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Only one of '" + START_POINT_PARAMETER_NAME + "', '" + TAG_PARAMETER_NAME + "', '" + COMMIT_ID_PARAMETER_NAME + "' can be specified\\.", dataProvider = "invalidStartPointOrTagOrCommitIdCombinations") public void testOnlyOneOfStartPointAttributesAllowed( String startPoint, String tag, String commitId) throws Exception { ProjectImpl devfileProject = new ProjectImpl( "myProject", new SourceImpl( "git", "https://github.com/eclipse/che.git", null, startPoint, tag, commitId, null), null); projectConverter.toWorkspaceProject(devfileProject); } @DataProvider public static Object[][] invalidStartPointOrTagOrCommitIdCombinations() { return new Object[][] { new Object[] {"a", "b", null}, new Object[] {"a", null, "b"}, new Object[] {null, "a", "b"}, new Object[] {"a", "b", "c"} }; } @Test public void testUndefinedCloneParametersNotTransferredToWorkspaceConfig() throws Exception { ProjectImpl devfileProject = new ProjectImpl( "myProject", new SourceImpl( "git", "https://github.com/eclipse/che.git", null, null, null, null, null), null); ProjectConfigImpl wsProject = projectConverter.toWorkspaceProject(devfileProject); SourceStorageImpl wsSource = wsProject.getSource(); assertFalse(wsSource.getParameters().containsKey(BRANCH_PARAMETER_NAME)); assertFalse(wsSource.getParameters().containsKey(START_POINT_PARAMETER_NAME)); assertFalse(wsSource.getParameters().containsKey(TAG_PARAMETER_NAME)); assertFalse(wsSource.getParameters().containsKey(COMMIT_ID_PARAMETER_NAME)); assertFalse(wsSource.getParameters().containsKey(SPARSE_CHECKOUT_DIR_PARAMETER_NAME)); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/convert/component/editor/EditorComponentToWorkspaceApplierTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert.component.editor; import static org.eclipse.che.api.core.model.workspace.config.Command.PLUGIN_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentFQNParser; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.wsplugins.PluginFQNParser; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class EditorComponentToWorkspaceApplierTest { @Mock private FileContentProvider fileContentProvider; private EditorComponentToWorkspaceApplier editorComponentApplier; private PluginFQNParser fqnParser = new PluginFQNParser(); @BeforeMethod public void setUp() { editorComponentApplier = new EditorComponentToWorkspaceApplier(new ComponentFQNParser(fqnParser)); } @Test public void shouldProvisionWorkspaceEditorAttributeDuringCheEditorComponentApplying() throws Exception { String editorId = "eclipse/super-editor/0.0.1"; // given WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl(); ComponentImpl editorComponent = new ComponentImpl(); editorComponent.setType(EDITOR_COMPONENT_TYPE); editorComponent.setAlias("editor"); editorComponent.setId(editorId); editorComponent.setMemoryLimit("12345M"); // when editorComponentApplier.apply(workspaceConfig, editorComponent, null); // then assertEquals(workspaceConfig.getAttributes().get(WORKSPACE_TOOLING_EDITOR_ATTRIBUTE), editorId); } @Test public void shouldProvisionWorkspaceEditorAttributeWithCustomRegistryDuringCheEditorComponentApplying() throws Exception { String editorId = "eclipse/super-editor/0.0.1"; String registryUrl = "https://myregistry.com/infolder/"; // given WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl(); ComponentImpl editorComponent = new ComponentImpl(); editorComponent.setType(EDITOR_COMPONENT_TYPE); editorComponent.setAlias("editor1"); editorComponent.setId(editorId); editorComponent.setRegistryUrl(registryUrl); editorComponent.setMemoryLimit("12345M"); // when editorComponentApplier.apply(workspaceConfig, editorComponent, null); // then assertEquals( workspaceConfig.getAttributes().get(WORKSPACE_TOOLING_EDITOR_ATTRIBUTE), registryUrl + "#" + editorId); } @Test public void shouldProvisionWorkspaceEditorAttributeWithReferenceDuringCheEditorComponentApplying() throws Exception { String reference = "https://myregistry.com/infolder/meta.yaml"; String meta = "apiVersion: v2\n" + "publisher: eclipse\n" + "name: super-editor\n" + "version: 0.0.1\n" + "type: Che Editor"; // given WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl(); ComponentImpl editorComponent = new ComponentImpl(); editorComponent.setType(EDITOR_COMPONENT_TYPE); editorComponent.setAlias("editor1"); editorComponent.setReference(reference); editorComponent.setMemoryLimit("12345M"); when(fileContentProvider.fetchContent(anyString())).thenReturn(meta); // when editorComponentApplier.apply(workspaceConfig, editorComponent, fileContentProvider); // then assertEquals( workspaceConfig.getAttributes().get(WORKSPACE_TOOLING_EDITOR_ATTRIBUTE), reference); assertEquals(editorComponent.getId(), "eclipse/super-editor/0.0.1"); } @Test public void shouldProvisionPluginCommandAttributesDuringCheEditorComponentApplying() throws Exception { // given ComponentImpl superPluginComponent = new ComponentImpl(); superPluginComponent.setAlias("editor"); superPluginComponent.setId("eclipse/super-editor/0.0.1"); superPluginComponent.setType(EDITOR_COMPONENT_TYPE); WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl(); CommandImpl command = new CommandImpl(); command.getAttributes().put(COMPONENT_ALIAS_COMMAND_ATTRIBUTE, "editor"); workspaceConfig.getCommands().add(command); // when editorComponentApplier.apply(workspaceConfig, superPluginComponent, null); // then assertEquals( workspaceConfig.getCommands().get(0).getAttributes().get(PLUGIN_ATTRIBUTE), "eclipse/super-editor/0.0.1"); } @Test public void shouldProvisionPluginCommandAttributeWhenIdIsURLToCustomPluginRegistry() throws Exception { // given ComponentImpl superPluginComponent = new ComponentImpl(); superPluginComponent.setAlias("editor"); superPluginComponent.setId("https://custom-plugin.registry/plugins#eclipse/super-editor/0.0.1"); superPluginComponent.setType(EDITOR_COMPONENT_TYPE); WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl(); CommandImpl command = new CommandImpl(); command.getAttributes().put(COMPONENT_ALIAS_COMMAND_ATTRIBUTE, "editor"); workspaceConfig.getCommands().add(command); // when editorComponentApplier.apply(workspaceConfig, superPluginComponent, null); // then assertEquals( workspaceConfig.getCommands().get(0).getAttributes().get(PLUGIN_ATTRIBUTE), "eclipse/super-editor/0.0.1"); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/convert/component/plugin/PluginComponentToWorkspaceApplierTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.convert.component.plugin; import static org.eclipse.che.api.core.model.workspace.config.Command.PLUGIN_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.COMPONENT_ALIAS_COMMAND_ATTRIBUTE; import static org.eclipse.che.api.workspace.server.devfile.Constants.PLUGIN_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.convert.component.ComponentFQNParser; import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.wsplugins.PluginFQNParser; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class PluginComponentToWorkspaceApplierTest { @Mock private FileContentProvider fileContentProvider; private PluginComponentToWorkspaceApplier pluginComponentApplier; private PluginFQNParser fqnParser = new PluginFQNParser(); @BeforeMethod public void setUp() { pluginComponentApplier = new PluginComponentToWorkspaceApplier(new ComponentFQNParser(fqnParser)); } @Test public void shouldProvisionPluginWorkspaceAttributeDuringChePluginComponentApplying() throws Exception { String superPluginId = "eclipse/super-plugin/0.0.1"; // given ComponentImpl superPluginComponent = new ComponentImpl(); superPluginComponent.setAlias("super-plugin"); superPluginComponent.setId(superPluginId); superPluginComponent.setType(PLUGIN_COMPONENT_TYPE); superPluginComponent.setMemoryLimit("1234M"); superPluginComponent.getPreferences().put("java-home", "/home/user/jdk11"); ComponentImpl customPluginComponent = new ComponentImpl(); customPluginComponent.setAlias("custom"); customPluginComponent.setId("publisher1/custom-plugin/v1"); customPluginComponent.setType(PLUGIN_COMPONENT_TYPE); WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl(); // when pluginComponentApplier.apply(workspaceConfig, superPluginComponent, null); pluginComponentApplier.apply(workspaceConfig, customPluginComponent, null); // then String workspaceTooling = workspaceConfig.getAttributes().get(WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE); assertTrue(workspaceTooling.matches("(.+/.+/.+),(.+/.+/.+)")); assertTrue(workspaceTooling.contains(superPluginId)); assertTrue(workspaceTooling.contains("publisher1/custom-plugin/v1")); } @Test public void shouldProvisionPluginWorkspaceAttributeWithCustomRegistryDuringChePluginComponentApplying() throws Exception { String superPluginId = "eclipse/super-plugin/0.0.1"; String registryUrl = "https://myregistry.com/infolder/"; // given ComponentImpl superPluginComponent = new ComponentImpl(); superPluginComponent.setAlias("super-plugin"); superPluginComponent.setId(superPluginId); superPluginComponent.setType(PLUGIN_COMPONENT_TYPE); superPluginComponent.setMemoryLimit("1234M"); ComponentImpl customPluginComponent = new ComponentImpl(); customPluginComponent.setAlias("custom"); customPluginComponent.setId("publisher1/custom-plugin/v1"); customPluginComponent.setRegistryUrl(registryUrl); customPluginComponent.setType(PLUGIN_COMPONENT_TYPE); WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl(); // when pluginComponentApplier.apply(workspaceConfig, superPluginComponent, null); pluginComponentApplier.apply(workspaceConfig, customPluginComponent, null); // then String workspaceTooling = workspaceConfig.getAttributes().get(WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE); assertTrue(workspaceTooling.matches("(.+/.+/.+),(.+/.+/.+)")); assertTrue(workspaceTooling.contains(superPluginId)); assertTrue(workspaceTooling.contains(registryUrl + "#" + "publisher1/custom-plugin/v1")); } @Test public void shouldProvisionPluginWorkspaceAttributeWithReferenceDuringChePluginComponentApplying() throws Exception { String superPluginId = "eclipse/super-plugin/0.0.1"; String reference = "https://myregistry.com/infolder/meta.yaml"; String meta = "apiVersion: v2\n" + "publisher: eclipse\n" + "name: super-plugin\n" + "version: 0.0.1\n" + "type: Che Plugin"; // given ComponentImpl superPluginComponent = new ComponentImpl(); superPluginComponent.setAlias("super-plugin"); superPluginComponent.setId(superPluginId); superPluginComponent.setType(PLUGIN_COMPONENT_TYPE); superPluginComponent.setMemoryLimit("1234M"); when(fileContentProvider.fetchContent(anyString())).thenReturn(meta); ComponentImpl customPluginComponent = new ComponentImpl(); customPluginComponent.setAlias("custom"); customPluginComponent.setType(PLUGIN_COMPONENT_TYPE); customPluginComponent.setReference(reference); WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl(); // when pluginComponentApplier.apply(workspaceConfig, superPluginComponent, fileContentProvider); pluginComponentApplier.apply(workspaceConfig, customPluginComponent, fileContentProvider); // then String workspaceTooling = workspaceConfig.getAttributes().get(WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE); assertTrue(workspaceTooling.matches("(.+/.+/.+),(https://.+/.+/.+)")); assertTrue(workspaceTooling.contains(superPluginId)); assertTrue(workspaceTooling.contains(reference)); assertEquals(customPluginComponent.getId(), "eclipse/super-plugin/0.0.1"); } @Test public void shouldProvisionPluginCommandAttributesDuringChePluginComponentApplying() throws Exception { // given ComponentImpl superPluginComponent = new ComponentImpl(); superPluginComponent.setAlias("super-plugin"); superPluginComponent.setId("eclipse/super-plugin/0.0.1"); superPluginComponent.setType(PLUGIN_COMPONENT_TYPE); WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl(); CommandImpl command = new CommandImpl(); command.getAttributes().put(COMPONENT_ALIAS_COMMAND_ATTRIBUTE, "super-plugin"); workspaceConfig.getCommands().add(command); // when pluginComponentApplier.apply(workspaceConfig, superPluginComponent, null); // then assertEquals( workspaceConfig.getCommands().get(0).getAttributes().get(PLUGIN_ATTRIBUTE), "eclipse/super-plugin/0.0.1"); } @Test public void shouldProvisionPluginCommandAttributeWhenIdIsURLToCustomPluginRegistry() throws Exception { // given ComponentImpl superPluginComponent = new ComponentImpl(); superPluginComponent.setAlias("super-plugin"); superPluginComponent.setId( "https://custom-plugin.registry/plugins/#eclipse/super-plugin/0.0.1"); superPluginComponent.setType(PLUGIN_COMPONENT_TYPE); WorkspaceConfigImpl workspaceConfig = new WorkspaceConfigImpl(); CommandImpl command = new CommandImpl(); command.getAttributes().put(COMPONENT_ALIAS_COMMAND_ATTRIBUTE, "super-plugin"); workspaceConfig.getCommands().add(command); // when pluginComponentApplier.apply(workspaceConfig, superPluginComponent, null); // then assertEquals( workspaceConfig.getCommands().get(0).getAttributes().get(PLUGIN_ATTRIBUTE), "eclipse/super-plugin/0.0.1"); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileIntegrityValidatorTest.java ================================================ /* * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.devfile.validator; import static org.eclipse.che.api.workspace.server.devfile.Constants.DOCKERIMAGE_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.OPENSHIFT_COMPONENT_TYPE; import static org.eclipse.che.api.workspace.server.devfile.Constants.PLUGIN_COMPONENT_TYPE; import static org.mockito.Mockito.mock; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.common.collect.ImmutableList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import org.testng.reporters.Files; @Listeners(MockitoTestNGListener.class) public class DevfileIntegrityValidatorTest { private ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); private DevfileImpl initialDevfile; private DevfileIntegrityValidator integrityValidator; @Mock private ComponentIntegrityValidator dummyComponentValidator; @BeforeMethod public void setUp() throws Exception { Map componentValidators = new HashMap<>(); componentValidators.put(KUBERNETES_COMPONENT_TYPE, dummyComponentValidator); componentValidators.put(OPENSHIFT_COMPONENT_TYPE, dummyComponentValidator); componentValidators.put(PLUGIN_COMPONENT_TYPE, dummyComponentValidator); componentValidators.put(EDITOR_COMPONENT_TYPE, dummyComponentValidator); integrityValidator = new DevfileIntegrityValidator(componentValidators); String devFileYamlContent = Files.readFile(getClass().getClassLoader().getResourceAsStream("devfile/devfile.yaml")); initialDevfile = objectMapper.readValue(devFileYamlContent, DevfileImpl.class); } @Test public void shouldValidateCorrectDevfile() throws Exception { // when integrityValidator.validateDevfile(initialDevfile); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Duplicate component alias found:'mvn-stack'") public void shouldThrowExceptionOnDuplicateComponentAlias() throws Exception { DevfileImpl broken = new DevfileImpl(initialDevfile); ComponentImpl component = new ComponentImpl(); component.setAlias(initialDevfile.getComponents().get(0).getAlias()); broken.getComponents().add(component); // when integrityValidator.validateDevfile(broken); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Duplicated endpoint name 'e1' found in 'dockerimage:latest' component") public void shouldThrowExceptionOnDuplicateEndpointName() throws Exception { DevfileImpl broken = new DevfileImpl(initialDevfile); ComponentImpl component = new ComponentImpl(); component.setType(DOCKERIMAGE_COMPONENT_TYPE); component.setImage("dockerimage:latest"); component.setEndpoints( ImmutableList.of( new EndpointImpl("e1", 8080, Collections.emptyMap()), new EndpointImpl("e1", 8082, Collections.emptyMap()))); broken.getComponents().add(component); // when integrityValidator.validateDevfile(broken); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Multiple editor components found: 'eclipse/che-theia/0.0.3', 'editor-2'") public void shouldThrowExceptionOnMultipleEditors() throws Exception { DevfileImpl broken = new DevfileImpl(initialDevfile); ComponentImpl component = new ComponentImpl(); component.setAlias("editor-2"); component.setType(EDITOR_COMPONENT_TYPE); broken.getComponents().add(component); // when integrityValidator.validateDevfile(broken); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Duplicate command name found:'build'") public void shouldThrowExceptionOnDuplicateCommandName() throws Exception { DevfileImpl broken = new DevfileImpl(initialDevfile); CommandImpl command = new CommandImpl(); command.setName(initialDevfile.getCommands().get(0).getName()); broken.getCommands().add(command); // when integrityValidator.validateDevfile(broken); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Command 'build' does not have actions.") public void shouldThrowExceptionWhenCommandDoesNotHaveActions() throws Exception { DevfileImpl broken = new DevfileImpl(initialDevfile); broken.getCommands().get(0).getActions().clear(); // when integrityValidator.validateDevfile(broken); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Multiple actions in command 'build' are not supported yet.") public void shouldThrowExceptionWhenCommandHasMultipleActions() throws Exception { DevfileImpl broken = new DevfileImpl(initialDevfile); broken.getCommands().get(0).getActions().add(new ActionImpl()); // when integrityValidator.validateDevfile(broken); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Command 'build' has action that refers to a component with unknown alias 'no_such_component'") public void shouldThrowExceptionOnUnexistingCommandActionComponent() throws Exception { DevfileImpl broken = new DevfileImpl(initialDevfile); broken.getCommands().get(0).getActions().clear(); ActionImpl action = new ActionImpl(); action.setComponent("no_such_component"); broken.getCommands().get(0).getActions().add(action); // when integrityValidator.validateDevfile(broken); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Duplicate project name found:'petclinic'") public void shouldThrowExceptionOnDuplicateProjectName() throws Exception { DevfileImpl broken = new DevfileImpl(initialDevfile); ProjectImpl project = new ProjectImpl(); project.setName(initialDevfile.getProjects().get(0).getName()); broken.getProjects().add(project); // when integrityValidator.validateDevfile(broken); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Duplicate environment variable 'foo' found in component 'test1'") public void shouldThrowExceptionOnDuplicateEnvironmentVariable() throws Exception { DevfileImpl broken = new DevfileImpl(initialDevfile); ComponentImpl k8s1 = new ComponentImpl(); k8s1.setType(OPENSHIFT_COMPONENT_TYPE); k8s1.setAlias("test1"); k8s1.getEnv().add(new EnvImpl("foo", "bar")); k8s1.getEnv().add(new EnvImpl("foo", "baz")); broken.getComponents().add(k8s1); // when integrityValidator.validateDevfile(broken); } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "Invalid project name found:'.*'. Name must contain only Latin letters," + "digits or these following special characters ._-") public void shouldThrowExceptionOnInvalidProjectName() throws Exception { DevfileImpl broken = new DevfileImpl(initialDevfile); broken.getProjects().get(0).setName("./" + initialDevfile.getProjects().get(0).getName()); // when integrityValidator.validateDevfile(broken); } @Test public void shouldNotValidateContentReferencesOnNonKuberenetesComponents() throws Exception { // given // just remove all the content-referencing components and check that all still works DevfileImpl devfile = new DevfileImpl(initialDevfile); Iterator it = devfile.getComponents().iterator(); while (it.hasNext()) { String componentType = it.next().getType(); if (componentType.equals(KUBERNETES_COMPONENT_TYPE) || componentType.equals(OPENSHIFT_COMPONENT_TYPE)) { it.remove(); } } // when integrityValidator.validateContentReferences(devfile, mock(FileContentProvider.class)); // then // no exception is thrown } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "There are multiple components 'dockerimage:latest' of type 'dockerimage' that cannot be" + " uniquely identified. Please add aliases that would distinguish the components.") public void shouldRequireAliasWhenDockerImageComponentsHaveSameImage() throws Exception { // given DevfileImpl devfile = new DevfileImpl(initialDevfile); ComponentImpl docker1 = new ComponentImpl(); docker1.setType(DOCKERIMAGE_COMPONENT_TYPE); docker1.setImage("dockerimage:latest"); ComponentImpl docker2 = new ComponentImpl(); docker2.setType(DOCKERIMAGE_COMPONENT_TYPE); docker2.setImage("dockerimage:latest"); devfile.getComponents().add(docker1); devfile.getComponents().add(docker2); // when integrityValidator.validateDevfile(devfile); // then // exception is thrown } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "There are multiple components 'list.yaml' of type 'kubernetes' that cannot be" + " uniquely identified. Please add aliases that would distinguish the components.") public void shouldRequireAliasWhenKubernetesComponentsHaveSameReference() throws Exception { // given DevfileImpl devfile = new DevfileImpl(initialDevfile); ComponentImpl k8s1 = new ComponentImpl(); k8s1.setType(KUBERNETES_COMPONENT_TYPE); k8s1.setReference("list.yaml"); ComponentImpl k8s2 = new ComponentImpl(); k8s2.setType(KUBERNETES_COMPONENT_TYPE); k8s2.setReference("list.yaml"); devfile.getComponents().add(k8s1); devfile.getComponents().add(k8s2); // when integrityValidator.validateDevfile(devfile); // then // exception is thrown } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "There are multiple components 'list.yaml' of type 'openshift' that cannot be" + " uniquely identified. Please add aliases that would distinguish the components.") public void shouldRequireAliasWhenOpenshiftComponentsHaveSameReference() throws Exception { // given DevfileImpl devfile = new DevfileImpl(initialDevfile); ComponentImpl k8s1 = new ComponentImpl(); k8s1.setType(OPENSHIFT_COMPONENT_TYPE); k8s1.setReference("list.yaml"); ComponentImpl k8s2 = new ComponentImpl(); k8s2.setType(OPENSHIFT_COMPONENT_TYPE); k8s2.setReference("list.yaml"); devfile.getComponents().add(k8s1); devfile.getComponents().add(k8s2); // when integrityValidator.validateDevfile(devfile); // then // exception is thrown } @Test( expectedExceptions = DevfileFormatException.class, expectedExceptionsMessageRegExp = "There are multiple components 'openshift' of type 'openshift' that cannot be" + " uniquely identified. Please add aliases that would distinguish the components.") public void shouldRequireAliasWhenOpenshiftComponentsHaveNoReference() throws Exception { // given DevfileImpl devfile = new DevfileImpl(initialDevfile); ComponentImpl k8s1 = new ComponentImpl(); k8s1.setType(OPENSHIFT_COMPONENT_TYPE); ComponentImpl k8s2 = new ComponentImpl(); k8s2.setType(OPENSHIFT_COMPONENT_TYPE); devfile.getComponents().add(k8s1); devfile.getComponents().add(k8s2); // when integrityValidator.validateDevfile(devfile); // then // exception is thrown } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/hc/HttpConnectionServerCheckerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.io.IOException; import java.lang.reflect.Method; import java.net.Authenticator; import java.net.HttpURLConnection; import java.net.PasswordAuthentication; import java.net.URL; import java.util.Timer; import java.util.concurrent.TimeUnit; import org.eclipse.che.commons.proxy.ProxyAuthenticator; import org.mockito.Mock; import org.mockito.stubbing.Answer; import org.mockito.testng.MockitoTestNGListener; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ @Listeners(MockitoTestNGListener.class) public class HttpConnectionServerCheckerTest { private String MACHINE_NAME = "mach1"; private String SERVER_REF = "ref1"; private URL SERVER_URL; @Mock private Timer timer; @Mock private HttpURLConnection conn; private HttpConnectionServerChecker checker; @BeforeMethod public void setUp() throws Exception { SERVER_URL = new URL("http://localhost"); checker = spy( new HttpConnectionServerChecker( SERVER_URL, MACHINE_NAME, SERVER_REF, 1, TimeUnit.SECONDS, timer, null)); lenient().doReturn(conn).when(checker).createConnection(nullable(URL.class)); lenient().when(conn.getResponseCode()).thenReturn(200); } @BeforeClass public void setup() { // These variables affect on ProxyAuthenticator's enums HTTP and HTTPS. It's very hard // to reinitialize them (internal fields in a private enum). // Until they do not affect other tests we can skip the cleanup phase. System.setProperty("http.proxyUser", "u1"); System.setProperty("http.proxyPassword", "p1"); } @Test(dataProvider = "successfulResponseCodeProvider") public void shouldConfirmConnectionSuccessIfResponseCodeIsBetween200And400(Integer responseCode) throws Exception { when(conn.getResponseCode()).thenReturn(responseCode); assertTrue(checker.isConnectionSuccessful(conn)); } @DataProvider public static Object[][] successfulResponseCodeProvider() { return new Object[][] {{200}, {201}, {210}, {301}, {302}, {303}}; } @Test(dataProvider = "nonSuccessfulResponseCodeProvider") public void shouldNotConfirmConnectionSuccessIfResponseCodeIsLessThan200Or400OrMore( Integer responseCode) throws Exception { when(conn.getResponseCode()).thenReturn(responseCode); assertFalse(checker.isConnectionSuccessful(conn)); } @DataProvider public static Object[][] nonSuccessfulResponseCodeProvider() { return new Object[][] {{199}, {400}, {401}, {402}, {403}, {404}, {405}, {409}, {500}}; } @Test public void shouldSetProxyAuthenticatorBeforeCreateConnection() throws Exception { Assert.assertFalse(isPasswordAuthenticationSet()); when(checker.createConnection(eq(SERVER_URL))) .thenAnswer( (Answer) invocation -> { assertTrue(isPasswordAuthenticationSet()); return conn; }); checker.isAvailable(); Assert.assertFalse(isPasswordAuthenticationSet()); } @Test public void shouldOpenConnectionToProvidedUrl() throws Exception { checker.isAvailable(); verify(checker).createConnection(eq(SERVER_URL)); } @Test public void shouldSetTimeoutsToConnection() throws Exception { checker.isAvailable(); verify(conn).setReadTimeout((int) TimeUnit.SECONDS.toMillis(3)); verify(conn).setConnectTimeout((int) TimeUnit.SECONDS.toMillis(3)); } @Test public void shouldBeAbleToConfirmAvailability() throws Exception { assertTrue(checker.isAvailable()); } @Test public void shouldBeAbleToRejectAvailability() throws Exception { when(conn.getResponseCode()).thenReturn(401); assertFalse(checker.isAvailable()); } @Test public void shouldRejectAvailabilityInCaseOfExceptionOnResponseCodeRetrieving() throws Exception { when(conn.getResponseCode()).thenThrow(new IOException()); assertFalse(checker.isAvailable()); } @Test public void shouldRejectAvailabilityInCaseOfExceptionOnConnectionOpening() throws Exception { when(checker.createConnection(nullable(URL.class))).thenThrow(new IOException()); assertFalse(checker.isAvailable()); } @Test public void shouldDisconnectIfAvailable() throws Exception { assertTrue(checker.isAvailable()); verify(conn).disconnect(); } @Test public void shouldDisconnectIfNotAvailable() throws Exception { when(conn.getResponseCode()).thenReturn(401); assertFalse(checker.isAvailable()); verify(conn).disconnect(); } public boolean isPasswordAuthenticationSet() { Authenticator authenticator = Authenticator.getDefault(); if (authenticator != null && authenticator instanceof ProxyAuthenticator) { ProxyAuthenticator proxyAuthenticator = (ProxyAuthenticator) authenticator; try { Method method = ProxyAuthenticator.class.getDeclaredMethod("getPasswordAuthentication", null); method.setAccessible(true); PasswordAuthentication value = (PasswordAuthentication) method.invoke(proxyAuthenticator, null); return value != null; } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } return false; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/hc/ServerCheckerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc; import static java.lang.String.format; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import java.util.Timer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ public class ServerCheckerTest { private static final String MACHINE_NAME = "mach1"; private static final String SERVER_REF = "ref1"; private static final long PERIOD_MS = 10; private static final long CHECKER_TIMEOUT_MS = 5000; private static final long TEST_TIMEOUT_MS = CHECKER_TIMEOUT_MS + 5000; private static final int SUCCESS_THRESHOLD = 1; private Timer timer; private TestServerChecker checker; @BeforeMethod public void setUp() throws Exception { timer = new Timer(true); } @AfterMethod public void tearDown() throws Exception { timer.cancel(); } @Test(timeOut = TEST_TIMEOUT_MS) public void successfulCheckTest() throws Exception { checker = new TestServerChecker( MACHINE_NAME, SERVER_REF, PERIOD_MS, CHECKER_TIMEOUT_MS, SUCCESS_THRESHOLD, TimeUnit.MILLISECONDS, timer); CompletableFuture reportCompFuture = checker.getReportCompFuture(); // not considered as available before start assertFalse(reportCompFuture.isDone()); // ensure server not available before start CountDownLatch isAvailableCountDownLatch = checker.setAvailable(false); checker.start(); isAvailableCountDownLatch.await(PERIOD_MS * 2, TimeUnit.MILLISECONDS); // not considered as available after check assertFalse(reportCompFuture.isDone()); // make server available isAvailableCountDownLatch = checker.setAvailable(true); assertEquals(reportCompFuture.get(), SERVER_REF); isAvailableCountDownLatch.await(PERIOD_MS * 2, TimeUnit.MILLISECONDS); } @Test(timeOut = TEST_TIMEOUT_MS) public void checkTimeoutTest() throws Exception { checker = new TestServerChecker( MACHINE_NAME, SERVER_REF, PERIOD_MS, PERIOD_MS * 2, SUCCESS_THRESHOLD, TimeUnit.MILLISECONDS, timer); // ensure server not available before start checker.setAvailable(false); checker.start(); CompletableFuture reportCompFuture = checker.getReportCompFuture(); try { reportCompFuture.get(); fail(); } catch (ExecutionException e) { assertTrue(e.getCause() instanceof InfrastructureException); assertEquals( e.getCause().getMessage(), format("Server '%s' in container '%s' not available.", SERVER_REF, MACHINE_NAME)); } } @Test(expectedExceptions = InfrastructureException.class) public void checkOnceThrowsExceptionIfServerIsNotAvailable() throws InfrastructureException { new TestServerChecker("test", "test", 1, 1, 1, TimeUnit.SECONDS, null).checkOnce(ref -> {}); } private static class TestServerChecker extends ServerChecker { private boolean isAvailable; private CountDownLatch isAvailableCountDownLatch = new CountDownLatch(1); protected TestServerChecker( String machineName, String serverRef, long period, long timeout, int successThreshold, TimeUnit timeUnit, Timer timer) { super(machineName, serverRef, timeout, timeUnit, timer); } @Override public boolean isAvailable() { isAvailableCountDownLatch.countDown(); return isAvailable; } public CountDownLatch setAvailable(boolean isAvailable) { this.isAvailable = isAvailable; this.isAvailableCountDownLatch = new CountDownLatch(1); return isAvailableCountDownLatch; } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/hc/ServersCheckerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.after; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.fail; import com.google.common.collect.ImmutableMap; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.token.MachineTokenProvider; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ @Listeners(MockitoTestNGListener.class) public class ServersCheckerTest { private static final String WSAGENT_HTTP_SERVER = "wsagent/http"; private static final String EXEC_AGENT_HTTP_SERVER = "exec-agent/http"; private static final String TERMINAL_SERVER = "terminal"; private static final String[] CONFIGURED_SERVERS = new String[] {WSAGENT_HTTP_SERVER, EXEC_AGENT_HTTP_SERVER, TERMINAL_SERVER}; private static final String MACHINE_NAME = "mach1"; private static final String MACHINE_TOKEN = "machineToken"; private static final String WORKSPACE_ID = "ws123"; private static final String USER_ID = "0000-0000-0007"; @Mock private Consumer readinessHandler; @Mock private MachineTokenProvider machineTokenProvider; @Mock private HttpConnectionServerChecker connectionChecker; @Mock private RuntimeIdentity runtimeIdentity; private Map servers; private ServersChecker checker; @BeforeMethod public void setUp() throws Exception { servers = new HashMap<>(); servers.putAll( ImmutableMap.of( WSAGENT_HTTP_SERVER, new ServerImpl().withUrl("http://localhost/api"), EXEC_AGENT_HTTP_SERVER, new ServerImpl().withUrl("http://localhost/exec-agent/process"), TERMINAL_SERVER, new ServerImpl().withUrl("http://localhost/terminal/pty"))); CompletableFuture compFuture = new CompletableFuture<>(); when(connectionChecker.getReportCompFuture()).thenReturn(compFuture); when(runtimeIdentity.getWorkspaceId()).thenReturn(WORKSPACE_ID); when(runtimeIdentity.getOwnerId()).thenReturn(USER_ID); checker = spy( new ServersChecker( runtimeIdentity, MACHINE_NAME, servers, machineTokenProvider, CONFIGURED_SERVERS)); when(checker.doCreateChecker(any(URL.class), anyString(), anyString())) .thenReturn(connectionChecker); when(machineTokenProvider.getToken(anyString(), anyString())).thenReturn(MACHINE_TOKEN); } @Test(timeOut = 5000) public void shouldUseMachineTokenWhenCallChecker() throws Exception { servers.clear(); servers.put("wsagent/http", new ServerImpl().withUrl("http://localhost")); checker.startAsync(readinessHandler); connectionChecker.getReportCompFuture().complete("wsagent/http"); verify(machineTokenProvider).getToken(USER_ID, WORKSPACE_ID); ArgumentCaptor tokenCaptor = ArgumentCaptor.forClass(String.class); verify(checker) .doCreateChecker( eq(new URL("http://localhost/")), eq("wsagent/http"), tokenCaptor.capture()); assertEquals(tokenCaptor.getValue(), MACHINE_TOKEN); } @Test(timeOut = 5000) public void shouldNotifyReadinessHandlerAboutEachServerReadiness() throws Exception { checker.startAsync(readinessHandler); verify(readinessHandler, after(500).never()).accept(anyString()); connectionChecker.getReportCompFuture().complete("test_ref"); verify(readinessHandler, times(3)).accept("test_ref"); } @Test(timeOut = 5000) public void shouldThrowExceptionIfAServerIsUnavailable() throws Exception { checker.startAsync(readinessHandler); connectionChecker .getReportCompFuture() .completeExceptionally(new InfrastructureException("my exception")); try { checker.await(); } catch (InfrastructureException e) { assertEquals(e.getMessage(), "my exception"); } } @Test(timeOut = 5000) public void shouldNotCheckNotConfiguredServers() throws Exception { servers.clear(); servers.putAll( ImmutableMap.of( "wsagent/http", new ServerImpl().withUrl("http://localhost"), "not-configured", new ServerImpl().withUrl("http://localhost"))); checker.startAsync(readinessHandler); connectionChecker.getReportCompFuture().complete("test_ref"); checker.await(); verify(readinessHandler).accept("test_ref"); } @Test(timeOut = 5000) public void awaitShouldReturnOnFirstUnavailability() throws Exception { CompletableFuture future1 = spy(new CompletableFuture<>()); CompletableFuture future2 = spy(new CompletableFuture<>()); CompletableFuture future3 = spy(new CompletableFuture<>()); when(connectionChecker.getReportCompFuture()) .thenReturn(future1) .thenReturn(future2) .thenReturn(future3); checker.startAsync(readinessHandler); future2.completeExceptionally(new InfrastructureException("error")); try { checker.await(); fail(); } catch (InfrastructureException ignored) { verify(future1, never()).complete(anyString()); verify(future2, never()).complete(anyString()); verify(future3, never()).complete(anyString()); verify(future1, never()).completeExceptionally(any(Throwable.class)); verify(future2).completeExceptionally(any(Throwable.class)); verify(future3, never()).completeExceptionally(any(Throwable.class)); } } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "oops!") public void throwsExceptionIfAnyServerIsNotAvailable() throws InfrastructureException { doThrow(new InfrastructureException("oops!")).when(connectionChecker).checkOnce(any()); checker.checkOnce(ref -> {}); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/hc/TerminalHttpConnectionServerCheckerTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc; import static org.mockito.Mockito.when; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.net.HttpURLConnection; import java.net.URL; import java.util.Timer; import java.util.concurrent.TimeUnit; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ @Listeners(MockitoTestNGListener.class) public class TerminalHttpConnectionServerCheckerTest { private String MACHINE_NAME = "mach1"; private String SERVER_REF = "ref1"; @Mock private Timer timer; @Mock private HttpURLConnection conn; private TerminalHttpConnectionServerChecker checker; @BeforeMethod public void setUp() throws Exception { checker = new TerminalHttpConnectionServerChecker( new URL("http://localhost"), MACHINE_NAME, SERVER_REF, 1, TimeUnit.SECONDS, timer, null); } @Test public void shouldConfirmConnectionSuccessIfResponseCodeIs404() throws Exception { when(conn.getResponseCode()).thenReturn(404); assertTrue(checker.isConnectionSuccessful(conn)); } @Test public void shouldNotConfirmConnectionSuccessIfResponseCodeIsNot404() throws Exception { when(conn.getResponseCode()).thenReturn(200); assertFalse(checker.isConnectionSuccessful(conn)); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/hc/probe/WorkspaceProbesFactoryTest.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.hc.probe; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.workspace.shared.Constants.SERVER_EXEC_AGENT_HTTP_REFERENCE; import static org.eclipse.che.api.workspace.shared.Constants.SERVER_TERMINAL_REFERENCE; import static org.eclipse.che.api.workspace.shared.Constants.SERVER_WS_AGENT_HTTP_REFERENCE; import static org.mockito.Mockito.lenient; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import jakarta.ws.rs.core.HttpHeaders; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.model.impl.ServerImpl; import org.eclipse.che.api.workspace.server.token.MachineTokenProvider; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author Alexander Garagatyi */ @Listeners(MockitoTestNGListener.class) public class WorkspaceProbesFactoryTest { private static final String WORKSPACE_ID = "wsId"; private static final String MACHINE_NAME = "machine1"; private static final String TOKEN = "token1"; private static final ServerImpl SERVER = new ServerImpl().withUrl("https://localhost:4040/path1"); private static final RuntimeIdentity IDENTITY = new RuntimeIdentityImpl(WORKSPACE_ID, "default", "id1", "infraNamespace"); @Mock private MachineTokenProvider tokenProvider; private WorkspaceProbesFactory probesFactory; @BeforeMethod public void setUp() throws Exception { lenient().when(tokenProvider.getToken(IDENTITY.getOwnerId(), WORKSPACE_ID)).thenReturn(TOKEN); probesFactory = new WorkspaceProbesFactory(tokenProvider); } @Test public void shouldNotCreateProbesFactoriesForOtherServers() throws Exception { WorkspaceProbes wsProbes = probesFactory.getProbes( IDENTITY, MACHINE_NAME, ImmutableMap.of("server1/http", SERVER, "terminal/http", SERVER, "terminal1", SERVER)); assertTrue(wsProbes.getProbes().isEmpty()); } @Test public void returnsProbesForAMachineForWsAgent() throws Exception { WorkspaceProbes wsProbes = probesFactory.getProbes( IDENTITY, MACHINE_NAME, singletonMap(SERVER_WS_AGENT_HTTP_REFERENCE, SERVER)); verifyHttpProbeConfig( wsProbes, SERVER_WS_AGENT_HTTP_REFERENCE, 3, 1, 10, 10, 120, "/path1/liveness", "localhost", 4040, "https", singletonMap(HttpHeaders.AUTHORIZATION, "Bearer " + TOKEN)); } @Test public void returnsProbesForAMachineForTerminal() throws Exception { WorkspaceProbes wsProbes = probesFactory.getProbes( IDENTITY, MACHINE_NAME, singletonMap( SERVER_TERMINAL_REFERENCE, new ServerImpl().withUrl("wss://localhost:4040/pty"))); verifyHttpProbeConfig( wsProbes, SERVER_TERMINAL_REFERENCE, 3, 1, 10, 10, 120, "/liveness", "localhost", 4040, "https", emptyMap()); } @Test public void returnsProbesForAMachineForExec() throws Exception { WorkspaceProbes wsProbes = probesFactory.getProbes( IDENTITY, MACHINE_NAME, singletonMap( SERVER_EXEC_AGENT_HTTP_REFERENCE, new ServerImpl().withUrl("https://localhost:4040/process"))); verifyHttpProbeConfig( wsProbes, SERVER_EXEC_AGENT_HTTP_REFERENCE, 3, 1, 10, 10, 120, "/liveness", "localhost", 4040, "https", emptyMap()); } public void verifyHttpProbeConfig( WorkspaceProbes wsProbes, String serverName, int failureThreshold, int successThreshold, int initialDelay, int period, int timeout, String path, String host, int port, String protocol, Map headers) throws Exception { assertEquals(wsProbes.getWorkspaceId(), WORKSPACE_ID); List probes = wsProbes.getProbes(); assertEquals(probes.size(), 1); ProbeFactory probeFactory = probes.get(0); assertTrue(probeFactory instanceof HttpProbeFactory); HttpProbeFactory httpProbeFactory = (HttpProbeFactory) probeFactory; assertEquals(httpProbeFactory.getMachineName(), MACHINE_NAME); assertEquals(httpProbeFactory.getServerName(), serverName); assertEquals(httpProbeFactory.getWorkspaceId(), WORKSPACE_ID); HttpProbeConfig probeConfig = httpProbeFactory.getProbeConfig(); assertEquals(probeConfig.getFailureThreshold(), failureThreshold); assertEquals(probeConfig.getSuccessThreshold(), successThreshold); assertEquals(probeConfig.getInitialDelaySeconds(), initialDelay); assertEquals(probeConfig.getPeriodSeconds(), period); assertEquals(probeConfig.getTimeoutSeconds(), timeout); assertEquals(probeConfig.getPath(), path); assertEquals(probeConfig.getHost(), host); assertEquals(probeConfig.getPort(), port); assertEquals(probeConfig.getScheme(), protocol); assertEquals(probeConfig.getHeaders(), headers); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/model/impl/ServerConfigImplTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.model.impl; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.INTERNAL_SERVER_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.REQUIRE_SUBDOMAIN; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.SERVER_NAME_ATTRIBUTE; import static org.testng.Assert.*; import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.Map; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; import org.testng.annotations.Test; public class ServerConfigImplTest { @Test public void testStoreEndpointNameIntoAttributes() { ServerConfig serverConfig = ServerConfigImpl.createFromEndpoint(new EndpointImpl("blabol", 123, emptyMap())); assertEquals(serverConfig.getAttributes().get(SERVER_NAME_ATTRIBUTE), "blabol"); } @Test public void testCreateFromEndpointMinimalEndpointShouldTranslateToHttpProtocol() { ServerConfig serverConfig = ServerConfigImpl.createFromEndpoint(new EndpointImpl("name", 123, emptyMap())); assertEquals(serverConfig.getProtocol(), "http"); } @Test public void testCreateFromEndpointMinimalEndpointShouldTranslateToNullPath() { ServerConfig serverConfig = ServerConfigImpl.createFromEndpoint(new EndpointImpl("name", 123, emptyMap())); assertNull(serverConfig.getPath()); } @Test public void testCreateFromEndpointCustomAttributesShouldPreserveInAttributes() { Map customAttributes = ImmutableMap.of("k1", "v1", "k2", "v2"); ServerConfig serverConfig = ServerConfigImpl.createFromEndpoint(new EndpointImpl("name", 123, customAttributes)); assertEquals(serverConfig.getAttributes().get("k1"), "v1"); assertEquals(serverConfig.getAttributes().get("k2"), "v2"); assertEquals(serverConfig.getAttributes().size(), 3); } @Test public void testCreateFromEndpointTranslatePath() { ServerConfig serverConfig = ServerConfigImpl.createFromEndpoint( new EndpointImpl("name", 123, singletonMap("path", "hello"))); assertEquals(serverConfig.getPath(), "hello"); } @Test public void testCreateFromEndpointTranslateProtocol() { ServerConfig serverConfig = ServerConfigImpl.createFromEndpoint( new EndpointImpl("name", 123, singletonMap("protocol", "hello"))); assertEquals(serverConfig.getProtocol(), "hello"); } @Test public void testCreateFromEndpointTranslatePublicTrue() { ServerConfig serverConfig = ServerConfigImpl.createFromEndpoint( new EndpointImpl("name", 123, singletonMap("public", "true"))); assertFalse(serverConfig.isInternal()); } @Test public void testCreateFromEndpointTranslatePublicWhatever() { ServerConfig serverConfig = ServerConfigImpl.createFromEndpoint( new EndpointImpl("name", 123, singletonMap("public", "whatever"))); assertFalse(serverConfig.isInternal()); } @Test public void testCreateFromEndpointTranslatePublicFalse() { ServerConfig serverConfig = ServerConfigImpl.createFromEndpoint( new EndpointImpl("name", 123, singletonMap("public", "false"))); assertFalse(serverConfig.getAttributes().isEmpty()); assertEquals( serverConfig.getAttributes().get(INTERNAL_SERVER_ATTRIBUTE), Boolean.TRUE.toString()); } @Test public void testCreateFromEndpointDevfileEndpointAttributeNotSetWhenDefault() { ServerConfig serverConfig = ServerConfigImpl.createFromEndpoint(new EndpointImpl("name", 123, new HashMap<>())); assertFalse(serverConfig.getAttributes().containsKey(REQUIRE_SUBDOMAIN)); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/environment/InternalEnvironmentFactoryTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.environment; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.CONTAINER_SOURCE_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.RECIPE_CONTAINER_SOURCE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.config.Environment; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link InternalEnvironmentFactory}. * * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class InternalEnvironmentFactoryTest { @Mock private RecipeRetriever recipeRetriever; @Mock private MachineConfigsValidator machinesValidator; @Captor private ArgumentCaptor> machinesCaptor; private InternalEnvironmentFactory environmentFactory; @BeforeMethod public void setUp() throws Exception { environmentFactory = spy(new TestEnvironmentFactory(recipeRetriever, machinesValidator)); final InternalEnvironment internalEnv = mock(InternalEnvironment.class); lenient().when(internalEnv.getMachines()).thenReturn(Collections.emptyMap()); lenient().when(environmentFactory.doCreate(any(), anyMap(), anyList())).thenReturn(internalEnv); } @Test public void shouldUseRetrievedRecipeWhileInternalEnvironmentCreation() throws Exception { // given RecipeImpl recipe = new RecipeImpl("type", "contentType", "content", "location"); InternalRecipe retrievedRecipe = mock(InternalRecipe.class); doReturn(retrievedRecipe).when(recipeRetriever).getRecipe(any()); EnvironmentImpl env = new EnvironmentImpl(recipe, null); // when environmentFactory.create(env); // then verify(recipeRetriever).getRecipe(recipe); verify(environmentFactory).doCreate(eq(retrievedRecipe), any(), any()); } @Test public void shouldUseNormalizedServersWhileInternalEnvironmentCreation() throws Exception { // given ServerConfigImpl server = new ServerConfigImpl("8080", "http", "/api", singletonMap("key", "value")); Map normalizedServers = ImmutableMap.of("server", mock(ServerConfig.class)); doReturn(normalizedServers).when(environmentFactory).normalizeServers(any()); ImmutableMap sourceServers = ImmutableMap.of("server", server); MachineConfigImpl machineConfig = new MachineConfigImpl(sourceServers, null, null, null); EnvironmentImpl env = new EnvironmentImpl(null, ImmutableMap.of("machine", machineConfig)); // when environmentFactory.create(env); // then verify(environmentFactory).normalizeServers(sourceServers); verify(environmentFactory).doCreate(any(), machinesCaptor.capture(), any()); Map internalMachines = machinesCaptor.getValue(); assertEquals(internalMachines.get("machine").getServers(), normalizedServers); } @Test public void shouldReturnCreatedInternalEnvironment() throws Exception { // given InternalEnvironment expectedEnv = mock(InternalEnvironment.class); when(environmentFactory.doCreate(any(), any(), any())).thenReturn(expectedEnv); Environment env = mock(Environment.class); // when InternalEnvironment createdEnv = environmentFactory.create(env); // then assertEquals(createdEnv, expectedEnv); } @Test public void shouldPassNullRecipeIfEnvironmentIsNull() throws Exception { // given InternalEnvironment expectedEnv = mock(InternalEnvironment.class); when(environmentFactory.doCreate(any(), any(), any())).thenReturn(expectedEnv); // when InternalEnvironment createdEnv = environmentFactory.create(null); // then assertEquals(createdEnv, expectedEnv); verify(environmentFactory).doCreate(eq(null), any(), any()); } @Test public void normalizeServersProtocols() throws InfrastructureException { ServerConfigImpl serverWithoutProtocol = new ServerConfigImpl("8080", "http", "/api", singletonMap("key", "value")); ServerConfigImpl udpServer = new ServerConfigImpl("8080/udp", "http", "/api", singletonMap("key", "value")); ServerConfigImpl normalizedServer = new ServerConfigImpl("8080/tcp", "http", "/api", singletonMap("key", "value")); Map servers = new HashMap<>(); servers.put("serverWithoutProtocol", serverWithoutProtocol); servers.put("udpServer", udpServer); Map normalizedServers = environmentFactory.normalizeServers(servers); assertEquals( normalizedServers, ImmutableMap.of("serverWithoutProtocol", normalizedServer, "udpServer", udpServer)); } @Test public void testDoNotOverrideRamLimitAttributeWhenMachineAlreadyContainsIt() throws Exception { final String ramLimit = "2147483648"; final InternalEnvironment internalEnv = mock(InternalEnvironment.class); final InternalMachineConfig machine = mock(InternalMachineConfig.class); when(environmentFactory.doCreate(any(), any(), any())).thenReturn(internalEnv); when(internalEnv.getMachines()).thenReturn(ImmutableMap.of("testMachine", machine)); Map machineAttr = new HashMap<>(); machineAttr.put(MEMORY_LIMIT_ATTRIBUTE, ramLimit); when(machine.getAttributes()).thenReturn(machineAttr); environmentFactory.create(mock(Environment.class)); assertEquals(machine.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), ramLimit); } @Test public void testApplyContainerSourceAttributeToTheMachineSpecifiedInEnv() throws Exception { // given final Environment sourceEnv = mock(Environment.class); MachineConfigImpl machineConfig = mock(MachineConfigImpl.class); final Map machineConfigMap = ImmutableMap.of("envMachine", machineConfig); doReturn(machineConfigMap).when(sourceEnv).getMachines(); when(environmentFactory.doCreate(any(), any(), any())) .thenAnswer( invocation -> { Map envMachines = invocation.getArgument(1); final InternalEnvironment internalEnv = mock(InternalEnvironment.class); when(internalEnv.getMachines()).thenReturn(envMachines); return internalEnv; }); // when InternalEnvironment resultEnv = environmentFactory.create(sourceEnv); // then assertEquals( resultEnv.getMachines().get("envMachine").getAttributes().get(CONTAINER_SOURCE_ATTRIBUTE), RECIPE_CONTAINER_SOURCE); } @Test public void testApplyContainerSourceAttributeToTheMachineThatComesFromRecipe() throws Exception { // given final Environment sourceEnv = mock(Environment.class); final InternalEnvironment internalEnv = mock(InternalEnvironment.class); final InternalMachineConfig internalMachine = new InternalMachineConfig(); when(internalEnv.getMachines()).thenReturn(ImmutableMap.of("internalMachine", internalMachine)); when(environmentFactory.doCreate(any(), any(), any())).thenReturn(internalEnv); // when InternalEnvironment resultEnv = environmentFactory.create(sourceEnv); // then assertEquals( resultEnv .getMachines() .get("internalMachine") .getAttributes() .get(CONTAINER_SOURCE_ATTRIBUTE), RECIPE_CONTAINER_SOURCE); } private static class TestEnvironmentFactory extends InternalEnvironmentFactory { private TestEnvironmentFactory( RecipeRetriever recipeRetriever, MachineConfigsValidator machinesValidator) { super(recipeRetriever, machinesValidator); } @Override protected InternalEnvironment doCreate(InternalRecipe recipe, Map machines, List list) throws InfrastructureException, ValidationException { return null; } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/environment/MachineConfigsValidatorTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.environment; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_REQUEST_ATTRIBUTE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.api.workspace.shared.Constants; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * Tests {@link MachineConfigsValidator}. * * @author Alexander Garagatyi * @author Sergii Leshchenko */ @Listeners(MockitoTestNGListener.class) public class MachineConfigsValidatorTest { private static final String MACHINE_NAME = "machine1"; private InternalMachineConfig machineConfig; private MachineConfigsValidator machinesValidator; @BeforeMethod public void setUp() throws Exception { machinesValidator = new MachineConfigsValidator(); machineConfig = machineMockWithServers(Constants.SERVER_WS_AGENT_HTTP_REFERENCE); } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Name of machine '.*' in environment is invalid", dataProvider = "invalidMachineNames") public void shouldFailIfMachinesNameAreInvalid(String machineName) throws Exception { // when machinesValidator.validate(singletonMap(machineName, machineConfig)); } @DataProvider(name = "invalidMachineNames") public Object[][] invalidMachineNames() { return new Object[][] { {""}, {"-123"}, {"123-"}, {"-123-"}, {"/123-"}, {"/123"}, {"123/"}, {"123_"}, {"!asdd/"}, }; } @Test(dataProvider = "validMachineNames") public void shouldNotFailIfMachinesNameAreValid(String machineName) throws Exception { // when machinesValidator.validate(singletonMap(machineName, machineConfig)); } @DataProvider(name = "validMachineNames") public Object[][] validMachineNames() { return new Object[][] { {"machine"}, {"machine123"}, {"machine-123"}, {"app/db"}, {"app_db"}, }; } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Machine '.*' in environment contains server conf '.*' with invalid port '.*'", dataProvider = "invalidServerPorts") public void shouldFailIfServerPortIsInvalid(String servicePort) throws Exception { // given ServerConfigImpl server = new ServerConfigImpl(servicePort, "https", "/some/path", singletonMap("key", "value")); when(machineConfig.getServers()) .thenReturn(singletonMap(Constants.SERVER_WS_AGENT_HTTP_REFERENCE, server)); // when machinesValidator.validate(singletonMap(MACHINE_NAME, machineConfig)); } @DataProvider(name = "invalidServerPorts") public Object[][] invalidServerPorts() { return new Object[][] { {"aaa"}, {"123aaa"}, {"8080/tpc2"}, {"8080/TCP"}, {"123udp"}, {""}, {"/123"}, }; } @Test(dataProvider = "validServerPorts") public void shouldNotFailIfServerPortIsValid(String servicePort) throws Exception { // given ServerConfigImpl server = new ServerConfigImpl(servicePort, "https", "/some/path", singletonMap("key", "value")); when(machineConfig.getServers()) .thenReturn(singletonMap(Constants.SERVER_WS_AGENT_HTTP_REFERENCE, server)); // when machinesValidator.validate(singletonMap(MACHINE_NAME, machineConfig)); } @DataProvider(name = "validServerPorts") public Object[][] validServerPorts() { return new Object[][] { {"1"}, {"12"}, {"8080"}, {"8080/tcp"}, {"8080/udp"}, }; } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Machine '.*' in environment contains server conf '.*' with invalid protocol '.*'", dataProvider = "invalidServerProtocols") public void shouldFailIfServerProtocolIsInvalid(String serviceProtocol) throws Exception { // given ServerConfigImpl server = new ServerConfigImpl("8080", serviceProtocol, "/some/path", singletonMap("key", "value")); when(machineConfig.getServers()) .thenReturn(singletonMap(Constants.SERVER_WS_AGENT_HTTP_REFERENCE, server)); // when machinesValidator.validate(singletonMap(MACHINE_NAME, machineConfig)); } @DataProvider(name = "invalidServerProtocols") public Object[][] invalidServerProtocols() { return new Object[][] {{"0"}, {"0sds"}, {"TCP"}, {"UDP"}, {"http@"}}; } @Test( expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "Machine '.*' in environment contains inconsistent memory attributes: Memory limit: '.*', Memory request: '.*'") public void shouldFailIfMemoryAttributesAreInconsistent() throws Exception { // given when(machineConfig.getAttributes()) .thenReturn( ImmutableMap.of( MEMORY_LIMIT_ATTRIBUTE, String.valueOf(1024L * 1024L * 1024L), MEMORY_REQUEST_ATTRIBUTE, String.valueOf(2048L * 1024L * 1024L))); // when machinesValidator.validate(singletonMap(MACHINE_NAME, machineConfig)); } @Test(dataProvider = "validMemoryAttributes") public void shouldSucceedIfMemoryAttributesAreConsistentOrNotPresent( String memoryLimit, String memoryRequest) throws Exception { // given Map attributes = new HashMap<>(); if (memoryLimit != null) { attributes.put(MEMORY_LIMIT_ATTRIBUTE, memoryLimit); } if (memoryRequest != null) { attributes.put(MEMORY_LIMIT_ATTRIBUTE, memoryRequest); } when(machineConfig.getAttributes()).thenReturn(attributes); // when machinesValidator.validate(singletonMap(MACHINE_NAME, machineConfig)); } @DataProvider(name = "validMemoryAttributes") public Object[][] validMemoryAttributes() { return new Object[][] { {null, String.valueOf(2048L * 1024L * 1024L)}, {String.valueOf(2048L * 1024L * 1024L), null}, {null, null}, {String.valueOf(2048L * 1024L * 1024L), String.valueOf(2048L * 1024L * 1024L)} }; } @Test(dataProvider = "validServerProtocols") public void shouldNotFailIfServerProtocolIsValid(String serviceProtocol) throws Exception { // given ServerConfigImpl server = new ServerConfigImpl("8080", serviceProtocol, "/some/path", singletonMap("key", "value")); when(machineConfig.getServers()) .thenReturn(singletonMap(Constants.SERVER_WS_AGENT_HTTP_REFERENCE, server)); // when machinesValidator.validate(singletonMap(MACHINE_NAME, machineConfig)); } @DataProvider(name = "validServerProtocols") public Object[][] validServerProtocols() { return new Object[][] {{"a"}, {"http"}, {"tcp"}, {"tcp2"}}; } private static InternalMachineConfig machineMock() { InternalMachineConfig machineConfig = mock(InternalMachineConfig.class); when(machineConfig.getServers()).thenReturn(emptyMap()); return machineConfig; } private static InternalMachineConfig machineMockWithServers(String... servers) { InternalMachineConfig machineConfig = machineMock(); when(machineConfig.getServers()).thenReturn(createServers(servers)); return machineConfig; } private static Map createServers(String... servers) { return Arrays.stream(servers) .collect( Collectors.toMap( Function.identity(), s -> new ServerConfigImpl("8080", "http", "/", singletonMap("key", "value")))); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/environment/ResourceLimitAttributesProvisionerTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.environment; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.CPU_REQUEST_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_REQUEST_ATTRIBUTE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.*; import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.Map; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** Tests {@link ResourceLimitAttributesProvisioner} */ @Listeners(MockitoTestNGListener.class) public class ResourceLimitAttributesProvisionerTest { @Test public void testSetsRamDefaultAttributesWhenTheyAreMissingInConfigAndNotPassedInRecipe() { long defaultMemoryLimit = 2048L; long defaultMemoryRequest = 1024L; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, 0L, 0L, defaultMemoryLimit, defaultMemoryRequest); long memLimit = Long.parseLong(machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE)); long memRequest = Long.parseLong(machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE)); assertEquals(memLimit, defaultMemoryLimit); assertEquals(memRequest, defaultMemoryRequest); } @Test public void testRamDefaultMemoryRequestIsIgnoredIfGreaterThanDefaultRamLimit() { long defaultMemoryLimit = 1024L; long defaultMemoryRequest = 2048L; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, 0L, 0L, defaultMemoryLimit, defaultMemoryRequest); long memLimit = Long.parseLong(machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE)); long memRequest = Long.parseLong(machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE)); assertEquals(memLimit, defaultMemoryLimit); assertEquals(memRequest, defaultMemoryLimit); } @Test public void testSkipDefaultMemoryAttributesWhenTheyAreNegative() { long defaultMemoryLimit = -1L; long defaultMemoryRequest = -1024L; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, 0L, 0L, defaultMemoryLimit, defaultMemoryRequest); long memLimit = Long.parseLong(machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE)); long memRequest = Long.parseLong(machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE)); assertEquals(memLimit, 0L); assertEquals(memRequest, 0L); } @Test public void testRamAttributesAreTakenFromRecipeWhenNotPresentInConfig() { long defaultMemoryLimit = 1024L; long defaultMemoryRequest = 2048L; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); long recipeLimit = 4096L; long recipeRequest = 2048L; ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, recipeLimit, recipeRequest, defaultMemoryLimit, defaultMemoryRequest); assertEquals( machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), String.valueOf(recipeLimit)); assertEquals( machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE), String.valueOf(recipeRequest)); } @Test public void testWhenRamAttributesTakenFromRecipeAreInconsistentAndNotPresentInConfigRequestIsIgnored() { long defaultMemoryLimit = 1024L; long defaultMemoryRequest = 2048L; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); // inconsistent attributes mean request > limit long recipeLimit = 2048L; long recipeRequest = 4096L; ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, recipeLimit, recipeRequest, defaultMemoryLimit, defaultMemoryRequest); assertEquals( machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), String.valueOf(recipeLimit)); assertEquals( machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE), String.valueOf(recipeLimit)); } @Test public void testWhenRamAttributesArePresentInMachineConfigValuesInRecipeAreIgnored() { long defaultMemoryLimit = 1024L; long defaultMemoryRequest = 2048L; InternalMachineConfig machineConfig = mockInternalMachineConfig( ImmutableMap.of(MEMORY_LIMIT_ATTRIBUTE, "1526", MEMORY_REQUEST_ATTRIBUTE, "512")); long recipeLimit = 4096L; long recipeRequest = 2048L; ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, recipeLimit, recipeRequest, defaultMemoryLimit, defaultMemoryRequest); assertEquals(machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), String.valueOf(1526L)); assertEquals(machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE), String.valueOf(512L)); } @Test public void testWhenRamAttributesArePresentInMachineAreNegativeDefaultsShouldBeApplied() { long defaultMemoryLimit = 2048L; long defaultMemoryRequest = 1024L; Map attributes = new HashMap<>(); attributes.put(MEMORY_LIMIT_ATTRIBUTE, "-1"); attributes.put(MEMORY_REQUEST_ATTRIBUTE, "-1"); InternalMachineConfig machineConfig = mockInternalMachineConfig(attributes); ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, 0L, 0L, defaultMemoryLimit, defaultMemoryRequest); assertEquals( machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), String.valueOf(defaultMemoryLimit)); assertEquals( machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE), String.valueOf(defaultMemoryRequest)); } @Test public void testWhenRamRequestAttributeIsPresentInMachineConfigValuesInRecipeAreIgnored() { long defaultMemoryLimit = 1024L; long defaultMemoryRequest = 2048L; Map attributes = new HashMap<>(); attributes.put(MEMORY_REQUEST_ATTRIBUTE, "512"); InternalMachineConfig machineConfig = mockInternalMachineConfig(attributes); long recipeLimit = 4096L; long recipeRequest = 2048L; ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, recipeLimit, recipeRequest, defaultMemoryLimit, defaultMemoryRequest); assertEquals( machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), String.valueOf(recipeLimit)); assertEquals(machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE), String.valueOf(512L)); } @Test public void testWhenRamLimitAttributeIsPresentInMachineConfigValuesInRecipeAreIgnored() { long defaultMemoryLimit = 1024L; long defaultMemoryRequest = 2048L; Map attributes = new HashMap<>(); attributes.put(MEMORY_LIMIT_ATTRIBUTE, "1526"); InternalMachineConfig machineConfig = mockInternalMachineConfig(attributes); long recipeLimit = 4096L; long recipeRequest = 2048L; ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, recipeLimit, recipeRequest, defaultMemoryLimit, defaultMemoryRequest); assertEquals(machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), String.valueOf(1526L)); assertEquals( machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE), String.valueOf(defaultMemoryRequest)); } @Test public void testWhenRamAttributesAreNotPresentInMachineConfigAndOnlyRequestIsProvidedInRecipe() { long defaultMemoryLimit = 2048L; long defaultMemoryRequest = 1024L; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); long recipeRequest = 1526L; ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, 0, recipeRequest, defaultMemoryLimit, defaultMemoryRequest); assertEquals( machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), String.valueOf(defaultMemoryLimit)); assertEquals( machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE), String.valueOf(recipeRequest)); } @Test public void testWhenRamAttributesAreNotPresentInMachineConfigAndOnlyLimitIsProvidedInRecipe() { long defaultMemoryLimit = 2048L; long defaultMemoryRequest = 1024L; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); long recipeLimit = 1526L; ResourceLimitAttributesProvisioner.provisionMemory( machineConfig, recipeLimit, 0, defaultMemoryLimit, defaultMemoryRequest); assertEquals( machineConfig.getAttributes().get(MEMORY_LIMIT_ATTRIBUTE), String.valueOf(recipeLimit)); assertEquals( machineConfig.getAttributes().get(MEMORY_REQUEST_ATTRIBUTE), String.valueOf(defaultMemoryRequest)); } @Test public void testSetsCPUDefaultAttributesWhenTheyAreMissingInConfigAndNotPassedInRecipe() { float defaultCPULimit = 0.500f; float defaultCPURequest = 0.200f; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, 0, 0, defaultCPULimit, defaultCPURequest); float cpuLimit = Float.parseFloat(machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE)); float cpuRequest = Float.parseFloat(machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE)); assertEquals(cpuLimit, defaultCPULimit); assertEquals(cpuRequest, defaultCPURequest); } @Test public void testSkipDefaultCPUAttributesWhenTheyAreNegative() { float defaultCPULimit = -1; float defaultCPURequest = -1; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, 0, 0, defaultCPULimit, defaultCPURequest); float cpuLimit = Float.parseFloat(machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE)); float cpuRequest = Float.parseFloat(machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE)); assertEquals(cpuLimit, 0); assertEquals(cpuRequest, 0); } @Test public void testRamDefaultCPURequestIsIgnoredIfGreaterThanDefaultCPULimit() { float defaultCPULimit = 0.2f; float defaultCPURequest = 0.5f; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, 0L, 0L, defaultCPULimit, defaultCPURequest); float memLimit = Float.parseFloat(machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE)); float memRequest = Float.parseFloat(machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE)); assertEquals(memLimit, defaultCPULimit); assertEquals(memRequest, defaultCPULimit); } @Test public void testCPUAttributesAreTakenFromRecipeWhenNotPresentInConfig() { float defaultCPULimit = 1.0f; float defaultCPURequest = 0.2f; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); float recipeLimit = 4f; float recipeRequest = 2f; ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, recipeLimit, recipeRequest, defaultCPULimit, defaultCPURequest); assertEquals( machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE), String.valueOf(recipeLimit)); assertEquals( machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE), String.valueOf(recipeRequest)); } @Test public void testWhenCPUAttributesTakenFromRecipeAreInconsistentAndNotPresentInConfigRequestIsIgnored() { float defaultCPULimit = 0.2f; float defaultCPURequest = 0.5f; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); // inconsistent attributes mean request > limit float recipeLimit = 0.3f; float recipeRequest = 0.6f; ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, recipeLimit, recipeRequest, defaultCPULimit, defaultCPURequest); assertEquals( machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE), String.valueOf(recipeLimit)); assertEquals( machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE), String.valueOf(recipeLimit)); } @Test public void testWhenCPUAttributesArePresentInMachineConfigValuesInRecipeAreIgnored() { float defaultCPULimit = 0.5f; float defaultCPURequest = 0.2f; InternalMachineConfig machineConfig = mockInternalMachineConfig( ImmutableMap.of(CPU_LIMIT_ATTRIBUTE, "0.512", CPU_REQUEST_ATTRIBUTE, "0.152")); float recipeLimit = 0.6f; float recipeRequest = 0.3f; ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, recipeLimit, recipeRequest, defaultCPULimit, defaultCPURequest); assertEquals(machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE), String.valueOf(0.512f)); assertEquals(machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE), String.valueOf(0.152f)); } @Test public void testWhenCPURequestAttributeIsPresentInMachineConfigValuesInRecipeAreIgnored() { float defaultCPULimit = 0.5f; float defaultCPURequest = 0.2f; Map attributes = new HashMap<>(); attributes.put(CPU_REQUEST_ATTRIBUTE, "0.512"); InternalMachineConfig machineConfig = mockInternalMachineConfig(attributes); float recipeLimit = 0.6f; float recipeRequest = 0.3f; ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, recipeLimit, recipeRequest, defaultCPULimit, defaultCPURequest); assertEquals( machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE), String.valueOf(recipeLimit)); assertEquals(machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE), String.valueOf(0.512f)); } @Test public void testWhenCPULimitAttributeIsPresentInMachineConfigValuesInRecipeAreIgnored() { float defaultCPULimit = 0.5f; float defaultCPURequest = 0.2f; Map attributes = new HashMap<>(); attributes.put(CPU_LIMIT_ATTRIBUTE, "0.152"); InternalMachineConfig machineConfig = mockInternalMachineConfig(attributes); float recipeLimit = 0.6f; float recipeRequest = 0.3f; ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, recipeLimit, recipeRequest, defaultCPULimit, defaultCPURequest); assertEquals(machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE), String.valueOf(0.152f)); assertEquals( machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE), String.valueOf(recipeRequest)); } @Test public void testWhenCPULimitAttributeIsPresentInMachineAreNegativeDefaultsShouldBeApplied() { float defaultCPULimit = 0.5f; float defaultCPURequest = 0.2f; Map attributes = new HashMap<>(); attributes.put(CPU_LIMIT_ATTRIBUTE, "-1"); attributes.put(CPU_REQUEST_ATTRIBUTE, "-1"); InternalMachineConfig machineConfig = mockInternalMachineConfig(attributes); ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, 0, 0, defaultCPULimit, defaultCPURequest); assertEquals( machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE), String.valueOf(defaultCPULimit)); assertEquals( machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE), String.valueOf(defaultCPURequest)); } @Test public void testWhenCPUAttributesAreNotPresentInMachineConfigAndOnlyRequestIsProvidedInRecipe() { float defaultCPULimit = 0.5f; float defaultCPURequest = 0.2f; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); float recipeRequest = 0.1526f; ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, 0, recipeRequest, defaultCPULimit, defaultCPURequest); assertEquals( machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE), String.valueOf(defaultCPULimit)); assertEquals( machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE), String.valueOf(recipeRequest)); } @Test public void testWhenCPUAttributesAreNotPresentInMachineConfigAndOnlyLimitIsProvidedInRecipe() { float defaultCPULimit = 0.5f; float defaultCPURequest = 0.2f; InternalMachineConfig machineConfig = mockInternalMachineConfig(new HashMap<>()); float recipeLimit = 0.152f; ResourceLimitAttributesProvisioner.provisionCPU( machineConfig, recipeLimit, 0, defaultCPULimit, defaultCPURequest); assertEquals( machineConfig.getAttributes().get(CPU_LIMIT_ATTRIBUTE), String.valueOf(recipeLimit)); assertEquals( machineConfig.getAttributes().get(CPU_REQUEST_ATTRIBUTE), String.valueOf(recipeLimit)); } private static InternalMachineConfig mockInternalMachineConfig(Map attributes) { final InternalMachineConfig machineConfigMock = mock(InternalMachineConfig.class); when(machineConfigMock.getAttributes()).thenReturn(attributes); return machineConfigMock; } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/provision/env/WorkspaceNameEnvVarProviderTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; import org.eclipse.che.commons.lang.Pair; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class WorkspaceNameEnvVarProviderTest { @Mock WorkspaceDao workspaceDao; @Mock WorkspaceImpl workspace; @Mock WorkspaceConfigImpl config; @Mock RuntimeIdentity runtimeIdentity; WorkspaceNameEnvVarProvider provider; @BeforeMethod public void setup() { provider = new WorkspaceNameEnvVarProvider(workspaceDao); } @Test public void shouldReturnNameVar() throws NotFoundException, ServerException, InfrastructureException { // given when(runtimeIdentity.getWorkspaceId()).thenReturn("ws-id111"); doReturn(workspace).when(workspaceDao).get(Mockito.eq("ws-id111")); when(workspace.getName()).thenReturn("ws-name"); // when Pair actual = provider.get(runtimeIdentity); // then assertEquals(actual.first, WorkspaceNameEnvVarProvider.CHE_WORKSPACE_NAME); assertEquals(actual.second, "ws-name"); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Not able to get workspace name for workspace with id ws-id111") public void shouldWrapNotFoundExceptionException() throws NotFoundException, ServerException, InfrastructureException { // given when(runtimeIdentity.getWorkspaceId()).thenReturn("ws-id111"); doThrow(new NotFoundException("Some message")).when(workspaceDao).get(Mockito.eq("ws-id111")); // when provider.get(runtimeIdentity); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Not able to get workspace name for workspace with id ws-id111") public void shouldWrapServerExceptionException() throws NotFoundException, ServerException, InfrastructureException { // given when(runtimeIdentity.getWorkspaceId()).thenReturn("ws-id111"); doThrow(new ServerException("Some message")).when(workspaceDao).get(Mockito.eq("ws-id111")); // when provider.get(runtimeIdentity); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/provision/env/WorkspaceNamespaceEnvVarProviderTest.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.spi.provision.env; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; import org.eclipse.che.commons.lang.Pair; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class WorkspaceNamespaceEnvVarProviderTest { @Mock WorkspaceDao workspaceDao; @Mock WorkspaceImpl workspace; @Mock RuntimeIdentity runtimeIdentity; WorkspaceNamespaceNameEnvVarProvider provider; @BeforeMethod public void setup() { provider = new WorkspaceNamespaceNameEnvVarProvider(workspaceDao); } @Test public void shouldReturnNamespaceVar() throws NotFoundException, ServerException, InfrastructureException { // given when(runtimeIdentity.getWorkspaceId()).thenReturn("ws-id111"); doReturn(workspace).when(workspaceDao).get(Mockito.eq("ws-id111")); when(workspace.getNamespace()).thenReturn("ws-namespace"); // when Pair actual = provider.get(runtimeIdentity); // then assertEquals(actual.first, WorkspaceNamespaceNameEnvVarProvider.CHE_WORKSPACE_NAMESPACE); assertEquals(actual.second, "ws-namespace"); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Not able to get workspace namespace for workspace with id ws-id111") public void shouldWrapNotFoundExceptionException() throws NotFoundException, ServerException, InfrastructureException { // given when(runtimeIdentity.getWorkspaceId()).thenReturn("ws-id111"); doThrow(new NotFoundException("Some message")).when(workspaceDao).get(Mockito.eq("ws-id111")); // when provider.get(runtimeIdentity); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Not able to get workspace namespace for workspace with id ws-id111") public void shouldWrapServerExceptionException() throws NotFoundException, ServerException, InfrastructureException { // given when(runtimeIdentity.getWorkspaceId()).thenReturn("ws-id111"); doThrow(new ServerException("Some message")).when(workspaceDao).get(Mockito.eq("ws-id111")); // when provider.get(runtimeIdentity); } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/wsplugins/PluginFQNParserTest.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.server.wsplugins; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEqualsNoOrder; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.net.URI; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ExtendedPluginFQN; import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.api.workspace.shared.Constants; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class PluginFQNParserTest { @Mock private FileContentProvider fileContentProvider; private PluginFQNParser parser; @BeforeClass public void setUp() throws Exception { parser = new PluginFQNParser(); } @Test public void shouldReturnEmptyListWhenNoPluginsOrEditors() throws Exception { Map attributes = ImmutableMap.of("testProperty", "testValue"); Collection result = parser.parsePlugins(attributes); assertTrue( result.isEmpty(), "PluginFQNParser should return empty list when attributes does not contain plugins or editors"); } @Test public void shouldComposeIdWhenAllPartsGivenToTheConstructor() { ExtendedPluginFQN pluginFQN = new ExtendedPluginFQN("reference", "publisher", "name", "version"); assertEquals(pluginFQN.getId(), "publisher/name/version"); } @Test(dataProvider = "validAttributesProvider") public void shouldParseAllPluginsAndEditor(AttributeParsingTestCase testCase) throws Exception { Collection actual = parser.parsePlugins(testCase.attributes); assertEqualsNoOrder(actual.toArray(), testCase.expectedPlugins.toArray()); } @Test(dataProvider = "validPluginStringProvider") public void shouldParsePluginOrEditorToExtendedFQN(String plugin, ExtendedPluginFQN expected) throws Exception { ExtendedPluginFQN actual = parser.parsePluginFQN(plugin); assertEquals(actual, expected); } @Test(dataProvider = "validPluginReferencesProvider") public void shouldParsePluginOrEditorFromReference( String reference, String pluginYaml, ExtendedPluginFQN expected) throws Exception { when(fileContentProvider.fetchContent(eq(reference))).thenReturn(pluginYaml); ExtendedPluginFQN actual = parser.evaluateFqn(reference, fileContentProvider); assertEquals(actual, expected); } @Test( dataProvider = "invalidAttributeStringProvider", expectedExceptions = InfrastructureException.class) public void shouldThrowExceptionWhenPluginStringIsInvalid(String plugin) throws Exception { Map attributes = createAttributes("", plugin); parser.parsePlugins(attributes); } @Test( dataProvider = "invalidAttributeStringProvider", expectedExceptions = InfrastructureException.class) public void shouldThrowExceptionWhenEditorStringIsInvalid(String editor) throws Exception { Map attributes = createAttributes(editor, ""); parser.parsePlugins(attributes); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Multiple editors.*") public void shouldThrowExceptionWhenMultipleEditorsDefined() throws Exception { Map attributes = createAttributes("publisher1/editor1/version1,publisher1/editor2/version1", ""); parser.parsePlugins(attributes); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Invalid Che tooling plugins configuration: plugin publisher1/testplugin/1.0 is duplicated") public void shouldThrowExceptionWhenDuplicatePluginDefined() throws Exception { Map attributes = createAttributes( "", formatPlugin("http://testregistry1:8080", "publisher1/testplugin/1.0"), formatPlugin("http://testregistry2:8080", "publisher1/testplugin/1.0")); parser.parsePlugins(attributes); } @Test( expectedExceptions = InfrastructureException.class, expectedExceptionsMessageRegExp = "Invalid Che tooling plugins configuration: plugin publisher1/testplugin/1.0 is duplicated") public void shouldDetectDuplicatedPluginsIfTheyArePrefixedOrSuffixedWithEmptySpaces() throws Exception { Map attributes = createAttributes( "", " " + formatPlugin("http://testregistry1:8080", "publisher1/testplugin/1.0"), formatPlugin("http://testregistry2:8080", "publisher1/testplugin/1.0") + " "); parser.parsePlugins(attributes); } @DataProvider(name = "invalidAttributeStringProvider") public static Object[][] invalidAttributeStringProvider() { return new Object[][] { {formatPlugin("http://bad registry url", "testplugin/1.0")}, {formatPlugin("http://testregistry:8080", "bad:pluginname/1.0")}, {formatPlugin("http://testregistry:8080", "/version")}, {formatPlugin("http://testregistry:8080", "id/")}, {formatPlugin("http://testregistry:8080", "name/version")}, {formatPlugin("http://testregistry:8080", "id:version")}, {formatPlugin("http://testregistry:8080", "publisher/name:version")}, }; } @DataProvider(name = "validAttributesProvider") public static Object[][] validAttributesProvider() { PluginFQN basicEditor = new PluginFQN(URI.create("http://registry:8080"), "publisher/editor/ver"); PluginFQN withRegistry = new PluginFQN(URI.create("http://registry:8080"), "publisher/plugin/1.0"); PluginFQN noRegistry = new PluginFQN(null, "publisher/pluginnoregistry/2.0"); PluginFQN pathRegistry = new PluginFQN( URI.create("http://registry/multiple/path"), "publisher/pluginpathregistry/3.0"); PluginFQN reference = new PluginFQN("http://mysite:8080/multiple/path/meta.yaml"); return new AttributeParsingTestCase[][] { { new AttributeParsingTestCase( "Test plugin with registry", ImmutableList.of(basicEditor, withRegistry), createAttributes(formatPlugin(basicEditor), formatPlugins(withRegistry))) }, { new AttributeParsingTestCase( "Test plugin with https registry", ImmutableList.of( new PluginFQN(URI.create("https://registry:8080"), "publisher/editor/ver")), createAttributes( null, formatPlugin( new PluginFQN(URI.create("https://registry:8080"), "publisher/editor/ver")))) }, { new AttributeParsingTestCase( "Test plugin with registry containing path", ImmutableList.of( new PluginFQN( URI.create("https://registry:8080/some/path/v3"), "publisher/editor/ver")), createAttributes( null, formatPlugin( new PluginFQN( URI.create("https://registry:8080/some/path/v3/"), "publisher/editor/ver")))) }, { new AttributeParsingTestCase( "Test plugin without registry", ImmutableList.of(basicEditor, noRegistry), createAttributes(formatPlugin(basicEditor), formatPlugins(noRegistry))) }, { new AttributeParsingTestCase( "Test plugin with multi-level path in registry", ImmutableList.of(basicEditor, pathRegistry), createAttributes(formatPlugin(basicEditor), formatPlugins(pathRegistry))) }, { new AttributeParsingTestCase( "Test plugin described by reference", ImmutableList.of(basicEditor, reference), createAttributes( formatPlugin(basicEditor), "http://mysite:8080/multiple/path/meta.yaml")) }, { new AttributeParsingTestCase( "Test attributes with no editor field", ImmutableList.of(withRegistry), createAttributes(null, formatPlugins(withRegistry))) }, { new AttributeParsingTestCase( "Test attributes with empty editor field", ImmutableList.of(withRegistry), createAttributes("", formatPlugins(withRegistry))) }, { new AttributeParsingTestCase( "Test attributes with no plugin field", ImmutableList.of(basicEditor), createAttributes(formatPlugin(basicEditor), (String[]) null)) }, { new AttributeParsingTestCase( "Test attributes with empty plugin field", ImmutableList.of(basicEditor), createAttributes(formatPlugin(basicEditor), "")) }, { new AttributeParsingTestCase( "Test attributes with no plugin or editor field", Collections.emptyList(), createAttributes(null, (String[]) null)) }, { new AttributeParsingTestCase( "Test attributes with empty plugin and editor field", Collections.emptyList(), createAttributes("", "")) }, { new AttributeParsingTestCase( "Test multiple plugins and an editor", ImmutableList.of(basicEditor, withRegistry, noRegistry, pathRegistry), createAttributes( formatPlugin(basicEditor), formatPlugins(withRegistry, noRegistry, pathRegistry))) }, }; } // Objects are // (String plugin, ExtendedPluginFQN expectedPlugin) @DataProvider(name = "validPluginStringProvider") public static Object[][] validPluginStringProvider() { return new Object[][] { { "http://registry:8080#publisher/editor/ver", new ExtendedPluginFQN( URI.create("http://registry:8080"), "publisher/editor/ver", "publisher", "editor", "ver") }, { "https://registry:8080#publisher/editor/ver", new ExtendedPluginFQN( URI.create("https://registry:8080"), "publisher/editor/ver", "publisher", "editor", "ver") }, { "https://che-registry.com.ua#publisher/editor/ver", new ExtendedPluginFQN( URI.create("https://che-registry.com.ua"), "publisher/editor/ver", "publisher", "editor", "ver") }, { "https://che-registry.com.ua/plugins#publisher/editor/ver", new ExtendedPluginFQN( URI.create("https://che-registry.com.ua/plugins"), "publisher/editor/ver", "publisher", "editor", "ver") }, { "https://che-registry.com.ua/plugins/v3/#publisher/editor/ver", new ExtendedPluginFQN( URI.create("https://che-registry.com.ua/plugins/v3"), "publisher/editor/ver", "publisher", "editor", "ver") }, { "https://che-registry.com.ua/some/long/path#publisher/editor/ver", new ExtendedPluginFQN( URI.create("https://che-registry.com.ua/some/long/path"), "publisher/editor/ver", "publisher", "editor", "ver") }, { "publisher/editor/ver", new ExtendedPluginFQN(null, "publisher/editor/ver", "publisher", "editor", "ver") }, { "publisher/editor/1.12.1", new ExtendedPluginFQN(null, "publisher/editor/1.12.1", "publisher", "editor", "1.12.1") }, { "publisher/editor/v2.12.x", new ExtendedPluginFQN(null, "publisher/editor/v2.12.x", "publisher", "editor", "v2.12.x") }, { "publisher/che-theia/next", new ExtendedPluginFQN(null, "publisher/che-theia/next", "publisher", "che-theia", "next") }, { "publisher/che-theia/2.12-latest", new ExtendedPluginFQN( null, "publisher/che-theia/2.12-latest", "publisher", "che-theia", "2.12-latest") }, }; } // Objects are // (String reference, String yamlContent, ExtendedPluginFQN expectedPlugin) @DataProvider(name = "validPluginReferencesProvider") public static Object[][] validPluginReferencesProvider() { return new Object[][] { { "http://registry:8080/publisher/editor/ver/meta.yaml", "apiVersion: v2\n" + "publisher: publisher\n" + "name: editor\n" + "version: ver\n" + "type: Che Editor", new ExtendedPluginFQN( "http://registry:8080/publisher/editor/ver/meta.yaml", "publisher", "editor", "ver") }, { "https://pastebin.com/1ij3475rh", "apiVersion: v2\n" + "publisher: publisher\n" + "name: editor\n" + "version: 0.0.5\n" + "type: Che Editor", new ExtendedPluginFQN("https://pastebin.com/1ij3475rh", "publisher", "editor", "0.0.5") }, { "https://che-registry.com.ua/publisher/plugin/0.0.5/meta.yaml", "apiVersion: v2\n" + "publisher: publisher\n" + "name: plugin123\n" + "version: 0.0.5\n" + "type: Che Plugin", new ExtendedPluginFQN( "https://che-registry.com.ua/publisher/plugin/0.0.5/meta.yaml", "publisher", "plugin123", "0.0.5") }, { "https://pastebin.com/raw/EBmz0YMS", "apiVersion: v2\n" + "publisher: publisher\n" + "name: numericVersion\n" + "version: 1.1\n" + "type: Che Plugin", new ExtendedPluginFQN( "https://pastebin.com/raw/EBmz0YMS", "publisher", "numericVersion", "1.1") } }; } private static String[] formatPlugins(PluginFQN... plugins) { return Arrays.stream(plugins).map(PluginFQNParserTest::formatPlugin).toArray(String[]::new); } private static String formatPlugin(PluginFQN plugin) { String registry = plugin.getRegistry() == null ? null : plugin.getRegistry().toString(); return formatPlugin(registry, plugin.getId()); } private static String formatPlugin(String registry, String id) { if (registry == null) { return id; } else { return registry + "#" + id; } } private static Map createAttributes(String editor, String... plugins) { Map attributes = new HashMap<>(); if (plugins != null) { String allPlugins = String.join(",", plugins); attributes.put(Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, allPlugins); } if (editor != null) { attributes.put(Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, editor); } return ImmutableMap.copyOf(attributes); } /** * Holder of data for a test case. Syntax sugar that primary goal is to allow IDE log test case * description (in one of the fields) and compare expected and actual arrays in a way that prints * diff when they are not equal */ private static class AttributeParsingTestCase { private String description; private List expectedPlugins; private Map attributes; public AttributeParsingTestCase( String description, List expectedPlugins, Map attributes) { this.description = description; this.expectedPlugins = expectedPlugins; this.attributes = attributes; } @Override public String toString() { return description; } } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule ================================================ org.eclipse.che.api.workspace.server.jpa.WorkspaceTckModule ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/devfile.json ================================================ { "apiVersion": "1.0.0", "metadata": { "name": "petclinic-dev-environment", "generateName": "petclinic-" }, "projects": [ { "name": "petclinic", "source": { "type": "git", "location": "git@github.com:spring-projects/spring-petclinic.git" } } ], "components": [ { "alias": "mvn-stack", "type": "chePlugin", "id": "eclipse/chemaven-jdk8/1.0.0" }, { "type": "cheEditor", "id": "eclipse/che-theia/0.0.3" }, { "alias": "jdt.ls", "type": "chePlugin", "id": "org.eclipse.chetheia-jdtls:0.0.3", "preferences": { "java.home": "/home/user/jdk11", "java.jdt.ls.vmargs": "-noverify -Xmx1G -XX:+UseG1GC -XX:+UseStringDeduplication", "java.jtg.memory": 12345, "java.boolean": true } }, { "type": "openshift", "reference": "petclinic.yaml", "selector": { "app.kubernetes.io/name": "mysql", "app.kubernetes.io/component": "database", "app.kubernetes.io/part-of": "petclinic" } } ], "commands": [ { "name": "build", "actions": [ { "type": "exec", "component": "mvn-stack", "command": "mvn package", "workdir": "/projects/spring-petclinic" } ] }, { "name": "run", "attributes": { "runType": "sequential" }, "actions": [ { "type": "exec", "component": "mvn-stack", "command": "mvn spring-boot:run", "workdir": "/projects/spring-petclinic" } ] }, { "name": "other", "actions": [ { "type": "exec", "component": "jdt.ls", "command": "run.sh" } ] } ] } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/devfile.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment generateName: petclinic- projects: - name: petclinic source: type: git location: 'git@github.com:spring-projects/spring-petclinic.git' components: - alias: mvn-stack type: chePlugin id: eclipse/chemaven-jdk8/1.0.0 - type: cheEditor id: eclipse/che-theia/0.0.3 - alias: jdt.ls type: chePlugin id: org.eclipse.chetheia-jdtls:0.0.3 preferences: java.home: '/home/user/jdk11' java.jdt.ls.vmargs: '-noverify -Xmx1G -XX:+UseG1GC -XX:+UseStringDeduplication' java.jtg.memory: 12345 java.boolean: true - type: openshift reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build actions: - type: exec component: mvn-stack command: mvn package workdir: /projects/spring-petclinic - name: run attributes: runType: sequential actions: - type: exec component: mvn-stack command: mvn spring-boot:run workdir: /projects/spring-petclinic - name: other actions: - type: exec component: jdt.ls command: run.sh ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/petclinic.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: v1 kind: List items: - apiVersion: v1 kind: Pod metadata: name: petclinic labels: app.kubernetes.io/name: petclinic app.kubernetes.io/component: webapp app.kubernetes.io/part-of: petclinic spec: containers: - name: server image: mariolet/petclinic ports: - containerPort: 8080 protocol: TCP resources: limits: memory: 512Mi - apiVersion: v1 kind: Pod metadata: name: mysql labels: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic spec: containers: - name: mysql image: centos/mysql-57-centos7 env: - name: MYSQL_USER value: petclinic - name: MYSQL_PASSWORD value: petclinic - name: MYSQL_ROOT_PASSWORD value: petclinic - name: MYSQL_DATABASE value: petclinic ports: - containerPort: 3306 protocol: TCP resources: limits: memory: 512Mi - apiVersion: v1 kind: Pod metadata: name: withoutLabels - kind: Service apiVersion: v1 metadata: name: mysql labels: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic spec: ports: - name: mysql port: 3306 targetPort: 3360 selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic - kind: Service apiVersion: v1 metadata: name: petclinic labels: app.kubernetes.io/name: petclinic app.kubernetes.io/component: webapp app.kubernetes.io/part-of: petclinic spec: ports: - name: web port: 8080 targetPort: 8080 selector: app: petclinic component: webapp - kind: Route apiVersion: v1 metadata: name: petclinic labels: app.kubernetes.io/name: petclinic app.kubernetes.io/component: webapp app.kubernetes.io/part-of: petclinic spec: to: kind: Service name: petclinic port: targetPort: web ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/command/devfile_action_without_commandline_and_reference.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: theia type: cheEditor id: eclipse/chetheia/0.0.3 commands: - name: build actions: - type: vscode-task ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/command/devfile_command_with_empty_preview_url.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build previewUrl: actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/command/devfile_command_with_preview_url.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build previewUrl: port: 65535 path: hello-world actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/command/devfile_command_with_preview_url_only_path.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build previewUrl: path: hello-world actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/command/devfile_command_with_preview_url_only_port.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build previewUrl: port: 8080 actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/command/devfile_command_with_preview_url_port_is_negative.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build previewUrl: port: -1 path: hello-world actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/command/devfile_command_with_preview_url_port_is_string.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build previewUrl: port: abc path: hello-world actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/command/devfile_command_with_preview_url_port_is_too_high.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build previewUrl: port: 123456 path: hello-world actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/command/devfile_missing_command_actions.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: theia type: cheEditor id: eclipse/chetheia/0.0.3 commands: - name: build ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/command/devfile_missing_command_name.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: theia type: cheEditor id: eclipse/chetheia/0.0.3 commands: - actions: - type: exec component: mvn-stack command: mvn package workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/command/devfile_multiple_commands_actions.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic - type: exec component: mysql command: mvn package workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/component/devfile_component_with_automount_secrets.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: chePlugin alias: maven id: eclipse/chemaven-jdk8/1.0.0 automountWorkspaceSecrets: false - type: dockerimage alias: maven2 image: eclipse/chemaven-jdk8/1.0.0 automountWorkspaceSecrets: true memoryLimit: 256Mi ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/component/devfile_component_with_undeclared_field.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: chePlugin alias: maven id: eclipse/chemaven-jdk8/1.0.0 unknown: blah-blah ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/component/devfile_missing_component_type.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: maven id: eclipse/chemaven-jdk8/1.0.0 ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/component/devfile_unknown_component_type.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: maven id: org.eclipse.chemaven-jdk8:1.0.0 type: unknown ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/component/devfile_without_any_component.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment projects: - name: petclinic source: type: git location: 'git@github.com:spring-projects/spring-petclinic.git' ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_empty_metadata.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: {} ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_just_generatename.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: generateName: test- ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_missing_api_version.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- metadata: name: petclinic-dev-environment ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_missing_metadata.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_missing_name_and_generatename.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: something: else ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_name_and_generatename.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: testName generateName: testGenerateName- ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_null_metadata.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2-1-0_just_schemaVersion.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- schemaVersion: 2.1.0 ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2-1-0_simple-devfile.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # schemaVersion: 2.1.0 metadata: name: spring-petclinic attributes: example: foo components: - name: maven container: image: quay.io/eclipse/che-java8-maven:nightly volumeMounts: - name: mavenrepo path: /root/.m2 env: - name: ENV_VAR value: value memoryLimit: 1536M ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2-1-0_unsupported_schemaVersion.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- schemaVersion: 2.1.0-beta ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2-1-0_with_invalid_plugin_definition.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # schemaVersion: 2.1.0 metadata: name: test-project attributes: type: workspace projects: - name: test-project git: remotes: origin: https://github.com/Test/Project components: - plugin: name: java language server id: redhat/java11/0.57.0 ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2-2-0-basic-nodejs.yaml ================================================ # # Copyright (c) 2012-2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # schemaVersion: 2.2.0 metadata: name: nodejs version: 1.0.1 displayName: Node.js Runtime description: Stack with Node.js 14 tags: ["NodeJS", "Express", "ubi8"] projectType: "nodejs" language: "nodejs" attributes: alpha.dockerimage-port: 3001 provider: Red Hat supportUrl: https://github.com/devfile-samples/devfile-support#support-information parent: id: nodejs registryUrl: "https://registry.devfile.io" components: - name: outerloop-build image: imageName: nodejs-image:latest dockerfile: uri: Dockerfile buildContext: . rootRequired: false - name: outerloop-deploy kubernetes: uri: outerloop-deploy.yaml commands: - id: build-image apply: component: outerloop-build - id: deployk8s apply: component: outerloop-deploy - id: deploy composite: commands: - build-image - deployk8s group: kind: deploy isDefault: true ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2-2-0-basic-python.yaml ================================================ # # Copyright (c) 2012-2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # schemaVersion: 2.2.0 metadata: name: python version: 1.0.0 provider: Red Hat supportUrl: https://github.com/devfile-samples/devfile-support#support-information attributes: alpha.dockerimage-port: 8081 parent: id: python registryUrl: "https://registry.devfile.io" components: - name: outerloop-build image: imageName: python-image:latest dockerfile: uri: docker/Dockerfile buildContext: . rootRequired: false - name: outerloop-deploy kubernetes: uri: outerloop-deploy.yaml commands: - id: build-image apply: component: outerloop-build - id: deployk8s apply: component: outerloop-deploy - id: deploy composite: commands: - build-image - deployk8s group: kind: deploy isDefault: true ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2-2-0-basic-quarkus.yaml ================================================ # # Copyright (c) 2012-2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # schemaVersion: 2.2.0 metadata: name: java-quarkus version: 1.1.0 provider: Red Hat supportUrl: https://github.com/devfile-samples/devfile-support#support-information website: https://quarkus.io displayName: Quarkus Java description: Upstream Quarkus with Java+GraalVM tags: ["Java", "Quarkus"] projectType: "quarkus" language: "java" attributes: alpha.dockerimage-port: 8081 parent: id: java-quarkus registryUrl: "https://registry.devfile.io" components: - name: outerloop-build image: imageName: java-quarkus-image:latest dockerfile: uri: src/main/docker/Dockerfile.jvm.staged buildContext: . rootRequired: false - name: outerloop-deploy kubernetes: uri: outerloop-deploy.yaml commands: - id: build-image apply: component: outerloop-build - id: deployk8s apply: component: outerloop-deploy - id: deploy composite: commands: - build-image - deployk8s group: kind: deploy isDefault: true ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2-2-0-basic-springboot.yaml ================================================ # # Copyright (c) 2012-2023 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # schemaVersion: 2.2.0 metadata: name: java-springboot version: 1.1.0 attributes: alpha.dockerimage-port: 8081 displayName: Java Spring Boot description: Java Spring Boot using Maven tags: ["Java", "Spring"] projectType: "springboot" language: "java" provider: Red Hat supportUrl: https://github.com/devfile-samples/devfile-support#support-information parent: id: java-springboot registryUrl: "https://registry.devfile.io" components: - name: outerloop-build image: imageName: java-springboot-image:latest dockerfile: uri: docker/Dockerfile buildContext: . rootRequired: false - name: outerloop-deploy kubernetes: uri: outerloop-deploy.yaml commands: - id: build-image apply: component: outerloop-build - id: deployk8s apply: component: outerloop-deploy - id: deploy composite: commands: - build-image - deployk8s group: kind: deploy isDefault: true ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2-2-0-quarkus.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # schemaVersion: 2.2.0 metadata: name: java-quarkus version: 1.1.0 provider: Red Hat supportUrl: https://github.com/devfile-samples/devfile-support#support-information website: https://quarkus.io displayName: Quarkus Java description: Upstream Quarkus with Java+GraalVM tags: ["Java", "Quarkus"] projectType: "quarkus" language: "java" attributes: alpha.dockerimage-port: 8081 starterProjects: - name: community zip: location: https://code.quarkus.io/d?e=io.quarkus%3Aquarkus-resteasy&e=io.quarkus%3Aquarkus-micrometer&e=io.quarkus%3Aquarkus-smallrye-health&e=io.quarkus%3Aquarkus-openshift&cn=devfile - name: redhat-product zip: location: https://code.quarkus.redhat.com/d?e=io.quarkus%3Aquarkus-resteasy&e=io.quarkus%3Aquarkus-smallrye-health&e=io.quarkus%3Aquarkus-openshift components: - name: outerloop-build image: imageName: java-quarkus-image:latest dockerfile: uri: src/main/docker/Dockerfile.jvm.staged buildContext: . rootRequired: false - name: outerloop-deploy kubernetes: uri: outerloop-deploy.yaml - name: tools container: image: quay.io/eclipse/che-quarkus:7.36.0 memoryLimit: 1512Mi mountSources: true volumeMounts: - name: m2 path: /home/user/.m2 endpoints: - name: '8080-http' targetPort: 8080 - name: m2 volume: size: 3Gi commands: - id: init-compile exec: component: tools commandLine: "mvn -Dmaven.repo.local=/home/user/.m2/repository compile" workingDir: $PROJECTS_ROOT - id: dev-run exec: component: tools commandLine: "mvn -Dmaven.repo.local=/home/user/.m2/repository quarkus:dev -Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" hotReloadCapable: true group: kind: run isDefault: true workingDir: $PROJECTS_ROOT - id: dev-debug exec: component: tools commandLine: "mvn -Dmaven.repo.local=/home/user/.m2/repository quarkus:dev -Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -Ddebug=${DEBUG_PORT}" hotReloadCapable: true group: kind: debug isDefault: true workingDir: $PROJECTS_ROOT - id: build-image apply: component: outerloop-build - id: deployk8s apply: component: outerloop-deploy - id: deploy composite: commands: - build-image - deployk8s group: kind: deploy isDefault: true events: postStart: - init-compile ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2-2-0-simple-devfile.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # schemaVersion: 2.2.0 metadata: name: spring-petclinic attributes: example: foo components: - name: maven container: image: quay.io/eclipse/che-java8-maven:nightly volumeMounts: - name: mavenrepo path: /root/.m2 env: - name: ENV_VAR value: value memoryLimit: 1536M ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_invalid_schemaVersion.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- schemaVersion: a.b.c ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_just_schemaVersion.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- schemaVersion: 2.0.0 ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_sample-devfile.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # schemaVersion: "2.0.0" metadata: name: "devfile example" version: "1.0.0" projects: - name: "my-project" git: remotes: origin: "https://github.com/devfile/api" checkoutFrom: revision: "master" remote: origin components: - name: editor attributes: kjkh: "128M" kjhkjh: "": "" plugin: id: eclipse/che-theia/latest - name: "ownplugin" plugin: id: acme/newPlugin/latest registryUrl: "https://acme.com/registry/" - name: "myplugin" plugin: uri: "https://github.com/johndoe/che-plugins/blob/master/cool-plugin/0.0.1/meta.yaml" - name: "mycontainer" container: image: "busybox" memoryLimit: "128M" mountSources: true endpoints: - name: term-websockets exposure: public protocol: ws attributes: type: terminal targetPort: 4000 - name: "production" kubernetes: uri: "https://somewhere/production-environment.yaml" ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_simple-devfile.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # schemaVersion: "2.0.0" metadata: name: "myDevfile" version: "0.0.1" projects: - name: "devworkspace-spec" git: remotes: origin: "https://github.com/devfile/api" commands: - id: buildschema exec: label: Build the schema commandLine: "./buildSchema.sh" component: build-tools group: kind: build isDefault: true - id: opendevfile vscodeTask: inlined: json - id: build-schema-and-open-devfile composite: label: Build schema and open devfile commands: - buildschema - opendevfile parallel: false - id: helloworld exec: env: - name: "USER" value: "John Doe" commandLine: 'echo "Hello ${USER}"' component: build-tools events: postStart: - "build-schema-and-open-devfile" components: - name: yaml-support plugin: id: redhat/vscode-yaml/latest - name: go-support plugin: id: ms-vscode/go/latest - name: editor plugin: id: eclipse/che-theia/latest registryUrl: "external-registry-url" - name: "build-tools" container: image: some container image with required build tools mountSources: true sourceMapping: /home/src ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_spring-boot-http-booster-devfile.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # schemaVersion: 2.0.0 metadata: name: spring-boot-http-booster attributes: type: workspace projects: - name: spring-boot-http-booster git: remotes: origin: https://github.com/snowdrop/spring-boot-http-booster checkoutFrom: revision: master components: - name: java-support plugin: id: redhat/java8/latest components: - name: vscode-java container: memoryLimit: 2Gi - name: m2 volume: size: 2G - name: dependency-analytics plugin: id: redhat/dependency-analytics/latest - name: maven-tooling container: image: registry.redhat.io/codeready-workspaces/stacks-java-rhel8:2.1 mountSources: true memoryLimit: 768Mi env: - name: JAVA_OPTS value: >- -XX:MaxRAMPercentage=50.0 -XX:+UseParallelGC -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xms20m -Djava.security.egd=file:/dev/./urandom -Duser.home=/home/jboss - name: MAVEN_OPTS value: $(JAVA_OPTS) endpoints: - name: tcp-8080 targetPort: 8080 exposure: public volumeMounts: - name: m2 path: /home/jboss/.m2 commands: - id: build exec: component: maven commandLine: mvn -Duser.home=${HOME} -DskipTests clean install workingDir: '${PROJECTS_ROOT}/spring-boot-http-booster' env: - name: MAVEN_OPTS value: "-Xmx200m" - id: debug-remote-java-application vscodeLaunch: inlined: | { "version": "0.2.0", "configurations": [ { "type": "java", "name": "Debug (Attach) - Remote", "request": "attach", "hostName": "localhost", "port": 8000 }] } - id: run exec: component: maven commandLine: 'mvn -Duser.home=${HOME} spring-boot:run' workingDir: '${PROJECTS_ROOT}/spring-boot-http-booster' env: - name: MAVEN_OPTS value: "-Xmx200m" - id: debug exec: component: maven commandLine: >- mvn -Duser.home=${HOME} spring-boot:run -Drun.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" workingDir: '${PROJECTS_ROOT}/spring-boot-http-booster' - id: test exec: component: maven commandLine: 'mvn -Duser.home=${HOME} verify' workingDir: '${PROJECTS_ROOT}/spring-boot-http-booster' env: - name: MAVEN_OPTS value: "-Xmx200m" - id: dependency-analysis exec: component: maven workingDir: '${PROJECTS_ROOT}/spring-boot-http-booster' commandLine: >- ${HOME}/stack-analysis.sh -f ${PROJECTS_ROOT}/spring-boot-http-booster/pom.xml -p ${PROJECTS_ROOT}/spring-boot-http-booster - id: deploy-to-openshift exec: component: maven commandLine: 'mvn fabric8:deploy -Popenshift -DskipTests' workingDir: '${PROJECTS_ROOT}/spring-boot-http-booster' ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_v2_unsupported_schemaVersion.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- schemaVersion: 22.33.44 ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_with_sparse_checkout_dir.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: spring-integration-samples generateName: spring-integration-samples- projects: - name: spring-integration-samples source: type: git location: 'https://github.com/spring-projects/spring-integration-samples.git' branch: master sparseCheckoutDir: basic/http/ ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/devfile/devfile_with_undeclared_field.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment unknown: blah-blah ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/dockerimage_component/devfile_dockerimage_component.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: dockerimage image: eclipe/maven-jdk8:latest volumes: - name: maven-repo containerPath: /root/.m2 env: - name: ENV_VAR value: value endpoints: - name: maven-server port: 3101 attributes: protocol: http secure: 'true' public: 'true' discoverable: 'false' memoryLimit: 1536M memoryRequest: 512M cpuLimit: 1.5 cpuRequest: 750m command: ['/bin/sh'] args: ['-c', 'echo', 'hi'] mountSources: true ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/dockerimage_component/devfile_dockerimage_component_with_indistinctive_field_selector.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: dockerimage image: eclipe/maven-jdk8:latest volumes: - name: maven-repo containerPath: /root/.m2 env: - name: ENV_VAR value: value endpoints: - name: maven-server port: 3101 attributes: protocol: http secure: 'true' public: 'true' discoverable: 'false' memoryLimit: 1536M selector: app.kubernetes.io/name: mysql ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/dockerimage_component/devfile_dockerimage_component_with_invalid_memory_limit.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # apiVersion: 1.0.0 metadata: name: terminal-sample components: - type: dockerimage memoryLimit: 0 image: eclipe/maven-jdk8:latest ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/dockerimage_component/devfile_dockerimage_component_with_missing_image.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: dockerimage memoryLimit: 1G ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/dockerimage_component/devfile_dockerimage_component_with_missing_memory_limit.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: dockerimage image: org.eclipse.cheubuntu-jdk:latest ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/dockerimage_component/devfile_dockerimage_component_without_entry_point.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: dockerimage image: eclipe/maven-jdk8:latest volumes: - name: maven-repo containerPath: /root/.m2 env: - name: ENV_VAR value: value endpoints: - name: maven-server port: 3101 attributes: protocol: http secure: 'true' public: 'true' discoverable: 'false' memoryLimit: 1536M ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_editor_component_with_bad_registry.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: cheEditor id: eclipse/theia/dev registryUrl: ftp://foo/bar ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_editor_component_with_custom_registry.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # apiVersion: 1.0.0 metadata: name: terminal-sample components: - type: cheEditor registryUrl: https://che-plugin-registry.openshift.io/v3/ id: eclipse/theia-ide/0.0.1 ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_editor_component_with_id_and_reference.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # apiVersion: 1.0.0 metadata: name: terminal-sample components: - type: cheEditor reference: https://gist.githubusercontent.com/sleshchenko/e132bd9d37e8cf8f2c0a2b4929ecda3e/raw/9c28962cc3a24dcaf357b5716d98068734f51165/meta.yaml id: eclipse/che-theia/1.0.0 ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_editor_component_with_indistinctive_field.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: cheEditor id: eclipse/theia/0.0.3 unknown: petclinic.yaml ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_editor_component_with_missing_id.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: cheEditor ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_editor_component_with_multiple_colons_in_id.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: cheEditor id: eclipse/theia/dev:v1 ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_editor_component_with_registry_in_id.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: cheEditor id: https://myregistry.com/eclipse/theia/dev:v1 ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_editor_component_without_version.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: cheEditor id: eclipse/theia ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_editor_plugins.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment projects: - name: petclinic source: type: git location: 'git@github.com:spring-projects/spring-petclinic.git' components: - type: cheEditor id: eclipse/chetheia/0.0.3 env: - name: ENV_VAR value: value volumes: - name: maven-repo containerPath: /root/.m2 endpoints: - name: editor-endpoint port: 8088 attributes: discoverable: 'true' public: 'false' secure: 'true' - alias: mvn-stack type: chePlugin id: eclipse/chemaven-jdk8/1.0.0 - type: chePlugin id: eclipse/chetheia-jdtls/0.0.3 env: - name: ENV_VAR value: value volumes: - name: maven-repo containerPath: /root/.m2 endpoints: - name: plugin-endpoint port: 8080 attributes: discoverable: 'true' public: 'false' secure: 'true' commands: - name: build actions: - type: exec component: mvn-stack command: mvn package workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_editor_plugins_components_with_invalid_memory_limit.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # apiVersion: 1.0.0 metadata: name: terminal-sample components: - type: cheEditor id: publisher/terminal-sample/0.0.1 memoryLimit: 0 - type: chePlugin id: publisher/terminal-sample/0.0.1 memoryLimit: 0 ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_editor_plugins_components_with_resource_limits.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # apiVersion: 1.0.0 metadata: name: terminal-sample components: - type: chePlugin id: check/terminal-sample/0.0.1 memoryLimit: 256 memoryRequest: 123Mi cpuLimit: 2 cpuRequest: 127m - type: chePlugin id: eclipse/chemaven-jdk8/1.0.0 memoryLimit: 108M memoryRequest: 26Mi cpuLimit: 1500m cpuRequest: 100m - alias: theia type: cheEditor id: eclipse/theia/0.0.3 memoryLimit: 1048M memoryRequest: 256M cpuLimit: 1500m cpuRequest: 100m ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_plugin_component_with_reference.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # apiVersion: 1.0.0 metadata: name: terminal-sample components: - type: chePlugin reference: https://raw.githubusercontent.com/eclipse/che-plugin-registry/myplugin/meta.yaml ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/editor_plugin_component/devfile_plugin_components_with_preferences.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # apiVersion: 1.0.0 metadata: name: terminal-sample components: - type: chePlugin id: check/terminal-sample/0.0.1 preferences: java.home: '/home/user/jdk11' java.jdt.ls.vmargs: '-noverify -Xmx1G -XX:+UseG1GC -XX:+UseStringDeduplication' java.jtg.memory: 12345 go.lintFlags: ["--enable-all", "--new"] ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_k8s_openshift_component_with_endpoints.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml endpoints: - name: test-endpoint-os port: 1234 attributes: protocol: http secure: 'true' public: 'true' discoverable: 'false' - alias: web-app type: kubernetes reference: petclinic.yaml endpoints: - name: test-endpoint-k8s port: 4321 attributes: protocol: http secure: 'true' public: 'true' discoverable: 'false' ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_k8s_openshift_component_with_env.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml env: - name: TEST_ENV value: any - alias: web-app type: kubernetes reference: petclinic.yaml env: - name: TEST_ENV value: any ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_kubernetes_component.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: kubernetes reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic mountSources: true volumes: - name: maven-repo containerPath: /root/.m2 commands: - name: build actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_kubernetes_component_absolute_reference.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: kubernetes reference: https://github.com/redhat-developer/devfile/blob/master/samples/web-nodejs-with-db-sample/mongo-db.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_kubernetes_component_content_without_reference.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: kubernetes referenceContent: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_kubernetes_component_reference_and_content_as_block.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: kubernetes reference: petclinic.yaml referenceContent: | kind: List items: - apiVersion: v1 kind: Pod metadata: name: ws spec: containers: - image: 'quay.io/eclipse/che-dev:nightly' name: dev resources: limits: memory: 512Mi selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_openshift_component.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic mountSources: true volumes: - name: maven-repo containerPath: /root/.m2 commands: - name: build actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_openshift_component_content_without_reference.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift referenceContent: this is content of file that is supposed to be in local field but it is missing selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic commands: - name: build actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_openshift_component_reference_and_content.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: openshift referenceContent: it is supposed to be a content of the file specified in the local field reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_openshift_component_reference_and_content_as_block.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: openshift referenceContent: | kind: List items: - apiVersion: v1 kind: Pod metadata: name: ws spec: containers: - image: 'quay.io/eclipse/che-dev:nightly' name: dev resources: limits: memory: 512Mi reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_openshift_component_with_indistinctive_field_id.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - alias: mysql type: openshift reference: petclinic.yaml selector: app.kubernetes.io/name: mysql app.kubernetes.io/component: database app.kubernetes.io/part-of: petclinic id: eclipse/chetheia/0.0.3 commands: - name: build actions: - type: exec component: mysql command: mvn clean workdir: /projects/spring-petclinic ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/schema_test/kubernetes_openshift_component/devfile_openshift_component_with_missing_reference_and_referenceContent.yaml ================================================ # # Copyright (c) 2012-2021 Red Hat, Inc. # This program and the accompanying materials are made # available under the terms of the Eclipse Public License 2.0 # which is available at https://www.eclipse.org/legal/epl-2.0/ # # SPDX-License-Identifier: EPL-2.0 # # Contributors: # Red Hat, Inc. - initial API and implementation # --- apiVersion: 1.0.0 metadata: name: petclinic-dev-environment components: - type: openshift ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/url_fetcher_test_resource.json ================================================ Hello ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/devfile/workspace_config.json ================================================ { "projects": [ { "source": { "location": "git@github.com:spring-projects/spring-petclinic.git", "type": "git", "parameters": {} }, "mixins": [], "name": "petclinic", "path": "/petclinic", "attributes": {} } ], "commands": [ { "commandLine": "mvn package", "name": "build", "type": "exec", "attributes": { "plugin": "eclipse/chemaven-jdk8/1.0.0", "workingDir": "/projects/spring-petclinic" } }, { "commandLine": "mvn spring-boot:run", "name": "run", "type": "exec", "attributes": { "plugin": "eclipse/chemaven-jdk8/1.0.0", "runType": "sequential", "workingDir": "/projects/spring-petclinic" } }, { "commandLine": "run.sh", "name": "other", "type": "exec", "attributes": { "plugin": "eclipse/chetheia-jdtls/0.0.3" } } ], "defaultEnv": "os-recipe", "environments": { "os-recipe": { "machines": {}, "recipe": { "contentType": "application/x-yaml", "type": "openshift", "content": "---\napiVersion: \"v1\"\nkind: \"List\"\nitems:\n- apiVersion: \"v1\"\n kind: \"Service\"\n metadata:\n labels:\n app.kubernetes.io/name: \"mysql\"\n app.kubernetes.io/component: \"database\"\n app.kubernetes.io/part-of: \"petclinic\"\n name: \"mysql\"\n spec:\n ports:\n - name: \"mysql\"\n port: 3306\n targetPort: 3360\n selector:\n app.kubernetes.io/name: \"mysql\"\n app.kubernetes.io/component: \"database\"\n app.kubernetes.io/part-of: \"petclinic\"\n- apiVersion: \"v1\"\n kind: \"Pod\"\n metadata:\n labels:\n app.kubernetes.io/name: \"mysql\"\n app.kubernetes.io/component: \"database\"\n app.kubernetes.io/part-of: \"petclinic\"\n name: \"petclinic\"\n spec:\n containers:\n - env:\n - name: \"MYSQL_USER\"\n value: \"petclinic\"\n - name: \"MYSQL_PASSWORD\"\n value: \"petclinic\"\n - name: \"MYSQL_ROOT_PASSWORD\"\n value: \"petclinic\"\n - name: \"MYSQL_DATABASE\"\n value: \"petclinic\"\n image: \"centos/mysql-57-centos7\"\n name: \"mysql\"\n ports:\n - containerPort: 3306\n protocol: \"TCP\"\n resources:\n limits:\n memory: \"512Mi\"\n" } } }, "name": "petclinic-dev-environment", "attributes": { "pluginComponentsAliases": "eclipse/chemaven-jdk8/1.0.0=mvn-stack,eclipse/chetheia/0.0.3=theia-ide,eclipse/chetheia-jdtls/0.0.3=jdt.ls", "editor": "eclipse/chetheia/0.0.3", "plugins": "eclipse/chemaven-jdk8/1.0.0,eclipse/chetheia-jdtls/0.0.3" } } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/invalid-json.json ================================================ invalid json file/ ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/invalid-recipes.json ================================================ invalid json file/ ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/logback-test.xml ================================================ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n target/log/codenvy.log %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/recipes.json ================================================ [ { "creator": "creator", "tags": [ "azz" ], "description": "description", "name": "name", "id": "Hello", "type": "type", "script": "script" }, { "creator": "creator", "tags": [ "azz" ], "description": "description", "name": "name", "id": "Hello", "type": "type", "script": "script" } ] ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/ws_conf_machine_source_dockerfile_content.json ================================================ { "name": "default", "defaultEnv": "dev-env", "description": "This is workspace description", "environments": [ { "name": "dev-env", "machineConfigs": [ { "type": "docker", "name": "site", "dev": true, "limits": { "ram": 1024 }, "source": { "type": "dockerfile", "content": "FROM codenvy/ubuntu_jdk8" } } ] } ] } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/ws_conf_machine_source_dockerfile_location.json ================================================ { "name": "default", "defaultEnv": "dev-env", "description": "This is workspace description", "environments": [ { "name": "dev-env", "machineConfigs": [ { "name": "dev", "type": "docker", "dev": true, "limits": { "ram": 2048 }, "source": { "location": "https://somewhere/Dockerfile", "type": "dockerfile" }, "servers": [ { "ref": "ref", "port": "9090/udp", "protocol": "protocol", "path": "/any/path" } ] } ] } ] } ================================================ FILE: wsmaster/che-core-api-workspace/src/test/resources/ws_conf_machine_source_dockerimage.json ================================================ { "name": "default", "defaultEnv": "dev-env", "description": "This is workspace description", "environments": [ { "name": "dev-env", "machineConfigs": [ { "type": "docker", "name": "site", "dev": true, "limits": { "ram": 1024 }, "source": { "type": "image", "location": "codenvy/ubuntu_jdk8" } } ] } ] } ================================================ FILE: wsmaster/che-core-api-workspace-shared/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-api-workspace-shared jar Che Core :: API :: Workspace :: Shared ${project.build.directory}/generated-sources/dto/ false com.google.code.gson gson io.swagger.core.v3 swagger-annotations-jakarta org.eclipse.che.core che-core-api-core org.eclipse.che.core che-core-api-dto org.eclipse.che.core che-core-api-model org.eclipse.che.core che-core-commons-annotations org.eclipse.che.core che-core-api-dto-maven-plugin ${project.version} process-sources generate org.eclipse.che.core che-core-api-workspace-shared ${project.version} org.eclipse.che.api.workspace.shared.dto ${dto-generator-out-directory} org.eclipse.che.api.workspace.server.dto.DtoServerImpls server maven-compiler-plugin pre-compile generate-sources compile org.codehaus.mojo build-helper-maven-plugin add-resource process-sources add-resource ${dto-generator-out-directory}/META-INF META-INF add-source process-sources add-source ${dto-generator-out-directory} ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/Constants.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.api.core.model.workspace.runtime.Machine; import org.eclipse.che.api.core.model.workspace.runtime.Server; /** * Constants for Workspace API * * @author Yevhenii Voevodin */ public final class Constants { public static final String LINK_REL_IDE_URL = "ide"; public static final String LINK_REL_SELF = "self"; public static final String LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL = "environment/outputChannel"; public static final String LINK_REL_ENVIRONMENT_STATUS_CHANNEL = "environment/statusChannel"; public static final String DEBUG_WORKSPACE_START = "debug-workspace-start"; public static final String DEBUG_WORKSPACE_START_LOG_LIMIT_BYTES = "che.workspace.startup_debug_log_limit_bytes"; public static final String WORKSPACE_STOPPED_BY = "stopped_by"; public static final String WORKSPACE_STOP_REASON = "stop_reason"; /** * The configuration property that defines available values for storage types that clients like * Dashboard should propose for users during workspace creation/update. */ public static final String CHE_WORKSPACE_STORAGE_AVAILABLE_TYPES = "che.workspace.storage.available_types"; /** * The configuration property that defines a preferred value for storage type that clients like * Dashboard should propose for users during workspace creation/update. */ public static final String CHE_WORKSPACE_STORAGE_PREFERRED_TYPE = "che.workspace.storage.preferred_type"; /** Property name for Che plugin registry url. */ public static final String CHE_WORKSPACE_PLUGIN_REGISTRY_URL_PROPERTY = "che.workspace.plugin_registry_url"; /** Property name for internal network Che plugin registry url. */ public static final String CHE_WORKSPACE_PLUGIN_REGISTRY_INTERNAL_URL_PROPERTY = "che.workspace.plugin_registry_internal_url"; /** Property name for Che Devfile Registry URL. */ public static final String CHE_WORKSPACE_DEVFILE_REGISTRY_URL_PROPERTY = "che.workspace.devfile_registry_url"; /** Property name for internal network Che Devfile Registry URL. */ public static final String CHE_WORKSPACE_DEVFILE_REGISTRY_INTERNAL_URL_PROPERTY = "che.workspace.devfile_registry_internal_url"; /** Name for property that specifies whether che is deployed with devworkspaces enabled */ public static final String CHE_DEVWORKSPACES_ENABLED_PROPERTY = "che.devworkspaces.enabled"; public static final String CHE_FACTORY_DEFAULT_EDITOR_PROPERTY = "che.factory.default_editor"; public static final String CHE_FACTORY_DEFAULT_PLUGINS_PROPERTY = "che.factory.default_plugins"; /** Name for environment variable of machine name */ public static final String CHE_MACHINE_NAME_ENV_VAR = "CHE_MACHINE_NAME"; /** * Describes time when workspace was created. Should be set/read from {@link * Workspace#getAttributes} */ public static final String CREATED_ATTRIBUTE_NAME = "created"; /** * Describes time when workspace was last updated or started. Should be set/read from {@link * Workspace#getAttributes} */ public static final String UPDATED_ATTRIBUTE_NAME = "updated"; /** * Describes time when workspace was last stopped. Should be set/read from {@link * Workspace#getAttributes} */ public static final String STOPPED_ATTRIBUTE_NAME = "stopped"; /** * Indicates that last workspace stop was abnormal. Should be set/read from {@link * Workspace#getAttributes} */ public static final String STOPPED_ABNORMALLY_ATTRIBUTE_NAME = "stoppedAbnormally"; /** * Describes latest workspace runtime error message. Should be set/read from {@link * Workspace#getAttributes} */ public static final String ERROR_MESSAGE_ATTRIBUTE_NAME = "errorMessage"; /** * Contains an identifier of an editor that should be used in a workspace. Should be set/read from * {@link WorkspaceConfig#getAttributes}. * *

    Value is plugin id. * *

    Example of the attribute value: 'eclipse/super-editor/0.0.1' * *

    This is beta constant that is subject to change or removal. */ public static final String WORKSPACE_TOOLING_EDITOR_ATTRIBUTE = "editor"; /** * The attribute allows to configure workspace to be ephemeral with no PVC attached on K8S / * OpenShift infrastructure. Should be set/read from {@link Devfile#getAttributes}. * *

    Value is expected to be boolean, and if set to 'false' regardless of the PVC strategy, * workspace volumes would be created as `emptyDir`. When a workspace Pod is removed for any * reason, the data in the `emptyDir` volume is deleted forever * * @see Che * PVC strategies * @see emptyDir */ public static final String PERSIST_VOLUMES_ATTRIBUTE = "persistVolumes"; /** * This attribute enables or disables merging plugins while provisioning a workspace. When * enabled, the plugin broker will be configured to attempt to merge plugins where possible (when * two or more plugins share the same image and do not have conflicting settings). Should be * set/read from {@link Devfile#getAttributes}. * *

    Value is expected to be boolean; if it set to true, then the broker will attempt to merge * plugins in the current devfile, otherwise it will not. The behavior when this property is not * present in a devfile is governed by the configuration property {@code * che.workspace.plugin_broker.default_merge_plugins}. */ public static final String MERGE_PLUGINS_ATTRIBUTE = "mergePlugins"; /** * The attribute allows to configure workspace with async storage support this configuration. Make * sense only in case org.eclipse.che.api.workspace.shared.Constants#PERSIST_VOLUMES_ATTRIBUTE set * to 'false'. * *

    Should be set/read from {@link Devfile#getAttributes}. * *

    Value is expected to be boolean, and if set to 'true' special plugin will be added to * workspace. It will provide ability to backup/restore project source to the async storage. * Workspace volumes still would be created as `emptyDir`. During stopping workspace project * source will be sent to the storage Pod and restore from it on next restarts. */ public static final String ASYNC_PERSIST_ATTRIBUTE = "asyncPersist"; /** * Contains a list of workspace tooling plugins that should be used in a workspace. Should be * set/read from {@link Devfile#getAttributes}. * *

    Value is comma separated list of plugins in a format: '< plugin1ID >,'
    * Spaces around commas are trimmed.
    * *

    This is beta constant that is subject to change or removal. * *

    Example of the attribute value: 'eclipse/plugin1/0.0.1, redhat/plugin2/1.0.0' */ public static final String WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE = "plugins"; /** * Describes workspace runtimes which perform start/stop of this workspace. Should be set/read * from {@link Workspace#getAttributes} */ public static final String WORKSPACE_RUNTIMES_ID_ATTRIBUTE = "org.eclipse.che.runtimes_id"; public static final String COMMAND_PREVIEW_URL_ATTRIBUTE_NAME = "previewUrl"; public static final String COMMAND_GOAL_ATTRIBUTE_NAME = "goal"; public static final String WORKSPACE_STATUS_CHANGED_METHOD = "workspace/statusChanged"; public static final String MACHINE_STATUS_CHANGED_METHOD = "machine/statusChanged"; public static final String SERVER_STATUS_CHANGED_METHOD = "server/statusChanged"; public static final String RUNTIME_LOG_METHOD = "runtime/log"; /** * JSON RPC methods for listening to machine logs. * * @deprecated use {@link #RUNTIME_LOG_METHOD} instead */ @Deprecated public static final String MACHINE_LOG_METHOD = "machine/log"; public static final String INSTALLER_LOG_METHOD = "installer/log"; public static final String INSTALLER_STATUS_CHANGED_METHOD = "installer/statusChanged"; public static final String BOOTSTRAPPER_STATUS_CHANGED_METHOD = "bootstrapper/statusChanged"; public static final String SERVER_WS_AGENT_HTTP_REFERENCE = "wsagent/http"; public static final String SERVER_TERMINAL_REFERENCE = "terminal"; public static final String SERVER_EXEC_AGENT_HTTP_REFERENCE = "exec-agent/http"; public static final String SUPPORTED_RECIPE_TYPES = "supportedRecipeTypes"; public static final String NO_ENVIRONMENT_RECIPE_TYPE = "no-environment"; /** Attribute of {@link Machine} to mark source of the container. */ public static final String CONTAINER_SOURCE_ATTRIBUTE = "source"; /** * Attribute of {@link Machine} that indicates by which plugin this machines is provisioned * *

    It contains plugin id, like "plugin": "eclipse/che-theia/master" */ public static final String PLUGIN_MACHINE_ATTRIBUTE = "plugin"; /** Mark containers applied to workspace with help recipe definition. */ public static final String RECIPE_CONTAINER_SOURCE = "recipe"; /** Mark containers created workspace api like tooling for user development. */ public static final String TOOL_CONTAINER_SOURCE = "tool"; /** The projects volume has a standard name used in a couple of locations. */ public static final String PROJECTS_VOLUME_NAME = "projects"; /** Attribute of {@link Server} that specifies exposure of which port created the server */ public static final String SERVER_PORT_ATTRIBUTE = "port"; /** When generating workspace name from generateName, append this many characters. */ public static final int WORKSPACE_GENERATE_NAME_CHARS_APPEND = 5; /** * The attribute of the workspace where we store the infrastructure namespace the workspace is * deployed to */ public static final String WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE = "infrastructureNamespace"; /** * The attribute for storing the infrastructure namespace of last used workspace, it recorded on * workspace stop if no more running */ public static final String LAST_ACTIVE_INFRASTRUCTURE_NAMESPACE = "lastUsedInfrastructureNamespace"; /** The attribute for storing the time then last active workspace was stopped */ public static final String LAST_ACTIVITY_TIME = "lastActivityTime"; /** The flag for removing a workspace after it's stopped */ public static final String REMOVE_WORKSPACE_AFTER_STOP = "removeWorkspaceImmediatelyAfterStop"; private Constants() {} } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/ProjectProblemImpl.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared; import org.eclipse.che.api.core.model.project.ProjectProblem; /** * @author Vitalii Parfonov */ public class ProjectProblemImpl implements ProjectProblem { public ProjectProblemImpl(ProjectProblem projectProblem) { this(projectProblem.getCode(), projectProblem.getMessage()); } public ProjectProblemImpl(int code, String message) { this.code = code; this.message = message; } int code; String message; @Override public int getCode() { return code; } @Override public String getMessage() { return message; } } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/BrokerStatus.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; /** * Statuses of a plugin broker that is used in the process of workspace start when sidecar-based * tooling is used. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksandr Garagatyi */ public enum BrokerStatus { /** Means that broker is started but neither error nor success was achieved yet. */ STARTED, /** Means that broker successfully finished execution. */ DONE, /** Means that broker execution failed. */ FAILED } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/CommandDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import java.util.Map; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.workspace.config.Command; import org.eclipse.che.api.workspace.shared.dto.devfile.PreviewUrlDto; import org.eclipse.che.dto.shared.DTO; /** * @author Alexander Garagatyi */ @DTO public interface CommandDto extends Command { @Override @FactoryParameter(obligation = MANDATORY) String getName(); void setName(String name); CommandDto withName(String name); @Override @FactoryParameter(obligation = MANDATORY) String getCommandLine(); void setCommandLine(String commandLine); CommandDto withCommandLine(String commandLine); @Override @FactoryParameter(obligation = MANDATORY) String getType(); void setType(String type); CommandDto withType(String type); @Override @FactoryParameter(obligation = OPTIONAL) Map getAttributes(); void setAttributes(Map attributes); CommandDto withAttributes(Map attributes); @Override PreviewUrlDto getPreviewUrl(); void setPreviewUrl(PreviewUrlDto previewUrl); CommandDto withPreviewUrl(PreviewUrlDto previewUrl); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/EnvironmentDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY; import java.util.Map; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.workspace.config.Environment; import org.eclipse.che.dto.shared.DTO; /** * @author Alexander Garagatyi */ @DTO public interface EnvironmentDto extends Environment { @Override @FactoryParameter(obligation = MANDATORY) RecipeDto getRecipe(); void setRecipe(RecipeDto recipe); EnvironmentDto withRecipe(RecipeDto recipe); @Override @FactoryParameter(obligation = MANDATORY) Map getMachines(); void setMachines(Map machines); EnvironmentDto withMachines(Map machines); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/MachineConfigDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import java.util.Map; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.workspace.config.MachineConfig; import org.eclipse.che.dto.shared.DTO; /** * @author Alexander Garagatyi */ @DTO public interface MachineConfigDto extends MachineConfig { @Override @FactoryParameter(obligation = OPTIONAL) Map getServers(); void setServers(Map servers); MachineConfigDto withServers(Map servers); @Override @FactoryParameter(obligation = OPTIONAL) Map getEnv(); void setEnv(Map env); MachineConfigDto withEnv(Map env); @Override @FactoryParameter(obligation = OPTIONAL) Map getAttributes(); void setAttributes(Map attributes); MachineConfigDto withAttributes(Map attributes); @Override @FactoryParameter(obligation = OPTIONAL) Map getVolumes(); void setVolumes(Map volumes); MachineConfigDto withVolumes(Map volumes); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/MachineDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.Machine; import org.eclipse.che.api.core.model.workspace.runtime.MachineStatus; import org.eclipse.che.dto.shared.DTO; /** * @author Alexander Garagatyi */ @DTO public interface MachineDto extends Machine { @Override Map getAttributes(); MachineDto withAttributes(Map attributes); @Override Map getServers(); MachineDto withServers(Map servers); @Override MachineStatus getStatus(); MachineDto withStatus(MachineStatus status); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/ProjectConfigDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.workspace.config.ProjectConfig; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.dto.shared.DTO; /** * @author Alexander Garagatyi */ @DTO public interface ProjectConfigDto extends ProjectConfig { @Override @FactoryParameter(obligation = MANDATORY) String getName(); void setName(String name); ProjectConfigDto withName(String name); @Override @FactoryParameter(obligation = MANDATORY) String getPath(); void setPath(String path); ProjectConfigDto withPath(String path); @Override @FactoryParameter(obligation = OPTIONAL) String getDescription(); void setDescription(String description); ProjectConfigDto withDescription(String description); @Override @FactoryParameter(obligation = OPTIONAL) String getType(); void setType(String type); ProjectConfigDto withType(String type); @Override @FactoryParameter(obligation = OPTIONAL) List getMixins(); void setMixins(List mixins); ProjectConfigDto withMixins(List mixins); @Override @FactoryParameter(obligation = OPTIONAL) Map> getAttributes(); void setAttributes(Map> attributes); ProjectConfigDto withAttributes(Map> attributes); @Override @FactoryParameter(obligation = OPTIONAL) SourceStorageDto getSource(); void setSource(SourceStorageDto source); ProjectConfigDto withSource(SourceStorageDto source); @FactoryParameter(obligation = OPTIONAL) List getLinks(); void setLinks(List links); ProjectConfigDto withLinks(List links); /** * Provides information about project errors. If project doesn't have any error this field is * empty. */ @Schema( description = "Optional information about project errors. If project doesn't have any error this field is empty") @Override List getProblems(); /** * @see #getProblems */ void setProblems(List problems); ProjectConfigDto withProblems(List problems); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/ProjectProblemDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import org.eclipse.che.api.core.model.project.ProjectProblem; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Kabashniuk */ @DTO public interface ProjectProblemDto extends ProjectProblem { int getCode(); void setCode(int status); ProjectProblemDto withCode(int status); String getMessage(); void setMessage(String message); ProjectProblemDto withMessage(String message); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/RecipeDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.workspace.config.Recipe; import org.eclipse.che.dto.shared.DTO; /** * @author Alexander Garagatyi */ @DTO public interface RecipeDto extends Recipe { @Override @FactoryParameter(obligation = MANDATORY) String getType(); void setType(String type); RecipeDto withType(String type); @Override @FactoryParameter(obligation = OPTIONAL) String getContentType(); void setContentType(String contentType); RecipeDto withContentType(String contentType); @Override @FactoryParameter(obligation = OPTIONAL) String getContent(); void setContent(String content); RecipeDto withContent(String content); @Override @FactoryParameter(obligation = OPTIONAL) String getLocation(); void setLocation(String location); RecipeDto withLocation(String location); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/RuntimeDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.Runtime; import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.dto.shared.DTO; /** * @author Alexander Garagatyi */ @DTO public interface RuntimeDto extends Runtime, Hyperlinks { @Override String getActiveEnv(); void setActiveEnv(String activeEnv); RuntimeDto withActiveEnv(String activeEnvName); @Override Map getMachines(); void setMachines(Map machines); RuntimeDto withMachines(Map machines); @Override String getOwner(); RuntimeDto withOwner(String owner); String getMachineToken(); RuntimeDto withMachineToken(String machineToken); void setMachineToken(String machineToken); @Override List getWarnings(); void setWarnings(List warnings); RuntimeDto withWarnings(List warnings); @Override List getCommands(); void setCommands(List commands); RuntimeDto withCommands(List commands); @Override RuntimeDto withLinks(List links); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/RuntimeIdentityDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.dto.shared.DTO; /** * @author gazarenkov */ @DTO public interface RuntimeIdentityDto extends RuntimeIdentity { @Override String getWorkspaceId(); RuntimeIdentityDto withWorkspaceId(String workspaceId); @Override String getEnvName(); RuntimeIdentityDto withEnvName(String envName); @Override String getOwnerId(); RuntimeIdentityDto withOwnerId(String ownerId); @Override String getInfrastructureNamespace(); RuntimeIdentityDto withInfrastructureNamespace(String infrastructureNamespace); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/ServerConfigDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import java.util.Map; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.dto.shared.DTO; /** * @author Alexander Garagatyi */ @DTO public interface ServerConfigDto extends ServerConfig { @Override @FactoryParameter(obligation = MANDATORY) String getPort(); void setPort(String port); ServerConfigDto withPort(String port); @Override @FactoryParameter(obligation = MANDATORY) String getProtocol(); void setProtocol(String protocol); ServerConfigDto withProtocol(String protocol); @Override @FactoryParameter(obligation = OPTIONAL) String getPath(); void setPath(String path); ServerConfigDto withPath(String path); @Override @FactoryParameter(obligation = OPTIONAL) Map getAttributes(); void setAttributes(Map attributes); ServerConfigDto withAttributes(Map attributes); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/ServerDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.Server; import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; import org.eclipse.che.dto.shared.DTO; /** * Describes how to access to exposed ports for servers inside machine * * @author Alexander Garagatyi */ @DTO public interface ServerDto extends Server { @Override String getUrl(); void setUrl(String url); ServerDto withUrl(String url); @Override ServerStatus getStatus(); ServerDto withStatus(ServerStatus status); @Override Map getAttributes(); ServerDto withAttributes(Map attributes); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/SourceStorageDto.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import java.util.Map; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.workspace.config.SourceStorage; import org.eclipse.che.dto.shared.DTO; /** * TODO Type and location are optional in case it is a subproject, that has an empty source. * * @author Alexander Garagatyi */ @DTO public interface SourceStorageDto extends SourceStorage { @Override @FactoryParameter(obligation = OPTIONAL) String getType(); void setType(String type); SourceStorageDto withType(String type); @Override @FactoryParameter(obligation = OPTIONAL) String getLocation(); void setLocation(String location); SourceStorageDto withLocation(String location); @Override @FactoryParameter(obligation = OPTIONAL) Map getParameters(); void setParameters(Map parameters); SourceStorageDto withParameters(Map parameters); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/VolumeDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import org.eclipse.che.api.core.model.workspace.config.Volume; import org.eclipse.che.dto.shared.DTO; /** * @author Alexander Garagatyi */ @DTO public interface VolumeDto extends Volume { @Override String getPath(); void setPath(String path); VolumeDto withPath(String path); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/WarningDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import org.eclipse.che.api.core.model.workspace.Warning; import org.eclipse.che.dto.shared.DTO; /** * @author Yevhenii Voevodin */ @DTO public interface WarningDto extends Warning { void setCode(int code); WarningDto withCode(int code); void setMessage(String message); WarningDto withMessage(String message); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/WorkspaceConfigDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.factory.FactoryParameter; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.dto.shared.DTO; /** * @author andrew00x */ @DTO public interface WorkspaceConfigDto extends WorkspaceConfig, Hyperlinks { @Override @FactoryParameter(obligation = OPTIONAL) String getName(); WorkspaceConfigDto withName(String name); void setName(String name); @Override @FactoryParameter(obligation = OPTIONAL) String getDefaultEnv(); void setDefaultEnv(String defaultEnvironment); WorkspaceConfigDto withDefaultEnv(String defaultEnvironment); @Override @FactoryParameter(obligation = OPTIONAL) String getDescription(); void setDescription(String description); WorkspaceConfigDto withDescription(String description); @Override @FactoryParameter(obligation = OPTIONAL) List getCommands(); void setCommands(List commands); WorkspaceConfigDto withCommands(List commands); @Override @FactoryParameter(obligation = MANDATORY) List getProjects(); void setProjects(List projects); WorkspaceConfigDto withProjects(List projects); @Override @FactoryParameter(obligation = OPTIONAL) Map getEnvironments(); void setEnvironments(Map environments); WorkspaceConfigDto withEnvironments(Map environments); @Override @FactoryParameter(obligation = OPTIONAL) Map getAttributes(); void setAttributes(Map attributes); WorkspaceConfigDto withAttributes(Map attributes); @Override WorkspaceConfigDto withLinks(List links); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/WorkspaceDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto; import java.util.Map; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.eclipse.che.dto.shared.DTO; /** * @author Yevhenii Voevodin */ @DTO public interface WorkspaceDto extends Workspace { @Override WorkspaceConfigDto getConfig(); void setConfig(WorkspaceConfigDto config); WorkspaceDto withConfig(WorkspaceConfigDto config); @Override DevfileDto getDevfile(); void setDevfile(DevfileDto devfile); WorkspaceDto withDevfile(DevfileDto devfile); @Override RuntimeDto getRuntime(); void setRuntime(RuntimeDto runtime); WorkspaceDto withRuntime(RuntimeDto runtime); void setId(String id); WorkspaceDto withId(String id); void setNamespace(String namespace); WorkspaceDto withNamespace(String owner); void setStatus(WorkspaceStatus status); WorkspaceDto withStatus(WorkspaceStatus status); void setTemporary(boolean isTemporary); WorkspaceDto withTemporary(boolean isTemporary); void setAttributes(Map attributes); WorkspaceDto withAttributes(Map attributes); Map getLinks(); void setLinks(Map links); WorkspaceDto withLinks(Map links); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/ComponentDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import java.io.Serializable; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.devfile.Component; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leshchenko */ @DTO public interface ComponentDto extends Component { @Override String getAlias(); void setAlias(String alias); ComponentDto withAlias(String alias); @Override String getType(); void setType(String type); ComponentDto withType(String type); @Override String getId(); void setId(String id); ComponentDto withId(String id); @Override String getRegistryUrl(); void setRegistryUrl(String registryUrl); ComponentDto withRegistryUrl(String registryUrl); @Override Map getPreferences(); void setPreferences(Map preferences); ComponentDto withPreferences(Map preferences); @Override String getReference(); void setReference(String reference); ComponentDto withReference(String reference); @Override String getReferenceContent(); void setReferenceContent(String referenceContent); ComponentDto withReferenceContent(String referenceContent); @Override List getEntrypoints(); void setEntrypoints(List entrypoints); ComponentDto withEntrypoints(List entrypoints); @Override Map getSelector(); void setSelector(Map selector); ComponentDto withSelector(Map selector); @Override String getImage(); void setImage(String image); ComponentDto withImage(String image); @Override String getMemoryLimit(); void setMemoryLimit(String memoryLimit); ComponentDto withMemoryLimit(String memoryLimit); @Override String getMemoryRequest(); void setMemoryRequest(String memoryRequest); ComponentDto withMemoryRequest(String memoryRequest); @Override String getCpuLimit(); void setCpuLimit(String cpuLimit); ComponentDto withCpuLimit(String cpuLimit); @Override String getCpuRequest(); void setCpuRequest(String cpuRequest); ComponentDto withCpuRequest(String cpuRequest); @Override Boolean getMountSources(); void setMountSources(Boolean mountSources); ComponentDto withMountSources(Boolean mountSources); @Override Boolean getAutomountWorkspaceSecrets(); void setAutomountWorkspaceSecrets(Boolean automountWorkspaceSecrets); ComponentDto withAutomountWorkspaceSecrets(Boolean automountWorkspaceSecrets); @Override List getCommand(); void setCommand(List command); ComponentDto withCommand(List command); @Override List getArgs(); void setArgs(List args); ComponentDto withArgs(List args); @Override List getVolumes(); void setVolumes(List volumes); ComponentDto withVolumes(List volumes); @Override List getEnv(); void setEnv(List env); ComponentDto withEnv(List env); @Override List getEndpoints(); void setEndpoints(List endpoints); ComponentDto withEndpoints(List endpoints); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/DevfileActionDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import org.eclipse.che.api.core.model.workspace.devfile.Action; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leshchenko */ @DTO public interface DevfileActionDto extends Action { @Override String getType(); void setType(String type); DevfileActionDto withType(String type); @Override String getComponent(); void setComponent(String component); DevfileActionDto withComponent(String component); @Override String getCommand(); void setCommand(String command); DevfileActionDto withCommand(String command); @Override String getWorkdir(); void setWorkdir(String workdir); DevfileActionDto withWorkdir(String workdir); @Override String getReference(); void setReference(String reference); DevfileActionDto withReference(String reference); @Override String getReferenceContent(); void setReferenceContent(String referenceContent); DevfileActionDto withReferenceContent(String referenceContent); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/DevfileCommandDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.devfile.Command; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leshchenko */ @DTO public interface DevfileCommandDto extends Command { @Override String getName(); void setName(String name); DevfileCommandDto withName(String name); void setPreviewUrl(PreviewUrlDto previewUrl); DevfileCommandDto withPreviewUrl(PreviewUrlDto previewUrl); PreviewUrlDto getPreviewUrl(); @Override List getActions(); void setActions(List actions); DevfileCommandDto withActions(List actions); @Override Map getAttributes(); void setAttributes(Map attributes); DevfileCommandDto withAttributes(Map attributes); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/DevfileDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.devfile.Devfile; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leshchenko */ @DTO public interface DevfileDto extends Devfile { @Override String getApiVersion(); void setApiVersion(String apiVersion); DevfileDto withApiVersion(String apiVersion); @Override List getProjects(); void setProjects(List projects); DevfileDto withProjects(List projects); @Override List getComponents(); void setComponents(List components); DevfileDto withComponents(List components); @Override List getCommands(); void setCommands(List commands); DevfileDto withCommands(List commands); @Override Map getAttributes(); void setAttributes(Map attributes); DevfileDto withAttributes(Map attributes); @Override MetadataDto getMetadata(); void setMetadata(MetadataDto metadata); DevfileDto withMetadata(MetadataDto metadata); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/DevfileVolumeDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import org.eclipse.che.api.core.model.workspace.devfile.Volume; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leshchenko */ @DTO public interface DevfileVolumeDto extends Volume { @Override String getName(); void setName(String name); DevfileVolumeDto withName(String name); @Override String getContainerPath(); void setContainerPath(String containerPath); DevfileVolumeDto withContainerPath(String containerPath); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/EndpointDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import java.util.Map; import org.eclipse.che.api.core.model.workspace.devfile.Endpoint; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leshchenko */ @DTO public interface EndpointDto extends Endpoint { @Override String getName(); void setName(String name); EndpointDto withName(String name); @Override Integer getPort(); void setPort(Integer port); EndpointDto withPort(Integer port); @Override Map getAttributes(); void setAttributes(Map attributes); EndpointDto withAttributes(Map attributes); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/EntrypointDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.workspace.devfile.Entrypoint; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leshchenko */ @DTO public interface EntrypointDto extends Entrypoint { @Override String getParentName(); void setParentName(String parentName); EntrypointDto withParentName(String parentName); @Override String getContainerName(); void setContainerName(String containerName); EntrypointDto withContainerName(String containerName); @Override Map getParentSelector(); void setParentSelector(Map parentSelector); EntrypointDto withParentSelector(Map parentSelector); @Override List getCommand(); void setCommand(List command); EntrypointDto withCommand(List command); @Override List getArgs(); void setArgs(List args); EntrypointDto withArgs(List args); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/EnvDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import org.eclipse.che.api.core.model.workspace.devfile.Env; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leshchenko */ @DTO public interface EnvDto extends Env { @Override String getName(); void setName(String name); EnvDto withName(String name); @Override String getValue(); void setValue(String value); EnvDto withValue(String value); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/MetadataDto.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import org.eclipse.che.api.core.model.workspace.devfile.Metadata; import org.eclipse.che.dto.shared.DTO; @DTO public interface MetadataDto extends Metadata { @Override String getName(); void setName(String name); MetadataDto withName(String name); MetadataDto withGenerateName(String generateName); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/PreviewUrlDto.java ================================================ /* * Copyright (c) 2012-2021 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import org.eclipse.che.api.core.model.workspace.devfile.PreviewUrl; import org.eclipse.che.dto.shared.DTO; @DTO public interface PreviewUrlDto extends PreviewUrl { @Override int getPort(); @Override String getPath(); void setPort(int port); PreviewUrlDto withPort(int port); void setPath(String path); PreviewUrlDto withPath(String path); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/ProjectDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import org.eclipse.che.api.core.model.workspace.devfile.Project; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leshchenko */ @DTO public interface ProjectDto extends Project { @Override String getName(); void setName(String name); ProjectDto withName(String name); @Override SourceDto getSource(); void setSource(SourceDto source); ProjectDto withSource(SourceDto source); @Override String getClonePath(); void setClonePath(String clonePath); ProjectDto withClonePath(String clonePath); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/devfile/SourceDto.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.devfile; import org.eclipse.che.api.core.model.workspace.devfile.Source; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leshchenko */ @DTO public interface SourceDto extends Source { @Override String getType(); void setType(String type); SourceDto withType(String type); @Override String getLocation(); void setLocation(String location); SourceDto withLocation(String location); @Override String getBranch(); void setBranch(String branch); SourceDto withBranch(String branch); @Override String getStartPoint(); void setStartPoint(String startPoint); SourceDto withStartPoint(String startPoint); @Override String getTag(); void setTag(String tag); SourceDto withTag(String tag); @Override String getCommitId(); void setCommitId(String commitId); SourceDto withCommitId(String commitId); @Override String getSparseCheckoutDir(); void setSparseCheckoutDir(String sparseCheckoutDir); SourceDto withSparseCheckoutDir(String sparseCheckoutDir); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/BootstrapperStatusEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.event; import org.eclipse.che.api.core.model.workspace.runtime.BootstrapperStatus; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.dto.shared.DTO; /** * Bootstrapper event status DTO. * * @author Max Shaposhnik (mshaposhnik@codenvy.com) */ @DTO public interface BootstrapperStatusEvent { BootstrapperStatus getStatus(); void setStatus(BootstrapperStatus status); BootstrapperStatusEvent withStatus(BootstrapperStatus status); String getMachineName(); void setMachineName(String machineName); BootstrapperStatusEvent withMachineName(String machineName); RuntimeIdentityDto getRuntimeId(); void setRuntimeId(RuntimeIdentityDto runtimeId); BootstrapperStatusEvent withRuntimeId(RuntimeIdentityDto runtimeId); String getError(); void setError(String error); BootstrapperStatusEvent withError(String error); String getTime(); void setTime(String time); BootstrapperStatusEvent withTime(String time); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/BrokerLogEvent.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.event; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.dto.shared.DTO; /** * @author Sergii Leshchenko */ @DTO public interface BrokerLogEvent { /** Returns the contents of the log event. */ String getText(); void setText(String text); BrokerLogEvent withText(String text); /** Returns runtime identity. */ RuntimeIdentityDto getRuntimeId(); void setRuntimeId(RuntimeIdentityDto runtimeId); BrokerLogEvent withRuntimeId(RuntimeIdentityDto runtimeId); /** Returns time in format '2017-06-27T17:11:09.306+03:00' */ String getTime(); void setTime(String time); BrokerLogEvent withTime(String time); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/BrokerStatusChangedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.event; import org.eclipse.che.api.workspace.shared.dto.BrokerStatus; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.dto.shared.DTO; /** * Event initiated by a plugin broker with the results of the plugin broker invocation for a * workspace. * *

    This API is in Beta and is subject to changes or removal. * * @author Oleksandr Garagatyi */ @DTO public interface BrokerStatusChangedEvent { /** Status of execution of a broker process. */ BrokerStatus getStatus(); BrokerStatusChangedEvent withStatus(BrokerStatus status); /** Returns identity of runtime to which broker belongs t. */ RuntimeIdentityDto getRuntimeId(); BrokerStatusChangedEvent withRuntimeId(RuntimeIdentityDto runtimeId); /** * Error message that explains the reason of the broker process failure. * *

    This method must return non-null value if {@link #getStatus() status} is {@link * BrokerStatus#FAILED}. */ String getError(); BrokerStatusChangedEvent withError(String error); /** * Stringified workspace tooling in JSON format. * *

    Current model of workspace tooling description has fields with names not supported by a DTO * framework (dashes in field name, field name not matching POJO getter), so we have to stringify * it to pass over Che JSON_RPC framework. * *

    This method must return non-null value if {@link #getStatus() status} is {@link * BrokerStatus#DONE}. */ String getTooling(); BrokerStatusChangedEvent withTooling(String tooling); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/MachineLogEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.event; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.dto.shared.DTO; /** * Defines event format for machine logs. * * @author Anton Korneta * @deprecated use {@link RuntimeLogEvent} instead */ @DTO @Deprecated public interface MachineLogEvent { /** Returns the contents of the log event. */ String getText(); void setText(String text); MachineLogEvent withText(String text); /** Returns the name of the machine that produces the logs. */ String getMachineName(); void setMachineName(String machineName); MachineLogEvent withMachineName(String machineName); /** Returns runtime identity. */ RuntimeIdentityDto getRuntimeId(); void setRuntimeId(RuntimeIdentityDto runtimeId); MachineLogEvent withRuntimeId(RuntimeIdentityDto runtimeId); /** Returns time in format '2017-06-27T17:11:09.306+03:00' */ String getTime(); void setTime(String time); MachineLogEvent withTime(String time); /** Returns standard streams, if present otherwise, null will be returned. */ String getStream(); void setStream(String stream); MachineLogEvent withStream(String stream); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/MachineStatusEvent.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.event; import org.eclipse.che.api.core.model.workspace.runtime.MachineStatus; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.dto.shared.DTO; /** * Describes event about status of machine * * @author Eugene Voevodin * @author Alexander Garagatyi */ // @EventOrigin("machine") @DTO public interface MachineStatusEvent { MachineStatus getEventType(); void setEventType(MachineStatus eventType); MachineStatusEvent withEventType(MachineStatus eventType); String getError(); void setError(String error); MachineStatusEvent withError(String error); String getMachineName(); MachineStatusEvent withMachineName(String machineName); /** * @return runtime identity */ RuntimeIdentityDto getIdentity(); MachineStatusEvent withIdentity(RuntimeIdentityDto identity); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/RuntimeLogEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.event; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.dto.shared.DTO; /** * Defines event format for runtime logs. * * @author Sergii Leshchenko */ @DTO public interface RuntimeLogEvent { /** Returns the contents of the log event. */ String getText(); void setText(String text); RuntimeLogEvent withText(String text); /** Returns runtime identity. */ RuntimeIdentityDto getRuntimeId(); void setRuntimeId(RuntimeIdentityDto runtimeId); RuntimeLogEvent withRuntimeId(RuntimeIdentityDto runtimeId); /** * Returns the name of the machine that produces the logs. * *

    May return null when log is produced by process which doesn't belong to any particular * machine. */ @Nullable String getMachineName(); void setMachineName(String machineName); RuntimeLogEvent withMachineName(String machineName); /** Returns time in format '2017-06-27T17:11:09.306+03:00' */ String getTime(); void setTime(String time); RuntimeLogEvent withTime(String time); /** Returns standard streams, if present otherwise, null will be returned. */ @Nullable String getStream(); void setStream(String stream); RuntimeLogEvent withStream(String stream); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/RuntimeStatusEvent.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.event; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.dto.shared.DTO; /** * Infrastructure specific status changes. * * @author gazarenkov */ @DTO public interface RuntimeStatusEvent { /** * @return new status */ String getStatus(); RuntimeStatusEvent withStatus(String status); /** * @return previous status */ String getPrevStatus(); RuntimeStatusEvent withPrevStatus(String status); /** * @return runtime identity */ RuntimeIdentityDto getIdentity(); RuntimeStatusEvent withIdentity(RuntimeIdentityDto identity); /** * Error message/log returned by infrastructure in case if it caused runtime failure Filled only * if failed == true */ @Nullable String getError(); RuntimeStatusEvent withError(String error); /** * @return whether Runtime is not workable anymore */ boolean isFailed(); RuntimeStatusEvent withFailed(boolean failed); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/ServerStatusEvent.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.event; import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus; import org.eclipse.che.api.workspace.shared.dto.RuntimeIdentityDto; import org.eclipse.che.dto.shared.DTO; /** * @author gazarenkov */ @DTO public interface ServerStatusEvent { ServerStatus getStatus(); ServerStatusEvent withStatus(ServerStatus status); String getServerName(); ServerStatusEvent withServerName(String serverName); String getServerUrl(); ServerStatusEvent withServerUrl(String serverUrl); String getMachineName(); ServerStatusEvent withMachineName(String machineName); /** * @return runtime identity */ RuntimeIdentityDto getIdentity(); ServerStatusEvent withIdentity(RuntimeIdentityDto identity); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/WorkspaceStatusEvent.java ================================================ /* * Copyright (c) 2012-2025 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.dto.event; import java.util.Map; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventOrigin; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.dto.shared.DTO; /** * Describes workspace status changes. * * @author Alexander Garagatyi * @author Yevhenii Voevodin */ @EventOrigin("workspace") @DTO public interface WorkspaceStatusEvent { WorkspaceStatus getStatus(); void setStatus(WorkspaceStatus status); WorkspaceStatusEvent withStatus(WorkspaceStatus status); /** * Returns previous workspace status. * * @see WorkspaceStatus for more information about certain values */ WorkspaceStatus getPrevStatus(); void setPrevStatus(WorkspaceStatus status); WorkspaceStatusEvent withPrevStatus(WorkspaceStatus status); /** The id of the workspace to which this event belongs to . */ String getWorkspaceId(); void setWorkspaceId(String machineId); WorkspaceStatusEvent withWorkspaceId(String machineId); /** Returns an error message value. */ @Nullable String getError(); void setError(String error); WorkspaceStatusEvent withError(String error); void setOptions(Map options); @Nullable Map getOptions(); WorkspaceStatusEvent withOptions(Map options); /** * @return whether event cause by some concrete user's request */ boolean isInitiatedByUser(); WorkspaceStatusEvent withInitiatedByUser(boolean isInitiatedByUser); } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/event/WorkspaceCreatedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.event; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.notification.EventOrigin; /** * Informs about workspace creation. * * @author Sergii Leschenko */ @EventOrigin("workspace") public class WorkspaceCreatedEvent { private final Workspace workspace; public WorkspaceCreatedEvent(Workspace workspace) { this.workspace = workspace; } public Workspace getWorkspace() { return workspace; } } ================================================ FILE: wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/event/WorkspaceRemovedEvent.java ================================================ /* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ package org.eclipse.che.api.workspace.shared.event; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.notification.EventOrigin; /** * Informs that workspace was removed. * * @author Sergii Leschenko * @author Alexander Andrienko */ @EventOrigin("workspace") public class WorkspaceRemovedEvent { private final Workspace workspace; public WorkspaceRemovedEvent(Workspace workspace) { this.workspace = workspace; } public Workspace getWorkspace() { return workspace; } } ================================================ FILE: wsmaster/che-core-sql-schema/pom.xml ================================================ 4.0.0 che-master-parent org.eclipse.che.core 7.118.0-SNAPSHOT che-core-sql-schema Che Core :: SQL :: Schema com.mycila license-maven-plugin **/*.sql ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.0.0-M8/1__init.sql ================================================ -- -- Copyright (c) 2012-2016 Codenvy, S.A. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Codenvy, S.A. - initial API and implementation -- -- Account --------------------------------------------------------------------- CREATE TABLE account ( id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(255), PRIMARY KEY (id) ); -- indexes CREATE UNIQUE INDEX index_account_name ON account (name); -------------------------------------------------------------------------------- -- User ------------------------------------------------------------------------ CREATE TABLE usr ( id VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, account_id VARCHAR(255) NOT NULL, password VARCHAR(255), PRIMARY KEY (id) ); -- indexes CREATE UNIQUE INDEX index_usr_email ON usr (email); -- constraints ALTER TABLE usr ADD CONSTRAINT fk_usr_account_id FOREIGN KEY (account_id) REFERENCES account (id); -------------------------------------------------------------------------------- -- User aliases ---------------------------------------------------------------- CREATE TABLE user_aliases ( user_id VARCHAR(255), alias VARCHAR(255) NOT NULL UNIQUE ); --indexes CREATE INDEX index_user_aliases_alias ON user_aliases (alias); --constraints ALTER TABLE user_aliases ADD CONSTRAINT fk_user_aliases_user_id FOREIGN KEY (user_id) REFERENCES usr (id); -------------------------------------------------------------------------------- -- Profile --------------------------------------------------------------------- CREATE TABLE profile ( userid VARCHAR(255) NOT NULL, PRIMARY KEY (userid) ); -- constraints ALTER TABLE profile ADD CONSTRAINT fk_profile_userid FOREIGN KEY (userid) REFERENCES usr (id); -------------------------------------------------------------------------------- -- Profile Attribute ----------------------------------------------------------- CREATE TABLE profile_attributes ( user_id VARCHAR(255), value_param VARCHAR(255) NOT NULL, name VARCHAR(255) ); -- constraints ALTER TABLE profile_attributes ADD CONSTRAINT unq_profile_attributes_0 UNIQUE (user_id, name); ALTER TABLE profile_attributes ADD CONSTRAINT fk_profile_attributes_user_id FOREIGN KEY (user_id) REFERENCES profile (userid); -------------------------------------------------------------------------------- -- Preferences ----------------------------------------------------------------- CREATE TABLE preference ( userid VARCHAR(255) NOT NULL, PRIMARY KEY (userid) ); -- constraints ALTER TABLE preference ADD CONSTRAINT fk_preference_userid FOREIGN KEY (userid) REFERENCES usr (id); -------------------------------------------------------------------------------- -- Preferences Preferences ----------------------------------------------------- CREATE TABLE preference_preferences ( preference_userid VARCHAR(255), value_param TEXT, name VARCHAR(255) ); -- constraints ALTER TABLE preference_preferences ADD CONSTRAINT fk_preference_preferences_preference_userid FOREIGN KEY (preference_userid) REFERENCES preference (userid); -------------------------------------------------------------------------------- -- SSH ------------------------------------------------------------------------- CREATE TABLE sshkeypair ( owner VARCHAR(255) NOT NULL, service VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, privatekey TEXT, publickey TEXT, PRIMARY KEY (owner, service, name) ); -- constraints ALTER TABLE sshkeypair ADD CONSTRAINT fk_sshkeypair_owner FOREIGN KEY (owner) REFERENCES usr (id); -------------------------------------------------------------------------------- -- Workspace configuration ----------------------------------------------------- CREATE TABLE workspaceconfig ( id BIGINT NOT NULL, defaultenv VARCHAR(255) NOT NULL, description TEXT, name VARCHAR(255) NOT NULL, PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Workspace ------------------------------------------------------------------- CREATE TABLE workspace ( id VARCHAR(255) NOT NULL, istemporary BOOLEAN, name VARCHAR(255), accountid VARCHAR(255) NOT NULL, config_id BIGINT, PRIMARY KEY (id) ); --constraints ALTER TABLE workspace ADD CONSTRAINT unq_workspace_0 UNIQUE (name, accountid); ALTER TABLE workspace ADD CONSTRAINT fx_workspace_accountid FOREIGN KEY (accountid) REFERENCES account (id); ALTER TABLE workspace ADD CONSTRAINT fk_workspace_config_id FOREIGN KEY (config_id) REFERENCES workspaceconfig (id); -------------------------------------------------------------------------------- --Workspace attributes --------------------------------------------------------- CREATE TABLE workspace_attributes ( workspace_id VARCHAR(255), attributes VARCHAR(255), attributes_key VARCHAR(255) ); --constraints ALTER TABLE workspace_attributes ADD CONSTRAINT fk_workspace_attributes_workspace_id FOREIGN KEY (workspace_id) REFERENCES workspace (id); -------------------------------------------------------------------------------- -- Project source -------------------------------------------------------------- CREATE TABLE sourcestorage ( id BIGINT NOT NULL, location TEXT, type VARCHAR(255), PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Project source parameters---------------------------------------------------- CREATE TABLE sourcestorage_parameters ( sourcestorage_id BIGINT, parameters VARCHAR(255), parameters_key VARCHAR(255) ); --constraints ALTER TABLE sourcestorage_parameters ADD CONSTRAINT fk_sourcestorage_parameters_sourcestorage_id FOREIGN KEY (sourcestorage_id) REFERENCES sourcestorage (id); -------------------------------------------------------------------------------- -- Project configuration ------------------------------------------------------- CREATE TABLE projectconfig ( id BIGINT NOT NULL, description TEXT, name VARCHAR(255), path VARCHAR(255) NOT NULL, type VARCHAR(255), source_id BIGINT, projects_id BIGINT, PRIMARY KEY (id) ); -- constraints ALTER TABLE projectconfig ADD CONSTRAINT fk_projectconfig_projects_id FOREIGN KEY (projects_id) REFERENCES workspaceconfig (id); ALTER TABLE projectconfig ADD CONSTRAINT fk_projectconfig_source_id FOREIGN KEY (source_id) REFERENCES sourcestorage (id); -------------------------------------------------------------------------------- -- Project attributes ---------------------------------------------------------- CREATE TABLE projectattribute ( id BIGINT NOT NULL, name VARCHAR(255), dbattributes_id BIGINT, PRIMARY KEY (id) ); --constraints ALTER TABLE projectattribute ADD CONSTRAINT fk_projectattribute_dbattributes_id FOREIGN KEY (dbattributes_id) REFERENCES projectconfig (id); -------------------------------------------------------------------------------- -- Project attribute values ---------------------------------------------------- CREATE TABLE projectattribute_values ( projectattribute_id BIGINT, values_param VARCHAR(255) ); --constraints ALTER TABLE projectattribute_values ADD CONSTRAINT fk_projectattribute_values_projectattribute_id FOREIGN KEY (projectattribute_id) REFERENCES projectattribute (id); -------------------------------------------------------------------------------- -- Project mixins -------------------------------------------------------------- CREATE TABLE projectconfig_mixins ( projectconfig_id BIGINT, mixins VARCHAR(255) ); --constraints ALTER TABLE projectconfig_mixins ADD CONSTRAINT fk_projectconfig_mixins_projectconfig_id FOREIGN KEY (projectconfig_id) REFERENCES projectconfig (id); -------------------------------------------------------------------------------- -- Commands -------------------------------------------------------------------- CREATE TABLE command ( id BIGINT NOT NULL, commandline TEXT, name VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL, commands_id BIGINT, PRIMARY KEY (id) ); --constraints ALTER TABLE command ADD CONSTRAINT fk_command_commands_id FOREIGN KEY (commands_id) REFERENCES workspaceconfig (id); -------------------------------------------------------------------------------- -- Command attributes ---------------------------------------------------------- CREATE TABLE command_attributes ( command_id BIGINT, value_param TEXT, name VARCHAR(255) ); --constraints ALTER TABLE command_attributes ADD CONSTRAINT fk_command_attributes_command_id FOREIGN KEY (command_id) REFERENCES command (id); -------------------------------------------------------------------------------- -- Workspace Environments ------------------------------------------------------ CREATE TABLE environment ( id BIGINT NOT NULL, content TEXT, contenttype VARCHAR(255), location TEXT, type VARCHAR(255), environments_id BIGINT, environments_key VARCHAR(255), PRIMARY KEY (id) ); --constraints ALTER TABLE environment ADD CONSTRAINT fk_environment_environments_id FOREIGN KEY (environments_id) REFERENCES workspaceconfig (id); -------------------------------------------------------------------------------- -- Environment machines -------------------------------------------------------- CREATE TABLE externalmachine ( id BIGINT NOT NULL, machines_id BIGINT, machines_key VARCHAR(255), PRIMARY KEY (id) ); --constraints ALTER TABLE externalmachine ADD CONSTRAINT fk_externalmachine_machines_id FOREIGN KEY (machines_id) REFERENCES environment (id); -------------------------------------------------------------------------------- -- Environment machine agents ------------------------------------------------- CREATE TABLE externalmachine_agents ( externalmachine_id BIGINT, agents VARCHAR(255) ); -- constraints ALTER TABLE externalmachine_agents ADD CONSTRAINT fk_externalmachine_agents_externalmachine_id FOREIGN KEY (externalmachine_id) REFERENCES externalmachine (id); -------------------------------------------------------------------------------- -- Environment machine attributes ---------------------------------------------- CREATE TABLE externalmachine_attributes ( externalmachine_id BIGINT, attributes VARCHAR(255), attributes_key VARCHAR(255) ); --constraints ALTER TABLE externalmachine_attributes ADD CONSTRAINT fk_externalmachine_attributes_externalmachine_id FOREIGN KEY (externalmachine_id) REFERENCES externalmachine (id); -------------------------------------------------------------------------------- -- Machine servers configuration ----------------------------------------------- CREATE TABLE serverconf ( id BIGINT NOT NULL, port VARCHAR(255), protocol VARCHAR(255), servers_id BIGINT, servers_key VARCHAR(255), PRIMARY KEY (id) ); --constraints ALTER TABLE serverconf ADD CONSTRAINT fk_serverconf_servers_id FOREIGN KEY (servers_id) REFERENCES externalmachine (id); -------------------------------------------------------------------------------- -- Machine server properties --------------------------------------------------- CREATE TABLE serverconf_properties ( serverconf_id BIGINT, properties VARCHAR(255), properties_key VARCHAR(255) ); --constraints ALTER TABLE serverconf_properties ADD CONSTRAINT fk_serverconf_properties_serverconf_id FOREIGN KEY (serverconf_id) REFERENCES serverconf (id); -------------------------------------------------------------------------------- -- Workspace snapshots --------------------------------------------------------- CREATE TABLE snapshot ( id VARCHAR(255) NOT NULL, creationdate BIGINT, description TEXT, envname VARCHAR(255) NOT NULL, isdev BOOLEAN, machinename VARCHAR(255) NOT NULL, type VARCHAR(255), workspaceid VARCHAR(255) NOT NULL, content TEXT, location VARCHAR(255), source_type VARCHAR(255), PRIMARY KEY (id) ); --indexes CREATE UNIQUE INDEX index_snapshot_workspaceid_envname_machinename ON snapshot (workspaceid, envname, machinename); --constraints ALTER TABLE snapshot ADD CONSTRAINT fk_snapshot_workspaceid FOREIGN KEY (workspaceid) REFERENCES workspace (id); -------------------------------------------------------------------------------- -- Recipe ---------------------------------------------------------------------- CREATE TABLE recipe ( id VARCHAR(255) NOT NULL, creator VARCHAR(255), description TEXT, name VARCHAR(255), script TEXT, type VARCHAR(255), PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Recipe tags ----------------------------------------------------------------- CREATE TABLE recipe_tags ( recipe_id VARCHAR(255), tag VARCHAR(255) ); --indexes CREATE INDEX index_recipe_tags_tag ON recipe_tags (tag); --constraints ALTER TABLE recipe_tags ADD CONSTRAINT fs_recipe_tags_recipe_id FOREIGN KEY (recipe_id) REFERENCES recipe (id); -------------------------------------------------------------------------------- -- Stack ----------------------------------------------------------------------- CREATE TABLE stack ( id VARCHAR(255) NOT NULL, creator VARCHAR(255), description TEXT, name VARCHAR(255) NOT NULL UNIQUE, scope VARCHAR(255), origin VARCHAR(255), type VARCHAR(255), data BYTEA, mediatype VARCHAR(255), icon_name VARCHAR(255), workspaceconfig_id BIGINT, PRIMARY KEY (id) ); --constraints ALTER TABLE stack ADD CONSTRAINT fk_stack_workspaceconfig_id FOREIGN KEY (workspaceconfig_id) REFERENCES workspaceconfig (id); -------------------------------------------------------------------------------- -- Stack components ------------------------------------------------------------ CREATE TABLE stack_components ( name VARCHAR(255), version VARCHAR(255), stack_id VARCHAR(255) ); --constraints ALTER TABLE stack_components ADD CONSTRAINT fk_stack_components_stack_id FOREIGN KEY (stack_id) REFERENCES stack (id); -------------------------------------------------------------------------------- -- Stack tags ------------------------------------------------------------------ CREATE TABLE stack_tags ( stack_id VARCHAR(255), tag VARCHAR(255) ); --indexes CREATE INDEX index_stack_tags_tag ON stack_tags (tag); --constraints ALTER TABLE stack_tags ADD CONSTRAINT fk_stack_tags_stack_id FOREIGN KEY (stack_id) REFERENCES stack (id); -------------------------------------------------------------------------------- -- Sequence table -------------------------------------------------------------- CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT NUMERIC(38), PRIMARY KEY (SEQ_NAME)); INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 0); -------------------------------------------------------------------------------- ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.0.0-M8/mysql/1__init.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Account --------------------------------------------------------------------- CREATE TABLE account ( id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(255), PRIMARY KEY (id) ); -- indexes CREATE UNIQUE INDEX index_account_name ON account (name); -------------------------------------------------------------------------------- -- User ------------------------------------------------------------------------ CREATE TABLE usr ( id VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, account_id VARCHAR(255) NOT NULL, password VARCHAR(255), PRIMARY KEY (id) ); -- indexes CREATE UNIQUE INDEX index_usr_email ON usr (email); -- constraints ALTER TABLE usr ADD CONSTRAINT fk_usr_account_id FOREIGN KEY (account_id) REFERENCES account (id); -------------------------------------------------------------------------------- -- User aliases ---------------------------------------------------------------- CREATE TABLE user_aliases ( user_id VARCHAR(255), alias VARCHAR(255) NOT NULL UNIQUE ); --indexes CREATE INDEX index_user_aliases_alias ON user_aliases (alias); --constraints ALTER TABLE user_aliases ADD CONSTRAINT fk_user_aliases_user_id FOREIGN KEY (user_id) REFERENCES usr (id); -------------------------------------------------------------------------------- -- Profile --------------------------------------------------------------------- CREATE TABLE profile ( userid VARCHAR(255) NOT NULL, PRIMARY KEY (userid) ); -- constraints ALTER TABLE profile ADD CONSTRAINT fk_profile_userid FOREIGN KEY (userid) REFERENCES usr (id); -------------------------------------------------------------------------------- -- Profile Attribute ----------------------------------------------------------- CREATE TABLE profile_attributes ( user_id VARCHAR(255), value_param VARCHAR(255) NOT NULL, name VARCHAR(255) ); -- constraints ALTER TABLE profile_attributes ADD CONSTRAINT unq_profile_attributes_0 UNIQUE (user_id, name); ALTER TABLE profile_attributes ADD CONSTRAINT fk_profile_attributes_user_id FOREIGN KEY (user_id) REFERENCES profile (userid); -------------------------------------------------------------------------------- -- Preferences ----------------------------------------------------------------- CREATE TABLE preference ( userid VARCHAR(255) NOT NULL, PRIMARY KEY (userid) ); -- constraints ALTER TABLE preference ADD CONSTRAINT fk_preference_userid FOREIGN KEY (userid) REFERENCES usr (id); -------------------------------------------------------------------------------- -- Preferences Preferences ----------------------------------------------------- CREATE TABLE preference_preferences ( preference_userid VARCHAR(255), value_param TEXT, name VARCHAR(255) ); -- constraints ALTER TABLE preference_preferences ADD CONSTRAINT fk_preference_preferences_preference_userid FOREIGN KEY (preference_userid) REFERENCES preference (userid); -------------------------------------------------------------------------------- -- SSH ------------------------------------------------------------------------- CREATE TABLE sshkeypair ( owner VARCHAR(255) NOT NULL, service VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, privatekey TEXT, publickey TEXT, PRIMARY KEY (owner, service, name) ); -- constraints ALTER TABLE sshkeypair ADD CONSTRAINT fk_sshkeypair_owner FOREIGN KEY (owner) REFERENCES usr (id); -------------------------------------------------------------------------------- -- Workspace configuration ----------------------------------------------------- CREATE TABLE workspaceconfig ( id BIGINT NOT NULL, defaultenv VARCHAR(255) NOT NULL, description TEXT, name VARCHAR(255) NOT NULL, PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Workspace ------------------------------------------------------------------- CREATE TABLE workspace ( id VARCHAR(255) NOT NULL, istemporary BOOLEAN, name VARCHAR(255), accountid VARCHAR(255) NOT NULL, config_id BIGINT, PRIMARY KEY (id) ); --constraints ALTER TABLE workspace ADD CONSTRAINT unq_workspace_0 UNIQUE (name, accountid); ALTER TABLE workspace ADD CONSTRAINT fx_workspace_accountid FOREIGN KEY (accountid) REFERENCES account (id); ALTER TABLE workspace ADD CONSTRAINT fk_workspace_config_id FOREIGN KEY (config_id) REFERENCES workspaceconfig (id); -------------------------------------------------------------------------------- --Workspace attributes --------------------------------------------------------- CREATE TABLE workspace_attributes ( workspace_id VARCHAR(255), attributes VARCHAR(255), attributes_key VARCHAR(255) ); --constraints ALTER TABLE workspace_attributes ADD CONSTRAINT fk_workspace_attributes_workspace_id FOREIGN KEY (workspace_id) REFERENCES workspace (id); -------------------------------------------------------------------------------- -- Project source -------------------------------------------------------------- CREATE TABLE sourcestorage ( id BIGINT NOT NULL, location TEXT, type VARCHAR(255), PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Project source parameters---------------------------------------------------- CREATE TABLE sourcestorage_parameters ( sourcestorage_id BIGINT, parameters VARCHAR(255), parameters_key VARCHAR(255) ); --constraints ALTER TABLE sourcestorage_parameters ADD CONSTRAINT fk_sourcestorage_parameters_sourcestorage_id FOREIGN KEY (sourcestorage_id) REFERENCES sourcestorage (id); -------------------------------------------------------------------------------- -- Project configuration ------------------------------------------------------- CREATE TABLE projectconfig ( id BIGINT NOT NULL, description TEXT, name VARCHAR(255), path VARCHAR(255) NOT NULL, type VARCHAR(255), source_id BIGINT, projects_id BIGINT, PRIMARY KEY (id) ); -- constraints ALTER TABLE projectconfig ADD CONSTRAINT fk_projectconfig_projects_id FOREIGN KEY (projects_id) REFERENCES workspaceconfig (id); ALTER TABLE projectconfig ADD CONSTRAINT fk_projectconfig_source_id FOREIGN KEY (source_id) REFERENCES sourcestorage (id); -------------------------------------------------------------------------------- -- Project attributes ---------------------------------------------------------- CREATE TABLE projectattribute ( id BIGINT NOT NULL, name VARCHAR(255), dbattributes_id BIGINT, PRIMARY KEY (id) ); --constraints ALTER TABLE projectattribute ADD CONSTRAINT fk_projectattribute_dbattributes_id FOREIGN KEY (dbattributes_id) REFERENCES projectconfig (id); -------------------------------------------------------------------------------- -- Project attribute values ---------------------------------------------------- CREATE TABLE projectattribute_values ( projectattribute_id BIGINT, `values_param` VARCHAR(255) ); --constraints ALTER TABLE projectattribute_values ADD CONSTRAINT fk_projectattribute_values_projectattribute_id FOREIGN KEY (projectattribute_id) REFERENCES projectattribute (id); -------------------------------------------------------------------------------- -- Project mixins -------------------------------------------------------------- CREATE TABLE projectconfig_mixins ( projectconfig_id BIGINT, mixins VARCHAR(255) ); --constraints ALTER TABLE projectconfig_mixins ADD CONSTRAINT fk_projectconfig_mixins_projectconfig_id FOREIGN KEY (projectconfig_id) REFERENCES projectconfig (id); -------------------------------------------------------------------------------- -- Commands -------------------------------------------------------------------- CREATE TABLE command ( id BIGINT NOT NULL, commandline TEXT, name VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL, commands_id BIGINT, PRIMARY KEY (id) ); --constraints ALTER TABLE command ADD CONSTRAINT fk_command_commands_id FOREIGN KEY (commands_id) REFERENCES workspaceconfig (id); -------------------------------------------------------------------------------- -- Command attributes ---------------------------------------------------------- CREATE TABLE command_attributes ( command_id BIGINT, value_param TEXT, name VARCHAR(255) ); --constraints ALTER TABLE command_attributes ADD CONSTRAINT fk_command_attributes_command_id FOREIGN KEY (command_id) REFERENCES command (id); -------------------------------------------------------------------------------- -- Workspace Environments ------------------------------------------------------ CREATE TABLE environment ( id BIGINT NOT NULL, content TEXT, contenttype VARCHAR(255), location TEXT, type VARCHAR(255), environments_id BIGINT, environments_key VARCHAR(255), PRIMARY KEY (id) ); --constraints ALTER TABLE environment ADD CONSTRAINT fk_environment_environments_id FOREIGN KEY (environments_id) REFERENCES workspaceconfig (id); -------------------------------------------------------------------------------- -- Environment machines -------------------------------------------------------- CREATE TABLE externalmachine ( id BIGINT NOT NULL, machines_id BIGINT, machines_key VARCHAR(255), PRIMARY KEY (id) ); --constraints ALTER TABLE externalmachine ADD CONSTRAINT fk_externalmachine_machines_id FOREIGN KEY (machines_id) REFERENCES environment (id); -------------------------------------------------------------------------------- -- Environment machine agents ------------------------------------------------- CREATE TABLE externalmachine_agents ( externalmachine_id BIGINT, agents VARCHAR(255) ); -- constraints ALTER TABLE externalmachine_agents ADD CONSTRAINT fk_externalmachine_agents_externalmachine_id FOREIGN KEY (externalmachine_id) REFERENCES externalmachine (id); -------------------------------------------------------------------------------- -- Environment machine attributes ---------------------------------------------- CREATE TABLE externalmachine_attributes ( externalmachine_id BIGINT, attributes VARCHAR(255), attributes_key VARCHAR(255) ); --constraints ALTER TABLE externalmachine_attributes ADD CONSTRAINT fk_externalmachine_attributes_externalmachine_id FOREIGN KEY (externalmachine_id) REFERENCES externalmachine (id); -------------------------------------------------------------------------------- -- Machine servers configuration ----------------------------------------------- CREATE TABLE serverconf ( id BIGINT NOT NULL, port VARCHAR(255), protocol VARCHAR(255), servers_id BIGINT, servers_key VARCHAR(255), PRIMARY KEY (id) ); --constraints ALTER TABLE serverconf ADD CONSTRAINT fk_serverconf_servers_id FOREIGN KEY (servers_id) REFERENCES externalmachine (id); -------------------------------------------------------------------------------- -- Machine server properties --------------------------------------------------- CREATE TABLE serverconf_properties ( serverconf_id BIGINT, properties VARCHAR(255), properties_key VARCHAR(255) ); --constraints ALTER TABLE serverconf_properties ADD CONSTRAINT fk_serverconf_properties_serverconf_id FOREIGN KEY (serverconf_id) REFERENCES serverconf (id); -------------------------------------------------------------------------------- -- Workspace snapshots --------------------------------------------------------- CREATE TABLE snapshot ( id VARCHAR(255) NOT NULL, creationdate BIGINT, description TEXT, envname VARCHAR(255) NOT NULL, isdev BOOLEAN, machinename VARCHAR(255) NOT NULL, type VARCHAR(255), workspaceid VARCHAR(255) NOT NULL, content TEXT, location VARCHAR(255), source_type VARCHAR(255), PRIMARY KEY (id) ); --indexes CREATE UNIQUE INDEX index_snapshot_workspaceid_envname_machinename ON snapshot (workspaceid, envname, machinename); --constraints ALTER TABLE snapshot ADD CONSTRAINT fk_snapshot_workspaceid FOREIGN KEY (workspaceid) REFERENCES workspace (id); -------------------------------------------------------------------------------- -- Recipe ---------------------------------------------------------------------- CREATE TABLE recipe ( id VARCHAR(255) NOT NULL, creator VARCHAR(255), description TEXT, name VARCHAR(255), script TEXT, type VARCHAR(255), PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Recipe tags ----------------------------------------------------------------- CREATE TABLE recipe_tags ( recipe_id VARCHAR(255), tag VARCHAR(255) ); --indexes CREATE INDEX index_recipe_tags_tag ON recipe_tags (tag); --constraints ALTER TABLE recipe_tags ADD CONSTRAINT fs_recipe_tags_recipe_id FOREIGN KEY (recipe_id) REFERENCES recipe (id); -------------------------------------------------------------------------------- -- Stack ----------------------------------------------------------------------- CREATE TABLE stack ( id VARCHAR(255) NOT NULL, creator VARCHAR(255), description TEXT, name VARCHAR(255) NOT NULL UNIQUE, scope VARCHAR(255), origin VARCHAR(255), type VARCHAR(255), data BLOB, mediatype VARCHAR(255), icon_name VARCHAR(255), workspaceconfig_id BIGINT, PRIMARY KEY (id) ); --constraints ALTER TABLE stack ADD CONSTRAINT fk_stack_workspaceconfig_id FOREIGN KEY (workspaceconfig_id) REFERENCES workspaceconfig (id); -------------------------------------------------------------------------------- -- Stack components ------------------------------------------------------------ CREATE TABLE stack_components ( name VARCHAR(255), version VARCHAR(255), stack_id VARCHAR(255) ); --constraints ALTER TABLE stack_components ADD CONSTRAINT fk_stack_components_stack_id FOREIGN KEY (stack_id) REFERENCES stack (id); -------------------------------------------------------------------------------- -- Stack tags ------------------------------------------------------------------ CREATE TABLE stack_tags ( stack_id VARCHAR(255), tag VARCHAR(255) ); --indexes CREATE INDEX index_stack_tags_tag ON stack_tags (tag); --constraints ALTER TABLE stack_tags ADD CONSTRAINT fk_stack_tags_stack_id FOREIGN KEY (stack_id) REFERENCES stack (id); -------------------------------------------------------------------------------- -- Sequence table -------------------------------------------------------------- CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT NUMERIC(38), PRIMARY KEY (SEQ_NAME)); INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 0); -------------------------------------------------------------------------------- ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.0.0-M9/1__add_index_on_workspace_temporary.sql ================================================ -- -- Copyright (c) 2012-2016 Codenvy, S.A. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Codenvy, S.A. - initial API and implementation -- --indexes CREATE INDEX index_workspace_istemporary ON workspace (istemporary); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.0.0-M9/2__update_local_links_in_environments.sql ================================================ -- -- Copyright (c) 2012-2016 Codenvy, S.A. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Codenvy, S.A. - initial API and implementation -- UPDATE environment SET location=regexp_replace(location, '${che.api}', ''); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.0.0-M9/mysql/2__update_local_links_in_environments.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- UPDATE environment SET location=REPLACE(location, '${che.api}', ''); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.11.0/1__optimize_user_search.sql ================================================ -- -- [2012] - [2017] Codenvy, S.A. -- All Rights Reserved. -- -- NOTICE: All information contained herein is, and remains -- the property of Codenvy S.A. and its suppliers, -- if any. The intellectual and technical concepts contained -- herein are proprietary to Codenvy S.A. -- and its suppliers and may be covered by U.S. and Foreign Patents, -- patents in process, and are protected by trade secret or copyright law. -- Dissemination of this information or reproduction of this material -- is strictly forbidden unless prior written permission is obtained -- from Codenvy S.A.. -- ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.11.0/postgresql/1__optimize_user_search.sql ================================================ -- -- [2012] - [2017] Codenvy, S.A. -- All Rights Reserved. -- -- NOTICE: All information contained herein is, and remains -- the property of Codenvy S.A. and its suppliers, -- if any. The intellectual and technical concepts contained -- herein are proprietary to Codenvy S.A. -- and its suppliers and may be covered by U.S. and Foreign Patents, -- patents in process, and are protected by trade secret or copyright law. -- Dissemination of this information or reproduction of this material -- is strictly forbidden unless prior written permission is obtained -- from Codenvy S.A.. -- CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE INDEX index_user_lower_email ON usr USING GIN (LOWER(email) gin_trgm_ops); CREATE INDEX index_user_lower_name ON usr USING GIN (LOWER(name) gin_trgm_ops); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.2.0/1__increase_project_attributes_values_length.sql ================================================ -- -- Copyright (c) 2012-2017 Codenvy, S.A. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Codenvy, S.A. - initial API and implementation -- ALTER TABLE projectattribute_values ALTER COLUMN values_param TYPE TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.2.0/mysql/1__increase_project_attributes_values_length.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE projectattribute_values MODIFY COLUMN `values_param` MEDIUMTEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.4.0/1__drop_user_to_account_relation.sql ================================================ -- -- Copyright (c) 2012-2017 Codenvy, S.A. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Codenvy, S.A. - initial API and implementation -- ALTER TABLE usr ADD name VARCHAR(255); UPDATE usr SET name = ( SELECT name FROM account WHERE id = usr.account_id ); ALTER TABLE usr ALTER COLUMN name SET NOT NULL; CREATE UNIQUE INDEX index_user_name ON usr (name); ALTER TABLE usr DROP COLUMN account_id; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.4.0/2__create_missed_account_indexes.sql ================================================ -- -- Copyright (c) 2012-2017 Codenvy, S.A. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Codenvy, S.A. - initial API and implementation -- CREATE INDEX index_account_type ON account (type); CREATE INDEX index_workspace_accountid ON workspace (accountid); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.4.0/mysql/1__drop_user_to_account_relation.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE usr ADD name VARCHAR(255); UPDATE usr SET name = ( SELECT name FROM account WHERE id = usr.account_id ); ALTER TABLE usr MODIFY name VARCHAR(255) NOT NULL; CREATE UNIQUE INDEX index_user_name ON usr (name); ALTER TABLE usr DROP FOREIGN KEY fk_usr_account_id; ALTER TABLE usr DROP COLUMN account_id; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.6.0/1__add_exec_agent_where_terminal_agent_is_present.sql ================================================ -- -- Copyright (c) 2012-2017 Codenvy, S.A. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Codenvy, S.A. - initial API and implementation -- INSERT INTO externalmachine_agents (externalmachine_id, agents) SELECT externalmachine_id, 'org.eclipse.che.exec' FROM externalmachine_agents WHERE agents = 'org.eclipse.che.terminal' ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.7.0/1__add_factory.sql ================================================ -- -- Copyright (c) 2012-2017 Codenvy, S.A. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Codenvy, S.A. - initial API and implementation -- -- Factory button -------------------------------------------------------------- CREATE TABLE che_factory_button ( id BIGINT NOT NULL, type VARCHAR(255), color VARCHAR(255), counter BOOLEAN, logo VARCHAR(255), style VARCHAR(255), PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Factory action -------------------------------------------------------------- CREATE TABLE che_factory_action ( entity_id BIGINT NOT NULL, id VARCHAR(255), PRIMARY KEY (entity_id) ); -------------------------------------------------------------------------------- -- Factory action properties --------------------------------------------------- CREATE TABLE che_factory_action_properties ( action_entity_id BIGINT NOT NULL, property_value VARCHAR(255), property_key VARCHAR(255) ); -- constraints ALTER TABLE che_factory_action_properties ADD CONSTRAINT fk_che_f_action_props_action_entity_id FOREIGN KEY (action_entity_id) REFERENCES che_factory_action (entity_id); CREATE INDEX che_f_action_entity_id_fk ON che_factory_action_properties (action_entity_id); -------------------------------------------------------------------------------- -- Factory on app closed action ------------------------------------------------ CREATE TABLE che_factory_on_app_closed_action ( id BIGINT NOT NULL, PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Factory on projects loaded action ------------------------------------------- CREATE TABLE che_factory_on_projects_loaded_action ( id BIGINT NOT NULL, PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Factory on app loaded action ------------------------------------------------ CREATE TABLE che_factory_on_app_loaded_action ( id BIGINT NOT NULL, PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Factory on app closed action value ------------------------------------------ CREATE TABLE che_factory_on_app_closed_action_value ( on_app_closed_id BIGINT NOT NULL, action_entity_id BIGINT NOT NULL, PRIMARY KEY (on_app_closed_id, action_entity_id) ); -- constraints ALTER TABLE che_factory_on_app_closed_action_value ADD CONSTRAINT fk_che_f_on_app_closed_entity_id FOREIGN KEY (action_entity_id) REFERENCES che_factory_action (entity_id); ALTER TABLE che_factory_on_app_closed_action_value ADD CONSTRAINT fk_che_f_on_app_closed_action_id FOREIGN KEY (on_app_closed_id) REFERENCES che_factory_on_app_closed_action (id); -------------------------------------------------------------------------------- -- Factory on project loaded action -------------------------------------------- CREATE TABLE che_factory_on_projects_loaded_action_value ( on_projects_loaded_id BIGINT NOT NULL, action_entity_id BIGINT NOT NULL, PRIMARY KEY (on_projects_loaded_id, action_entity_id) ); -- constraints ALTER TABLE che_factory_on_projects_loaded_action_value ADD CONSTRAINT fk_che_f_on_projects_loaded_entity_id FOREIGN KEY (action_entity_id) REFERENCES che_factory_action (entity_id); ALTER TABLE che_factory_on_projects_loaded_action_value ADD CONSTRAINT fk_che_f_on_projects_loaded_action_id FOREIGN KEY (on_projects_loaded_id) REFERENCES che_factory_on_projects_loaded_action (id); -------------------------------------------------------------------------------- -- Factory on app loaded action ------------------------------------------------ CREATE TABLE che_factory_on_app_loaded_action_value ( on_app_loaded_id BIGINT NOT NULL, action_entity_id BIGINT NOT NULL, PRIMARY KEY (on_app_loaded_id, action_entity_id) ); -- constraints ALTER TABLE che_factory_on_app_loaded_action_value ADD CONSTRAINT fk_che_f_on_app_loaded_entity_id FOREIGN KEY (action_entity_id) REFERENCES che_factory_action (entity_id); ALTER TABLE che_factory_on_app_loaded_action_value ADD CONSTRAINT fk_che_f_on_app_loaded_action_id FOREIGN KEY (on_app_loaded_id) REFERENCES che_factory_on_app_loaded_action (id); -------------------------------------------------------------------------------- -- Factory ide ----------------------------------------------------------------- CREATE TABLE che_factory_ide ( id BIGINT NOT NULL, on_app_closed_id BIGINT, on_app_loaded_id BIGINT, on_projects_loaded_id BIGINT, PRIMARY KEY (id) ); -- constraints ALTER TABLE che_factory_ide ADD CONSTRAINT fk_che_f_ide_on_app_closed_id FOREIGN KEY (on_app_closed_id) REFERENCES che_factory_on_app_closed_action (id); ALTER TABLE che_factory_ide ADD CONSTRAINT fk_che_f_ide_on_projects_loaded_id FOREIGN KEY (on_projects_loaded_id) REFERENCES che_factory_on_projects_loaded_action (id); ALTER TABLE che_factory_ide ADD CONSTRAINT fk_che_f_ide_on_app_loaded_id FOREIGN KEY (on_app_loaded_id) REFERENCES che_factory_on_app_loaded_action (id); -------------------------------------------------------------------------------- -- Factory --------------------------------------------------------------------- CREATE TABLE che_factory ( id VARCHAR(255) NOT NULL, name VARCHAR(255), version VARCHAR(255) NOT NULL, created BIGINT, user_id VARCHAR(255), creation_strategy VARCHAR(255), match_reopen VARCHAR(255), referrer VARCHAR(255), since BIGINT, until BIGINT, button_id BIGINT, ide_id BIGINT, workspace_id BIGINT, PRIMARY KEY (id) ); -- constraints ALTER TABLE che_factory ADD CONSTRAINT fk_che_f_user_id FOREIGN KEY (user_id) REFERENCES usr (id); ALTER TABLE che_factory ADD CONSTRAINT fk_che_f_workspace_id FOREIGN KEY (workspace_id) REFERENCES workspaceconfig (id); ALTER TABLE che_factory ADD CONSTRAINT fk_che_f_button_id FOREIGN KEY (button_id) REFERENCES che_factory_button (id); ALTER TABLE che_factory ADD CONSTRAINT fk_che_f_ide_id FOREIGN KEY (ide_id) REFERENCES che_factory_ide (id); CREATE UNIQUE INDEX index_che_factory_name_user_id ON che_factory (user_id, name); -------------------------------------------------------------------------------- -- Factory Images -------------------------------------------------------------- CREATE TABLE che_factory_image ( image_data BYTEA, media_type VARCHAR(255), name VARCHAR(255), factory_id VARCHAR(255) NOT NULL ); -- constraints ALTER TABLE che_factory_image ADD CONSTRAINT fk_che_factory_image_factory_id FOREIGN KEY (factory_id) REFERENCES che_factory (id); -------------------------------------------------------------------------------- ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.7.0/2__remove_match_policy.sql ================================================ -- -- [2012] - [2017] Codenvy, S.A. -- All Rights Reserved. -- -- NOTICE: All information contained herein is, and remains -- the property of Codenvy S.A. and its suppliers, -- if any. The intellectual and technical concepts contained -- herein are proprietary to Codenvy S.A. -- and its suppliers and may be covered by U.S. and Foreign Patents, -- patents in process, and are protected by trade secret or copyright law. -- Dissemination of this information or reproduction of this material -- is strictly forbidden unless prior written permission is obtained -- from Codenvy S.A.. -- ALTER TABLE che_factory DROP COLUMN match_reopen; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.7.0/mysql/1__add_factory.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Factory button -------------------------------------------------------------- CREATE TABLE che_factory_button ( id BIGINT NOT NULL, type VARCHAR(255), color VARCHAR(255), counter BOOLEAN, logo VARCHAR(255), style VARCHAR(255), PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Factory action -------------------------------------------------------------- CREATE TABLE che_factory_action ( entity_id BIGINT NOT NULL, id VARCHAR(255), PRIMARY KEY (entity_id) ); -------------------------------------------------------------------------------- -- Factory action properties --------------------------------------------------- CREATE TABLE che_factory_action_properties ( action_entity_id BIGINT NOT NULL, property_value VARCHAR(255), property_key VARCHAR(255) ); -- constraints ALTER TABLE che_factory_action_properties ADD CONSTRAINT fk_che_f_action_props_action_entity_id FOREIGN KEY (action_entity_id) REFERENCES che_factory_action (entity_id); CREATE INDEX che_f_action_entity_id_fk ON che_factory_action_properties (action_entity_id); -------------------------------------------------------------------------------- -- Factory on app closed action ------------------------------------------------ CREATE TABLE che_factory_on_app_closed_action ( id BIGINT NOT NULL, PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Factory on projects loaded action ------------------------------------------- CREATE TABLE che_factory_on_projects_loaded_action ( id BIGINT NOT NULL, PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Factory on app loaded action ------------------------------------------------ CREATE TABLE che_factory_on_app_loaded_action ( id BIGINT NOT NULL, PRIMARY KEY (id) ); -------------------------------------------------------------------------------- -- Factory on app closed action value ------------------------------------------ CREATE TABLE che_factory_on_app_closed_action_value ( on_app_closed_id BIGINT NOT NULL, action_entity_id BIGINT NOT NULL, PRIMARY KEY (on_app_closed_id, action_entity_id) ); -- constraints ALTER TABLE che_factory_on_app_closed_action_value ADD CONSTRAINT fk_che_f_on_app_closed_entity_id FOREIGN KEY (action_entity_id) REFERENCES che_factory_action (entity_id); ALTER TABLE che_factory_on_app_closed_action_value ADD CONSTRAINT fk_che_f_on_app_closed_action_id FOREIGN KEY (on_app_closed_id) REFERENCES che_factory_on_app_closed_action (id); -------------------------------------------------------------------------------- -- Factory on project loaded action -------------------------------------------- CREATE TABLE che_factory_on_projects_loaded_action_value ( on_projects_loaded_id BIGINT NOT NULL, action_entity_id BIGINT NOT NULL, PRIMARY KEY (on_projects_loaded_id, action_entity_id) ); -- constraints ALTER TABLE che_factory_on_projects_loaded_action_value ADD CONSTRAINT fk_che_f_on_projects_loaded_entity_id FOREIGN KEY (action_entity_id) REFERENCES che_factory_action (entity_id); ALTER TABLE che_factory_on_projects_loaded_action_value ADD CONSTRAINT fk_che_f_on_projects_loaded_action_id FOREIGN KEY (on_projects_loaded_id) REFERENCES che_factory_on_projects_loaded_action (id); -------------------------------------------------------------------------------- -- Factory on app loaded action ------------------------------------------------ CREATE TABLE che_factory_on_app_loaded_action_value ( on_app_loaded_id BIGINT NOT NULL, action_entity_id BIGINT NOT NULL, PRIMARY KEY (on_app_loaded_id, action_entity_id) ); -- constraints ALTER TABLE che_factory_on_app_loaded_action_value ADD CONSTRAINT fk_che_f_on_app_loaded_entity_id FOREIGN KEY (action_entity_id) REFERENCES che_factory_action (entity_id); ALTER TABLE che_factory_on_app_loaded_action_value ADD CONSTRAINT fk_che_f_on_app_loaded_action_id FOREIGN KEY (on_app_loaded_id) REFERENCES che_factory_on_app_loaded_action (id); -------------------------------------------------------------------------------- -- Factory ide ----------------------------------------------------------------- CREATE TABLE che_factory_ide ( id BIGINT NOT NULL, on_app_closed_id BIGINT, on_app_loaded_id BIGINT, on_projects_loaded_id BIGINT, PRIMARY KEY (id) ); -- constraints ALTER TABLE che_factory_ide ADD CONSTRAINT fk_che_f_ide_on_app_closed_id FOREIGN KEY (on_app_closed_id) REFERENCES che_factory_on_app_closed_action (id); ALTER TABLE che_factory_ide ADD CONSTRAINT fk_che_f_ide_on_projects_loaded_id FOREIGN KEY (on_projects_loaded_id) REFERENCES che_factory_on_projects_loaded_action (id); ALTER TABLE che_factory_ide ADD CONSTRAINT fk_che_f_ide_on_app_loaded_id FOREIGN KEY (on_app_loaded_id) REFERENCES che_factory_on_app_loaded_action (id); -------------------------------------------------------------------------------- -- Factory --------------------------------------------------------------------- CREATE TABLE che_factory ( id VARCHAR(255) NOT NULL, name VARCHAR(255), version VARCHAR(255) NOT NULL, created BIGINT, user_id VARCHAR(255), creation_strategy VARCHAR(255), match_reopen VARCHAR(255), referrer VARCHAR(255), since BIGINT, until BIGINT, button_id BIGINT, ide_id BIGINT, workspace_id BIGINT, PRIMARY KEY (id) ); -- constraints ALTER TABLE che_factory ADD CONSTRAINT fk_che_f_user_id FOREIGN KEY (user_id) REFERENCES usr (id); ALTER TABLE che_factory ADD CONSTRAINT fk_che_f_workspace_id FOREIGN KEY (workspace_id) REFERENCES workspaceconfig (id); ALTER TABLE che_factory ADD CONSTRAINT fk_che_f_button_id FOREIGN KEY (button_id) REFERENCES che_factory_button (id); ALTER TABLE che_factory ADD CONSTRAINT fk_che_f_ide_id FOREIGN KEY (ide_id) REFERENCES che_factory_ide (id); CREATE UNIQUE INDEX index_che_factory_name_user_id ON che_factory (user_id, name); -------------------------------------------------------------------------------- -- Factory Images -------------------------------------------------------------- CREATE TABLE che_factory_image ( image_data BLOB, media_type VARCHAR(255), name VARCHAR(255), factory_id VARCHAR(255) NOT NULL ); -- constraints ALTER TABLE che_factory_image ADD CONSTRAINT fk_che_factory_image_factory_id FOREIGN KEY (factory_id) REFERENCES che_factory (id); -------------------------------------------------------------------------------- ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/5.8.0/1__add_foreigh_key_indexes.sql ================================================ -- -- [2012] - [2017] Codenvy, S.A. -- All Rights Reserved. -- -- NOTICE: All information contained herein is, and remains -- the property of Codenvy S.A. and its suppliers, -- if any. The intellectual and technical concepts contained -- herein are proprietary to Codenvy S.A. -- and its suppliers and may be covered by U.S. and Foreign Patents, -- patents in process, and are protected by trade secret or copyright law. -- Dissemination of this information or reproduction of this material -- is strictly forbidden unless prior written permission is obtained -- from Codenvy S.A.. -- CREATE INDEX index_command_attributes_commandid ON command_attributes (command_id); CREATE INDEX index_command_commandsid ON command (commands_id); CREATE INDEX index_factory_action_properties_entityid ON che_factory_action_properties (action_entity_id); CREATE INDEX index_factory_buttonid ON che_factory (button_id); CREATE INDEX index_factory_ideid ON che_factory (ide_id); CREATE INDEX index_factory_userid ON che_factory (user_id); CREATE INDEX index_factory_workspaceid ON che_factory (workspace_id); CREATE INDEX index_factory_images_factoryid ON che_factory_image (factory_id); CREATE INDEX index_ide_onappclosedid ON che_factory_ide (on_app_closed_id); CREATE INDEX index_ide_onapploadedid ON che_factory_ide (on_app_loaded_id); CREATE INDEX index_ide_onprojectsloadedid ON che_factory_ide (on_projects_loaded_id); CREATE INDEX index_environment_environmentsid ON environment (environments_id); CREATE INDEX index_externalmachine_agents_externalmachineid ON externalmachine_agents (externalmachine_id); CREATE INDEX index_externalmachine_attributes_externalmachineid ON externalmachine_attributes (externalmachine_id); CREATE INDEX index_externalmachine_machinesid ON externalmachine (machines_id); CREATE INDEX index_preference_preferences_userid ON preference_preferences (preference_userid); CREATE INDEX index_profile_attributes_userid ON profile_attributes (user_id); CREATE INDEX index_projectattribute_dbattributesid ON projectattribute (dbattributes_id); CREATE INDEX index_projectattribute_values_projectattributeid ON projectattribute_values (projectattribute_id); CREATE INDEX index_projectconfig_mixins_projectconfigid ON projectconfig_mixins (projectconfig_id); CREATE INDEX index_projectconfig_projectsid ON projectconfig (projects_id); CREATE INDEX index_projectconfig_sourceid ON projectconfig (source_id); CREATE INDEX index_recipe_tags_recipeid ON recipe_tags (recipe_id); CREATE INDEX index_serverconf_properties_serverconfid ON serverconf_properties (serverconf_id); CREATE INDEX index_serverconf_serversid ON serverconf (servers_id); CREATE INDEX index_snapshot_workspaceid ON snapshot (workspaceid); CREATE INDEX index_sourcestorage_parameters_sourcestorageid ON sourcestorage_parameters (sourcestorage_id); CREATE INDEX index_stack_components_stackid ON stack_components (stack_id); CREATE INDEX index_stack_tags_stackid ON stack_tags (stack_id); CREATE INDEX index_stack_workspaceconfigid ON stack (workspaceconfig_id); CREATE INDEX index_user_aliases_userid ON user_aliases (user_id); CREATE INDEX index_workspace_configid ON workspace (config_id); CREATE INDEX index_workspace_attributes_workspaceid ON workspace_attributes (workspace_id); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/10__move_dockerimage_recipe_location_to_content.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- --Moving location of 'dockerimage' type recipes into content UPDATE environment SET content=location, location = null WHERE type = 'dockerimage' ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/11__increase_workspace_attributes_values_length.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE workspace_attributes ALTER COLUMN attributes TYPE TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/12__remove_stack_sources.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE stack DROP COLUMN type; ALTER TABLE stack DROP COLUMN origin; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/1__add_path_to_serverconf.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE serverconf ADD path VARCHAR(255); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/2__rename_agents_to_installers.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Environment machine installers ------------------------------------------------- CREATE TABLE externalmachine_installers ( externalmachine_id BIGINT, installers VARCHAR(255) ); -- constraints ALTER TABLE externalmachine_installers ADD CONSTRAINT fk_externalmachine_installers_externalmachine_id FOREIGN KEY (externalmachine_id) REFERENCES externalmachine (id); INSERT INTO externalmachine_installers SELECT * FROM externalmachine_agents; DROP TABLE externalmachine_agents; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/3__add_installer.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Installer --------------------------------------------------------------------- CREATE TABLE installer ( internal_id BIGINT NOT NULL, id VARCHAR(255) NOT NULL, name VARCHAR(255), version VARCHAR(255) NOT NULL, description VARCHAR(255), script TEXT, PRIMARY KEY (internal_id) ); --constraints ALTER TABLE installer ADD CONSTRAINT unq_installer_key UNIQUE (id, version); ---------------------------------------------------------------------------------------- -- Installer dependencies ---------------------------------------------------------------- CREATE TABLE installer_dependencies ( inst_int_id BIGINT NOT NULL, dependency VARCHAR(255) NOT NULL ); --constraints ALTER TABLE installer_dependencies ADD CONSTRAINT fk_installer_dependencies_inst_int_id FOREIGN KEY (inst_int_id) REFERENCES installer (internal_id); ALTER TABLE installer_dependencies ADD CONSTRAINT unq_installer_dependency UNIQUE (inst_int_id, dependency); ---------------------------------------------------------------------------------------- -- Installer properties ---------------------------------------------------------------- CREATE TABLE installer_properties ( inst_int_id BIGINT NOT NULL, name VARCHAR(255) NOT NULL, value_param VARCHAR(255) NOT NULL ); --constraints ALTER TABLE installer_properties ADD CONSTRAINT fk_installer_properties_inst_int_id FOREIGN KEY (inst_int_id) REFERENCES installer (internal_id); ALTER TABLE installer_properties ADD CONSTRAINT unq_installer_property UNIQUE (inst_int_id, name); ---------------------------------------------------------------------------------------- -- Installer properties ---------------------------------------------------------------- CREATE TABLE installer_servers ( id BIGINT, inst_int_id BIGINT, server_key VARCHAR(255), port VARCHAR(255), protocol VARCHAR(255), path VARCHAR(255), PRIMARY KEY (id) ); --constraints ALTER TABLE installer_servers ADD CONSTRAINT fk_installer_servers_inst_int_id FOREIGN KEY (inst_int_id) REFERENCES installer (internal_id); ALTER TABLE installer_servers ADD CONSTRAINT unq_installer_server UNIQUE (inst_int_id, server_key); ---------------------------------------------------------------------------------------- ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/4__remove_old_recipe.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- DROP TABLE recipe_tags; DROP TABLE recipe; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/5__add_machine_env.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Machine env ---------------------------------------------- CREATE TABLE externalmachine_env ( externalmachine_id BIGINT, env_value VARCHAR(255), env_key VARCHAR(255) ); --constraints ALTER TABLE externalmachine_env ADD CONSTRAINT fk_externalmachine_env_externalmachine_id FOREIGN KEY (externalmachine_id) REFERENCES externalmachine (id); --indexes CREATE INDEX index_externalmachine_env_externalmachine_id ON externalmachine_env (externalmachine_id); -------------------------------------------------------------------------------- ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/6__remove_snapshots.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- DROP TABLE snapshot; DELETE FROM workspace_attributes WHERE attributes_key = 'snapshotted_at'; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/7__add_machine_volumes.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Machine volumes configuration ----------------------------------------------- CREATE TABLE machine_volume ( id BIGINT NOT NULL, name VARCHAR(255), path VARCHAR(255) NOT NULL, machine_id BIGINT, PRIMARY KEY (id) ); --constraints ALTER TABLE machine_volume ADD CONSTRAINT fk_machine_volume_id FOREIGN KEY (machine_id) REFERENCES externalmachine (id); -------------------------------------------------------------------------------- --indexes CREATE INDEX index_machine_volume_machine_id ON machine_volume (machine_id); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/8__add_serverconf_attributes.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- ServerConfig attributes ---------------------------------------------- CREATE TABLE serverconf_attributes ( serverconf_id BIGINT, attributes VARCHAR(255), attributes_key VARCHAR(255) ); --constraints ALTER TABLE serverconf_attributes ADD CONSTRAINT fk_serverconf_attributes_serverconf_id FOREIGN KEY (serverconf_id) REFERENCES serverconf (id); --indexes CREATE INDEX index_serverconf_attributes_serverconf_id ON serverconf_attributes (serverconf_id); ------------------------------------------------------------------------- -- InstallerServerConfig attributes ---------------------------------------------- CREATE TABLE installer_serverconf_attributes ( serverconf_id BIGINT, attributes VARCHAR(255), attributes_key VARCHAR(255) ); --constraints ALTER TABLE installer_serverconf_attributes ADD CONSTRAINT fk_installer_serverconf_attributes_serverconf_id FOREIGN KEY (serverconf_id) REFERENCES installer_servers (id); --indexes CREATE INDEX index_installer_serverconf_attributes_serverconf_id ON installer_serverconf_attributes (serverconf_id); ------------------------------------------------------------------------- ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/9__increase_externalmachine_env_value_length.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE externalmachine_env ALTER COLUMN env_value TYPE TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/mysql/11__increase_workspace_attributes_values_length.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE workspace_attributes MODIFY COLUMN attributes TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.0.0/mysql/9__increase_externalmachine_env_value_length.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE externalmachine_env MODIFY COLUMN env_value TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.10.0/1__add_workspace_cfg_attributes.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- --Workspace config attributes --------------------------------------------------------- CREATE TABLE che_workspace_cfg_attributes ( workspace_id BIGINT, attributes VARCHAR(255), attributes_key VARCHAR(255) ); --constraints ALTER TABLE che_workspace_cfg_attributes ADD CONSTRAINT fk_che_workspace_cfg_attr_workspace_id FOREIGN KEY (workspace_id) REFERENCES workspaceconfig (id); -------------------------------------------------------------------------------- --indexes CREATE INDEX index_che_workspace_cfg_attributes_ws_id ON che_workspace_cfg_attributes (workspace_id); -------------------------------------------------------------------------------- ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.10.0/2__change_signature_key_pair_id.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Rename old table ALTER TABLE che_sign_key_pair RENAME TO che_sign_key_pair_old; -- Create new key pair table CREATE TABLE che_sign_key_pair ( workspace_id VARCHAR(255) NOT NULL, public_key BIGINT NOT NULL, private_key BIGINT NOT NULL, PRIMARY KEY (workspace_id) ); -- Constraint ALTER TABLE che_sign_key_pair ADD CONSTRAINT fk_sign_workspace_id FOREIGN KEY (workspace_id) REFERENCES workspace (id); -- Copy data INSERT INTO che_sign_key_pair SELECT r.workspace_id, k.public_key, k.private_key FROM che_k8s_runtime AS r, (SELECT public_key, private_key FROM che_sign_key_pair_old LIMIT 1) AS k; -- Cleanup DROP TABLE che_sign_key_pair_old CASCADE; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.11.0/1__add_signature_key_constraints.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- remove key pair records which linked to non-existing keys DELETE FROM che_sign_key_pair kp WHERE NOT EXISTS ( SELECT id FROM che_sign_key k WHERE k.id = kp.private_key ) OR NOT EXISTS ( SELECT id FROM che_sign_key k WHERE k.id = kp.public_key ); -- remove duplicated keys id if any DELETE FROM che_sign_key_pair kp1 WHERE ( SELECT count(*) FROM che_sign_key_pair kp2 WHERE kp1.private_key = kp2.private_key OR kp1.private_key = kp2.public_key ) > 1; -- remove keys which have no more key pair references to it DELETE FROM che_sign_key k WHERE NOT EXISTS ( SELECT * FROM che_sign_key_pair kp WHERE kp.private_key = k.id OR kp.public_key = k.id ); -- add pair uniqueness constraint CREATE UNIQUE INDEX index_sign_public_private_key_id ON che_sign_key_pair (public_key, private_key); -- add foreign key indexes CREATE INDEX index_sign_public_key_id ON che_sign_key_pair (public_key); CREATE INDEX index_sign_private_key_id ON che_sign_key_pair (private_key); -- add keys table constraints ALTER TABLE che_sign_key_pair ADD CONSTRAINT fk_sign_public_key_id FOREIGN KEY (public_key) REFERENCES che_sign_key (id); ALTER TABLE che_sign_key_pair ADD CONSTRAINT fk_sign_private_key_id FOREIGN KEY (private_key) REFERENCES che_sign_key (id); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.11.0/mysql/1__add_signature_key_constraints.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- remove key pair records which linked to non-existing keys DELETE kp FROM che_sign_key_pair AS kp WHERE NOT EXISTS ( SELECT id FROM che_sign_key k WHERE k.id = kp.private_key ) OR NOT EXISTS ( SELECT id FROM che_sign_key k WHERE k.id = kp.public_key ); -- remove duplicated keys id if any DELETE FROM che_sign_key_pair WHERE ( SELECT cnt FROM (SELECT count(*) AS cnt FROM che_sign_key_pair kp2 JOIN che_sign_key_pair ON (che_sign_key_pair.private_key = kp2.private_key OR che_sign_key_pair.private_key = kp2.public_key)) AS C ) > 1; -- remove keys which have no more key pair references to it DELETE FROM che_sign_key WHERE NOT EXISTS ( SELECT * FROM (SELECT public_key FROM che_sign_key_pair kp JOIN che_sign_key ON (kp.private_key = che_sign_key.id OR kp.public_key = che_sign_key.id)) AS S ); -- add pair uniqueness constraint CREATE UNIQUE INDEX index_sign_public_private_key_id ON che_sign_key_pair (public_key, private_key); -- add foreign key indexes CREATE INDEX index_sign_public_key_id ON che_sign_key_pair (public_key); CREATE INDEX index_sign_private_key_id ON che_sign_key_pair (private_key); -- add keys table constraints ALTER TABLE che_sign_key_pair ADD CONSTRAINT fk_sign_public_key_id FOREIGN KEY (public_key) REFERENCES che_sign_key (id); ALTER TABLE che_sign_key_pair ADD CONSTRAINT fk_sign_private_key_id FOREIGN KEY (private_key) REFERENCES che_sign_key (id); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.12.0/1__rename_project_attributes_values_field.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE projectattribute_values RENAME COLUMN values_param TO attribute_values; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.12.0/mysql/1__rename_project_attributes_values_field.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE projectattribute_values CHANGE `values_param` attribute_values MEDIUMTEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.15.0/1__remove_not_null_constraint_from_env_name_fields.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Allow null as defaultEnv field in workspace config ALTER TABLE workspaceconfig ALTER COLUMN defaultenv DROP NOT NULL; ALTER TABLE che_k8s_runtime DROP PRIMARY KEY; ALTER TABLE che_k8s_runtime ADD PRIMARY KEY (workspace_id); -- Allow null as env_name field in kubernetes runtime ALTER TABLE che_k8s_runtime ALTER COLUMN env_name DROP NOT NULL; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.15.0/2__add_commands_to_k8s_runtime.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Commands -------------------------------------------------------------------- CREATE TABLE k8s_runtime_command ( id BIGINT NOT NULL, commandline TEXT, name VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL, workspace_id VARCHAR(255), PRIMARY KEY (id) ); --indexes CREATE INDEX index_k8s_runtime_command_ws_id ON k8s_runtime_command (workspace_id); --constraints ALTER TABLE k8s_runtime_command ADD CONSTRAINT fk_k8s_runtime_workspace_id FOREIGN KEY (workspace_id) REFERENCES che_k8s_runtime (workspace_id); -------------------------------------------------------------------------------- -- Command attributes ---------------------------------------------------------- CREATE TABLE k8s_runtime_command_attributes ( command_id BIGINT NOT NULL, name VARCHAR(255) NOT NULL, value_param TEXT ); --indexes CREATE INDEX index_k8s_runtime_command_attr_command_id ON k8s_runtime_command_attributes (command_id); --constraints ALTER TABLE k8s_runtime_command_attributes ADD CONSTRAINT fk_k8s_runtime_command_attr_command_id FOREIGN KEY (command_id) REFERENCES k8s_runtime_command (id); -------------------------------------------------------------------------------- ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.15.0/mysql/1__remove_not_null_constraint_from_env_name_fields.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Allow null as defaultEnv field in workspace config ALTER TABLE workspaceconfig MODIFY COLUMN defaultenv VARCHAR(255); ALTER TABLE che_k8s_runtime DROP PRIMARY KEY; ALTER TABLE che_k8s_runtime ADD PRIMARY KEY (workspace_id); -- Allow null as env_name field in kubernetes runtime ALTER TABLE che_k8s_runtime MODIFY COLUMN env_name VARCHAR(255); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.15.0/postgresql/1__remove_not_null_constraint_from_env_name_fields.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Allow null as defaultEnv field in workspace config ALTER TABLE workspaceconfig ALTER COLUMN defaultenv DROP NOT NULL; ALTER TABLE che_k8s_runtime DROP CONSTRAINT che_k8s_runtime_pkey; ALTER TABLE che_k8s_runtime ADD PRIMARY KEY (workspace_id); -- Allow null as env_name field in kubernetes runtime ALTER TABLE che_k8s_runtime ALTER COLUMN env_name DROP NOT NULL; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.16.0/1__increase_workspace_config_attributes_values_length.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE che_workspace_cfg_attributes ALTER COLUMN attributes TYPE TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.16.0/2__create_workspace_activity_table.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- CREATE TABLE che_workspace_activity ( workspace_id VARCHAR(255) NOT NULL, status VARCHAR(255), created BIGINT, last_starting BIGINT, last_running BIGINT, last_stopping BIGINT, last_stopped BIGINT, expiration BIGINT, PRIMARY KEY (workspace_id) ); ALTER TABLE che_workspace_activity ADD CONSTRAINT ws_activity_workspace_id FOREIGN KEY (workspace_id) REFERENCES workspace (id); CREATE INDEX che_index_ws_activity_last_starting ON che_workspace_activity (status, last_starting); CREATE INDEX che_index_ws_activity_last_running ON che_workspace_activity (status, last_running); CREATE INDEX che_index_ws_activity_last_stopping ON che_workspace_activity (status, last_stopping); CREATE INDEX che_index_ws_activity_last_stopped ON che_workspace_activity (status, last_stopped); CREATE INDEX che_index_ws_activity_expiration ON che_workspace_activity (expiration); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.16.0/3__bootstrap_ws_activity_data.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- copy data from the old workspace expiration table. Leave the old table and data intact, we just -- won't be using it anymore, but leave it there so that we don't make breaking schema changes -- straight away. -- We specialize for postgres and mysql, leaving this default for H2. INSERT INTO che_workspace_activity (workspace_id, created, expiration, status, last_running, last_stopped) SELECT a.workspace_id, cast(a.attributes as bigint), e.expiration, CASE WHEN r.status = '0' THEN 'STARTING' WHEN r.status = '1' THEN 'RUNNING' WHEN r.status = '2' THEN 'STOPPING' ELSE 'STOPPED' -- also handles the lack of explicit status END, cast(a_forRunning.attributes as bigint), cast(a_forStopped.attributes as bigint) FROM workspace_attributes AS a -- pull in the existing expiration times LEFT JOIN che_workspace_expiration AS e ON a.workspace_id = e.workspace_id -- pull in the recorded current status of the workspaces LEFT JOIN che_k8s_runtime AS r ON a.workspace_id = r.workspace_id -- consider the 'updated' time of a running workspace as its "last_running" event time LEFT JOIN workspace_attributes AS a_forRunning ON a.workspace_id = a_forRunning.workspace_id AND r.status = '1' AND a_forRunning.attributes_key = 'updated' -- pick up the 'stopped' timestamp from the workspace attributes (if any) LEFT JOIN workspace_attributes AS a_forStopped ON a.workspace_id = a_forStopped.workspace_id AND a_forStopped.attributes_key = 'stopped' -- we're basing all of the above on workspaces that have the 'created' attribute that stores the -- timestamp when the workspace was created WHERE a.attributes_key = 'created'; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.16.0/mysql/1__increase_workspace_config_attributes_values_length.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE che_workspace_cfg_attributes MODIFY COLUMN attributes TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.16.0/mysql/3__bootstrap_ws_activity_data.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- copy data from the old workspace expiration table. Leave the old table and data intact, we just -- won't be using it anymore, but leave it there so that we don't make breaking schema changes -- straight away. INSERT INTO che_workspace_activity (workspace_id, created, expiration, status, last_running, last_stopped) SELECT a.workspace_id, a.attributes, e.expiration, CASE WHEN r.status = '0' THEN 'STARTING' WHEN r.status = '1' THEN 'RUNNING' WHEN r.status = '2' THEN 'STOPPING' ELSE 'STOPPED' -- also handles the lack of explicit status END, a_forRunning.attributes, a_forStopped.attributes FROM workspace_attributes AS a -- pull in the existing expiration times LEFT JOIN che_workspace_expiration AS e ON a.workspace_id = e.workspace_id -- pull in the recorded current status of the workspaces LEFT JOIN che_k8s_runtime AS r ON a.workspace_id = r.workspace_id -- consider the 'updated' time of a running workspace as its "last_running" event time LEFT JOIN workspace_attributes AS a_forRunning ON a.workspace_id = a_forRunning.workspace_id AND r.status = 'RUNNING' AND a_forRunning.attributes_key = 'updated' -- pick up the 'stopped' timestamp from the workspace attributes (if any) LEFT JOIN workspace_attributes AS a_forStopped ON a.workspace_id = a_forStopped.workspace_id AND a_forStopped.attributes_key = 'stopped' -- we're basing all of the above on workspaces that have the 'created' attribute that stores the -- timestamp when the workspace was created WHERE a.attributes_key = 'created'; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.17.0/1__convert_enums_to_strings.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- UPDATE che_k8s_machine SET status = 'STARTING' WHERE status = '0'; UPDATE che_k8s_machine SET status = 'RUNNING' WHERE status = '1'; UPDATE che_k8s_machine SET status = 'STOPPED' WHERE status = '2'; UPDATE che_k8s_machine SET status = 'FAILED' WHERE status = '3'; UPDATE che_k8s_runtime SET status = 'STARTING' WHERE status = '0'; UPDATE che_k8s_runtime SET status = 'RUNNING' WHERE status = '1'; UPDATE che_k8s_runtime SET status = 'STOPPING' WHERE status = '2'; UPDATE che_k8s_runtime SET status = 'STOPPED' WHERE status = '3'; UPDATE che_k8s_server SET status = 'RUNNING' WHERE status = '0'; UPDATE che_k8s_server SET status = 'STOPPED' WHERE status = '1'; UPDATE che_k8s_server SET status = 'UNKNOWN' WHERE status = '2'; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.3.0/1__add_fk_indexes.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Indexes for "reference-side" foreign keys CREATE INDEX che_index_externalmachine_installers_externalmachine_id ON externalmachine_installers(externalmachine_id); CREATE INDEX che_index_factory_on_app_closed_action_value_action_entity_id ON che_factory_on_app_closed_action_value(action_entity_id); CREATE INDEX che_index_factory_on_app_loaded_action_value_action_entity_id ON che_factory_on_app_loaded_action_value(action_entity_id); CREATE INDEX che_index_factory_on_projects_loaded_action_value_action_entity_id ON che_factory_on_projects_loaded_action_value(action_entity_id); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.3.0/mysql/1__add_fk_indexes.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Indexes for "reference-side" foreign keys CREATE INDEX che_index_externalmachine_installers_externalmachine_id ON externalmachine_installers(externalmachine_id); CREATE INDEX che_index_factory_on_app_closed_action_value_action_entity_id ON che_factory_on_app_closed_action_value(action_entity_id); CREATE INDEX che_index_factory_on_app_loaded_action_value_action_entity_id ON che_factory_on_app_loaded_action_value(action_entity_id); CREATE INDEX che_index_factory_on_proj_loaded_action_value_action_entity_id ON che_factory_on_projects_loaded_action_value(action_entity_id); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.4.0/1__add_workspace_expirations.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- CREATE TABLE che_workspace_expiration ( workspace_id VARCHAR(255) NOT NULL, expiration BIGINT NOT NULL, PRIMARY KEY (workspace_id) ); --constraints ALTER TABLE che_workspace_expiration ADD CONSTRAINT ws_expiration_workspace_id FOREIGN KEY (workspace_id) REFERENCES workspace (id); --indexes CREATE INDEX che_index_ws_expiration_expiration ON che_workspace_expiration (expiration); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.4.0/2__add_signature_key.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Signature key --------------------------------------------------------------------- CREATE TABLE che_sign_key ( id BIGINT NOT NULL, algorithm VARCHAR(255) NOT NULL, encoding_format VARCHAR(255) NOT NULL, encoded_value BLOB NOT NULL, PRIMARY KEY (id) ); -- Signature key pair ---------------------------------------------------------------- CREATE TABLE che_sign_key_pair ( id VARCHAR(255) NOT NULL, public_key BIGINT NOT NULL, private_key BIGINT NOT NULL, PRIMARY KEY (id) ); --constraints ALTER TABLE che_sign_key_pair ADD CONSTRAINT fk_sign_public_key_id FOREIGN KEY (public_key) REFERENCES che_sign_key (id); ALTER TABLE che_sign_key_pair ADD CONSTRAINT fk_sign_private_key_id FOREIGN KEY (private_key) REFERENCES che_sign_key (id); --indexes CREATE INDEX index_sign_public_key_id ON che_sign_key_pair (public_key); CREATE INDEX index_sign_private_key_id ON che_sign_key_pair (private_key); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.4.0/3__add_k8s_runtimes.sql ================================================ -- -- Copyright (c) 2012-2018 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Runtimes -------------------------------------------------------------------- CREATE TABLE che_k8s_runtime ( workspace_id VARCHAR(255) NOT NULL UNIQUE, env_name VARCHAR(255) NOT NULL, owner_id VARCHAR(255) NOT NULL, namespace VARCHAR(255) NOT NULL, status VARCHAR(255) NOT NULL, PRIMARY KEY (workspace_id, env_name, owner_id) ); --indexes CREATE UNIQUE INDEX index_che_k8s_runtime_ws_id ON che_k8s_runtime (workspace_id); --constraints ALTER TABLE che_k8s_runtime ADD CONSTRAINT fk_che_k8s_runtime_workspace FOREIGN KEY (workspace_id) REFERENCES workspace (id); -------------------------------------------------------------------------------- -- Machines --------------------------------------------------------------------- CREATE TABLE che_k8s_machine ( workspace_id VARCHAR(255) NOT NULL, machine_name VARCHAR(255) NOT NULL, pod_name VARCHAR(255) NOT NULL, container_name VARCHAR(255) NOT NULL, status VARCHAR(255) NOT NULL, PRIMARY KEY (workspace_id, machine_name) ); --constraints ALTER TABLE che_k8s_machine ADD CONSTRAINT fk_che_k8s_machine_runtime FOREIGN KEY (workspace_id) REFERENCES che_k8s_runtime (workspace_id); CREATE TABLE che_k8s_machine_attributes ( workspace_id VARCHAR(255), machine_name VARCHAR(255), attribute_key VARCHAR(255), attribute VARCHAR(255) ); --indexes CREATE INDEX index_che_k8s_machine_attr_workspace_id_machine_name ON che_k8s_machine_attributes(workspace_id, machine_name); --constraints ALTER TABLE che_k8s_machine_attributes ADD CONSTRAINT fk_che_k8s_machine_attributes_machine FOREIGN KEY (workspace_id, machine_name) REFERENCES che_k8s_machine (workspace_id, machine_name); -------------------------------------------------------------------------------- -- Servers --------------------------------------------------------------------- CREATE TABLE che_k8s_server ( workspace_id VARCHAR(255) NOT NULL, machine_name VARCHAR(255) NOT NULL, server_name VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, status VARCHAR(255) NOT NULL, PRIMARY KEY (workspace_id, machine_name, server_name) ); --constraints ALTER TABLE che_k8s_server ADD CONSTRAINT fk_che_k8s_server_machine FOREIGN KEY (workspace_id, machine_name) REFERENCES che_k8s_machine (workspace_id, machine_name); CREATE TABLE che_k8s_server_attributes ( workspace_id VARCHAR(255), machine_name VARCHAR(255), server_name VARCHAR(255), attribute_key VARCHAR(255), attribute VARCHAR(255) ); --indexes CREATE INDEX index_che_k8s_server_attr_ws_id_machine_name ON che_k8s_server_attributes(workspace_id, machine_name, server_name); --constraints ALTER TABLE che_k8s_server_attributes ADD CONSTRAINT fk_che_k8s_server_attributes_machine FOREIGN KEY (workspace_id, machine_name, server_name) REFERENCES che_k8s_server (workspace_id, machine_name, server_name); -------------------------------------------------------------------------------- ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/6.4.0/postgresql/2__add_signature_key.sql ================================================ -- -- Copyright (c) 2012-2017 Red Hat, Inc. -- All rights reserved. This program and the accompanying materials -- are made available under the terms of the Eclipse Public License v1.0 -- which accompanies this distribution, and is available at -- http://www.eclipse.org/legal/epl-v10.html -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- Signature key --------------------------------------------------------------------- CREATE TABLE che_sign_key ( id BIGINT NOT NULL, algorithm VARCHAR(255) NOT NULL, encoding_format VARCHAR(255) NOT NULL, encoded_value BYTEA NOT NULL, PRIMARY KEY (id) ); -- Signature key pair ---------------------------------------------------------------- CREATE TABLE che_sign_key_pair ( id VARCHAR(255) NOT NULL, public_key BIGINT NOT NULL, private_key BIGINT NOT NULL, PRIMARY KEY (id) ); --constraints ALTER TABLE che_sign_key_pair ADD CONSTRAINT fk_sign_public_key_id FOREIGN KEY (public_key) REFERENCES che_sign_key (id); ALTER TABLE che_sign_key_pair ADD CONSTRAINT fk_sign_private_key_id FOREIGN KEY (private_key) REFERENCES che_sign_key (id); --indexes CREATE INDEX index_sign_public_key_id ON che_sign_key_pair (public_key); CREATE INDEX index_sign_private_key_id ON che_sign_key_pair (private_key); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta4.0/1__add_devfile.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- add devfile table CREATE TABLE devfile ( id BIGINT NOT NULL, spec_version VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY (id) ); -- devfile attributes CREATE TABLE devfile_attributes ( devfile_id BIGINT, value_param TEXT, name VARCHAR(255) ); -- constraints & indexes ALTER TABLE devfile_attributes ADD CONSTRAINT fk_devfile_attributes_devfile_id FOREIGN KEY (devfile_id) REFERENCES devfile (id); CREATE UNIQUE INDEX index_devfile_attributes_names ON devfile_attributes (devfile_id, name); ----------------------------------------------------------------------------------------------------------------------------------- -- devfile project CREATE TABLE devfile_project ( id BIGINT NOT NULL, name VARCHAR(255) NOT NULL, clone_path TEXT, type VARCHAR(255) NOT NULL, location TEXT, branch VARCHAR(255), start_point VARCHAR(255), tag VARCHAR(255), commit_id VARCHAR(255), devfile_id BIGINT, PRIMARY KEY (id) ); -- constraints & indexes ALTER TABLE devfile_project ADD CONSTRAINT fk_devfile_projects_id FOREIGN KEY (devfile_id) REFERENCES devfile (id); CREATE UNIQUE INDEX index_devfile_project_name ON devfile_project (devfile_id, name); ----------------------------------------------------------------------------------------------------------------------------------- -- devfile command CREATE TABLE devfile_command ( id BIGINT NOT NULL, name VARCHAR(255) NOT NULL, devfile_id BIGINT, PRIMARY KEY (id) ); -- constraints & indexes ALTER TABLE devfile_command ADD CONSTRAINT fk_devfile_command_id FOREIGN KEY (devfile_id) REFERENCES devfile (id); CREATE UNIQUE INDEX index_devfile_command_name ON devfile_command (devfile_id, name); -- devfile command attributes CREATE TABLE devfile_command_attributes ( devfile_command_id BIGINT, value_param TEXT, name VARCHAR(255) ); -- constraints & indexes ALTER TABLE devfile_command_attributes ADD CONSTRAINT fk_devfile_command_attributes_command_id FOREIGN KEY (devfile_command_id) REFERENCES devfile_command (id); CREATE UNIQUE INDEX index_devfile_command_attributes_name ON devfile_command_attributes (devfile_command_id, name); -- devfile command action CREATE TABLE devfile_action ( id BIGINT NOT NULL, type VARCHAR(255) NOT NULL, component VARCHAR(255) NOT NULL, command TEXT NOT NULL, workdir TEXT, devfile_command_id BIGINT, PRIMARY KEY (id) ); -- constraints & indexes ALTER TABLE devfile_action ADD CONSTRAINT fk_devfile_actions_id FOREIGN KEY (devfile_command_id) REFERENCES devfile_command (id); CREATE INDEX index_action_command_id ON devfile_action (devfile_command_id); ----------------------------------------------------------------------------------------------------------------------------------- -- devfile component CREATE TABLE devfile_component ( id BIGINT NOT NULL, type VARCHAR(255) NOT NULL, alias VARCHAR(255), component_id TEXT, reference TEXT, reference_content TEXT, image TEXT, memory_limit VARCHAR(255), mount_sources BOOLEAN, devfile_id BIGINT, PRIMARY KEY (id) ); -- constraints & indexes ALTER TABLE devfile_component ADD CONSTRAINT fk_devfile_component_id FOREIGN KEY (devfile_id) REFERENCES devfile (id); -- component command CREATE TABLE devfile_component_command ( devfile_component_id BIGINT, command TEXT NOT NULL ); -- constraints & indexes ALTER TABLE devfile_component_command ADD CONSTRAINT fk_component_command_component_id FOREIGN KEY (devfile_component_id) REFERENCES devfile_component (id); CREATE INDEX index_command_component_id ON devfile_component_command (devfile_component_id); -- component arg CREATE TABLE devfile_component_arg ( devfile_component_id BIGINT, args TEXT ); --constraints ALTER TABLE devfile_component_arg ADD CONSTRAINT fk_component_command_arg_id FOREIGN KEY (devfile_component_id) REFERENCES devfile_component (id); CREATE INDEX index_args_component_id ON devfile_component_arg (devfile_component_id); -- component selector CREATE TABLE devfile_component_selector ( devfile_component_id BIGINT, selector_key VARCHAR(255), selector VARCHAR(255) ); -- constraints & indexes ALTER TABLE devfile_component_selector ADD CONSTRAINT fk_component_selector_id FOREIGN KEY (devfile_component_id) REFERENCES devfile_component (id); CREATE UNIQUE INDEX index_devfile_component_selector ON devfile_component_selector (devfile_component_id, selector_key); -- devfile endpoint CREATE TABLE devfile_endpoint ( id BIGINT NOT NULL, name VARCHAR(255) NOT NULL, port INTEGER NOT NULL, devfile_component_id BIGINT, PRIMARY KEY (id) ); -- constraints & indexes ALTER TABLE devfile_endpoint ADD CONSTRAINT fk_devfile_endpoint_id FOREIGN KEY (devfile_component_id) REFERENCES devfile_component (id); CREATE UNIQUE INDEX index_devfile_endpoint_component_name ON devfile_endpoint (devfile_component_id, name); -- devfile endpoint attributes CREATE TABLE devfile_endpoint_attributes ( devfile_endpoint_id BIGINT, value_param TEXT, name VARCHAR(255) ); -- constraints & indexes ALTER TABLE devfile_endpoint_attributes ADD CONSTRAINT fk_devfile_endpoint_attributes_id FOREIGN KEY (devfile_endpoint_id) REFERENCES devfile_endpoint (id); CREATE UNIQUE INDEX index_devfile_endpoint_attributes_name ON devfile_endpoint_attributes (devfile_endpoint_id, name); -- devfile component env CREATE TABLE devfile_env ( id BIGINT NOT NULL, name VARCHAR(255) NOT NULL, value_param TEXT, devfile_component_id BIGINT, PRIMARY KEY (id) ); -- constraints & indexes ALTER TABLE devfile_env ADD CONSTRAINT fk_devfile_env_id FOREIGN KEY (devfile_component_id) REFERENCES devfile_component (id); CREATE UNIQUE INDEX index_devfile_env_component_name ON devfile_env (devfile_component_id, name); -- devfile component volume CREATE TABLE devfile_volume ( id BIGINT NOT NULL, name VARCHAR(255) NOT NULL, container_path TEXT, devfile_component_id BIGINT, PRIMARY KEY (id) ); -- constraints & indexes ALTER TABLE devfile_volume ADD CONSTRAINT fk_devfile_volumes_id FOREIGN KEY (devfile_component_id) REFERENCES devfile_component (id); CREATE UNIQUE INDEX index_devfile_volume_component_name ON devfile_volume (devfile_component_id, name); -- devfile component entrypoint CREATE TABLE devfile_entrypoint ( id BIGINT NOT NULL, parent_name VARCHAR(255) NOT NULL, container_name VARCHAR(255) NOT NULL, devfile_component_id BIGINT, PRIMARY KEY (id) ); -- constraints & indexes ALTER TABLE devfile_entrypoint ADD CONSTRAINT fk_devfile_entrypoints_id FOREIGN KEY (devfile_component_id) REFERENCES devfile_component (id); CREATE UNIQUE INDEX index_devfile_entrypoint_id_parent_container ON devfile_entrypoint (devfile_component_id, parent_name, container_name); -- entrypoint arg CREATE TABLE devfile_entrypoint_arg ( devfile_entrypoint_id BIGINT, arg VARCHAR(255) NOT NULL ); -- constraints & indexes ALTER TABLE devfile_entrypoint_arg ADD CONSTRAINT fk_entrypoint_arg_id FOREIGN KEY (devfile_entrypoint_id) REFERENCES devfile_entrypoint (id); CREATE INDEX index_entrypoint_arg_entrypoint_id ON devfile_entrypoint_arg (devfile_entrypoint_id); -- entrypoint commands CREATE TABLE devfile_entrypoint_commands ( devfile_entrypoint_id BIGINT, command VARCHAR(255) NOT NULL ); -- constraints & indexes ALTER TABLE devfile_entrypoint_commands ADD CONSTRAINT fk_entrypoint_commands_id FOREIGN KEY (devfile_entrypoint_id) REFERENCES devfile_entrypoint (id); CREATE INDEX index_entrypoint_commands_entrypoint_id ON devfile_entrypoint_commands (devfile_entrypoint_id); CREATE TABLE devfile_entrypoint_selector ( devfile_entrypoint_id BIGINT, selector_key VARCHAR(255) NOT NULL, selector VARCHAR(255) NOT NULL ); -- constraints & indexes ALTER TABLE devfile_entrypoint_selector ADD CONSTRAINT fk_entrypoint_selector_id FOREIGN KEY (devfile_entrypoint_id) REFERENCES devfile_entrypoint (id); CREATE UNIQUE INDEX index_entrypoint_selectors_keys ON devfile_entrypoint_selector (devfile_entrypoint_id, selector_key); ----------------------------------------------------------------------------------------------------------------------------------- -- add devfile into workspace ALTER TABLE workspace ADD COLUMN devfile_id BIGINT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta5.0/1__devfile_command_reference.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- devfile command action references ALTER TABLE devfile_action ALTER COLUMN component DROP NOT NULL; ALTER TABLE devfile_action ALTER COLUMN command DROP NOT NULL; ALTER TABLE devfile_action ADD COLUMN reference TEXT; ALTER TABLE devfile_action ADD COLUMN reference_content TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta5.0/mysql/1__devfile_command_reference.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- devfile command action references ALTER TABLE devfile_action MODIFY COLUMN component VARCHAR(255) NULL DEFAULT NULL; ALTER TABLE devfile_action MODIFY COLUMN command TEXT NULL DEFAULT NULL; ALTER TABLE devfile_action ADD COLUMN reference TEXT; ALTER TABLE devfile_action ADD COLUMN reference_content TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta6.0/1__add_devfile_component_prefs.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- component preferences CREATE TABLE devfile_component_preferences ( devfile_component_id BIGINT, preference_key VARCHAR(255), preference VARCHAR(255) ); -- constraints & indexes ALTER TABLE devfile_component_preferences ADD CONSTRAINT fk_prefs_component_id FOREIGN KEY (devfile_component_id) REFERENCES devfile_component (id); CREATE UNIQUE INDEX index_devfile_component_prefs ON devfile_component_preferences (devfile_component_id, preference_key); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta7.0/1__add_registry_url_to_devfile_component.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE devfile_component ADD COLUMN registry_url TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta8.0-RC2.0/1__devfile_metadata.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE devfile RENAME COLUMN spec_version TO api_version; ALTER TABLE devfile RENAME COLUMN name TO meta_name; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta8.0-RC2.0/2__devfile_make_some_fields_optional.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE devfile_entrypoint ALTER COLUMN parent_name DROP NOT NULL; ALTER TABLE devfile_entrypoint ALTER COLUMN container_name DROP NOT NULL; ALTER TABLE devfile_action ALTER COLUMN component DROP NOT NULL; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta8.0-RC2.0/mysql/1__devfile_metadata.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE devfile CHANGE spec_version api_version VARCHAR(255) NOT NULL; ALTER TABLE devfile CHANGE name meta_name VARCHAR(255) NOT NULL; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.0.0-beta8.0-RC2.0/mysql/2__devfile_make_some_fields_optional.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE devfile_entrypoint MODIFY COLUMN parent_name VARCHAR(255) NULL DEFAULT NULL, MODIFY COLUMN container_name VARCHAR(255) NULL DEFAULT NULL; ALTER TABLE devfile_action MODIFY COLUMN component VARCHAR(255) NULL DEFAULT NULL; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.10.0/1__add_devfile_plugin_editor_component_cpu_limit_request.sql ================================================ -- -- Copyright (c) 2012-2020 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE devfile_component ADD COLUMN cpu_limit VARCHAR(255); ALTER TABLE devfile_component ADD COLUMN cpu_request VARCHAR(255); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.10.0/2__add_devfile_plugin_editor_component_ram_request.sql ================================================ -- -- Copyright (c) 2012-2020 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE devfile_component ADD COLUMN memory_request VARCHAR(255); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.11.0/1__update_inconsistent_stopped_workspace_activities.sql ================================================ -- -- Copyright (c) 2012-2020 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- UPDATE che_workspace_activity SET last_stopped = 0 WHERE status = 'STOPPED' AND last_stopped IS NULL; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.16.0/1__add_devfile_component_automount_workspace_secrets.sql ================================================ -- -- Copyright (c) 2012-2020 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE devfile_component ADD COLUMN automount_secrets BOOLEAN; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.2.0/1__remove_installers.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- DROP TABLE installer_serverconf_attributes; DROP TABLE installer_dependencies; DROP TABLE installer_servers; DROP TABLE installer_properties; DROP TABLE installer; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.20.0/1__userdevfile.sql ================================================ -- -- Copyright (c) 2012-2020 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- -- add userdevfile table CREATE TABLE userdevfile ( id VARCHAR(255) NOT NULL UNIQUE, accountid VARCHAR(255) NOT NULL, devfile_id BIGINT NOT NULL UNIQUE, meta_generated_name VARCHAR(255) , meta_name VARCHAR(255) , name VARCHAR(255) NOT NULL , description TEXT , PRIMARY KEY (id) ); CREATE INDEX index_userdevfile_devfile_id ON userdevfile (devfile_id); CREATE INDEX index_userdevfile_name ON userdevfile(name); ALTER TABLE userdevfile ADD CONSTRAINT unq_userdevfile_0 UNIQUE (name, accountid); ALTER TABLE userdevfile ADD CONSTRAINT fx_userdevfile_accountid FOREIGN KEY (accountid) REFERENCES account (id); ALTER TABLE userdevfile ADD CONSTRAINT fk_userdevfile_devfile_id FOREIGN KEY (devfile_id) REFERENCES devfile (id); ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.21.0/1__remove_installers.sql ================================================ -- -- Copyright (c) 2012-2020 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- DROP TABLE externalmachine_installers; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.26.0/1__remove_factory_button_and_image.sql ================================================ -- -- Copyright (c) 2012-2020 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- DROP TABLE IF EXISTS che_factory_image; ALTER TABLE che_factory DROP CONSTRAINT fk_che_f_button_id; ALTER TABLE che_factory DROP COLUMN button_id; DROP TABLE IF EXISTS che_factory_button; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.26.0/mysql/1__remove_factory_button_and_image.sql ================================================ -- -- Copyright (c) 2012-2020 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- DROP TABLE IF EXISTS che_factory_image; ALTER TABLE che_factory DROP FOREIGN KEY fk_che_f_button_id; ALTER TABLE che_factory DROP COLUMN button_id; DROP TABLE IF EXISTS che_factory_button; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.4.0/1__add_devfile_source_sparse_checkout_dir.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE devfile_project ADD COLUMN sparse_checkout_dir TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.4.0/2__add_preview_url_to_devfile_command.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- ALTER TABLE devfile_command ADD COLUMN preview_url_port INTEGER; ALTER TABLE devfile_command ADD COLUMN preview_url_path TEXT; ALTER TABLE k8s_runtime_command ADD COLUMN preview_url_port INTEGER; ALTER TABLE k8s_runtime_command ADD COLUMN preview_url_path TEXT; ALTER TABLE command ADD COLUMN preview_url_port INTEGER; ALTER TABLE command ADD COLUMN preview_url_path TEXT; ================================================ FILE: wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.6.0/1__drop_che_workspace_expiration.sql ================================================ -- -- Copyright (c) 2012-2019 Red Hat, Inc. -- This program and the accompanying materials are made -- available under the terms of the Eclipse Public License 2.0 -- which is available at https://www.eclipse.org/legal/epl-2.0/ -- -- SPDX-License-Identifier: EPL-2.0 -- -- Contributors: -- Red Hat, Inc. - initial API and implementation -- DROP TABLE IF EXISTS che_workspace_expiration; ================================================ FILE: wsmaster/pom.xml ================================================ 4.0.0 che-core-parent org.eclipse.che.core 7.118.0-SNAPSHOT ../core/pom.xml che-master-parent pom Che Core :: API :: Che Master Parent che-core-api-auth-shared che-core-api-auth che-core-api-auth-azure-devops che-core-api-auth-bitbucket che-core-api-auth-github che-core-api-auth-github-common che-core-api-auth-gitlab che-core-api-auth-gitlab-common che-core-api-auth-openshift che-core-api-workspace-shared che-core-api-workspace che-core-api-user-shared che-core-api-devfile-shared che-core-api-devfile che-core-api-account che-core-api-user che-core-api-factory-azure-devops che-core-api-factory-git-ssh che-core-api-factory-shared che-core-api-factory che-core-api-factory-github che-core-api-factory-github-common che-core-api-factory-gitlab che-core-api-factory-gitlab-common che-core-api-factory-bitbucket che-core-api-factory-bitbucket-server che-core-api-ssh che-core-api-ssh-shared che-core-sql-schema che-core-api-system che-core-api-system-shared che-core-api-logger-shared che-core-api-logger che-core-api-metrics